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

483 lines
18 KiB
TypeScript
Raw Normal View History

2025-09-15 09:31:47 +00:00
import React, { useState } from "react";
import { FaTimes, FaSave, FaUpload, FaMinus } from "react-icons/fa";
import {
PmFaultNotification,
FaultTypeEnum,
CriticalityLevelEnum,
NotificationStatusEnum,
} from "../../../types/pm";
import { mockWorkCenters } from "../../../mocks/mockWorkCenters";
import { mockEmployees } from "../../../mocks/mockEmployees";
import { PriorityEnum } from "../../../types/common";
interface NewFaultNotificationModalProps {
isOpen: boolean;
onClose: () => void;
onSave: (notification: Partial<PmFaultNotification>) => void;
}
const NewFaultNotificationModal: React.FC<NewFaultNotificationModalProps> = ({
isOpen,
onClose,
onSave,
}) => {
const [notificationData, setNotificationData] = useState<
Partial<PmFaultNotification>
>({
notificationCode: `ARZ-${new Date().getFullYear()}-${String(
Date.now()
).slice(-3)}`,
faultType: FaultTypeEnum.Mechanical,
priority: PriorityEnum.Normal,
severity: CriticalityLevelEnum.Medium,
status: NotificationStatusEnum.Open,
followUpRequired: false,
isActive: true,
images: [],
});
const [errors, setErrors] = useState<Record<string, string>>({});
const [uploadedImages, setUploadedImages] = useState<string[]>([]);
if (!isOpen) return null;
const validateForm = () => {
const newErrors: Record<string, string> = {};
if (!notificationData.workCenterId) {
newErrors.workCenterId = "İş merkezi seçimi gerekli";
}
if (!notificationData.title?.trim()) {
newErrors.title = "Başlık gerekli";
}
if (!notificationData.description?.trim()) {
newErrors.description = "Açıklama gerekli";
}
if (!notificationData.location?.trim()) {
newErrors.location = "Konum gerekli";
}
if (!notificationData.reportedBy?.trim()) {
newErrors.reportedBy = "Bildiren kişi gerekli";
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleInputChange = (
field: keyof PmFaultNotification,
value: string | number | boolean | undefined
) => {
setNotificationData((prev) => ({
...prev,
[field]: value,
}));
// Clear error when user starts typing
if (errors[field]) {
setErrors((prev) => ({
...prev,
[field]: "",
}));
}
};
const handleWorkCenterChange = (workCenterId: string) => {
const selectedWorkCenter = mockWorkCenters.find(
(eq) => eq.id === workCenterId
);
if (selectedWorkCenter) {
setNotificationData((prev) => ({
...prev,
workCenterId: selectedWorkCenter.id,
workCenterCode: selectedWorkCenter.code,
workCenterName: selectedWorkCenter.name,
location: selectedWorkCenter.location,
}));
}
};
const handleImageUpload = (event: React.ChangeEvent<HTMLInputElement>) => {
const files = event.target.files;
if (files) {
const imageNames = Array.from(files).map((file) => file.name);
setUploadedImages((prev) => [...prev, ...imageNames]);
setNotificationData((prev) => ({
...prev,
images: [...(prev.images || []), ...imageNames],
}));
}
};
const removeImage = (index: number) => {
setUploadedImages((prev) => prev.filter((_, i) => i !== index));
setNotificationData((prev) => ({
...prev,
images: prev.images?.filter((_, i) => i !== index) || [],
}));
};
const handleSave = () => {
if (validateForm()) {
const notificationToSave: Partial<PmFaultNotification> = {
...notificationData,
id: `FN${Date.now()}`,
reportedAt: new Date(),
creationTime: new Date(),
lastModificationTime: new Date(),
};
onSave(notificationToSave);
onClose();
// Reset form
setNotificationData({
notificationCode: `ARZ-${new Date().getFullYear()}-${String(
Date.now()
).slice(-3)}`,
faultType: FaultTypeEnum.Mechanical,
priority: PriorityEnum.Normal,
severity: CriticalityLevelEnum.Medium,
status: NotificationStatusEnum.Open,
followUpRequired: false,
isActive: true,
images: [],
});
setUploadedImages([]);
setErrors({});
}
};
return (
<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-xl font-bold text-gray-900">
Yeni Arıza Bildirimi
</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-3 space-y-3">
{/* 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-1">
Bildirim Kodu *
</label>
<input
type="text"
value={notificationData.notificationCode || ""}
onChange={(e) =>
handleInputChange("notificationCode", e.target.value)
}
className="w-full 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="ARZ-2024-001"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
İş Merkezi *
</label>
<select
value={notificationData.workCenterId || ""}
onChange={(e) => handleWorkCenterChange(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.workCenterId ? "border-red-500" : "border-gray-300"
}`}
>
<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="text-red-500 text-xs mt-1">
{errors.workCenterId}
</p>
)}
</div>
</div>
{/* Title and Description */}
<div className="space-y-3">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Arıza Başlığı *
</label>
<input
type="text"
value={notificationData.title || ""}
onChange={(e) => handleInputChange("title", 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.title ? "border-red-500" : "border-gray-300"
}`}
placeholder="Arıza başlığı"
/>
{errors.title && (
<p className="text-red-500 text-xs mt-1">{errors.title}</p>
)}
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Detaylı ıklama *
</label>
<textarea
value={notificationData.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="Arızanın detaylııklaması, belirtiler ve gözlemler"
/>
{errors.description && (
<p className="text-red-500 text-xs mt-1">
{errors.description}
</p>
)}
</div>
</div>
{/* Location and Reporter */}
<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">
Konum *
</label>
<input
type="text"
value={notificationData.location || ""}
onChange={(e) => handleInputChange("location", 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.location ? "border-red-500" : "border-gray-300"
}`}
placeholder="Atölye A - Hat 1"
/>
{errors.location && (
<p className="text-red-500 text-xs mt-1">{errors.location}</p>
)}
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Bildiren Kişi *
</label>
<select
value={notificationData.reportedBy || ""}
onChange={(e) =>
handleInputChange("reportedBy", 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.reportedBy ? "border-red-500" : "border-gray-300"
}`}
>
<option value="">Bildiren kişi seçin</option>
{mockEmployees.map((employee) => (
<option key={employee.id} value={employee.fullName}>
{employee.fullName} - {employee.jobPosition?.name}
</option>
))}
</select>
{errors.reportedBy && (
<p className="text-red-500 text-xs mt-1">{errors.reportedBy}</p>
)}
</div>
</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">
Arıza Türü
</label>
<select
value={notificationData.faultType || FaultTypeEnum.Mechanical}
onChange={(e) =>
handleInputChange(
"faultType",
e.target.value as FaultTypeEnum
)
}
className="w-full 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"
>
<option value={FaultTypeEnum.Mechanical}>Mekanik</option>
<option value={FaultTypeEnum.Electrical}>Elektrik</option>
<option value={FaultTypeEnum.Hydraulic}>Hidrolik</option>
<option value={FaultTypeEnum.Pneumatic}>Pnömatik</option>
<option value={FaultTypeEnum.Software}>Yazılım</option>
<option value={FaultTypeEnum.Safety}>Güvenlik</option>
<option value={FaultTypeEnum.Performance}>Performans</option>
<option value={FaultTypeEnum.Other}>Diğer</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Öncelik
</label>
<select
value={notificationData.priority || PriorityEnum.Normal}
onChange={(e) =>
handleInputChange("priority", e.target.value as PriorityEnum)
}
className="w-full 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"
>
<option value={PriorityEnum.Low}>Düşük</option>
<option value={PriorityEnum.Normal}>Normal</option>
<option value={PriorityEnum.High}>Yüksek</option>
<option value={PriorityEnum.Urgent}>Acil</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Kritiklik
</label>
<select
value={notificationData.severity || CriticalityLevelEnum.Medium}
onChange={(e) =>
handleInputChange(
"severity",
e.target.value as CriticalityLevelEnum
)
}
className="w-full 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"
>
<option value={CriticalityLevelEnum.Low}>Düşük</option>
<option value={CriticalityLevelEnum.Medium}>Orta</option>
<option value={CriticalityLevelEnum.High}>Yüksek</option>
<option value={CriticalityLevelEnum.Critical}>Kritik</option>
</select>
</div>
</div>
{/* Estimated Repair Time */}
<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">
Tahmini Onarım Süresi (dakika)
</label>
<input
type="number"
value={notificationData.estimatedRepairTime || ""}
onChange={(e) =>
handleInputChange(
"estimatedRepairTime",
parseInt(e.target.value) || undefined
)
}
className="w-full 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="120"
min="0"
/>
</div>
<div className="flex items-center">
<label className="flex items-center">
<input
type="checkbox"
checked={notificationData.followUpRequired || false}
onChange={(e) =>
handleInputChange("followUpRequired", 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">
Takip gerekli
</span>
</label>
</div>
</div>
{/* Image Upload */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Fotoğraflar
</label>
<div className="border-2 border-dashed border-gray-300 rounded-lg p-4">
<div className="text-center">
<FaUpload className="mx-auto h-10 w-10 text-gray-400" />
<div className="mt-4">
<label htmlFor="image-upload" className="cursor-pointer">
<span className="mt-2 block text-sm font-medium text-gray-900">
Fotoğraf yükleyin
</span>
<span className="mt-1 block text-sm text-gray-500">
PNG, JPG, GIF formatında dosyalar
</span>
</label>
<input
id="image-upload"
name="image-upload"
type="file"
multiple
accept="image/*"
className="sr-only"
onChange={handleImageUpload}
/>
</div>
</div>
</div>
{uploadedImages.length > 0 && (
<div className="mt-4">
<h4 className="text-sm font-medium text-gray-700 mb-1">
Yüklenen Fotoğraflar:
</h4>
<div className="space-y-2">
{uploadedImages.map((image, index) => (
<div
key={index}
className="flex items-center justify-between p-1.5 bg-gray-50 rounded"
>
<span className="text-sm text-gray-600">{image}</span>
<button
type="button"
onClick={() => removeImage(index)}
className="text-red-600 hover:text-red-800"
>
<FaMinus className="w-3 h-3" />
</button>
</div>
))}
</div>
</div>
)}
</div>
</div>
{/* Footer */}
<div className="flex items-center justify-end space-x-2 p-3 border-t border-gray-200">
<button
onClick={onClose}
className="px-3 py-1 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 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 NewFaultNotificationModal;