erp-platform/ui/src/views/supplyChain/components/ApprovalWorkflows.tsx

405 lines
15 KiB
TypeScript
Raw Normal View History

2025-09-15 09:31:47 +00:00
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";
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 (
<div className="space-y-3 pt-2">
{/* Header */}
<div className="flex items-center justify-between">
<div>
2025-09-15 21:02:48 +00:00
<h2 className="text-2xl font-bold text-gray-900">Onay Süreçleri</h2>
<p className="text-gray-600">
2025-09-15 09:31:47 +00:00
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>
{/* Approval Workflow Modal */}
<ApprovalWorkflowModal
isOpen={showModal}
onClose={handleCloseModal}
onSave={handleSaveWorkflow}
workflow={editingWorkflow}
mode={modalMode}
/>
</div>
);
};
export default ApprovalWorkflows;