erp-platform/ui/src/views/maintenance/components/WorkCenterForm.tsx
2025-09-17 12:46:58 +03:00

652 lines
26 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, useEffect, useCallback } from 'react'
import { useNavigate, useParams } from 'react-router-dom'
import {
FaSave,
FaTimes,
FaCog,
FaCalendar,
FaExclamationTriangle,
FaMapMarkerAlt,
FaWrench,
} from 'react-icons/fa'
import LoadingSpinner from '../../../components/common/LoadingSpinner'
import {
CriticalityLevelEnum,
PmWorkCenter,
PmWorkCenterSpecification,
WorkCenterStatusEnum,
} from '../../../types/pm'
import { mockWorkCenters } from '../../../mocks/mockWorkCenters'
import { mockDepartments } from '../../../mocks/mockDepartments'
import { HrDepartment } from '../../../types/hr'
import { Container } from '@/components/shared'
import { ROUTES_ENUM } from '@/routes/route.constant'
import { getCriticalityLevelText, getWorkCenterStatusText } from '@/utils/erp'
interface ValidationErrors {
[key: string]: string
}
const WorkCenterForm: React.FC = () => {
const navigate = useNavigate()
const { id } = useParams<{ id: string }>()
const isEdit = Boolean(id)
const [loading, setLoading] = useState(false)
const [saving, setSaving] = useState(false)
const [errors, setErrors] = useState<ValidationErrors>({})
const [departments, setDepartments] = useState<HrDepartment[]>([])
const [formData, setFormData] = useState<PmWorkCenter>({
id: '',
code: '',
name: '',
description: '',
workCenterId: '',
workCenterType: {
id: '',
code: '',
name: '',
category: '',
isActive: true,
},
manufacturer: '',
model: '',
serialNumber: '',
installationDate: new Date('2022-03-15'),
warrantyExpiry: new Date('2022-03-15'),
location: '',
departmentId: '',
status: WorkCenterStatusEnum.Operational,
criticality: CriticalityLevelEnum.Medium,
specifications: [],
maintenancePlans: [],
workOrders: [],
downTimeHistory: [],
capacity: 0,
costPerHour: 0,
setupTime: 0,
machineTypeId: '',
isActive: true,
creationTime: new Date('2022-03-15'),
lastModificationTime: new Date('2024-01-15'),
})
const loadDepartments = useCallback(async () => {
try {
setDepartments(mockDepartments)
} catch (error) {
console.error('Error loading departments:', error)
}
}, [])
const loadFormData = useCallback(async () => {
setLoading(true)
try {
if (isEdit && id) {
// Simulate API call
await new Promise((resolve) => setTimeout(resolve, 1000))
const mockWorkCenter: PmWorkCenter = mockWorkCenters.find((eq) => eq.id === id)!
setFormData(mockWorkCenter)
}
} catch (error) {
console.error('Error loading form data:', error)
} finally {
setLoading(false)
}
}, [isEdit, id])
useEffect(() => {
loadDepartments()
loadFormData()
}, [loadDepartments, loadFormData])
const validateForm = (): boolean => {
const newErrors: ValidationErrors = {}
if (!formData.code.trim()) {
newErrors.workCenterCode = 'İş Merkezi kodu zorunludur'
}
if (!formData.name.trim()) {
newErrors.name = 'İş Merkezi adı zorunludur'
}
if (!formData.workCenterType) {
newErrors.workCenterType = 'İş Merkezi tipi seçilmelidir'
}
if (!formData.location.trim()) {
newErrors.location = 'Lokasyon zorunludur'
}
if (!formData.departmentId) {
newErrors.departmentId = 'Departman seçilmelidir'
}
if (!formData.installationDate) {
newErrors.installationDate = 'Kurulum tarihi zorunludur'
}
setErrors(newErrors)
return Object.keys(newErrors).length === 0
}
const handleInputChange = (
field: keyof PmWorkCenter,
value: string | boolean | PmWorkCenterSpecification[],
) => {
setFormData((prev) => ({
...prev,
[field]: value,
}))
// Clear error when user starts typing
if (errors[field]) {
setErrors((prev) => ({
...prev,
[field]: '',
}))
}
}
const addSpecification = () => {
const newSpec: PmWorkCenterSpecification = {
id: '',
workCenterId: '',
specificationName: '',
specificationValue: '',
unit: '',
isRequired: false,
}
setFormData((prev) => ({
...prev,
specifications: [...prev.specifications, newSpec],
}))
}
const updateSpecification = (
index: number,
field: keyof PmWorkCenterSpecification,
value: string | boolean,
) => {
const updatedSpecs = [...formData.specifications]
updatedSpecs[index] = { ...updatedSpecs[index], [field]: value }
setFormData((prev) => ({
...prev,
specifications: updatedSpecs,
}))
}
const removeSpecification = (index: number) => {
setFormData((prev) => ({
...prev,
specifications: prev.specifications.filter((_, i) => i !== index),
}))
}
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
if (!validateForm()) {
return
}
setSaving(true)
try {
// Simulate API call
await new Promise((resolve) => setTimeout(resolve, 2000))
console.log('WorkCenter data:', {
...formData,
id: isEdit ? id : undefined,
})
// Show success message
alert(isEdit ? 'İş Merkezi başarıyla güncellendi!' : 'İş Merkezi başarıyla oluşturuldu!')
// Navigate back to list
navigate(ROUTES_ENUM.protected.maintenance.workcenters)
} catch (error) {
console.error('Error saving Workcenter:', error)
alert('Bir hata oluştu. Lütfen tekrar deneyin.')
} finally {
setSaving(false)
}
}
const handleCancel = () => {
navigate(ROUTES_ENUM.protected.maintenance.workcenters)
}
if (loading) {
return <LoadingSpinner />
}
return (
<Container>
<div className="space-y-2">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h2 className="text-lg font-semibold text-gray-900">
{isEdit ? 'İş Merkezi Düzenle' : 'Yeni İş Merkezi'}
</h2>
<p className="text-sm text-gray-600 mt-1">
{isEdit
? 'Mevcut iş merkezi bilgilerini güncelleyin'
: 'Yeni iş merkezi bilgilerini girin'}
</p>
</div>
</div>
{/* Form */}
<form onSubmit={handleSubmit} className="space-y-3">
{/* Basic Information */}
<div className="bg-white shadow rounded-lg">
<div className="px-4 py-3 border-b border-gray-200">
<h3 className="text-base font-medium text-gray-900 flex items-center">
<FaCog className="w-5 h-5 mr-2" />
Genel Bilgiler
</h3>
</div>
<div className="px-4 py-3 space-y-3">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
İş Merkezi Kodu *
</label>
<select
value={formData.workCenterId}
onChange={(e) => handleInputChange('workCenterId', e.target.value)}
className={`block w-full px-2.5 py-1.5 text-sm border rounded-md shadow-sm focus:outline-none focus:ring-1 ${
errors.workCenterId
? 'border-red-300 focus:border-red-500 focus:ring-red-500'
: 'border-gray-300 focus:border-blue-500 focus:ring-blue-500'
}`}
>
<option value="">İş Merkezi Seçin</option>
{mockWorkCenters.map((workCenter) => (
<option key={workCenter.id} value={workCenter.id}>
{workCenter.code} - {workCenter.name}
</option>
))}
</select>
{errors.workCenterId && (
<p className="mt-1 text-sm text-red-600">{errors.workCenterId}</p>
)}
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
İş Merkezi Tipi *
</label>
<select
value={formData.workCenterType?.id}
onChange={(e) => handleInputChange('workCenterType', e.target.value)}
className={`block w-full px-2.5 py-1.5 text-sm border rounded-md shadow-sm focus:outline-none focus:ring-1 ${
errors.workCenterType
? 'border-red-300 focus:border-red-500 focus:ring-red-500'
: 'border-gray-300 focus:border-blue-500 focus:ring-blue-500'
}`}
>
<option value="">Tip seçin</option>
{mockWorkCenters.map((wc) => (
<option key={wc.workCenterType?.id} value={wc.workCenterType?.id}>
{wc.workCenterType?.code} - {wc.workCenterType?.name}
</option>
))}
</select>
{errors.workCenterType && (
<p className="mt-1 text-sm text-red-600">{errors.workCenterType}</p>
)}
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
İş Merkezi Adı *
</label>
<input
type="text"
value={formData.name}
onChange={(e) => handleInputChange('name', e.target.value)}
className={`block w-full px-2.5 py-1.5 text-sm border rounded-md shadow-sm focus:outline-none focus:ring-1 ${
errors.name
? 'border-red-300 focus:border-red-500 focus:ring-red-500'
: 'border-gray-300 focus:border-blue-500 focus:ring-blue-500'
}`}
placeholder="İş Merkezi adı"
/>
{errors.name && <p className="mt-1 text-sm text-red-600">{errors.name}</p>}
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">ıklama</label>
<textarea
value={formData.description}
onChange={(e) => handleInputChange('description', e.target.value)}
rows={2}
className="block w-full px-2.5 py-1.5 text-sm border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
placeholder="İş Merkezi açıklaması"
/>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Üretici</label>
<input
type="text"
value={formData.manufacturer}
onChange={(e) => handleInputChange('manufacturer', e.target.value)}
className="block w-full px-2.5 py-1.5 text-sm border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
placeholder="Üretici firma"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Model</label>
<input
type="text"
value={formData.model}
onChange={(e) => handleInputChange('model', e.target.value)}
className="block w-full px-2.5 py-1.5 text-sm border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
placeholder="Model numarası"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Seri Numarası
</label>
<input
type="text"
value={formData.serialNumber}
onChange={(e) => handleInputChange('serialNumber', e.target.value)}
className="block w-full px-2.5 py-1.5 text-sm border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
placeholder="Seri numarası"
/>
</div>
</div>
</div>
</div>
{/* Location and Status */}
<div className="bg-white shadow rounded-lg">
<div className="px-4 py-3 border-b border-gray-200">
<h3 className="text-base font-medium text-gray-900 flex items-center">
<FaMapMarkerAlt className="w-5 h-5 mr-2" />
Lokasyon ve Durum
</h3>
</div>
<div className="px-4 py-3 space-y-3">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Lokasyon *</label>
<input
type="text"
value={formData.location}
onChange={(e) => handleInputChange('location', e.target.value)}
className={`block w-full px-2.5 py-1.5 text-sm border rounded-md shadow-sm focus:outline-none focus:ring-1 ${
errors.location
? 'border-red-300 focus:border-red-500 focus:ring-red-500'
: 'border-gray-300 focus:border-blue-500 focus:ring-blue-500'
}`}
placeholder="İş Merkezinin bulunduğu lokasyon"
/>
{errors.location && <p className="mt-1 text-sm text-red-600">{errors.location}</p>}
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Departman *
</label>
<select
value={formData.departmentId}
onChange={(e) => handleInputChange('departmentId', e.target.value)}
className={`block w-full px-2.5 py-1.5 text-sm border rounded-md shadow-sm focus:outline-none focus:ring-1 ${
errors.departmentId
? 'border-red-300 focus:border-red-500 focus:ring-red-500'
: 'border-gray-300 focus:border-blue-500 focus:ring-blue-500'
}`}
>
<option value="">Departman seçin</option>
{departments.map((dept) => (
<option key={dept.id} value={dept.id}>
{dept.name}
</option>
))}
</select>
{errors.departmentId && (
<p className="mt-1 text-sm text-red-600">{errors.departmentId}</p>
)}
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Durum</label>
<select
value={formData.status}
onChange={(e) => handleInputChange('status', e.target.value)}
className="block w-full px-2.5 py-1.5 text-sm border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
>
{Object.values(WorkCenterStatusEnum).map((status) => (
<option key={status} value={status}>
{getWorkCenterStatusText(status)}
</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Kritiklik Seviyesi
</label>
<select
value={formData.criticality}
onChange={(e) => handleInputChange('criticality', e.target.value)}
className="block w-full px-2.5 py-1.5 text-sm border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
>
{Object.values(CriticalityLevelEnum).map((level) => (
<option key={level} value={level}>
{getCriticalityLevelText(level)}
</option>
))}
</select>
</div>
</div>
</div>
</div>
{/* Date Information */}
<div className="bg-white shadow rounded-lg">
<div className="px-4 py-3 border-b border-gray-200">
<h3 className="text-base font-medium text-gray-900 flex items-center">
<FaCalendar className="w-5 h-5 mr-2" />
Tarih Bilgileri
</h3>
</div>
<div className="px-4 py-3 space-y-3">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Kurulum Tarihi *
</label>
<input
type="date"
value={formData.installationDate.toISOString()}
onChange={(e) => handleInputChange('installationDate', e.target.value)}
className={`block w-full px-2.5 py-1.5 text-sm border rounded-md shadow-sm focus:outline-none focus:ring-1 ${
errors.installationDate
? 'border-red-300 focus:border-red-500 focus:ring-red-500'
: 'border-gray-300 focus:border-blue-500 focus:ring-blue-500'
}`}
/>
{errors.installationDate && (
<p className="mt-1 text-sm text-red-600">{errors.installationDate}</p>
)}
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Garanti Bitiş Tarihi
</label>
<input
type="date"
value={formData.warrantyExpiry?.toISOString()}
onChange={(e) => handleInputChange('warrantyExpiry', e.target.value)}
className="block w-full px-2.5 py-1.5 text-sm border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
/>
</div>
</div>
</div>
</div>
{/* Specifications */}
<div className="bg-white shadow rounded-lg">
<div className="px-4 py-3 border-b border-gray-200">
<div className="flex items-center justify-between">
<h3 className="text-base font-medium text-gray-900 flex items-center">
<FaWrench className="w-5 h-5 mr-2" />
Teknik Özellikler
</h3>
<button
type="button"
onClick={addSpecification}
className="inline-flex items-center px-2.5 py-1.5 border border-transparent text-sm leading-4 font-medium rounded-md text-blue-700 bg-blue-100 hover:bg-blue-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
>
<FaCog className="w-4 h-4 mr-1" />
Özellik Ekle
</button>
</div>
</div>
<div className="px-4 py-3">
{formData.specifications.length === 0 ? (
<p className="text-gray-500 text-sm">Henüz teknik özellik eklenmemiş.</p>
) : (
<div className="space-y-3">
{formData.specifications.map((spec, index) => (
<div key={index} className="grid grid-cols-1 md:grid-cols-4 gap-3 items-end">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Özellik Adı
</label>
<input
type="text"
value={spec.specificationName}
onChange={(e) =>
updateSpecification(index, 'specificationName', e.target.value)
}
className="block w-full px-2.5 py-1.5 text-sm border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
placeholder="Özellik adı"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Değer
</label>
<input
type="text"
value={spec.specificationValue}
onChange={(e) =>
updateSpecification(index, 'specificationValue', e.target.value)
}
className="block w-full px-2.5 py-1.5 text-sm border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
placeholder="Değer"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Birim
</label>
<input
type="text"
value={spec.unit}
onChange={(e) => updateSpecification(index, 'unit', e.target.value)}
className="block w-full px-2.5 py-1.5 text-sm border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
placeholder="Birim"
/>
</div>
<div className="flex items-center space-x-2">
<label className="flex items-center">
<input
type="checkbox"
checked={spec.isRequired}
onChange={(e) =>
updateSpecification(index, 'isRequired', e.target.checked)
}
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
/>
<span className="ml-2 text-sm text-gray-700">Zorunlu</span>
</label>
<button
type="button"
onClick={() => removeSpecification(index)}
className="inline-flex items-center p-1.5 border border-transparent text-sm font-medium rounded text-red-700 bg-red-100 hover:bg-red-200"
>
<FaExclamationTriangle className="w-3 h-3" />
</button>
</div>
</div>
))}
</div>
)}
</div>
</div>
{/* Status */}
<div className="bg-white shadow rounded-lg">
<div className="px-4 py-3">
<div className="flex items-center">
<input
type="checkbox"
id="isActive"
checked={formData.isActive}
onChange={(e) => handleInputChange('isActive', e.target.checked)}
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
/>
<label htmlFor="isActive" className="ml-2 block text-sm text-gray-900">
Aktif
</label>
</div>
</div>
</div>
{/* Form Actions */}
<div className="flex items-center justify-end space-x-2 bg-white px-4 py-3 rounded-lg shadow">
<button
type="button"
onClick={handleCancel}
className="inline-flex items-center px-3 py-1.5 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
>
<FaTimes className="w-4 h-4 mr-2" />
İptal
</button>
<button
type="submit"
disabled={saving}
className="inline-flex items-center px-3 py-1.5 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
>
{saving ? (
<>
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2"></div>
Kaydediliyor...
</>
) : (
<>
<FaSave className="w-4 h-4 mr-2" />
{isEdit ? 'Güncelle' : 'Kaydet'}
</>
)}
</button>
</div>
</form>
</div>
</Container>
)
}
export default WorkCenterForm