erp-platform/ui/src/views/maintenance/components/NewCalendarEventModal.tsx
2025-09-17 12:46:58 +03:00

367 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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'
import { getPriorityText, getWorkOrderStatusText } from '@/utils/erp'
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"
>
{Object.values(PriorityEnum).map((priority) => (
<option key={priority} value={priority}>
{getPriorityText(priority)}
</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"
>
{Object.values(WorkOrderStatusEnum).map((status) => (
<option key={status} value={status}>
{getWorkOrderStatusText(status)}
</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