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

365 lines
14 KiB
TypeScript
Raw Normal View History

2025-09-15 21:29:07 +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'
2025-09-17 08:58:20 +00:00
import { getTeamRoleText } from '@/utils/erp'
2025-09-15 09:31:47 +00:00
interface NewTeamModalProps {
2025-09-15 21:29:07 +00:00
isOpen: boolean
onClose: () => void
onSave: (team: Partial<Team>) => void
2025-09-15 09:31:47 +00:00
}
2025-09-15 21:29:07 +00:00
const NewTeamModal: React.FC<NewTeamModalProps> = ({ isOpen, onClose, onSave }) => {
2025-09-15 09:31:47 +00:00
const [teamData, setTeamData] = useState<Partial<Team>>({
2025-09-15 21:29:07 +00:00
code: '',
name: '',
description: '',
2025-09-15 09:31:47 +00:00
isActive: true,
specializations: [],
members: [],
2025-09-15 21:29:07 +00:00
})
2025-09-15 09:31:47 +00:00
2025-09-15 21:29:07 +00:00
const [selectedEmployees, setSelectedEmployees] = useState<string[]>([])
const [newSpecialization, setNewSpecialization] = useState('')
const [errors, setErrors] = useState<Record<string, string>>({})
2025-09-15 09:31:47 +00:00
const validateForm = () => {
2025-09-15 21:29:07 +00:00
const newErrors: Record<string, string> = {}
2025-09-15 09:31:47 +00:00
if (!teamData.code?.trim()) {
2025-09-15 21:29:07 +00:00
newErrors.teamCode = 'Ekip kodu gerekli'
2025-09-15 09:31:47 +00:00
}
if (!teamData.name?.trim()) {
2025-09-15 21:29:07 +00:00
newErrors.teamName = 'Ekip adı gerekli'
2025-09-15 09:31:47 +00:00
}
if (!teamData.description?.trim()) {
2025-09-15 21:29:07 +00:00
newErrors.description = 'Açıklama gerekli'
2025-09-15 09:31:47 +00:00
}
if (!teamData.managerId) {
2025-09-15 21:29:07 +00:00
newErrors.managerId = 'Ekip yöneticisi seçilmeli'
2025-09-15 09:31:47 +00:00
}
if (selectedEmployees.length === 0) {
2025-09-15 21:29:07 +00:00
newErrors.members = 'En az bir ekip üyesi seçilmeli'
2025-09-15 09:31:47 +00:00
}
2025-09-15 21:29:07 +00:00
setErrors(newErrors)
return Object.keys(newErrors).length === 0
}
2025-09-15 09:31:47 +00:00
2025-09-15 21:29:07 +00:00
const handleInputChange = (field: keyof Team, value: string | boolean | string[]) => {
2025-09-15 09:31:47 +00:00
setTeamData((prev) => ({
...prev,
[field]: value,
2025-09-15 21:29:07 +00:00
}))
2025-09-15 09:31:47 +00:00
// Clear error when user starts typing
if (errors[field]) {
setErrors((prev) => ({
...prev,
2025-09-15 21:29:07 +00:00
[field]: '',
}))
2025-09-15 09:31:47 +00:00
}
2025-09-15 21:29:07 +00:00
}
2025-09-15 09:31:47 +00:00
const addSpecialization = () => {
2025-09-15 21:29:07 +00:00
if (newSpecialization.trim() && !teamData.specializations?.includes(newSpecialization.trim())) {
2025-09-15 09:31:47 +00:00
setTeamData((prev) => ({
...prev,
2025-09-15 21:29:07 +00:00
specializations: [...(prev.specializations || []), newSpecialization.trim()],
}))
setNewSpecialization('')
2025-09-15 09:31:47 +00:00
}
2025-09-15 21:29:07 +00:00
}
2025-09-15 09:31:47 +00:00
const removeSpecialization = (index: number) => {
setTeamData((prev) => ({
...prev,
2025-09-15 21:29:07 +00:00
specializations: prev.specializations?.filter((_, i) => i !== index) || [],
}))
}
2025-09-15 09:31:47 +00:00
const handleEmployeeSelection = (employees: string[]) => {
2025-09-15 21:29:07 +00:00
setSelectedEmployees(employees)
2025-09-15 09:31:47 +00:00
if (errors.members) {
setErrors((prev) => ({
...prev,
2025-09-15 21:29:07 +00:00
members: '',
}))
2025-09-15 09:31:47 +00:00
}
2025-09-15 21:29:07 +00:00
}
2025-09-15 09:31:47 +00:00
const handleMemberRoleChange = (employeeName: string, role: TeamRoleEnum) => {
2025-09-15 21:29:07 +00:00
const employee = mockEmployees.find((emp) => emp.fullName === employeeName)
2025-09-15 09:31:47 +00:00
if (role === TeamRoleEnum.Lead && employee) {
2025-09-15 21:29:07 +00:00
handleInputChange('managerId', employee.id)
2025-09-15 09:31:47 +00:00
}
2025-09-15 21:29:07 +00:00
}
2025-09-15 09:31:47 +00:00
const handleSave = () => {
if (validateForm()) {
2025-09-15 21:29:07 +00:00
const members: TeamMember[] = selectedEmployees.map((employeeName, index) => {
const employee = mockEmployees.find((emp) => emp.fullName === employeeName)
const isLeader = index === 0 // First selected employee becomes leader
2025-09-15 09:31:47 +00:00
2025-09-15 21:29:07 +00:00
return {
id: `TM${Date.now()}-${index}`,
teamId: '',
employeeId: employee?.id || '',
employee: employee,
role: isLeader ? TeamRoleEnum.Lead : TeamRoleEnum.Member,
joinDate: new Date(),
isActive: true,
2025-09-15 09:31:47 +00:00
}
2025-09-15 21:29:07 +00:00
})
2025-09-15 09:31:47 +00:00
2025-09-15 21:29:07 +00:00
const teamId = `MT${Date.now()}`
2025-09-15 09:31:47 +00:00
// Update members with the teamId
members.forEach((member) => {
2025-09-15 21:29:07 +00:00
member.teamId = teamId
})
2025-09-15 09:31:47 +00:00
const teamToSave: Partial<Team> = {
...teamData,
id: teamId,
2025-09-15 21:29:07 +00:00
managerId: members.find((m) => m.role === TeamRoleEnum.Lead)?.employeeId,
2025-09-15 09:31:47 +00:00
members,
creationTime: new Date(),
lastModificationTime: new Date(),
2025-09-15 21:29:07 +00:00
}
2025-09-15 09:31:47 +00:00
2025-09-15 21:29:07 +00:00
onSave(teamToSave)
onClose()
2025-09-15 09:31:47 +00:00
// Reset form
setTeamData({
2025-09-15 21:29:07 +00:00
code: '',
name: '',
description: '',
2025-09-15 09:31:47 +00:00
isActive: true,
specializations: [],
members: [],
2025-09-15 21:29:07 +00:00
})
setSelectedEmployees([])
setNewSpecialization('')
setErrors({})
2025-09-15 09:31:47 +00:00
}
2025-09-15 21:29:07 +00:00
}
2025-09-15 09:31:47 +00:00
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">
2025-09-15 21:29:07 +00:00
<h2 className="text-lg font-semibold text-gray-900">Yeni Ekip Oluştur</h2>
2025-09-15 09:31:47 +00:00
<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>
2025-09-15 21:29:07 +00:00
<label className="block text-sm font-medium text-gray-700 mb-2">Ekip Kodu *</label>
2025-09-15 09:31:47 +00:00
<input
type="text"
2025-09-15 21:29:07 +00:00
value={teamData.code || ''}
onChange={(e) => handleInputChange('code', e.target.value)}
2025-09-15 09:31:47 +00:00
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 ${
2025-09-15 21:29:07 +00:00
errors.code ? 'border-red-500' : 'border-gray-300'
2025-09-15 09:31:47 +00:00
}`}
placeholder="MEC-001"
/>
2025-09-15 21:29:07 +00:00
{errors.code && <p className="text-red-500 text-xs mt-1">{errors.code}</p>}
2025-09-15 09:31:47 +00:00
</div>
<div>
2025-09-15 21:29:07 +00:00
<label className="block text-sm font-medium text-gray-700 mb-2">Ekip Adı *</label>
2025-09-15 09:31:47 +00:00
<input
type="text"
2025-09-15 21:29:07 +00:00
value={teamData.name || ''}
onChange={(e) => handleInputChange('name', e.target.value)}
2025-09-15 09:31:47 +00:00
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 ${
2025-09-15 21:29:07 +00:00
errors.name ? 'border-red-500' : 'border-gray-300'
2025-09-15 09:31:47 +00:00
}`}
placeholder="Mekanik Bakım Ekibi"
/>
2025-09-15 21:29:07 +00:00
{errors.name && <p className="text-red-500 text-xs mt-1">{errors.name}</p>}
2025-09-15 09:31:47 +00:00
</div>
</div>
{/* Description */}
<div>
2025-09-15 21:29:07 +00:00
<label className="block text-sm font-medium text-gray-700 mb-2">ıklama *</label>
2025-09-15 09:31:47 +00:00
<textarea
2025-09-15 21:29:07 +00:00
value={teamData.description || ''}
onChange={(e) => handleInputChange('description', e.target.value)}
2025-09-15 09:31:47 +00:00
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 ${
2025-09-15 21:29:07 +00:00
errors.description ? 'border-red-500' : 'border-gray-300'
2025-09-15 09:31:47 +00:00
}`}
placeholder="Ekip açıklaması ve sorumlulukları"
/>
{errors.description && (
2025-09-15 21:29:07 +00:00
<p className="text-red-500 text-xs mt-1">{errors.description}</p>
2025-09-15 09:31:47 +00:00
)}
</div>
{/* Team Members */}
<div>
2025-09-15 21:29:07 +00:00
<label className="block text-sm font-medium text-gray-700 mb-2">Ekip Üyeleri *</label>
2025-09-15 09:31:47 +00:00
<MultiSelectEmployee
selectedEmployees={selectedEmployees}
onChange={handleEmployeeSelection}
placeholder="Ekip üyelerini seçin"
2025-09-15 21:29:07 +00:00
className={errors.members ? 'border-red-500' : ''}
2025-09-15 09:31:47 +00:00
/>
2025-09-15 21:29:07 +00:00
{errors.members && <p className="text-red-500 text-xs mt-1">{errors.members}</p>}
2025-09-15 09:31:47 +00:00
{/* Selected Members Display */}
{selectedEmployees.length > 0 && (
<div className="mt-3 p-3 bg-gray-50 rounded-lg">
2025-09-15 21:29:07 +00:00
<h4 className="text-sm font-medium text-gray-700 mb-2">Seçili Ekip Üyeleri:</h4>
2025-09-15 09:31:47 +00:00
<div className="space-y-1.5">
{selectedEmployees.map((employeeName, index) => {
2025-09-15 21:29:07 +00:00
const employee = mockEmployees.find((emp) => emp.fullName === employeeName)
2025-09-15 09:31:47 +00:00
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>
2025-09-15 21:29:07 +00:00
<p className="text-sm font-medium text-gray-900">{employeeName}</p>
<p className="text-sm text-gray-600">{employee?.jobPosition?.name}</p>
2025-09-15 09:31:47 +00:00
</div>
</div>
<div className="flex items-center space-x-2">
<select
value={
teamData.managerId === employee?.id
? TeamRoleEnum.Lead
: TeamRoleEnum.Member
}
onChange={(e) =>
2025-09-15 21:29:07 +00:00
handleMemberRoleChange(employeeName, e.target.value as TeamRoleEnum)
2025-09-15 09:31:47 +00:00
}
className="text-xs px-1.5 py-1 border border-gray-300 rounded"
>
2025-09-17 08:58:20 +00:00
{Object.values(TeamRoleEnum).map((role) => (
<option key={role} value={role}>
{getTeamRoleText(role)}
</option>
))}
2025-09-15 09:31:47 +00:00
</select>
</div>
</div>
2025-09-15 21:29:07 +00:00
)
2025-09-15 09:31:47 +00:00
})}
</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) => {
2025-09-15 21:29:07 +00:00
if (e.key === 'Enter') {
e.preventDefault()
addSpecialization()
2025-09-15 09:31:47 +00:00
}
}}
/>
<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>
2025-09-15 21:29:07 +00:00
{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"
2025-09-15 09:31:47 +00:00
>
2025-09-15 21:29:07 +00:00
<FaMinus className="w-3 h-3" />
</button>
</span>
))}
</div>
)}
2025-09-15 09:31:47 +00:00
</div>
</div>
{/* Status */}
<div>
<label className="flex items-center">
<input
type="checkbox"
checked={teamData.isActive || false}
2025-09-15 21:29:07 +00:00
onChange={(e) => handleInputChange('isActive', e.target.checked)}
2025-09-15 09:31:47 +00:00
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>
)
2025-09-15 21:29:07 +00:00
)
}
2025-09-15 09:31:47 +00:00
2025-09-15 21:29:07 +00:00
export default NewTeamModal