erp-platform/ui/src/views/supplychain/components/ApprovalWorkflows.tsx
2025-09-16 11:41:15 +03:00

367 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 {
FaPlus,
FaSearch,
FaEdit,
FaTrash,
FaCodeBranch,
FaUsers,
FaCog,
FaClock,
FaCheckCircle,
FaEye,
} from 'react-icons/fa'
import { MmApprovalWorkflow } from '../../../types/mm'
import ApprovalWorkflowModal from './ApprovalWorkflowModal'
import { mockApprovalWorkflows } from '../../../mocks/mockApprovalWorkflows'
import {
getApprovalLevelColor,
getApprovalLevelText,
getRequestTypeColor,
getRequestTypeText,
} from '../../../utils/erp'
import { Container } from '@/components/shared'
const ApprovalWorkflows: React.FC = () => {
const [searchTerm, setSearchTerm] = useState('')
const [showModal, setShowModal] = useState(false)
const [editingWorkflow, setEditingWorkflow] = useState<MmApprovalWorkflow | null>(null)
const [selectedWorkflow, setSelectedWorkflow] = useState<MmApprovalWorkflow | null>(null)
const [modalMode, setModalMode] = useState<'create' | 'view' | 'edit'>('create')
// Mock data - replace with actual API calls
const [workflows] = useState<MmApprovalWorkflow[]>(mockApprovalWorkflows)
const filteredWorkflows = workflows.filter(
(workflow) =>
workflow.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
workflow.departmentId.toLowerCase().includes(searchTerm.toLowerCase()),
)
const getTotalSteps = (workflow: MmApprovalWorkflow) => {
return workflow.approvalLevels.length
}
const getRequiredSteps = (workflow: MmApprovalWorkflow) => {
return workflow.approvalLevels.filter((level) => level.isRequired).length
}
const getMaxTimeout = (workflow: MmApprovalWorkflow) => {
return Math.max(...workflow.approvalLevels.map((level) => level.timeoutDays || 0))
}
const handleEdit = (workflow: MmApprovalWorkflow) => {
setEditingWorkflow(workflow)
setModalMode('edit')
setShowModal(true)
}
const handleView = (workflow: MmApprovalWorkflow) => {
setEditingWorkflow(workflow)
setModalMode('view')
setShowModal(true)
}
const handleAddNew = () => {
setEditingWorkflow(null)
setModalMode('create')
setShowModal(true)
}
const handleViewDetails = (workflow: MmApprovalWorkflow) => {
setSelectedWorkflow(workflow)
}
const handleSaveWorkflow = (workflow: MmApprovalWorkflow) => {
// TODO: Implement save logic
console.log('Saving workflow:', workflow)
setShowModal(false)
setEditingWorkflow(null)
}
const handleCloseModal = () => {
setShowModal(false)
setEditingWorkflow(null)
}
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">Onay Süreçleri</h2>
<p className="text-gray-600">Departman bazlı satınalma onay süreçlerini yönetin</p>
</div>
<button
onClick={handleAddNew}
className="bg-blue-600 text-white px-3 py-1.5 rounded-lg hover:bg-blue-700 flex items-center space-x-2 text-sm"
>
<FaPlus className="w-4 h-4" />
<span>Yeni Onay Süreci</span>
</button>
</div>
{/* Search Bar */}
<div className="relative">
<FaSearch className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
<input
type="text"
placeholder="Onay süreci ara..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full pl-10 pr-4 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
/>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
{/* Workflow List */}
<div className="space-y-3 pt-2">
<h3 className="text-base font-semibold text-gray-900">Onay Süreçleri</h3>
{filteredWorkflows.map((workflow) => (
<div
key={workflow.id}
className="bg-white rounded-lg shadow-md border border-gray-200 p-3 hover:shadow-lg transition-shadow cursor-pointer"
onClick={() => handleViewDetails(workflow)}
>
<div className="flex items-start justify-between mb-2">
<div className="flex-1">
<div className="flex items-center space-x-2 mb-1">
<FaCodeBranch className="w-4 h-4 text-gray-600" />
<h4 className="text-base font-semibold text-gray-900">{workflow.name}</h4>
<span
className={`px-2 py-1 rounded-full text-xs font-medium ${getRequestTypeColor(
workflow.requestType,
)}`}
>
{getRequestTypeText(workflow.requestType)}
</span>
</div>
<p className="text-xs text-gray-600 mb-1">
Departman: {workflow.department?.name}
</p>
<p className="text-sm text-gray-600">
Limit: {workflow.amountThreshold.toLocaleString()} ve üzeri
</p>
</div>
<div className="flex space-x-1">
<button
onClick={(e) => {
e.stopPropagation()
handleView(workflow)
}}
className="p-1.5 text-gray-400 hover:text-blue-600 hover:bg-blue-50 rounded-md transition-colors"
title="Görüntüle"
>
<FaEye className="w-4 h-4" />
</button>
<button
onClick={(e) => {
e.stopPropagation()
handleEdit(workflow)
}}
className="p-1.5 text-gray-400 hover:text-green-600 hover:bg-green-50 rounded-md transition-colors"
title="Düzenle"
>
<FaEdit className="w-4 h-4" />
</button>
<button
onClick={(e) => e.stopPropagation()}
className="p-1.5 text-gray-400 hover:text-red-600 hover:bg-red-50 rounded-md transition-colors"
title="Sil"
>
<FaTrash className="w-4 h-4" />
</button>
</div>
</div>
<div className="grid grid-cols-2 gap-3 text-xs mb-2">
<div className="flex items-center justify-between">
<span className="text-gray-500 flex items-center">
<FaUsers className="w-4 h-4 mr-1" />
Toplam Adım
</span>
<span className="font-medium">{getTotalSteps(workflow)}</span>
</div>
<div className="flex items-center justify-between">
<span className="text-gray-500 flex items-center">
<FaCheckCircle className="w-4 h-4 mr-1" />
Zorunlu Adım
</span>
<span className="font-medium">{getRequiredSteps(workflow)}</span>
</div>
<div className="flex items-center justify-between">
<span className="text-gray-500 flex items-center">
<FaClock className="w-4 h-4 mr-1" />
Maks. Süre
</span>
<span className="font-medium">{getMaxTimeout(workflow)} gün</span>
</div>
<div className="flex items-center justify-between">
<span className="text-gray-500">Son Güncelleme</span>
<span className="font-medium">
{workflow.lastModificationTime.toLocaleDateString('tr-TR')}
</span>
</div>
</div>
<div className="flex items-center justify-between">
<span
className={`px-2 py-1 rounded-full text-xs font-medium ${
workflow.isActive ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
}`}
>
{workflow.isActive ? 'Aktif' : 'Pasif'}
</span>
<div className="flex items-center space-x-1">
{workflow.approvalLevels.some((level) => level.isParallel) && (
<span className="bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded flex items-center">
<FaCodeBranch className="w-3 h-3 mr-1" />
Paralel Onay
</span>
)}
</div>
</div>
</div>
))}
{filteredWorkflows.length === 0 && (
<div className="text-center py-6">
<FaCodeBranch className="w-12 h-12 text-gray-400 mx-auto mb-4" />
<h3 className="text-base font-medium text-gray-900 mb-2">Onay süreci bulunamadı</h3>
<p className="text-sm text-gray-500">
Arama kriterlerinizi değiştirin veya yeni bir onay süreci ekleyin.
</p>
</div>
)}
</div>
{/* Workflow Details */}
<div className="space-y-3 pt-2">
<h3 className="text-base font-semibold text-gray-900">Onay Adımları</h3>
{selectedWorkflow ? (
<div className="bg-white rounded-lg shadow-md border border-gray-200 p-3">
<div className="mb-3">
<h4 className="font-semibold text-base text-gray-900 mb-2">
{selectedWorkflow.name}
</h4>
<div className="grid grid-cols-2 gap-1 text-xs text-gray-600 mb-2">
<div>Departman: {selectedWorkflow.departmentId}</div>
<div>Tür: {getRequestTypeText(selectedWorkflow.requestType)}</div>
<div>Tutar Limiti: {selectedWorkflow.amountThreshold.toLocaleString()}</div>
<div>Durum: {selectedWorkflow.isActive ? 'Aktif' : 'Pasif'}</div>
</div>
</div>
<div className="space-y-2">
{selectedWorkflow.approvalLevels
.sort((a, b) => a.sequence - b.sequence)
.map((level, index) => (
<div key={level.id} className="border border-gray-200 rounded-lg p-2">
<div className="flex items-center justify-between mb-2">
<div className="flex items-center space-x-2">
<span className="bg-blue-100 text-blue-800 text-xs font-medium px-2 py-1 rounded">
Adım {level.sequence}
</span>
<span
className={`px-2 py-1 rounded-full text-xs font-medium ${getApprovalLevelColor(
level.level,
)}`}
>
{getApprovalLevelText(level.level)}
</span>
{level.isRequired && (
<span className="bg-red-100 text-red-800 text-xs px-2 py-1 rounded">
Zorunlu
</span>
)}
{level.isParallel && (
<span className="bg-purple-100 text-purple-800 text-xs px-2 py-1 rounded">
Paralel
</span>
)}
</div>
{level.timeoutDays && (
<span className="text-xs text-gray-500 flex items-center">
<FaClock className="w-3 h-3 mr-1" />
{level.timeoutDays} gün
</span>
)}
</div>
<div className="text-xs">
<div className="font-medium text-gray-900 mb-1">Onaylayanlar:</div>
<div className="space-y-1">
{level.approverNames.map((name, idx) => (
<div key={idx} className="flex items-center space-x-2">
<FaUsers className="w-4 h-4 text-gray-400" />
<span className="text-gray-700">{name}</span>
<span className="text-xs text-gray-500">
({level.approverUserIds[idx]})
</span>
</div>
))}
</div>
</div>
{index < selectedWorkflow.approvalLevels.length - 1 && (
<div className="flex justify-center mt-3">
<div className="w-px h-4 bg-gray-300"></div>
</div>
)}
</div>
))}
</div>
{/* Workflow Statistics */}
<div className="mt-3 pt-3 border-t border-gray-100">
<div className="grid grid-cols-3 gap-3 text-xs">
<div className="text-center">
<div className="font-medium text-gray-900">
{getTotalSteps(selectedWorkflow)}
</div>
<div className="text-gray-500">Toplam Adım</div>
</div>
<div className="text-center">
<div className="font-medium text-gray-900">
{getRequiredSteps(selectedWorkflow)}
</div>
<div className="text-gray-500">Zorunlu Adım</div>
</div>
<div className="text-center">
<div className="font-medium text-gray-900">
{getMaxTimeout(selectedWorkflow)}
</div>
<div className="text-gray-500">Maks. Gün</div>
</div>
</div>
</div>
</div>
) : (
<div className="bg-gray-50 rounded-lg p-6 text-center">
<FaCog className="w-12 h-12 text-gray-400 mx-auto mb-4" />
<h3 className="text-base font-medium text-gray-900 mb-2">
Onay Adımlarını Görüntüle
</h3>
<p className="text-sm text-gray-500">
Detaylarını görmek için sol taraftan bir onay süreci seçin.
</p>
</div>
)}
</div>
</div>
</div>
{/* Approval Workflow Modal */}
<ApprovalWorkflowModal
isOpen={showModal}
onClose={handleCloseModal}
onSave={handleSaveWorkflow}
workflow={editingWorkflow}
mode={modalMode}
/>
</Container>
)
}
export default ApprovalWorkflows