erp-platform/ui/src/views/maintenance/components/NewTeamModal.tsx
2025-09-17 11:58:20 +03:00

364 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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">ı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