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

719 lines
26 KiB
TypeScript
Raw Normal View History

2025-09-15 09:31:47 +00:00
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";
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,
machineType: "",
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("/admin/maintenance");
} catch (error) {
console.error("Error saving Workcenter:", error);
alert("Bir hata oluştu. Lütfen tekrar deneyin.");
} finally {
setSaving(false);
}
};
const handleCancel = () => {
navigate("/admin/maintenance");
};
if (loading) {
return <LoadingSpinner />;
}
return (
<div className="space-y-3 pt-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>
<option value="MACHINE">Makine</option>
<option value="VEHICLE">Araç</option>
<option value="TOOL">Alet</option>
<option value="FACILITY">Tesis</option>
<option value="SYSTEM">Sistem</option>
<option value="OTHER">Diğer</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"
>
<option value="OPERATIONAL">Çalışır Durumda</option>
<option value="DOWN">Arızalı</option>
<option value="MAINTENANCE">Bakımda</option>
<option value="RETIRED">Kullanım Dışı</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"
>
<option value="LOW">Düşük</option>
<option value="MEDIUM">Orta</option>
<option value="HIGH">Yüksek</option>
<option value="CRITICAL">Kritik</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>
);
};
export default WorkCenterForm;