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

435 lines
15 KiB
TypeScript
Raw Normal View History

2025-09-15 09:31:47 +00:00
import React, { useState, useEffect } from "react";
import { FaTimes, FaSave, FaCalendar, FaClock } from "react-icons/fa";
import { PmCalendarEvent, WorkOrderStatusEnum } from "../../../types/pm";
import { mockWorkCenters } from "../../../mocks/mockWorkCenters";
import { mockEmployees } from "../../../mocks/mockEmployees";
import { PriorityEnum } from "../../../types/common";
interface NewCalendarEventModalProps {
isOpen: boolean;
onClose: () => void;
onSave: (event: Partial<PmCalendarEvent>) => void;
selectedDate?: Date;
}
const NewCalendarEventModal: React.FC<NewCalendarEventModalProps> = ({
isOpen,
onClose,
onSave,
selectedDate,
}) => {
const getInitialStartTime = () => {
if (selectedDate) {
const hour = selectedDate.getHours();
const minute = selectedDate.getMinutes();
return `${hour.toString().padStart(2, "0")}:${minute
.toString()
.padStart(2, "0")}`;
}
return "09:00";
};
const getInitialEndTime = () => {
if (selectedDate) {
const endTime = new Date(selectedDate);
endTime.setHours(endTime.getHours() + 1);
const hour = endTime.getHours();
const minute = endTime.getMinutes();
return `${hour.toString().padStart(2, "0")}:${minute
.toString()
.padStart(2, "0")}`;
}
return "10:00";
};
const [eventData, setEventData] = useState<Partial<PmCalendarEvent>>({
title: "",
type: "plan",
date: selectedDate || new Date(),
startTime: getInitialStartTime(),
endTime: getInitialEndTime(),
status: WorkOrderStatusEnum.Planned,
priority: PriorityEnum.Normal,
assignedTo: "",
workCenterCode: "",
duration: 60,
});
const [errors, setErrors] = useState<Record<string, string>>({});
// Update form data when selectedDate changes
useEffect(() => {
if (selectedDate) {
const getInitialStartTime = () => {
const hour = selectedDate.getHours();
const minute = selectedDate.getMinutes();
return `${hour.toString().padStart(2, "0")}:${minute
.toString()
.padStart(2, "0")}`;
};
const getInitialEndTime = () => {
const endTime = new Date(selectedDate);
endTime.setHours(endTime.getHours() + 1);
const hour = endTime.getHours();
const minute = endTime.getMinutes();
return `${hour.toString().padStart(2, "0")}:${minute
.toString()
.padStart(2, "0")}`;
};
setEventData((prev) => ({
...prev,
date: selectedDate,
startTime: getInitialStartTime(),
endTime: getInitialEndTime(),
}));
}
}, [selectedDate]);
if (!isOpen) return null;
const validateForm = () => {
const newErrors: Record<string, string> = {};
if (!eventData.title?.trim()) {
newErrors.title = "Başlık gerekli";
}
if (!eventData.workCenterCode?.trim()) {
newErrors.workCenterCode = "İş Merkezi seçimi gerekli";
}
if (!eventData.startTime) {
newErrors.startTime = "Başlangıç saati gerekli";
}
if (!eventData.endTime) {
newErrors.endTime = "Bitiş saati gerekli";
}
if (eventData.startTime && eventData.endTime) {
const start = new Date(`2000-01-01 ${eventData.startTime}`);
const end = new Date(`2000-01-01 ${eventData.endTime}`);
if (start >= end) {
newErrors.endTime = "Bitiş saati başlangıç saatinden sonra olmalı";
}
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleSave = () => {
if (validateForm()) {
// Calculate duration
if (eventData.startTime && eventData.endTime) {
const start = new Date(`2000-01-01 ${eventData.startTime}`);
const end = new Date(`2000-01-01 ${eventData.endTime}`);
const durationMinutes = (end.getTime() - start.getTime()) / (1000 * 60);
eventData.duration = durationMinutes;
}
onSave({
...eventData,
id: `E${Date.now()}`, // Generate a simple ID
});
onClose();
// Reset form
setEventData({
title: "",
type: "plan",
date: selectedDate || new Date(),
startTime: getInitialStartTime(),
endTime: getInitialEndTime(),
status: WorkOrderStatusEnum.Planned,
priority: PriorityEnum.Normal,
assignedTo: "",
workCenterCode: "",
duration: 60,
});
setErrors({});
}
};
const handleInputChange = (
field: keyof PmCalendarEvent,
value:
| string
| Date
| PriorityEnum
| WorkOrderStatusEnum
| "plan"
| "workorder"
| "scheduled"
) => {
setEventData((prev) => ({
...prev,
[field]: value,
}));
// Clear error when user starts typing
if (errors[field]) {
setErrors((prev) => ({
...prev,
[field]: "",
}));
}
};
const generateTimeOptions = () => {
const options = [];
for (let hour = 0; hour < 24; hour++) {
for (let minute = 0; minute < 60; minute += 30) {
const timeString = `${hour.toString().padStart(2, "0")}:${minute
.toString()
.padStart(2, "0")}`;
options.push(timeString);
}
}
return options;
};
const timeOptions = generateTimeOptions();
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-lg mx-4 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 Bakım Planlaması
</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 className="md:col-span-2">
<label className="block text-sm font-medium text-gray-700 mb-1">
Başlık *
</label>
<input
type="text"
value={eventData.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="Bakım planı 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">
Tip
</label>
<select
value={eventData.type || "plan"}
onChange={(e) =>
handleInputChange(
"type",
e.target.value as "plan" | "workorder"
)
}
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="plan">Bakım Planı</option>
<option value="workorder">İş Emri</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Öncelik
</label>
<select
value={eventData.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">
İş Merkezi *
</label>
<select
value={eventData.workCenterCode || ""}
onChange={(e) =>
handleInputChange("workCenterCode", 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.workCenterCode ? "border-red-500" : "border-gray-300"
}`}
>
<option value="">İş merkezi seçin</option>
{mockWorkCenters.map((workCenter) => (
<option key={workCenter.id} value={workCenter.code}>
{workCenter.code} - {workCenter.name}
</option>
))}
</select>
{errors.workCenterCode && (
<p className="text-red-500 text-xs mt-1">
{errors.workCenterCode}
</p>
)}
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Atanan Kişi
</label>
<select
value={eventData.assignedTo || ""}
onChange={(e) =>
handleInputChange("assignedTo", 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"
>
<option value="">Kişi seçin</option>
{mockEmployees.map((employee) => (
<option key={employee.id} value={employee.fullName}>
{employee.fullName} ({employee.code})
</option>
))}
</select>
</div>
</div>
{/* Date and Time */}
<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">
<FaCalendar className="inline w-4 h-4 mr-2" />
Tarih
</label>
<input
type="date"
value={eventData.date?.toISOString().split("T")[0] || ""}
onChange={(e) =>
handleInputChange("date", new Date(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"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
<FaClock className="inline w-4 h-4 mr-2" />
Başlangıç Saati *
</label>
<select
value={eventData.startTime || "09:00"}
onChange={(e) => handleInputChange("startTime", 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.startTime ? "border-red-500" : "border-gray-300"
}`}
>
{timeOptions.map((time) => (
<option key={time} value={time}>
{time}
</option>
))}
</select>
{errors.startTime && (
<p className="text-red-500 text-xs mt-1">{errors.startTime}</p>
)}
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Bitiş Saati *
</label>
<select
value={eventData.endTime || "10:00"}
onChange={(e) => handleInputChange("endTime", 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.endTime ? "border-red-500" : "border-gray-300"
}`}
>
{timeOptions.map((time) => (
<option key={time} value={time}>
{time}
</option>
))}
</select>
{errors.endTime && (
<p className="text-red-500 text-xs mt-1">{errors.endTime}</p>
)}
</div>
</div>
{/* Status */}
{eventData.type === "workorder" && (
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Durum
</label>
<select
value={eventData.status || "scheduled"}
onChange={(e) =>
handleInputChange(
"status",
e.target.value as WorkOrderStatusEnum | "scheduled"
)
}
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="scheduled">Planlanmış</option>
<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>
{/* 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 NewCalendarEventModal;