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