erp-platform/ui/src/views/mrp/components/WorkOrders.tsx
2025-09-16 00:02:48 +03:00

451 lines
15 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 } from 'react'
import { FaCog, FaPlus, FaEdit, FaTrash, FaClock, FaCheckCircle, FaPlay } from 'react-icons/fa'
import { MrpWorkOrder } from '../../../types/mrp'
import DataTable, { Column } from '../../../components/common/DataTable'
import { mockWorkOrders } from '../../../mocks/mockWorkOrders'
import NewWorkOrderForm from './NewWorkOrderForm'
import EditWorkOrderForm from './EditWorkOrderForm'
import ViewWorkOrderModal from './ViewWorkOrderModal'
import CompleteWorkOrderModal from './CompleteWorkOrderModal'
import Widget from '../../../components/common/Widget'
import { PriorityEnum } from '../../../types/common'
import {
getPriorityColor,
getPriorityText,
getWorkOrderStatusColor,
getWorkOrderStatusText,
} from '../../../utils/erp'
import { WorkOrderStatusEnum } from '../../../types/pm'
import { Container } from '@/components/shared'
const WorkOrders: React.FC = () => {
const [workOrders, setWorkOrders] = useState<MrpWorkOrder[]>(mockWorkOrders)
const [searchTerm, setSearchTerm] = useState('')
const [selectedStatus, setSelectedStatus] = useState<WorkOrderStatusEnum | 'all'>('all')
const [selectedPriority, setSelectedPriority] = useState<PriorityEnum | 'all'>('all')
// Modal states
const [isNewWorkOrderOpen, setIsNewWorkOrderOpen] = useState(false)
const [isEditWorkOrderOpen, setIsEditWorkOrderOpen] = useState(false)
const [isViewWorkOrderOpen, setIsViewWorkOrderOpen] = useState(false)
const [isCompleteWorkOrderOpen, setIsCompleteWorkOrderOpen] = useState(false)
const [selectedWorkOrder, setSelectedWorkOrder] = useState<MrpWorkOrder | null>(null)
// Event handlers
const handleAdd = () => {
setIsNewWorkOrderOpen(true)
}
const handleEdit = (workOrder: MrpWorkOrder) => {
setSelectedWorkOrder(workOrder)
setIsEditWorkOrderOpen(true)
}
const handleDelete = (id: string) => {
if (window.confirm('Bu iş emrini silmek istediğinizden emin misiniz?')) {
setWorkOrders((prev) => prev.filter((wo) => wo.id !== id))
}
}
const handleStart = (workOrder: MrpWorkOrder) => {
setWorkOrders((prev) =>
prev.map((wo) =>
wo.id === workOrder.id
? {
...wo,
status: WorkOrderStatusEnum.InProgress,
lastModificationTime: new Date(),
}
: wo,
),
)
}
const handleComplete = (workOrder: MrpWorkOrder) => {
setSelectedWorkOrder(workOrder)
setIsCompleteWorkOrderOpen(true)
}
const handleViewDetails = (workOrder: MrpWorkOrder) => {
setSelectedWorkOrder(workOrder)
setIsViewWorkOrderOpen(true)
}
// Modal handlers
const handleNewWorkOrderSave = (
newWorkOrderData: Omit<MrpWorkOrder, 'id' | 'creationTime' | 'lastModificationTime'>,
) => {
const newWorkOrder: MrpWorkOrder = {
...newWorkOrderData,
id: `WO-${Date.now()}`,
creationTime: new Date(),
lastModificationTime: new Date(),
}
setWorkOrders((prev) => [...prev, newWorkOrder])
}
const handleEditWorkOrderSave = (updatedWorkOrder: MrpWorkOrder) => {
setWorkOrders((prev) =>
prev.map((wo) => (wo.id === updatedWorkOrder.id ? updatedWorkOrder : wo)),
)
}
const handleCompleteWorkOrder = (
workOrderId: string,
confirmedQuantity: number,
scrapQuantity: number,
) => {
setWorkOrders((prev) =>
prev.map((wo) => {
if (wo.id === workOrderId) {
const newConfirmedQuantity = wo.confirmedQuantity + confirmedQuantity
const newScrapQuantity = wo.scrapQuantity + scrapQuantity
const newStatus =
newConfirmedQuantity + newScrapQuantity >= wo.plannedQuantity
? WorkOrderStatusEnum.Completed
: wo.status
return {
...wo,
confirmedQuantity: newConfirmedQuantity,
scrapQuantity: newScrapQuantity,
status: newStatus,
lastModificationTime: new Date(),
}
}
return wo
}),
)
}
const filteredWorkOrders = workOrders.filter((workOrder) => {
if (
searchTerm &&
!workOrder.workOrderNumber.toLowerCase().includes(searchTerm.toLowerCase()) &&
!workOrder.productionOrder?.orderNumber?.toLowerCase().includes(searchTerm.toLowerCase())
) {
return false
}
if (selectedStatus !== 'all' && workOrder.status !== selectedStatus) {
return false
}
if (selectedPriority !== 'all' && workOrder.productionOrder?.priority !== selectedPriority) {
return false
}
return true
})
const columns: Column<MrpWorkOrder>[] = [
{
key: 'workOrderNumber',
header: 'İş Emri No',
sortable: true,
render: (workOrder: MrpWorkOrder) => (
<div>
<div className="font-medium text-gray-900">{workOrder.workOrderNumber}</div>
<div className="text-sm text-gray-500">Sıra: {workOrder.sequence}</div>
</div>
),
},
{
key: 'productionOrder',
header: 'Üretim Emri',
render: (workOrder: MrpWorkOrder) => (
<div>
<div className="font-medium text-gray-900">
{workOrder.productionOrder?.orderNumber || 'N/A'}
</div>
</div>
),
},
{
key: 'material',
header: 'Malzeme',
render: (workOrder: MrpWorkOrder) => (
<div>
<div className="text-gray-900">
{workOrder.material?.code}
{' - '}
{workOrder.material?.name || workOrder.materialId}
</div>
</div>
),
},
{
key: 'workCenter',
header: 'İş Merkezi',
render: (workOrder: MrpWorkOrder) => (
<div>
<div className="font-medium text-gray-900">
{workOrder.workCenter?.name || workOrder.workCenterId}
</div>
<div className="flex items-center gap-2">
<span>{workOrder.operation?.name || workOrder.operationId}</span>
</div>
<div className="text-sm text-gray-500">{workOrder.assignedOperators.length} Operatör</div>
</div>
),
},
{
key: 'quantities',
header: 'Miktarlar',
render: (workOrder: MrpWorkOrder) => (
<div className="text-sm">
<div>Plan: {workOrder.plannedQuantity}</div>
<div>Tamamlanan: {workOrder.confirmedQuantity}</div>
{workOrder.scrapQuantity > 0 && (
<div className="text-red-600">Fire: {workOrder.scrapQuantity}</div>
)}
</div>
),
},
{
key: 'schedule',
header: 'Planlama',
render: (workOrder: MrpWorkOrder) => (
<div className="text-sm">
<div className="flex items-center gap-1">
<FaClock className="w-3 h-3 text-gray-400" />
<span>
Başlangıç: {new Date(workOrder.plannedStartDate).toLocaleDateString('tr-TR')}
</span>
</div>
<div className="flex items-center gap-1">
<FaClock className="w-3 h-3 text-gray-400" />
<span>Bitiş: {new Date(workOrder.plannedEndDate).toLocaleDateString('tr-TR')}</span>
</div>
</div>
),
},
{
key: 'priority',
header: 'Öncelik',
render: (workOrder: MrpWorkOrder) =>
workOrder.productionOrder?.priority ? (
<span
className={`px-2 py-1 text-xs font-medium rounded-full ${getPriorityColor(
workOrder.productionOrder.priority,
)}`}
>
{getPriorityText(workOrder.productionOrder.priority)}
</span>
) : (
'-'
),
},
{
key: 'status',
header: 'Durum',
render: (workOrder: MrpWorkOrder) => (
<span
className={`px-2 py-1 text-xs font-medium rounded-full ${getWorkOrderStatusColor(
workOrder.status,
)}`}
>
{getWorkOrderStatusText(workOrder.status)}
</span>
),
},
{
key: 'actions',
header: 'İşlemler',
render: (workOrder: MrpWorkOrder) => (
<div className="flex gap-1">
{workOrder.status === WorkOrderStatusEnum.Released && (
<button
onClick={() => handleStart(workOrder)}
className="p-1 text-green-600 hover:bg-green-50 rounded"
title="Başlat"
>
<FaPlay className="w-4 h-4" />
</button>
)}
{workOrder.status === WorkOrderStatusEnum.InProgress && (
<button
onClick={() => handleComplete(workOrder)}
className="p-1 text-blue-600 hover:bg-blue-50 rounded"
title="Tamamla"
>
<FaCheckCircle className="w-4 h-4" />
</button>
)}
<button
onClick={() => handleViewDetails(workOrder)}
className="p-1 text-purple-600 hover:bg-purple-50 rounded"
title="Detayları Görüntüle"
>
<FaCog className="w-4 h-4" />
</button>
<button
onClick={() => handleEdit(workOrder)}
className="p-1 text-blue-600 hover:bg-blue-50 rounded"
title="Düzenle"
>
<FaEdit className="w-4 h-4" />
</button>
<button
onClick={() => handleDelete(workOrder.id)}
className="p-1 text-red-600 hover:bg-red-50 rounded"
title="Sil"
>
<FaTrash className="w-4 h-4" />
</button>
</div>
),
},
]
// Calculate statistics
const totalWorkOrders = workOrders.length
const inProgressOrders = workOrders.filter(
(wo) => wo.status === WorkOrderStatusEnum.InProgress,
).length
const completedOrders = workOrders.filter(
(wo) => wo.status === WorkOrderStatusEnum.Completed,
).length
const delayedOrders = workOrders.filter(
(wo) => wo.status !== WorkOrderStatusEnum.Completed && new Date(wo.plannedEndDate) < new Date(),
).length
// Status distribution
const statusDistribution = Object.values(WorkOrderStatusEnum).map((status) => ({
status,
count: workOrders.filter((wo) => wo.status === status).length,
percentage:
(workOrders.filter((wo) => wo.status === status).length / workOrders.length) * 100 || 0,
}))
return (
<Container>
<div className="space-y-2">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h2 className="text-2xl font-bold text-gray-900">İş Emirleri</h2>
<p className="text-gray-600">Üretim operasyonlarının detaylı takibi</p>
</div>
<button
onClick={handleAdd}
className="flex items-center gap-2 px-3 py-1.5 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors text-sm"
>
<FaPlus className="w-4 h-4" />
Yeni İş Emri
</button>
</div>
{/* Stats Cards */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-3">
<Widget title="Toplam İş Emri" value={totalWorkOrders} color="blue" icon="FaCog" />
<Widget title="İşlemde" value={inProgressOrders} color="yellow" icon="FaPlay" />
<Widget title="Tamamlanan" value={completedOrders} color="green" icon="FaCheckCircle" />
<Widget title="Geciken" value={delayedOrders} color="red" icon="FaExclamationCircle" />
</div>
{/* Status Distribution */}
<div className="bg-white rounded-lg shadow-sm border p-3">
<h3 className="text-sm font-semibold text-gray-900 mb-2">Durum Dağılımı</h3>
<div className="grid grid-cols-2 md:grid-cols-5 gap-2">
{statusDistribution.map(({ status, count, percentage }) => (
<div key={status} className="text-center p-2 border rounded-lg">
<div
className={`inline-block px-1.5 py-0.5 text-xs font-medium rounded-full mb-1 ${getWorkOrderStatusColor(
status,
)}`}
>
{getWorkOrderStatusText(status)}
</div>
<div className="text-xl font-bold text-gray-900">{count}</div>
<div className="text-xs text-gray-500">{percentage.toFixed(1)}%</div>
</div>
))}
</div>
</div>
{/* Filters */}
<div className="flex gap-2 items-center">
<div className="flex-1">
<input
type="text"
placeholder="İş emri numarası ara..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full px-2 py-1 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
<select
value={selectedStatus}
onChange={(e) => setSelectedStatus(e.target.value as WorkOrderStatusEnum | 'all')}
className="px-2 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="all">Tüm Durumlar</option>
{Object.values(WorkOrderStatusEnum).map((status) => (
<option key={status} value={status}>
{getWorkOrderStatusText(status)}
</option>
))}
</select>
<select
value={selectedPriority}
onChange={(e) => setSelectedPriority(e.target.value as PriorityEnum | 'all')}
className="px-2 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="all">Tüm Öncelikler</option>
{Object.values(PriorityEnum).map((priority) => (
<option key={priority} value={priority}>
{getPriorityText(priority)}
</option>
))}
</select>
</div>
{/* Data Table */}
<div className="bg-white rounded-lg shadow-sm border">
<DataTable data={filteredWorkOrders} columns={columns} />
</div>
{filteredWorkOrders.length === 0 && (
<div className="text-center py-12">
<FaCog className="w-12 h-12 text-gray-400 mx-auto mb-4" />
<h3 className="text-lg font-medium text-gray-900 mb-2">İş emri bulunamadı</h3>
<p className="text-gray-500">Arama kriterlerinizi değiştirmeyi deneyin.</p>
</div>
)}
</div>
{/* Modals */}
<NewWorkOrderForm
isOpen={isNewWorkOrderOpen}
onClose={() => setIsNewWorkOrderOpen(false)}
onSave={handleNewWorkOrderSave}
/>
<EditWorkOrderForm
isOpen={isEditWorkOrderOpen}
onClose={() => setIsEditWorkOrderOpen(false)}
onSave={handleEditWorkOrderSave}
workOrder={selectedWorkOrder}
/>
<ViewWorkOrderModal
isOpen={isViewWorkOrderOpen}
onClose={() => setIsViewWorkOrderOpen(false)}
workOrder={selectedWorkOrder}
/>
<CompleteWorkOrderModal
isOpen={isCompleteWorkOrderOpen}
onClose={() => setIsCompleteWorkOrderOpen(false)}
onConfirm={handleCompleteWorkOrder}
workOrder={selectedWorkOrder}
/>
</Container>
)
}
export default WorkOrders