364 lines
14 KiB
TypeScript
364 lines
14 KiB
TypeScript
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'
|
||
import { getTeamRoleText } from '@/utils/erp'
|
||
|
||
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">Açı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"
|
||
>
|
||
{Object.values(TeamRoleEnum).map((role) => (
|
||
<option key={role} value={role}>
|
||
{getTeamRoleText(role)}
|
||
</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
|