908 lines
34 KiB
TypeScript
908 lines
34 KiB
TypeScript
import React, { useState, useEffect } from "react";
|
||
import {
|
||
FaTimes,
|
||
FaPlus,
|
||
FaTrash,
|
||
FaCalendar,
|
||
FaClock,
|
||
FaSave,
|
||
} from "react-icons/fa";
|
||
import {
|
||
PmMaintenanceWorkOrder,
|
||
WorkOrderTypeEnum,
|
||
WorkOrderStatusEnum,
|
||
PmWorkOrderMaterial,
|
||
PmWorkOrderActivity,
|
||
} from "../../../types/pm";
|
||
import { mockWorkCenters } from "../../../mocks/mockWorkCenters";
|
||
import { mockMaintenanceTeams } from "../../../mocks/mockMaintenanceTeams";
|
||
import { mockEmployees } from "../../../mocks/mockEmployees";
|
||
import { mockMaterials } from "../../../mocks/mockMaterials";
|
||
import { PriorityEnum } from "../../../types/common";
|
||
|
||
interface EditWorkOrderModalProps {
|
||
isOpen: boolean;
|
||
onClose: () => void;
|
||
onSave: (workOrder: PmMaintenanceWorkOrder) => void;
|
||
workOrder: PmMaintenanceWorkOrder;
|
||
}
|
||
|
||
const EditWorkOrderModal: React.FC<EditWorkOrderModalProps> = ({
|
||
isOpen,
|
||
onClose,
|
||
onSave,
|
||
workOrder,
|
||
}) => {
|
||
const [formData, setFormData] = useState({
|
||
workOrderNumber: "",
|
||
workCenterId: "",
|
||
orderType: WorkOrderTypeEnum.Corrective,
|
||
priority: PriorityEnum.Normal,
|
||
status: WorkOrderStatusEnum.Created,
|
||
description: "",
|
||
reportedBy: "",
|
||
assignedTo: "",
|
||
maintenanceTeamId: "",
|
||
scheduledStart: "",
|
||
scheduledEnd: "",
|
||
actualStart: "",
|
||
actualEnd: "",
|
||
estimatedCost: 0,
|
||
actualCost: 0,
|
||
notes: "",
|
||
completionNotes: "",
|
||
});
|
||
|
||
const [materials, setMaterials] = useState<PmWorkOrderMaterial[]>([]);
|
||
const [activities, setActivities] = useState<PmWorkOrderActivity[]>([]);
|
||
const [errors, setErrors] = useState<Record<string, string>>({});
|
||
|
||
useEffect(() => {
|
||
if (workOrder && isOpen) {
|
||
setFormData({
|
||
workOrderNumber: workOrder.workOrderNumber,
|
||
workCenterId: workOrder.workCenterId,
|
||
orderType: workOrder.orderType,
|
||
priority: workOrder.priority,
|
||
status: workOrder.status,
|
||
description: workOrder.description,
|
||
reportedBy: workOrder.reportedBy,
|
||
assignedTo: workOrder.assignedTo || "",
|
||
maintenanceTeamId: workOrder.maintenanceTeamId || "",
|
||
scheduledStart: workOrder.scheduledStart
|
||
? workOrder.scheduledStart.toISOString().slice(0, 16)
|
||
: "",
|
||
scheduledEnd: workOrder.scheduledEnd
|
||
? workOrder.scheduledEnd.toISOString().slice(0, 16)
|
||
: "",
|
||
actualStart: workOrder.actualStart
|
||
? workOrder.actualStart.toISOString().slice(0, 16)
|
||
: "",
|
||
actualEnd: workOrder.actualEnd
|
||
? workOrder.actualEnd.toISOString().slice(0, 16)
|
||
: "",
|
||
estimatedCost: workOrder.estimatedCost,
|
||
actualCost: workOrder.actualCost,
|
||
notes: workOrder.notes || "",
|
||
completionNotes: workOrder.completionNotes || "",
|
||
});
|
||
setMaterials(workOrder.materials);
|
||
setActivities(workOrder.activities);
|
||
}
|
||
}, [workOrder, isOpen]);
|
||
|
||
const validateForm = () => {
|
||
const newErrors: Record<string, string> = {};
|
||
|
||
if (!formData.description.trim()) {
|
||
newErrors.description = "Açıklama alanı zorunludur";
|
||
}
|
||
if (!formData.workCenterId) {
|
||
newErrors.workCenterId = "İş merkezi seçimi zorunludur";
|
||
}
|
||
if (!formData.reportedBy.trim()) {
|
||
newErrors.reportedBy = "Bildiren kişi zorunludur";
|
||
}
|
||
if (formData.estimatedCost < 0) {
|
||
newErrors.estimatedCost = "Tahmini maliyet negatif olamaz";
|
||
}
|
||
if (formData.actualCost < 0) {
|
||
newErrors.actualCost = "Gerçek maliyet negatif olamaz";
|
||
}
|
||
|
||
setErrors(newErrors);
|
||
return Object.keys(newErrors).length === 0;
|
||
};
|
||
|
||
const handleSubmit = (e: React.FormEvent) => {
|
||
e.preventDefault();
|
||
if (!validateForm()) return;
|
||
|
||
const updatedWorkOrder: PmMaintenanceWorkOrder = {
|
||
...workOrder,
|
||
workOrderNumber: formData.workOrderNumber,
|
||
workCenterId: formData.workCenterId,
|
||
orderType: formData.orderType,
|
||
priority: formData.priority,
|
||
status: formData.status,
|
||
description: formData.description,
|
||
reportedBy: formData.reportedBy,
|
||
assignedTo: formData.assignedTo || undefined,
|
||
maintenanceTeamId: formData.maintenanceTeamId || undefined,
|
||
scheduledStart: formData.scheduledStart
|
||
? new Date(formData.scheduledStart)
|
||
: undefined,
|
||
scheduledEnd: formData.scheduledEnd
|
||
? new Date(formData.scheduledEnd)
|
||
: undefined,
|
||
actualStart: formData.actualStart
|
||
? new Date(formData.actualStart)
|
||
: undefined,
|
||
actualEnd: formData.actualEnd ? new Date(formData.actualEnd) : undefined,
|
||
estimatedCost: formData.estimatedCost,
|
||
actualCost: formData.actualCost,
|
||
materials,
|
||
activities,
|
||
notes: formData.notes || undefined,
|
||
completionNotes: formData.completionNotes || undefined,
|
||
lastModificationTime: new Date(),
|
||
};
|
||
|
||
onSave(updatedWorkOrder);
|
||
onClose();
|
||
};
|
||
|
||
const addMaterial = () => {
|
||
const newMaterial: PmWorkOrderMaterial = {
|
||
id: `mat-${Date.now()}`,
|
||
workOrderId: workOrder.id,
|
||
materialId: "",
|
||
materialCode: "",
|
||
materialName: "",
|
||
plannedQuantity: 1,
|
||
actualQuantity: 0,
|
||
unitCost: 0,
|
||
totalCost: 0,
|
||
};
|
||
setMaterials([...materials, newMaterial]);
|
||
};
|
||
|
||
const removeMaterial = (index: number) => {
|
||
setMaterials(materials.filter((_, i) => i !== index));
|
||
};
|
||
|
||
const updateMaterial = (
|
||
index: number,
|
||
field: string,
|
||
value: string | number
|
||
) => {
|
||
const updated = [...materials];
|
||
if (field === "materialId") {
|
||
const selectedMaterial = mockMaterials.find((m) => m.id === value);
|
||
if (selectedMaterial) {
|
||
updated[index] = {
|
||
...updated[index],
|
||
materialId: value as string,
|
||
materialCode: selectedMaterial.code,
|
||
materialName: selectedMaterial.name,
|
||
unitCost: selectedMaterial.costPrice,
|
||
totalCost:
|
||
updated[index].plannedQuantity * selectedMaterial.costPrice,
|
||
};
|
||
}
|
||
} else {
|
||
const material = updated[index];
|
||
if (field === "plannedQuantity") {
|
||
material.plannedQuantity = value as number;
|
||
material.totalCost = material.plannedQuantity * material.unitCost;
|
||
} else if (field === "actualQuantity") {
|
||
material.actualQuantity = value as number;
|
||
} else if (field === "unitCost") {
|
||
material.unitCost = value as number;
|
||
material.totalCost = material.plannedQuantity * material.unitCost;
|
||
}
|
||
}
|
||
setMaterials(updated);
|
||
};
|
||
|
||
const addActivity = () => {
|
||
const newActivity: PmWorkOrderActivity = {
|
||
id: `act-${Date.now()}`,
|
||
workOrderId: workOrder.id,
|
||
activityDescription: "",
|
||
plannedDuration: 60,
|
||
actualDuration: 0,
|
||
performedBy: "",
|
||
notes: "",
|
||
};
|
||
setActivities([...activities, newActivity]);
|
||
};
|
||
|
||
const removeActivity = (index: number) => {
|
||
setActivities(activities.filter((_, i) => i !== index));
|
||
};
|
||
|
||
const updateActivity = (
|
||
index: number,
|
||
field: string,
|
||
value: string | number
|
||
) => {
|
||
const updated = [...activities];
|
||
const activity = updated[index];
|
||
if (field === "activityDescription") {
|
||
activity.activityDescription = value as string;
|
||
} else if (field === "plannedDuration") {
|
||
activity.plannedDuration = value as number;
|
||
} else if (field === "actualDuration") {
|
||
activity.actualDuration = value as number;
|
||
} else if (field === "performedBy") {
|
||
activity.performedBy = value as string;
|
||
} else if (field === "notes") {
|
||
activity.notes = value as string;
|
||
}
|
||
setActivities(updated);
|
||
};
|
||
|
||
const toggleActivityCompletion = (index: number) => {
|
||
const updated = [...activities];
|
||
const activity = updated[index];
|
||
if (activity.completedAt) {
|
||
activity.completedAt = undefined;
|
||
} else {
|
||
activity.completedAt = new Date();
|
||
}
|
||
setActivities(updated);
|
||
};
|
||
|
||
if (!isOpen) return null;
|
||
|
||
return (
|
||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||
<div className="bg-white rounded-lg p-4 w-full max-w-5xl mx-4 max-h-[90vh] overflow-y-auto">
|
||
<div className="flex items-center justify-between mb-4">
|
||
<h3 className="text-lg font-semibold text-gray-900 flex items-center">
|
||
<FaSave className="w-5 h-5 mr-2 text-blue-600" />
|
||
İş Emrini Düzenle
|
||
</h3>
|
||
<button
|
||
onClick={onClose}
|
||
className="text-gray-400 hover:text-gray-600"
|
||
>
|
||
<FaTimes className="w-5 h-5" />
|
||
</button>
|
||
</div>
|
||
|
||
<form onSubmit={handleSubmit} className="space-y-4">
|
||
{/* Basic Information */}
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||
İş Emri No
|
||
</label>
|
||
<input
|
||
type="text"
|
||
value={formData.workOrderNumber}
|
||
onChange={(e) =>
|
||
setFormData({ ...formData, workOrderNumber: e.target.value })
|
||
}
|
||
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||
readOnly
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||
Durum
|
||
</label>
|
||
<select
|
||
value={formData.status}
|
||
onChange={(e) =>
|
||
setFormData({
|
||
...formData,
|
||
status: e.target.value as WorkOrderStatusEnum,
|
||
})
|
||
}
|
||
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||
>
|
||
<option value={WorkOrderStatusEnum.Created}>Oluşturuldu</option>
|
||
<option value={WorkOrderStatusEnum.Planned}>Planlandı</option>
|
||
<option value={WorkOrderStatusEnum.Released}>
|
||
Serbest Bırakıldı
|
||
</option>
|
||
<option value={WorkOrderStatusEnum.InProgress}>
|
||
Devam Ediyor
|
||
</option>
|
||
<option value={WorkOrderStatusEnum.OnHold}>Beklemede</option>
|
||
<option value={WorkOrderStatusEnum.Completed}>
|
||
Tamamlandı
|
||
</option>
|
||
<option value={WorkOrderStatusEnum.Cancelled}>
|
||
İptal Edildi
|
||
</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||
İş Merkezi *
|
||
</label>
|
||
<select
|
||
value={formData.workCenterId}
|
||
onChange={(e) =>
|
||
setFormData({ ...formData, workCenterId: e.target.value })
|
||
}
|
||
className={`w-full px-3 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="mt-1 text-sm text-red-600">
|
||
{errors.workCenterId}
|
||
</p>
|
||
)}
|
||
</div>
|
||
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||
İş Emri Tipi
|
||
</label>
|
||
<select
|
||
value={formData.orderType}
|
||
onChange={(e) =>
|
||
setFormData({
|
||
...formData,
|
||
orderType: e.target.value as WorkOrderTypeEnum,
|
||
})
|
||
}
|
||
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||
>
|
||
<option value={WorkOrderTypeEnum.Preventive}>Önleyici</option>
|
||
<option value={WorkOrderTypeEnum.Corrective}>Düzeltici</option>
|
||
<option value={WorkOrderTypeEnum.Emergency}>Acil</option>
|
||
<option value={WorkOrderTypeEnum.Inspection}>İnceleme</option>
|
||
<option value={WorkOrderTypeEnum.Calibration}>
|
||
Kalibrasyon
|
||
</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||
Öncelik
|
||
</label>
|
||
<select
|
||
value={formData.priority}
|
||
onChange={(e) =>
|
||
setFormData({
|
||
...formData,
|
||
priority: e.target.value as PriorityEnum,
|
||
})
|
||
}
|
||
className="w-full px-3 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>
|
||
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||
Açıklama *
|
||
</label>
|
||
<textarea
|
||
value={formData.description}
|
||
onChange={(e) =>
|
||
setFormData({ ...formData, description: e.target.value })
|
||
}
|
||
rows={2}
|
||
className={`w-full px-3 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="İş emri açıklaması..."
|
||
/>
|
||
{errors.description && (
|
||
<p className="mt-1 text-sm text-red-600">{errors.description}</p>
|
||
)}
|
||
</div>
|
||
|
||
{/* Assignment */}
|
||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||
Bildiren *
|
||
</label>
|
||
<input
|
||
type="text"
|
||
value={formData.reportedBy}
|
||
onChange={(e) =>
|
||
setFormData({ ...formData, reportedBy: e.target.value })
|
||
}
|
||
className={`w-full px-3 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"
|
||
}`}
|
||
placeholder="Bildiren kişi adı"
|
||
/>
|
||
{errors.reportedBy && (
|
||
<p className="mt-1 text-sm text-red-600">{errors.reportedBy}</p>
|
||
)}
|
||
</div>
|
||
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||
Atanan Kişi
|
||
</label>
|
||
<select
|
||
value={formData.assignedTo}
|
||
onChange={(e) =>
|
||
setFormData({ ...formData, assignedTo: e.target.value })
|
||
}
|
||
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||
>
|
||
<option value="">Kişi Seçin</option>
|
||
{mockEmployees.map((employee) => (
|
||
<option key={employee.id} value={employee.fullName}>
|
||
{employee.fullName} - {employee.jobPosition?.name}
|
||
</option>
|
||
))}
|
||
</select>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||
Bakım Ekibi
|
||
</label>
|
||
<select
|
||
value={formData.maintenanceTeamId}
|
||
onChange={(e) =>
|
||
setFormData({
|
||
...formData,
|
||
maintenanceTeamId: e.target.value,
|
||
})
|
||
}
|
||
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||
>
|
||
<option value="">Ekip Seçin</option>
|
||
{mockMaintenanceTeams.map((team) => (
|
||
<option key={team.id} value={team.id}>
|
||
{team.name}
|
||
</option>
|
||
))}
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Schedule */}
|
||
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||
<FaCalendar className="w-4 h-4 inline mr-2" />
|
||
Planlanan Başlangıç
|
||
</label>
|
||
<input
|
||
type="datetime-local"
|
||
value={formData.scheduledStart}
|
||
onChange={(e) =>
|
||
setFormData({ ...formData, scheduledStart: e.target.value })
|
||
}
|
||
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||
<FaCalendar className="w-4 h-4 inline mr-2" />
|
||
Planlanan Bitiş
|
||
</label>
|
||
<input
|
||
type="datetime-local"
|
||
value={formData.scheduledEnd}
|
||
onChange={(e) =>
|
||
setFormData({ ...formData, scheduledEnd: e.target.value })
|
||
}
|
||
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||
<FaCalendar className="w-4 h-4 inline mr-2" />
|
||
Gerçek Başlangıç
|
||
</label>
|
||
<input
|
||
type="datetime-local"
|
||
value={formData.actualStart}
|
||
onChange={(e) =>
|
||
setFormData({ ...formData, actualStart: e.target.value })
|
||
}
|
||
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||
<FaCalendar className="w-4 h-4 inline mr-2" />
|
||
Gerçek Bitiş
|
||
</label>
|
||
<input
|
||
type="datetime-local"
|
||
value={formData.actualEnd}
|
||
onChange={(e) =>
|
||
setFormData({ ...formData, actualEnd: e.target.value })
|
||
}
|
||
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Costs */}
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||
Tahmini Maliyet
|
||
</label>
|
||
<input
|
||
type="number"
|
||
min="0"
|
||
step="0.01"
|
||
value={formData.estimatedCost}
|
||
onChange={(e) =>
|
||
setFormData({
|
||
...formData,
|
||
estimatedCost: parseFloat(e.target.value) || 0,
|
||
})
|
||
}
|
||
className={`w-full px-3 py-1.5 text-sm border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 ${
|
||
errors.estimatedCost ? "border-red-500" : "border-gray-300"
|
||
}`}
|
||
placeholder="0.00"
|
||
/>
|
||
{errors.estimatedCost && (
|
||
<p className="mt-1 text-sm text-red-600">
|
||
{errors.estimatedCost}
|
||
</p>
|
||
)}
|
||
</div>
|
||
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||
Gerçek Maliyet
|
||
</label>
|
||
<input
|
||
type="number"
|
||
min="0"
|
||
step="0.01"
|
||
value={formData.actualCost}
|
||
onChange={(e) =>
|
||
setFormData({
|
||
...formData,
|
||
actualCost: parseFloat(e.target.value) || 0,
|
||
})
|
||
}
|
||
className={`w-full px-3 py-1.5 text-sm border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 ${
|
||
errors.actualCost ? "border-red-500" : "border-gray-300"
|
||
}`}
|
||
placeholder="0.00"
|
||
/>
|
||
{errors.actualCost && (
|
||
<p className="mt-1 text-sm text-red-600">{errors.actualCost}</p>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Materials */}
|
||
<div>
|
||
<div className="flex items-center justify-between mb-4">
|
||
<h4 className="text-lg font-medium text-gray-900">Malzemeler</h4>
|
||
<button
|
||
type="button"
|
||
onClick={addMaterial}
|
||
className="bg-blue-600 text-white px-3 py-1.5 text-sm rounded-lg hover:bg-blue-700 flex items-center space-x-2"
|
||
>
|
||
<FaPlus className="w-4 h-4" />
|
||
<span>Malzeme Ekle</span>
|
||
</button>
|
||
</div>
|
||
|
||
{materials.map((material, index) => (
|
||
<div
|
||
key={material.id || index}
|
||
className="bg-gray-50 p-3 rounded-lg mb-2"
|
||
>
|
||
<div className="grid grid-cols-1 md:grid-cols-6 gap-4">
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||
Malzeme
|
||
</label>
|
||
<select
|
||
value={material.materialId}
|
||
onChange={(e) =>
|
||
updateMaterial(index, "materialId", e.target.value)
|
||
}
|
||
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||
>
|
||
<option value="">Malzeme Seçin</option>
|
||
{mockMaterials.map((mat) => (
|
||
<option key={mat.id} value={mat.id}>
|
||
{mat.code} - {mat.name}
|
||
</option>
|
||
))}
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||
Planlanan
|
||
</label>
|
||
<input
|
||
type="number"
|
||
min="0"
|
||
value={material.plannedQuantity}
|
||
onChange={(e) =>
|
||
updateMaterial(
|
||
index,
|
||
"plannedQuantity",
|
||
parseInt(e.target.value) || 0
|
||
)
|
||
}
|
||
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||
/>
|
||
</div>
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||
Kullanılan
|
||
</label>
|
||
<input
|
||
type="number"
|
||
min="0"
|
||
value={material.actualQuantity}
|
||
onChange={(e) =>
|
||
updateMaterial(
|
||
index,
|
||
"actualQuantity",
|
||
parseInt(e.target.value) || 0
|
||
)
|
||
}
|
||
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||
/>
|
||
</div>
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||
Birim Fiyat
|
||
</label>
|
||
<input
|
||
type="number"
|
||
min="0"
|
||
step="0.01"
|
||
value={material.unitCost}
|
||
onChange={(e) =>
|
||
updateMaterial(
|
||
index,
|
||
"unitCost",
|
||
parseFloat(e.target.value) || 0
|
||
)
|
||
}
|
||
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||
/>
|
||
</div>
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||
Toplam
|
||
</label>
|
||
<input
|
||
type="text"
|
||
value={`₺${material.totalCost.toFixed(2)}`}
|
||
readOnly
|
||
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg bg-gray-100"
|
||
/>
|
||
</div>
|
||
<div className="flex items-end">
|
||
<button
|
||
type="button"
|
||
onClick={() => removeMaterial(index)}
|
||
className="text-red-600 hover:text-red-800 p-2"
|
||
>
|
||
<FaTrash className="w-4 h-4" />
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
{/* Activities */}
|
||
<div>
|
||
<div className="flex items-center justify-between mb-4">
|
||
<h4 className="text-lg font-medium text-gray-900">Aktiviteler</h4>
|
||
<button
|
||
type="button"
|
||
onClick={addActivity}
|
||
className="bg-green-600 text-white px-3 py-1.5 text-sm rounded-lg hover:bg-green-700 flex items-center space-x-2"
|
||
>
|
||
<FaPlus className="w-4 h-4" />
|
||
<span>Aktivite Ekle</span>
|
||
</button>
|
||
</div>
|
||
|
||
{activities.map((activity, index) => (
|
||
<div
|
||
key={activity.id || index}
|
||
className="bg-gray-50 p-3 rounded-lg mb-2"
|
||
>
|
||
<div className="grid grid-cols-1 md:grid-cols-6 gap-4">
|
||
<div className="md:col-span-2">
|
||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||
Aktivite Açıklaması
|
||
</label>
|
||
<input
|
||
type="text"
|
||
value={activity.activityDescription}
|
||
onChange={(e) =>
|
||
updateActivity(
|
||
index,
|
||
"activityDescription",
|
||
e.target.value
|
||
)
|
||
}
|
||
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||
placeholder="Aktivite açıklaması"
|
||
/>
|
||
</div>
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||
<FaClock className="w-4 h-4 inline mr-1" />
|
||
Planlanan (dk)
|
||
</label>
|
||
<input
|
||
type="number"
|
||
min="0"
|
||
value={activity.plannedDuration}
|
||
onChange={(e) =>
|
||
updateActivity(
|
||
index,
|
||
"plannedDuration",
|
||
parseInt(e.target.value) || 0
|
||
)
|
||
}
|
||
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||
/>
|
||
</div>
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||
<FaClock className="w-4 h-4 inline mr-1" />
|
||
Gerçek (dk)
|
||
</label>
|
||
<input
|
||
type="number"
|
||
min="0"
|
||
value={activity.actualDuration}
|
||
onChange={(e) =>
|
||
updateActivity(
|
||
index,
|
||
"actualDuration",
|
||
parseInt(e.target.value) || 0
|
||
)
|
||
}
|
||
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||
/>
|
||
</div>
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||
Yapan
|
||
</label>
|
||
<input
|
||
type="text"
|
||
value={activity.performedBy}
|
||
onChange={(e) =>
|
||
updateActivity(index, "performedBy", e.target.value)
|
||
}
|
||
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||
placeholder="Yapan kişi"
|
||
/>
|
||
</div>
|
||
<div className="flex items-end space-x-2">
|
||
<button
|
||
type="button"
|
||
onClick={() => toggleActivityCompletion(index)}
|
||
className={`px-3 py-2 rounded text-xs ${
|
||
activity.completedAt
|
||
? "bg-green-600 text-white"
|
||
: "bg-gray-300 text-gray-700"
|
||
}`}
|
||
>
|
||
{activity.completedAt ? "Tamamlandı" : "Bekliyor"}
|
||
</button>
|
||
<button
|
||
type="button"
|
||
onClick={() => removeActivity(index)}
|
||
className="text-red-600 hover:text-red-800 p-2"
|
||
>
|
||
<FaTrash className="w-4 h-4" />
|
||
</button>
|
||
</div>
|
||
</div>
|
||
{activity.notes !== undefined && (
|
||
<div className="mt-3">
|
||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||
Aktivite Notları
|
||
</label>
|
||
<textarea
|
||
value={activity.notes}
|
||
onChange={(e) =>
|
||
updateActivity(index, "notes", e.target.value)
|
||
}
|
||
rows={1}
|
||
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||
placeholder="Aktivite hakkında notlar..."
|
||
/>
|
||
</div>
|
||
)}
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
{/* Notes */}
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||
İş Emri Notları
|
||
</label>
|
||
<textarea
|
||
value={formData.notes}
|
||
onChange={(e) =>
|
||
setFormData({ ...formData, notes: e.target.value })
|
||
}
|
||
rows={2}
|
||
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||
placeholder="Genel notlar..."
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||
Tamamlanma Notları
|
||
</label>
|
||
<textarea
|
||
value={formData.completionNotes}
|
||
onChange={(e) =>
|
||
setFormData({ ...formData, completionNotes: e.target.value })
|
||
}
|
||
rows={2}
|
||
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||
placeholder="Tamamlanma ile ilgili notlar..."
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Form Actions */}
|
||
<div className="flex justify-end space-x-3 pt-4 border-t border-gray-200">
|
||
<button
|
||
type="button"
|
||
onClick={onClose}
|
||
className="px-4 py-2 text-sm text-gray-600 border border-gray-300 rounded-lg hover:bg-gray-50"
|
||
>
|
||
İptal
|
||
</button>
|
||
<button
|
||
type="submit"
|
||
className="px-4 py-2 text-sm bg-blue-600 text-white rounded-lg hover:bg-blue-700 flex items-center space-x-2"
|
||
>
|
||
<FaSave className="w-4 h-4" />
|
||
<span>Kaydet</span>
|
||
</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default EditWorkOrderModal;
|