erp-platform/ui/src/views/maintenance/components/NewTeamModal.tsx

420 lines
15 KiB
TypeScript
Raw Normal View History

2025-09-15 09:31:47 +00:00
import React, { useState } from "react";
import { FaTimes, FaSave, FaPlus, FaMinus } from "react-icons/fa";
import { TeamRoleEnum } from "../../../types/common";
import MultiSelectEmployee from "../../../components/common/MultiSelectEmployee";
import { mockEmployees } from "../../../mocks/mockEmployees";
import { Team, TeamMember } from "../../../types/common";
interface NewTeamModalProps {
isOpen: boolean;
onClose: () => void;
onSave: (team: Partial<Team>) => void;
}
const NewTeamModal: React.FC<NewTeamModalProps> = ({
isOpen,
onClose,
onSave,
}) => {
const [teamData, setTeamData] = useState<Partial<Team>>({
code: "",
name: "",
description: "",
isActive: true,
specializations: [],
members: [],
});
const [selectedEmployees, setSelectedEmployees] = useState<string[]>([]);
const [newSpecialization, setNewSpecialization] = useState("");
const [errors, setErrors] = useState<Record<string, string>>({});
const validateForm = () => {
const newErrors: Record<string, string> = {};
if (!teamData.code?.trim()) {
newErrors.teamCode = "Ekip kodu gerekli";
}
if (!teamData.name?.trim()) {
newErrors.teamName = "Ekip adı gerekli";
}
if (!teamData.description?.trim()) {
newErrors.description = "Açıklama gerekli";
}
if (!teamData.managerId) {
newErrors.managerId = "Ekip yöneticisi seçilmeli";
}
if (selectedEmployees.length === 0) {
newErrors.members = "En az bir ekip üyesi seçilmeli";
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleInputChange = (
field: keyof Team,
value: string | boolean | string[]
) => {
setTeamData((prev) => ({
...prev,
[field]: value,
}));
// Clear error when user starts typing
if (errors[field]) {
setErrors((prev) => ({
...prev,
[field]: "",
}));
}
};
const addSpecialization = () => {
if (
newSpecialization.trim() &&
!teamData.specializations?.includes(newSpecialization.trim())
) {
setTeamData((prev) => ({
...prev,
specializations: [
...(prev.specializations || []),
newSpecialization.trim(),
],
}));
setNewSpecialization("");
}
};
const removeSpecialization = (index: number) => {
setTeamData((prev) => ({
...prev,
specializations:
prev.specializations?.filter((_, i) => i !== index) || [],
}));
};
const handleEmployeeSelection = (employees: string[]) => {
setSelectedEmployees(employees);
if (errors.members) {
setErrors((prev) => ({
...prev,
members: "",
}));
}
};
const handleMemberRoleChange = (employeeName: string, role: TeamRoleEnum) => {
const employee = mockEmployees.find((emp) => emp.fullName === employeeName);
if (role === TeamRoleEnum.Lead && employee) {
handleInputChange("managerId", employee.id);
}
};
const handleSave = () => {
if (validateForm()) {
const members: TeamMember[] = selectedEmployees.map(
(employeeName, index) => {
const employee = mockEmployees.find(
(emp) => emp.fullName === employeeName
);
const isLeader = index === 0; // First selected employee becomes leader
return {
id: `TM${Date.now()}-${index}`,
teamId: "",
employeeId: employee?.id || "",
employee: employee,
role: isLeader ? TeamRoleEnum.Lead : TeamRoleEnum.Member,
joinDate: new Date(),
isActive: true,
};
}
);
const teamId = `MT${Date.now()}`;
// Update members with the teamId
members.forEach((member) => {
member.teamId = teamId;
});
const teamToSave: Partial<Team> = {
...teamData,
id: teamId,
managerId: members.find((m) => m.role === TeamRoleEnum.Lead)
?.employeeId,
members,
creationTime: new Date(),
lastModificationTime: new Date(),
};
onSave(teamToSave);
onClose();
// Reset form
setTeamData({
code: "",
name: "",
description: "",
isActive: true,
specializations: [],
members: [],
});
setSelectedEmployees([]);
setNewSpecialization("");
setErrors({});
}
};
return (
isOpen && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg w-full max-w-3xl max-h-[90vh] overflow-y-auto">
{/* Header */}
<div className="flex items-center justify-between p-4 border-b border-gray-200">
<h2 className="text-lg font-semibold text-gray-900">
Yeni Ekip Oluştur
</h2>
<button
onClick={onClose}
className="text-gray-400 hover:text-gray-600 transition-colors"
>
<FaTimes className="w-5 h-5" />
</button>
</div>
{/* Content */}
<div className="p-4 space-y-4">
{/* Basic Info */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Ekip Kodu *
</label>
<input
type="text"
value={teamData.code || ""}
onChange={(e) => handleInputChange("code", e.target.value)}
className={`w-full px-2.5 py-1.5 text-sm border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 ${
errors.code ? "border-red-500" : "border-gray-300"
}`}
placeholder="MEC-001"
/>
{errors.code && (
<p className="text-red-500 text-xs mt-1">{errors.code}</p>
)}
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Ekip Adı *
</label>
<input
type="text"
value={teamData.name || ""}
onChange={(e) => handleInputChange("name", e.target.value)}
className={`w-full px-2.5 py-1.5 text-sm border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 ${
errors.name ? "border-red-500" : "border-gray-300"
}`}
placeholder="Mekanik Bakım Ekibi"
/>
{errors.name && (
<p className="text-red-500 text-xs mt-1">{errors.name}</p>
)}
</div>
</div>
{/* Description */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
ıklama *
</label>
<textarea
value={teamData.description || ""}
onChange={(e) =>
handleInputChange("description", e.target.value)
}
rows={2}
className={`w-full px-2.5 py-1.5 text-sm border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 ${
errors.description ? "border-red-500" : "border-gray-300"
}`}
placeholder="Ekip açıklaması ve sorumlulukları"
/>
{errors.description && (
<p className="text-red-500 text-xs mt-1">
{errors.description}
</p>
)}
</div>
{/* Team Members */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Ekip Üyeleri *
</label>
<MultiSelectEmployee
selectedEmployees={selectedEmployees}
onChange={handleEmployeeSelection}
placeholder="Ekip üyelerini seçin"
className={errors.members ? "border-red-500" : ""}
/>
{errors.members && (
<p className="text-red-500 text-xs mt-1">{errors.members}</p>
)}
{/* Selected Members Display */}
{selectedEmployees.length > 0 && (
<div className="mt-3 p-3 bg-gray-50 rounded-lg">
<h4 className="text-sm font-medium text-gray-700 mb-2">
Seçili Ekip Üyeleri:
</h4>
<div className="space-y-1.5">
{selectedEmployees.map((employeeName, index) => {
const employee = mockEmployees.find(
(emp) => emp.fullName === employeeName
);
return (
<div
key={index}
className="flex items-center justify-between p-2 bg-white rounded border"
>
<div className="flex items-center space-x-3">
<div className="w-8 h-8 bg-blue-100 rounded-full flex items-center justify-center">
<span className="text-sm font-medium text-blue-600">
{employee?.firstName?.charAt(0)}
{employee?.lastName?.charAt(0)}
</span>
</div>
<div>
<p className="text-sm font-medium text-gray-900">
{employeeName}
</p>
<p className="text-sm text-gray-600">
{employee?.jobPosition?.name}
</p>
</div>
</div>
<div className="flex items-center space-x-2">
<select
value={
teamData.managerId === employee?.id
? TeamRoleEnum.Lead
: TeamRoleEnum.Member
}
onChange={(e) =>
handleMemberRoleChange(
employeeName,
e.target.value as TeamRoleEnum
)
}
className="text-xs px-1.5 py-1 border border-gray-300 rounded"
>
<option value={TeamRoleEnum.Member}>Üye</option>
<option value={TeamRoleEnum.Lead}>Lider</option>
<option value={TeamRoleEnum.Specialist}>
Uzman
</option>
<option value={TeamRoleEnum.Manager}>
Yönetici
</option>
</select>
</div>
</div>
);
})}
</div>
</div>
)}
</div>
{/* Specializations */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Uzmanlık Alanları
</label>
<div className="space-y-2">
<div className="flex space-x-1.5">
<input
type="text"
value={newSpecialization}
onChange={(e) => setNewSpecialization(e.target.value)}
className="flex-1 px-2.5 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="Uzmanlık alanı ekle"
onKeyPress={(e) => {
if (e.key === "Enter") {
e.preventDefault();
addSpecialization();
}
}}
/>
<button
type="button"
onClick={addSpecialization}
className="px-2.5 py-1.5 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
>
<FaPlus className="w-4 h-4" />
</button>
</div>
{teamData.specializations &&
teamData.specializations.length > 0 && (
<div className="flex flex-wrap gap-2">
{teamData.specializations.map((spec, index) => (
<span
key={index}
className="inline-flex items-center px-2.5 py-1 rounded-full text-xs bg-blue-100 text-blue-800"
>
{spec}
<button
type="button"
onClick={() => removeSpecialization(index)}
className="ml-2 text-blue-600 hover:text-blue-800"
>
<FaMinus className="w-3 h-3" />
</button>
</span>
))}
</div>
)}
</div>
</div>
{/* Status */}
<div>
<label className="flex items-center">
<input
type="checkbox"
checked={teamData.isActive || false}
onChange={(e) =>
handleInputChange("isActive", e.target.checked)
}
className="rounded border-gray-300 text-blue-600 shadow-sm focus:border-blue-300 focus:ring focus:ring-blue-200 focus:ring-opacity-50"
/>
<span className="ml-2 text-sm text-gray-700">Ekip aktif</span>
</label>
</div>
</div>
{/* Footer */}
<div className="flex items-center justify-end space-x-2 p-4 border-t border-gray-200">
<button
onClick={onClose}
className="px-3 py-1.5 text-sm text-gray-600 border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors"
>
İptal
</button>
<button
onClick={handleSave}
className="px-3 py-1.5 text-sm bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors flex items-center space-x-2"
>
<FaSave className="w-4 h-4" />
<span>Kaydet</span>
</button>
</div>
</div>
</div>
)
);
};
export default NewTeamModal;