erp-platform/ui/src/views/supplychains/components/ApprovalWorkflowModal.tsx
2025-09-16 11:40:38 +03:00

387 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, useEffect } from 'react'
import { FaTimes, FaSave, FaUsers, FaPlus, FaTrash } from 'react-icons/fa'
import {
MmApprovalWorkflow,
MmApprovalWorkflowLevel,
RequestTypeEnum,
ApprovalLevelEnum,
} from '../../../types/mm'
import MultiSelectEmployee from '../../../components/common/MultiSelectEmployee'
import { mockDepartments } from '../../../mocks/mockDepartments'
interface ApprovalWorkflowModalProps {
isOpen: boolean
onClose: () => void
onSave: (workflow: MmApprovalWorkflow) => void
workflow?: MmApprovalWorkflow | null
mode: 'create' | 'view' | 'edit'
}
const ApprovalWorkflowModal: React.FC<ApprovalWorkflowModalProps> = ({
isOpen,
onClose,
onSave,
workflow,
mode,
}) => {
const [formData, setFormData] = useState<Partial<MmApprovalWorkflow>>({
name: '',
departmentId: '',
requestType: RequestTypeEnum.Material,
amountThreshold: 0,
approvalLevels: [],
isActive: true,
})
useEffect(() => {
if (mode === 'create') {
// Reset form to initial empty state for new workflow
setFormData({
name: '',
departmentId: '',
requestType: RequestTypeEnum.Material,
amountThreshold: 0,
approvalLevels: [],
isActive: true,
})
} else if (workflow) {
// Load existing workflow data for view/edit modes
setFormData(workflow)
}
}, [workflow, mode])
const handleInputChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>,
) => {
const { name, value, type } = e.target
setFormData((prev) => ({
...prev,
[name]:
type === 'number'
? parseFloat(value) || 0
: type === 'checkbox'
? (e.target as HTMLInputElement).checked
: value,
}))
}
const addApprovalLevel = () => {
const newLevel: MmApprovalWorkflowLevel = {
id: `level-${Date.now()}`,
workflowId: formData.id || '',
level: ApprovalLevelEnum.Supervisor,
approverUserIds: [],
approverNames: [],
sequence: (formData.approvalLevels?.length || 0) + 1,
isRequired: true,
isParallel: false,
}
setFormData((prev) => ({
...prev,
approvalLevels: [...(prev.approvalLevels || []), newLevel],
}))
}
const removeApprovalLevel = (index: number) => {
setFormData((prev) => ({
...prev,
approvalLevels: prev.approvalLevels?.filter((_, i) => i !== index) || [],
}))
}
const updateApprovalLevel = (
index: number,
field: keyof MmApprovalWorkflowLevel,
value: string | number | boolean | string[] | undefined,
) => {
setFormData((prev) => ({
...prev,
approvalLevels:
prev.approvalLevels?.map((level, i) =>
i === index ? { ...level, [field]: value } : level,
) || [],
}))
}
const handleApproverNamesChange = (index: number, employees: string[]) => {
updateApprovalLevel(index, 'approverNames', employees)
}
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
if (mode !== 'view') {
const newWorkflow: MmApprovalWorkflow = {
id: workflow?.id || `WF-${Date.now()}`,
name: formData.name || '',
departmentId: formData.departmentId || '',
requestType: formData.requestType || RequestTypeEnum.Material,
amountThreshold: formData.amountThreshold || 0,
approvalLevels: formData.approvalLevels || [],
isActive: formData.isActive ?? true,
creationTime: workflow?.creationTime || new Date(),
lastModificationTime: new Date(),
}
onSave(newWorkflow)
}
onClose()
}
if (!isOpen) return null
const isReadOnly = mode === 'view'
const modalTitle =
mode === 'create'
? 'Yeni Onay Süreci'
: mode === 'edit'
? 'Onay Sürecini Düzenle'
: 'Onay Süreci Detayları'
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>
<button onClick={onClose} className="text-gray-400 hover:text-gray-600 p-1">
<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">
<h4 className="text-base font-medium text-gray-900 border-b pb-1">Temel Bilgiler</h4>
<div>
<label className="block text-sm font-medium text-gray-700">Süreç Adı</label>
<input
type="text"
name="workflowName"
value={formData.name || ''}
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>
<label className="block text-sm font-medium text-gray-700">Departman</label>
<select
value={formData.departmentId || ''}
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>
<label className="block text-sm font-medium text-gray-700">Talep Tipi</label>
<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>
<label className="block text-sm font-medium text-gray-700">Tutar Eşiği (TL)</label>
<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"
/>
<span className="text-sm font-medium text-gray-700">Aktif</span>
</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) => (
<div key={level.id} className="border rounded-lg p-2 bg-gray-50">
<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}
onChange={(e) => updateApprovalLevel(index, 'level', e.target.value)}
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"
>
<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>
<option value={ApprovalLevelEnum.FinanceManager}>
Mali İşler Müdürü
</option>
<option value={ApprovalLevelEnum.TechnicalManager}>Teknik Müdür</option>
</select>
</div>
<div>
<label className="block text-xs font-medium text-gray-600">
Onaylayıcılar
</label>
<MultiSelectEmployee
selectedEmployees={level.approverNames || []}
onChange={(employees) => handleApproverNamesChange(index, employees)}
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"
value={level.timeoutDays || ''}
onChange={(e) =>
updateApprovalLevel(
index,
'timeoutDays',
parseInt(e.target.value) || undefined,
)
}
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) =>
updateApprovalLevel(index, 'isRequired', e.target.checked)
}
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) =>
updateApprovalLevel(index, 'isParallel', e.target.checked)
}
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"
>
{mode === 'view' ? 'Kapat' : 'İptal'}
</button>
{mode !== 'view' && (
<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>
)
}
export default ApprovalWorkflowModal