338 lines
13 KiB
TypeScript
338 lines
13 KiB
TypeScript
|
|
import React, { useState } from "react";
|
|||
|
|
import { FaTimes, FaPlay, FaCalendar, FaUser } from "react-icons/fa";
|
|||
|
|
import { PmMaintenanceWorkOrder, WorkOrderStatusEnum } from "../../../types/pm";
|
|||
|
|
import { mockEmployees } from "../../../mocks/mockEmployees";
|
|||
|
|
import { mockMaintenanceTeams } from "../../../mocks/mockMaintenanceTeams";
|
|||
|
|
|
|||
|
|
interface StartWorkOrderModalProps {
|
|||
|
|
isOpen: boolean;
|
|||
|
|
onClose: () => void;
|
|||
|
|
onStart: (
|
|||
|
|
workOrders: PmMaintenanceWorkOrder[],
|
|||
|
|
startData: StartWorkOrderData
|
|||
|
|
) => void;
|
|||
|
|
workOrders: PmMaintenanceWorkOrder[];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
interface StartWorkOrderData {
|
|||
|
|
actualStart: Date;
|
|||
|
|
assignedTo?: string;
|
|||
|
|
maintenanceTeamId?: string;
|
|||
|
|
notes?: string;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const StartWorkOrderModal: React.FC<StartWorkOrderModalProps> = ({
|
|||
|
|
isOpen,
|
|||
|
|
onClose,
|
|||
|
|
onStart,
|
|||
|
|
workOrders,
|
|||
|
|
}) => {
|
|||
|
|
const [formData, setFormData] = useState({
|
|||
|
|
actualStart: new Date().toISOString().slice(0, 16),
|
|||
|
|
assignedTo: "",
|
|||
|
|
maintenanceTeamId: "",
|
|||
|
|
notes: "",
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const [errors, setErrors] = useState<Record<string, string>>({});
|
|||
|
|
|
|||
|
|
const validateForm = () => {
|
|||
|
|
const newErrors: Record<string, string> = {};
|
|||
|
|
|
|||
|
|
if (!formData.actualStart) {
|
|||
|
|
newErrors.actualStart = "Başlangıç tarihi zorunludur";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const startDate = new Date(formData.actualStart);
|
|||
|
|
const now = new Date();
|
|||
|
|
if (startDate > now) {
|
|||
|
|
newErrors.actualStart = "Başlangıç tarihi gelecekte olamaz";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
setErrors(newErrors);
|
|||
|
|
return Object.keys(newErrors).length === 0;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleSubmit = (e: React.FormEvent) => {
|
|||
|
|
e.preventDefault();
|
|||
|
|
if (!validateForm()) return;
|
|||
|
|
|
|||
|
|
const startData: StartWorkOrderData = {
|
|||
|
|
actualStart: new Date(formData.actualStart),
|
|||
|
|
assignedTo: formData.assignedTo || undefined,
|
|||
|
|
maintenanceTeamId: formData.maintenanceTeamId || undefined,
|
|||
|
|
notes: formData.notes || undefined,
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
onStart(workOrders, startData);
|
|||
|
|
onClose();
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const canStartWorkOrders = workOrders.every(
|
|||
|
|
(wo) =>
|
|||
|
|
wo.status === WorkOrderStatusEnum.Created ||
|
|||
|
|
wo.status === WorkOrderStatusEnum.Planned ||
|
|||
|
|
wo.status === WorkOrderStatusEnum.Released
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
const inProgressWorkOrders = workOrders.filter(
|
|||
|
|
(wo) => wo.status === WorkOrderStatusEnum.InProgress
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
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-lg 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">
|
|||
|
|
<FaPlay className="w-5 h-5 mr-2 text-green-600" />
|
|||
|
|
İş Emirlerini Başlat
|
|||
|
|
</h3>
|
|||
|
|
<button
|
|||
|
|
onClick={onClose}
|
|||
|
|
className="text-gray-400 hover:text-gray-600"
|
|||
|
|
>
|
|||
|
|
<FaTimes className="w-5 h-5" />
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Work Orders Summary */}
|
|||
|
|
<div className="mb-4">
|
|||
|
|
<h4 className="text-base font-medium text-gray-900 mb-2">
|
|||
|
|
Seçilen İş Emirleri ({workOrders.length})
|
|||
|
|
</h4>
|
|||
|
|
<div className="max-h-32 overflow-y-auto border border-gray-200 rounded-lg">
|
|||
|
|
{workOrders.map((workOrder) => (
|
|||
|
|
<div
|
|||
|
|
key={workOrder.id}
|
|||
|
|
className="p-3 border-b border-gray-100 last:border-b-0"
|
|||
|
|
>
|
|||
|
|
<div className="flex items-center justify-between">
|
|||
|
|
<div>
|
|||
|
|
<p className="font-medium text-sm text-gray-900">
|
|||
|
|
{workOrder.workOrderNumber}
|
|||
|
|
</p>
|
|||
|
|
<p className="text-xs text-gray-600 truncate">
|
|||
|
|
{workOrder.description}
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
<div className="text-right">
|
|||
|
|
<span
|
|||
|
|
className={`px-2 py-1 rounded-full text-xs font-medium ${
|
|||
|
|
workOrder.status === WorkOrderStatusEnum.Created
|
|||
|
|
? "bg-gray-100 text-gray-800"
|
|||
|
|
: workOrder.status === WorkOrderStatusEnum.Planned
|
|||
|
|
? "bg-blue-100 text-blue-800"
|
|||
|
|
: workOrder.status === WorkOrderStatusEnum.Released
|
|||
|
|
? "bg-purple-100 text-purple-800"
|
|||
|
|
: workOrder.status === WorkOrderStatusEnum.InProgress
|
|||
|
|
? "bg-orange-100 text-orange-800"
|
|||
|
|
: "bg-gray-100 text-gray-800"
|
|||
|
|
}`}
|
|||
|
|
>
|
|||
|
|
{workOrder.status === WorkOrderStatusEnum.Created &&
|
|||
|
|
"Oluşturuldu"}
|
|||
|
|
{workOrder.status === WorkOrderStatusEnum.Planned &&
|
|||
|
|
"Planlandı"}
|
|||
|
|
{workOrder.status === WorkOrderStatusEnum.Released &&
|
|||
|
|
"Serbest Bırakıldı"}
|
|||
|
|
{workOrder.status === WorkOrderStatusEnum.InProgress &&
|
|||
|
|
"Devam Ediyor"}
|
|||
|
|
{workOrder.status === WorkOrderStatusEnum.OnHold &&
|
|||
|
|
"Beklemede"}
|
|||
|
|
{workOrder.status === WorkOrderStatusEnum.Completed &&
|
|||
|
|
"Tamamlandı"}
|
|||
|
|
{workOrder.status === WorkOrderStatusEnum.Cancelled &&
|
|||
|
|
"İptal Edildi"}
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
))}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Validation Messages */}
|
|||
|
|
{!canStartWorkOrders && (
|
|||
|
|
<div className="mb-4 p-3 bg-yellow-50 border border-yellow-200 rounded-lg">
|
|||
|
|
<div className="flex items-start space-x-2">
|
|||
|
|
<FaTimes className="w-5 h-5 text-yellow-600 mt-0.5" />
|
|||
|
|
<div>
|
|||
|
|
<h5 className="font-medium text-yellow-800 mb-1">Uyarı</h5>
|
|||
|
|
<p className="text-yellow-700 text-sm">
|
|||
|
|
Bazı iş emirleri başlatılamaz durumda. Sadece "Oluşturuldu",
|
|||
|
|
"Planlandı" veya "Serbest Bırakıldı" durumundaki iş emirleri
|
|||
|
|
başlatılabilir.
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
{inProgressWorkOrders.length > 0 && (
|
|||
|
|
<div className="mb-4 p-3 bg-blue-50 border border-blue-200 rounded-lg">
|
|||
|
|
<div className="flex items-start space-x-2">
|
|||
|
|
<FaPlay className="w-5 h-5 text-blue-600 mt-0.5" />
|
|||
|
|
<div>
|
|||
|
|
<h5 className="font-medium text-blue-800 mb-1">Bilgi</h5>
|
|||
|
|
<p className="text-blue-700 text-sm">
|
|||
|
|
{inProgressWorkOrders.length} iş emri zaten devam ediyor
|
|||
|
|
durumunda. Bu iş emirleri için sadece atama ve not
|
|||
|
|
güncellemesi yapılacak.
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
<form onSubmit={handleSubmit} className="space-y-4">
|
|||
|
|
{/* Start DateTime */}
|
|||
|
|
<div>
|
|||
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|||
|
|
<FaCalendar className="w-4 h-4 inline mr-2" />
|
|||
|
|
Başlangıç Tarihi ve Saati *
|
|||
|
|
</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 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 ${
|
|||
|
|
errors.actualStart ? "border-red-500" : "border-gray-300"
|
|||
|
|
}`}
|
|||
|
|
/>
|
|||
|
|
{errors.actualStart && (
|
|||
|
|
<p className="mt-1 text-xs text-red-600">{errors.actualStart}</p>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Assignment */}
|
|||
|
|
<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">
|
|||
|
|
<FaUser className="w-4 h-4 inline mr-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>
|
|||
|
|
|
|||
|
|
{/* Notes */}
|
|||
|
|
<div>
|
|||
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|||
|
|
Başlangıç 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="İş emirlerinin başlatılması ile ilgili notlar..."
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Summary */}
|
|||
|
|
<div className="bg-gray-50 rounded-lg p-3">
|
|||
|
|
<h5 className="font-medium text-gray-900 mb-2">Özet</h5>
|
|||
|
|
<div className="grid grid-cols-2 gap-4 text-sm">
|
|||
|
|
<div>
|
|||
|
|
<span className="text-gray-600">Toplam İş Emri:</span>
|
|||
|
|
<span className="ml-2 font-medium">{workOrders.length}</span>
|
|||
|
|
</div>
|
|||
|
|
<div>
|
|||
|
|
<span className="text-gray-600">Başlatılabilir:</span>
|
|||
|
|
<span className="ml-2 font-medium text-green-600">
|
|||
|
|
{
|
|||
|
|
workOrders.filter(
|
|||
|
|
(wo) =>
|
|||
|
|
wo.status === WorkOrderStatusEnum.Created ||
|
|||
|
|
wo.status === WorkOrderStatusEnum.Planned ||
|
|||
|
|
wo.status === WorkOrderStatusEnum.Released
|
|||
|
|
).length
|
|||
|
|
}
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
<div>
|
|||
|
|
<span className="text-gray-600">Zaten Başlamış:</span>
|
|||
|
|
<span className="ml-2 font-medium text-blue-600">
|
|||
|
|
{inProgressWorkOrders.length}
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
<div>
|
|||
|
|
<span className="text-gray-600">Başlangıç Tarihi:</span>
|
|||
|
|
<span className="ml-2 font-medium">
|
|||
|
|
{new Date(formData.actualStart).toLocaleDateString("tr-TR")}{" "}
|
|||
|
|
{new Date(formData.actualStart).toLocaleTimeString("tr-TR", {
|
|||
|
|
hour: "2-digit",
|
|||
|
|
minute: "2-digit",
|
|||
|
|
})}
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Form Actions */}
|
|||
|
|
<div className="flex justify-end space-x-2 pt-4 border-t border-gray-200">
|
|||
|
|
<button
|
|||
|
|
type="button"
|
|||
|
|
onClick={onClose}
|
|||
|
|
className="px-4 py-1.5 text-sm text-gray-600 border border-gray-300 rounded-lg hover:bg-gray-50"
|
|||
|
|
>
|
|||
|
|
İptal
|
|||
|
|
</button>
|
|||
|
|
<button
|
|||
|
|
type="submit"
|
|||
|
|
className="px-4 py-1.5 text-sm bg-green-600 text-white rounded-lg hover:bg-green-700 flex items-center space-x-2"
|
|||
|
|
>
|
|||
|
|
<FaPlay className="w-4 h-4" />
|
|||
|
|
<span>İş Emirlerini Başlat</span>
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</form>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
export default StartWorkOrderModal;
|