erp-platform/ui/src/views/supplychains/components/ApprovalWorkflowModal.tsx

388 lines
15 KiB
TypeScript
Raw Normal View History

2025-09-15 21:42:39 +00:00
import React, { useState, useEffect } from 'react'
import { FaTimes, FaSave, FaUsers, FaPlus, FaTrash } from 'react-icons/fa'
2025-09-15 09:31:47 +00:00
import {
MmApprovalWorkflow,
MmApprovalWorkflowLevel,
RequestTypeEnum,
ApprovalLevelEnum,
2025-09-15 21:42:39 +00:00
} from '../../../types/mm'
import MultiSelectEmployee from '../../../components/common/MultiSelectEmployee'
import { mockDepartments } from '../../../mocks/mockDepartments'
2025-09-15 09:31:47 +00:00
interface ApprovalWorkflowModalProps {
2025-09-15 21:42:39 +00:00
isOpen: boolean
onClose: () => void
onSave: (workflow: MmApprovalWorkflow) => void
workflow?: MmApprovalWorkflow | null
mode: 'create' | 'view' | 'edit'
2025-09-15 09:31:47 +00:00
}
const ApprovalWorkflowModal: React.FC<ApprovalWorkflowModalProps> = ({
isOpen,
onClose,
onSave,
workflow,
mode,
}) => {
const [formData, setFormData] = useState<Partial<MmApprovalWorkflow>>({
2025-09-15 21:42:39 +00:00
name: '',
departmentId: '',
2025-09-15 09:31:47 +00:00
requestType: RequestTypeEnum.Material,
amountThreshold: 0,
approvalLevels: [],
isActive: true,
2025-09-15 21:42:39 +00:00
})
2025-09-15 09:31:47 +00:00
useEffect(() => {
2025-09-15 21:42:39 +00:00
if (mode === 'create') {
2025-09-15 09:31:47 +00:00
// Reset form to initial empty state for new workflow
setFormData({
2025-09-15 21:42:39 +00:00
name: '',
departmentId: '',
2025-09-15 09:31:47 +00:00
requestType: RequestTypeEnum.Material,
amountThreshold: 0,
approvalLevels: [],
isActive: true,
2025-09-15 21:42:39 +00:00
})
2025-09-15 09:31:47 +00:00
} else if (workflow) {
// Load existing workflow data for view/edit modes
2025-09-15 21:42:39 +00:00
setFormData(workflow)
2025-09-15 09:31:47 +00:00
}
2025-09-15 21:42:39 +00:00
}, [workflow, mode])
2025-09-15 09:31:47 +00:00
const handleInputChange = (
2025-09-15 21:42:39 +00:00
e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>,
2025-09-15 09:31:47 +00:00
) => {
2025-09-15 21:42:39 +00:00
const { name, value, type } = e.target
2025-09-15 09:31:47 +00:00
setFormData((prev) => ({
...prev,
[name]:
2025-09-15 21:42:39 +00:00
type === 'number'
2025-09-15 09:31:47 +00:00
? parseFloat(value) || 0
2025-09-15 21:42:39 +00:00
: type === 'checkbox'
? (e.target as HTMLInputElement).checked
: value,
}))
}
2025-09-15 09:31:47 +00:00
const addApprovalLevel = () => {
const newLevel: MmApprovalWorkflowLevel = {
id: `level-${Date.now()}`,
2025-09-15 21:42:39 +00:00
workflowId: formData.id || '',
2025-09-15 09:31:47 +00:00
level: ApprovalLevelEnum.Supervisor,
approverUserIds: [],
approverNames: [],
sequence: (formData.approvalLevels?.length || 0) + 1,
isRequired: true,
isParallel: false,
2025-09-15 21:42:39 +00:00
}
2025-09-15 09:31:47 +00:00
setFormData((prev) => ({
...prev,
approvalLevels: [...(prev.approvalLevels || []), newLevel],
2025-09-15 21:42:39 +00:00
}))
}
2025-09-15 09:31:47 +00:00
const removeApprovalLevel = (index: number) => {
setFormData((prev) => ({
...prev,
approvalLevels: prev.approvalLevels?.filter((_, i) => i !== index) || [],
2025-09-15 21:42:39 +00:00
}))
}
2025-09-15 09:31:47 +00:00
const updateApprovalLevel = (
index: number,
field: keyof MmApprovalWorkflowLevel,
2025-09-15 21:42:39 +00:00
value: string | number | boolean | string[] | undefined,
2025-09-15 09:31:47 +00:00
) => {
setFormData((prev) => ({
...prev,
approvalLevels:
prev.approvalLevels?.map((level, i) =>
2025-09-15 21:42:39 +00:00
i === index ? { ...level, [field]: value } : level,
2025-09-15 09:31:47 +00:00
) || [],
2025-09-15 21:42:39 +00:00
}))
}
2025-09-15 09:31:47 +00:00
const handleApproverNamesChange = (index: number, employees: string[]) => {
2025-09-15 21:42:39 +00:00
updateApprovalLevel(index, 'approverNames', employees)
}
2025-09-15 09:31:47 +00:00
const handleSubmit = (e: React.FormEvent) => {
2025-09-15 21:42:39 +00:00
e.preventDefault()
if (mode !== 'view') {
2025-09-15 09:31:47 +00:00
const newWorkflow: MmApprovalWorkflow = {
id: workflow?.id || `WF-${Date.now()}`,
2025-09-15 21:42:39 +00:00
name: formData.name || '',
departmentId: formData.departmentId || '',
2025-09-15 09:31:47 +00:00
requestType: formData.requestType || RequestTypeEnum.Material,
amountThreshold: formData.amountThreshold || 0,
approvalLevels: formData.approvalLevels || [],
isActive: formData.isActive ?? true,
creationTime: workflow?.creationTime || new Date(),
lastModificationTime: new Date(),
2025-09-15 21:42:39 +00:00
}
onSave(newWorkflow)
2025-09-15 09:31:47 +00:00
}
2025-09-15 21:42:39 +00:00
onClose()
}
2025-09-15 09:31:47 +00:00
2025-09-15 21:42:39 +00:00
if (!isOpen) return null
2025-09-15 09:31:47 +00:00
2025-09-15 21:42:39 +00:00
const isReadOnly = mode === 'view'
2025-09-15 09:31:47 +00:00
const modalTitle =
2025-09-15 21:42:39 +00:00
mode === 'create'
? 'Yeni Onay Süreci'
: mode === 'edit'
? 'Onay Sürecini Düzenle'
: 'Onay Süreci Detayları'
2025-09-15 09:31:47 +00:00
return (
<div className="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
<div className="relative top-5 mx-auto p-4 border w-11/12 max-w-5xl shadow-lg rounded-md bg-white">
<div className="flex items-center justify-between pb-2 border-b">
<h3 className="text-base font-semibold text-gray-900 flex items-center">
<FaUsers className="mr-2 text-blue-600" />
{modalTitle}
</h3>
2025-09-15 21:42:39 +00:00
<button onClick={onClose} className="text-gray-400 hover:text-gray-600 p-1">
2025-09-15 09:31:47 +00:00
<FaTimes />
</button>
</div>
<form onSubmit={handleSubmit} className="mt-2">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{/* Temel Bilgiler */}
<div className="space-y-3">
2025-09-15 21:42:39 +00:00
<h4 className="text-base font-medium text-gray-900 border-b pb-1">Temel Bilgiler</h4>
2025-09-15 09:31:47 +00:00
<div>
2025-09-15 21:42:39 +00:00
<label className="block text-sm font-medium text-gray-700">Süreç Adı</label>
2025-09-15 09:31:47 +00:00
<input
type="text"
name="workflowName"
2025-09-15 21:42:39 +00:00
value={formData.name || ''}
2025-09-15 09:31:47 +00:00
onChange={handleInputChange}
readOnly={isReadOnly}
className="mt-1 block w-full border border-gray-300 rounded-md px-2 py-1.5 text-sm focus:outline-none focus:ring-1 focus:ring-blue-500"
required
/>
</div>
<div>
2025-09-15 21:42:39 +00:00
<label className="block text-sm font-medium text-gray-700">Departman</label>
2025-09-15 09:31:47 +00:00
<select
2025-09-15 21:42:39 +00:00
value={formData.departmentId || ''}
2025-09-15 09:31:47 +00:00
onChange={handleInputChange}
className="mt-1 block w-full border border-gray-300 rounded-md px-2 py-1.5 text-sm focus:outline-none focus:ring-1 focus:ring-blue-500"
required
>
<option value="">Departman Seçin...</option>
{mockDepartments.map((dept) => (
<option key={dept.id} value={dept.id}>
{dept.name}
</option>
))}
</select>
</div>
<div>
2025-09-15 21:42:39 +00:00
<label className="block text-sm font-medium text-gray-700">Talep Tipi</label>
2025-09-15 09:31:47 +00:00
<select
name="requestType"
value={formData.requestType || RequestTypeEnum.Material}
onChange={handleInputChange}
disabled={isReadOnly}
className="mt-1 block w-full border border-gray-300 rounded-md px-2 py-1.5 text-sm focus:outline-none focus:ring-1 focus:ring-blue-500"
>
<option value={RequestTypeEnum.Material}>Malzeme</option>
<option value={RequestTypeEnum.Service}>Hizmet</option>
<option value={RequestTypeEnum.WorkCenter}>İş Merkezi</option>
<option value={RequestTypeEnum.Maintenance}>Bakım</option>
</select>
</div>
<div>
2025-09-15 21:42:39 +00:00
<label className="block text-sm font-medium text-gray-700">Tutar Eşiği (TL)</label>
2025-09-15 09:31:47 +00:00
<input
type="number"
name="amountThreshold"
value={formData.amountThreshold || 0}
onChange={handleInputChange}
readOnly={isReadOnly}
className="mt-1 block w-full border border-gray-300 rounded-md px-2 py-1.5 text-sm focus:outline-none focus:ring-1 focus:ring-blue-500"
/>
</div>
<div>
<label className="flex items-center">
<input
type="checkbox"
name="isActive"
checked={formData.isActive || false}
onChange={handleInputChange}
disabled={isReadOnly}
className="mr-2"
/>
2025-09-15 21:42:39 +00:00
<span className="text-sm font-medium text-gray-700">Aktif</span>
2025-09-15 09:31:47 +00:00
</label>
</div>
</div>
{/* Onay Seviyeleri */}
<div className="space-y-3">
<div className="flex items-center justify-between">
<h4 className="text-base font-medium text-gray-900 border-b pb-1">
Onay Seviyeleri
</h4>
{!isReadOnly && (
<button
type="button"
onClick={addApprovalLevel}
className="px-3 py-1 bg-blue-600 text-white rounded-md hover:bg-blue-700 flex items-center text-sm"
>
<FaPlus className="mr-1" />
Seviye Ekle
</button>
)}
</div>
<div className="space-y-2 max-h-56 overflow-y-auto p-1">
{formData.approvalLevels?.map((level, index) => (
2025-09-15 21:42:39 +00:00
<div key={level.id} className="border rounded-lg p-2 bg-gray-50">
2025-09-15 09:31:47 +00:00
<div className="flex items-center justify-between mb-1">
<span className="text-sm font-medium text-gray-700">
Seviye {level.sequence}
</span>
{!isReadOnly && (
<button
type="button"
onClick={() => removeApprovalLevel(index)}
className="text-red-600 hover:text-red-800"
>
<FaTrash />
</button>
)}
</div>
<div className="grid grid-cols-1 gap-1">
<div>
<label className="block text-xs font-medium text-gray-600">
Onay Seviyesi
</label>
<select
value={level.level}
2025-09-15 21:42:39 +00:00
onChange={(e) => updateApprovalLevel(index, 'level', e.target.value)}
2025-09-15 09:31:47 +00:00
disabled={isReadOnly}
className="mt-1 block w-full text-sm border border-gray-300 rounded-md px-2 py-1 focus:outline-none focus:ring-1 focus:ring-blue-500"
>
2025-09-15 21:42:39 +00:00
<option value={ApprovalLevelEnum.Supervisor}>Süpervizör</option>
<option value={ApprovalLevelEnum.Manager}>Müdür</option>
<option value={ApprovalLevelEnum.Director}>Direktör</option>
<option value={ApprovalLevelEnum.GeneralManager}>Genel Müdür</option>
2025-09-15 09:31:47 +00:00
<option value={ApprovalLevelEnum.FinanceManager}>
Mali İşler Müdürü
</option>
2025-09-15 21:42:39 +00:00
<option value={ApprovalLevelEnum.TechnicalManager}>Teknik Müdür</option>
2025-09-15 09:31:47 +00:00
</select>
</div>
<div>
<label className="block text-xs font-medium text-gray-600">
Onaylayıcılar
</label>
<MultiSelectEmployee
selectedEmployees={level.approverNames || []}
2025-09-15 21:42:39 +00:00
onChange={(employees) => handleApproverNamesChange(index, employees)}
2025-09-15 09:31:47 +00:00
disabled={isReadOnly}
placeholder="Onaylayıcı seçin..."
className="mt-1"
/>
</div>
<div>
<label className="block text-xs font-medium text-gray-600">
Timeout (Gün)
</label>
<input
type="number"
2025-09-15 21:42:39 +00:00
value={level.timeoutDays || ''}
2025-09-15 09:31:47 +00:00
onChange={(e) =>
updateApprovalLevel(
index,
2025-09-15 21:42:39 +00:00
'timeoutDays',
parseInt(e.target.value) || undefined,
2025-09-15 09:31:47 +00:00
)
}
readOnly={isReadOnly}
min="1"
className="mt-1 block w-full text-sm border border-gray-300 rounded-md px-2 py-1 focus:outline-none focus:ring-1 focus:ring-blue-500"
/>
</div>
<div className="flex space-x-3">
<label className="flex items-center">
<input
type="checkbox"
checked={level.isRequired}
onChange={(e) =>
2025-09-15 21:42:39 +00:00
updateApprovalLevel(index, 'isRequired', e.target.checked)
2025-09-15 09:31:47 +00:00
}
disabled={isReadOnly}
className="mr-1"
/>
<span className="text-xs text-gray-600">Zorunlu</span>
</label>
<label className="flex items-center">
<input
type="checkbox"
checked={level.isParallel}
onChange={(e) =>
2025-09-15 21:42:39 +00:00
updateApprovalLevel(index, 'isParallel', e.target.checked)
2025-09-15 09:31:47 +00:00
}
disabled={isReadOnly}
className="mr-1"
/>
<span className="text-xs text-gray-600">Paralel</span>
</label>
</div>
</div>
</div>
))}
</div>
{formData.approvalLevels?.length === 0 && (
<div className="text-center text-gray-500 text-sm py-2">
Henüz onay seviyesi eklenmedi
</div>
)}
</div>
</div>
{/* Action Buttons */}
<div className="flex justify-end space-x-3 mt-4 pt-3 border-t">
<button
type="button"
onClick={onClose}
className="px-3 py-1.5 border border-gray-300 rounded-md text-sm text-gray-700 hover:bg-gray-50"
>
2025-09-15 21:42:39 +00:00
{mode === 'view' ? 'Kapat' : 'İptal'}
2025-09-15 09:31:47 +00:00
</button>
2025-09-15 21:42:39 +00:00
{mode !== 'view' && (
2025-09-15 09:31:47 +00:00
<button
type="submit"
className="px-3 py-1.5 bg-blue-600 text-white rounded-md hover:bg-blue-700 flex items-center text-sm"
>
<FaSave className="mr-2" />
Kaydet
</button>
)}
</div>
</form>
</div>
</div>
2025-09-15 21:42:39 +00:00
)
}
2025-09-15 09:31:47 +00:00
2025-09-15 21:42:39 +00:00
export default ApprovalWorkflowModal