307 lines
12 KiB
TypeScript
307 lines
12 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
|