Supply Management

This commit is contained in:
Sedat Öztürk 2025-09-16 00:42:39 +03:00
parent 4b7fd59e61
commit 811a575ff7
21 changed files with 6426 additions and 7554 deletions

View file

@ -3270,16 +3270,6 @@
"RequiredPermissionName": null,
"IsDisabled": false
},
{
"ParentCode": "App.SupplyChain",
"Code": "App.SupplyChain.Requisitions",
"DisplayName": "Satınalma İstekleri",
"Order": 6,
"Url": "/admin/supplychain/requisitions",
"Icon": "FcPlanner",
"RequiredPermissionName": null,
"IsDisabled": false
},
{
"ParentCode": "App.SupplyChain",
"Code": "App.SupplyChain.Quotations",

View file

@ -1,20 +1,20 @@
import React, { useState, useEffect } from "react";
import { FaTimes, FaSave, FaUsers, FaPlus, FaTrash } from "react-icons/fa";
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";
} 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";
isOpen: boolean
onClose: () => void
onSave: (workflow: MmApprovalWorkflow) => void
workflow?: MmApprovalWorkflow | null
mode: 'create' | 'view' | 'edit'
}
const ApprovalWorkflowModal: React.FC<ApprovalWorkflowModalProps> = ({
@ -25,119 +25,117 @@ const ApprovalWorkflowModal: React.FC<ApprovalWorkflowModalProps> = ({
mode,
}) => {
const [formData, setFormData] = useState<Partial<MmApprovalWorkflow>>({
name: "",
departmentId: "",
name: '',
departmentId: '',
requestType: RequestTypeEnum.Material,
amountThreshold: 0,
approvalLevels: [],
isActive: true,
});
})
useEffect(() => {
if (mode === "create") {
if (mode === 'create') {
// Reset form to initial empty state for new workflow
setFormData({
name: "",
departmentId: "",
name: '',
departmentId: '',
requestType: RequestTypeEnum.Material,
amountThreshold: 0,
approvalLevels: [],
isActive: true,
});
})
} else if (workflow) {
// Load existing workflow data for view/edit modes
setFormData(workflow);
setFormData(workflow)
}
}, [workflow, mode]);
}, [workflow, mode])
const handleInputChange = (
e: React.ChangeEvent<
HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement
>
e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>,
) => {
const { name, value, type } = e.target;
const { name, value, type } = e.target
setFormData((prev) => ({
...prev,
[name]:
type === "number"
type === 'number'
? parseFloat(value) || 0
: type === "checkbox"
? (e.target as HTMLInputElement).checked
: value,
}));
};
: type === 'checkbox'
? (e.target as HTMLInputElement).checked
: value,
}))
}
const addApprovalLevel = () => {
const newLevel: MmApprovalWorkflowLevel = {
id: `level-${Date.now()}`,
workflowId: formData.id || "",
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
value: string | number | boolean | string[] | undefined,
) => {
setFormData((prev) => ({
...prev,
approvalLevels:
prev.approvalLevels?.map((level, i) =>
i === index ? { ...level, [field]: value } : level
i === index ? { ...level, [field]: value } : level,
) || [],
}));
};
}))
}
const handleApproverNamesChange = (index: number, employees: string[]) => {
updateApprovalLevel(index, "approverNames", employees);
};
updateApprovalLevel(index, 'approverNames', employees)
}
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (mode !== "view") {
e.preventDefault()
if (mode !== 'view') {
const newWorkflow: MmApprovalWorkflow = {
id: workflow?.id || `WF-${Date.now()}`,
name: formData.name || "",
departmentId: formData.departmentId || "",
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);
}
onSave(newWorkflow)
}
onClose();
};
onClose()
}
if (!isOpen) return null;
if (!isOpen) return null
const isReadOnly = mode === "view";
const isReadOnly = mode === 'view'
const modalTitle =
mode === "create"
? "Yeni Onay Süreci"
: mode === "edit"
? "Onay Sürecini Düzenle"
: "Onay Süreci Detayları";
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">
@ -147,10 +145,7 @@ const ApprovalWorkflowModal: React.FC<ApprovalWorkflowModalProps> = ({
<FaUsers className="mr-2 text-blue-600" />
{modalTitle}
</h3>
<button
onClick={onClose}
className="text-gray-400 hover:text-gray-600 p-1"
>
<button onClick={onClose} className="text-gray-400 hover:text-gray-600 p-1">
<FaTimes />
</button>
</div>
@ -159,18 +154,14 @@ const ApprovalWorkflowModal: React.FC<ApprovalWorkflowModalProps> = ({
<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>
<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>
<label className="block text-sm font-medium text-gray-700">Süreç Adı</label>
<input
type="text"
name="workflowName"
value={formData.name || ""}
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"
@ -179,11 +170,9 @@ const ApprovalWorkflowModal: React.FC<ApprovalWorkflowModalProps> = ({
</div>
<div>
<label className="block text-sm font-medium text-gray-700">
Departman
</label>
<label className="block text-sm font-medium text-gray-700">Departman</label>
<select
value={formData.departmentId || ""}
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
@ -198,9 +187,7 @@ const ApprovalWorkflowModal: React.FC<ApprovalWorkflowModalProps> = ({
</div>
<div>
<label className="block text-sm font-medium text-gray-700">
Talep Tipi
</label>
<label className="block text-sm font-medium text-gray-700">Talep Tipi</label>
<select
name="requestType"
value={formData.requestType || RequestTypeEnum.Material}
@ -216,9 +203,7 @@ const ApprovalWorkflowModal: React.FC<ApprovalWorkflowModalProps> = ({
</div>
<div>
<label className="block text-sm font-medium text-gray-700">
Tutar Eşiği (TL)
</label>
<label className="block text-sm font-medium text-gray-700">Tutar Eşiği (TL)</label>
<input
type="number"
name="amountThreshold"
@ -239,9 +224,7 @@ const ApprovalWorkflowModal: React.FC<ApprovalWorkflowModalProps> = ({
disabled={isReadOnly}
className="mr-2"
/>
<span className="text-sm font-medium text-gray-700">
Aktif
</span>
<span className="text-sm font-medium text-gray-700">Aktif</span>
</label>
</div>
</div>
@ -266,10 +249,7 @@ const ApprovalWorkflowModal: React.FC<ApprovalWorkflowModalProps> = ({
<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 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}
@ -292,30 +272,18 @@ const ApprovalWorkflowModal: React.FC<ApprovalWorkflowModalProps> = ({
</label>
<select
value={level.level}
onChange={(e) =>
updateApprovalLevel(index, "level", e.target.value)
}
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.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>
<option value={ApprovalLevelEnum.TechnicalManager}>Teknik Müdür</option>
</select>
</div>
@ -325,9 +293,7 @@ const ApprovalWorkflowModal: React.FC<ApprovalWorkflowModalProps> = ({
</label>
<MultiSelectEmployee
selectedEmployees={level.approverNames || []}
onChange={(employees) =>
handleApproverNamesChange(index, employees)
}
onChange={(employees) => handleApproverNamesChange(index, employees)}
disabled={isReadOnly}
placeholder="Onaylayıcı seçin..."
className="mt-1"
@ -340,12 +306,12 @@ const ApprovalWorkflowModal: React.FC<ApprovalWorkflowModalProps> = ({
</label>
<input
type="number"
value={level.timeoutDays || ""}
value={level.timeoutDays || ''}
onChange={(e) =>
updateApprovalLevel(
index,
"timeoutDays",
parseInt(e.target.value) || undefined
'timeoutDays',
parseInt(e.target.value) || undefined,
)
}
readOnly={isReadOnly}
@ -360,11 +326,7 @@ const ApprovalWorkflowModal: React.FC<ApprovalWorkflowModalProps> = ({
type="checkbox"
checked={level.isRequired}
onChange={(e) =>
updateApprovalLevel(
index,
"isRequired",
e.target.checked
)
updateApprovalLevel(index, 'isRequired', e.target.checked)
}
disabled={isReadOnly}
className="mr-1"
@ -376,11 +338,7 @@ const ApprovalWorkflowModal: React.FC<ApprovalWorkflowModalProps> = ({
type="checkbox"
checked={level.isParallel}
onChange={(e) =>
updateApprovalLevel(
index,
"isParallel",
e.target.checked
)
updateApprovalLevel(index, 'isParallel', e.target.checked)
}
disabled={isReadOnly}
className="mr-1"
@ -408,9 +366,9 @@ const ApprovalWorkflowModal: React.FC<ApprovalWorkflowModalProps> = ({
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"}
{mode === 'view' ? 'Kapat' : 'İptal'}
</button>
{mode !== "view" && (
{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"
@ -423,7 +381,7 @@ const ApprovalWorkflowModal: React.FC<ApprovalWorkflowModalProps> = ({
</form>
</div>
</div>
);
};
)
}
export default ApprovalWorkflowModal;
export default ApprovalWorkflowModal

View file

@ -1,4 +1,4 @@
import React, { useState } from "react";
import React, { useState } from 'react'
import {
FaPlus,
FaSearch,
@ -10,382 +10,345 @@ import {
FaClock,
FaCheckCircle,
FaEye,
} from "react-icons/fa";
import { MmApprovalWorkflow } from "../../../types/mm";
import ApprovalWorkflowModal from "./ApprovalWorkflowModal";
import { mockApprovalWorkflows } from "../../../mocks/mockApprovalWorkflows";
} 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";
} 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"
);
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 [workflows] = useState<MmApprovalWorkflow[]>(mockApprovalWorkflows)
const filteredWorkflows = workflows.filter(
(workflow) =>
workflow.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
workflow.departmentId.toLowerCase().includes(searchTerm.toLowerCase())
);
workflow.departmentId.toLowerCase().includes(searchTerm.toLowerCase()),
)
const getTotalSteps = (workflow: MmApprovalWorkflow) => {
return workflow.approvalLevels.length;
};
return workflow.approvalLevels.length
}
const getRequiredSteps = (workflow: MmApprovalWorkflow) => {
return workflow.approvalLevels.filter((level) => level.isRequired).length;
};
return workflow.approvalLevels.filter((level) => level.isRequired).length
}
const getMaxTimeout = (workflow: MmApprovalWorkflow) => {
return Math.max(
...workflow.approvalLevels.map((level) => level.timeoutDays || 0)
);
};
return Math.max(...workflow.approvalLevels.map((level) => level.timeoutDays || 0))
}
const handleEdit = (workflow: MmApprovalWorkflow) => {
setEditingWorkflow(workflow);
setModalMode("edit");
setShowModal(true);
};
setEditingWorkflow(workflow)
setModalMode('edit')
setShowModal(true)
}
const handleView = (workflow: MmApprovalWorkflow) => {
setEditingWorkflow(workflow);
setModalMode("view");
setShowModal(true);
};
setEditingWorkflow(workflow)
setModalMode('view')
setShowModal(true)
}
const handleAddNew = () => {
setEditingWorkflow(null);
setModalMode("create");
setShowModal(true);
};
setEditingWorkflow(null)
setModalMode('create')
setShowModal(true)
}
const handleViewDetails = (workflow: MmApprovalWorkflow) => {
setSelectedWorkflow(workflow);
};
setSelectedWorkflow(workflow)
}
const handleSaveWorkflow = (workflow: MmApprovalWorkflow) => {
// TODO: Implement save logic
console.log("Saving workflow:", workflow);
setShowModal(false);
setEditingWorkflow(null);
};
console.log('Saving workflow:', workflow)
setShowModal(false)
setEditingWorkflow(null)
}
const handleCloseModal = () => {
setShowModal(false);
setEditingWorkflow(null);
};
setShowModal(false)
setEditingWorkflow(null)
}
return (
<div className="space-y-3 pt-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>
)}
<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>
{/* 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)}
{/* 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>
Tutar Limiti:
{selectedWorkflow.amountThreshold.toLocaleString()}
<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>
Durum: {selectedWorkflow.isActive ? "Aktif" : "Pasif"}
</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>
))}
<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
{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>
)}
{level.isParallel && (
<span className="bg-purple-100 text-purple-800 text-xs px-2 py-1 rounded">
Paralel
<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>
{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 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>
<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>
{/* 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>
{index < selectedWorkflow.approvalLevels.length - 1 && (
<div className="flex justify-center mt-3">
<div className="w-px h-4 bg-gray-300"></div>
</div>
)}
<div className="text-gray-500">Toplam Adım</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 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-gray-500">Toplam Adım</div>
</div>
<div className="text-center">
<div className="font-medium text-gray-900">
{getRequiredSteps(selectedWorkflow)}
<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 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 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>
@ -397,8 +360,8 @@ const ApprovalWorkflows: React.FC = () => {
workflow={editingWorkflow}
mode={modalMode}
/>
</div>
);
};
</Container>
)
}
export default ApprovalWorkflows;
export default ApprovalWorkflows

File diff suppressed because it is too large Load diff

View file

@ -1,25 +1,18 @@
import React, { useState, useEffect } from "react";
import {
FaTimes,
FaSave,
FaTruck,
FaMapMarkerAlt,
FaCalendar,
FaBox,
} from "react-icons/fa";
import React, { useState, useEffect } from 'react'
import { FaTimes, FaSave, FaTruck, FaMapMarkerAlt, FaCalendar, FaBox } from 'react-icons/fa'
import {
MmGoodsReceipt,
ReceiptStatusEnum,
QualityStatusEnum,
MmGoodsReceiptItem,
} from "../../../types/mm";
} from '../../../types/mm'
interface DeliveryTrackingModalProps {
isOpen: boolean;
onClose: () => void;
onSave: (receipt: MmGoodsReceipt) => void;
receipt?: MmGoodsReceipt | null;
mode: "create" | "view" | "edit";
isOpen: boolean
onClose: () => void
onSave: (receipt: MmGoodsReceipt) => void
receipt?: MmGoodsReceipt | null
mode: 'create' | 'view' | 'edit'
}
const DeliveryTrackingModal: React.FC<DeliveryTrackingModalProps> = ({
@ -30,123 +23,114 @@ const DeliveryTrackingModal: React.FC<DeliveryTrackingModalProps> = ({
mode,
}) => {
const [formData, setFormData] = useState<Partial<MmGoodsReceipt>>({
receiptNumber: "",
orderId: "",
receiptNumber: '',
orderId: '',
receiptDate: new Date(),
receivedBy: "",
warehouseId: "",
receivedBy: '',
warehouseId: '',
status: ReceiptStatusEnum.Pending,
notes: "",
notes: '',
items: [],
});
})
useEffect(() => {
if (mode === "create") {
if (mode === 'create') {
// Reset form to initial empty state for new goods receipt
setFormData({
receiptNumber: "",
orderId: "",
receiptNumber: '',
orderId: '',
receiptDate: new Date(),
receivedBy: "",
warehouseId: "",
receivedBy: '',
warehouseId: '',
status: ReceiptStatusEnum.Pending,
notes: "",
notes: '',
items: [],
});
})
} else if (receipt) {
// Load existing receipt data for view/edit modes
setFormData(receipt);
setFormData(receipt)
}
}, [receipt, mode]);
}, [receipt, mode])
const handleInputChange = (
e: React.ChangeEvent<
HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement
>
e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>,
) => {
const { name, value, type } = e.target;
const { name, value, type } = e.target
setFormData((prev) => ({
...prev,
[name]:
type === "number"
? parseFloat(value) || 0
: type === "date"
? new Date(value)
: value,
}));
};
type === 'number' ? parseFloat(value) || 0 : type === 'date' ? new Date(value) : value,
}))
}
const addReceiptItem = () => {
const newItem: MmGoodsReceiptItem = {
id: `item-${Date.now()}`,
receiptId: formData.id || "",
orderItemId: "",
materialId: "",
receiptId: formData.id || '',
orderItemId: '',
materialId: '',
receivedQuantity: 0,
acceptedQuantity: 0,
rejectedQuantity: 0,
qualityStatus: QualityStatusEnum.Pending,
};
}
setFormData((prev) => ({
...prev,
items: [...(prev.items || []), newItem],
}));
};
}))
}
const removeReceiptItem = (index: number) => {
setFormData((prev) => ({
...prev,
items: prev.items?.filter((_, i) => i !== index) || [],
}));
};
}))
}
const updateReceiptItem = (
index: number,
field: keyof MmGoodsReceiptItem,
value: string | number | QualityStatusEnum | Date | undefined
value: string | number | QualityStatusEnum | Date | undefined,
) => {
setFormData((prev) => ({
...prev,
items:
prev.items?.map((item, i) =>
i === index ? { ...item, [field]: value } : item
) || [],
}));
};
items: prev.items?.map((item, i) => (i === index ? { ...item, [field]: value } : item)) || [],
}))
}
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (mode !== "view") {
e.preventDefault()
if (mode !== 'view') {
const newReceipt: MmGoodsReceipt = {
id: receipt?.id || `GR-${Date.now()}`,
receiptNumber:
formData.receiptNumber ||
`GR-${new Date().getFullYear()}-${Date.now().toString().slice(-3)}`,
orderId: formData.orderId || "",
orderId: formData.orderId || '',
receiptDate: formData.receiptDate || new Date(),
receivedBy: formData.receivedBy || "",
warehouseId: formData.warehouseId || "",
receivedBy: formData.receivedBy || '',
warehouseId: formData.warehouseId || '',
status: formData.status || ReceiptStatusEnum.Pending,
notes: formData.notes,
items: formData.items || [],
creationTime: receipt?.creationTime || new Date(),
lastModificationTime: new Date(),
};
onSave(newReceipt);
}
onSave(newReceipt)
}
onClose();
};
onClose()
}
if (!isOpen) return null;
if (!isOpen) return null
const isReadOnly = mode === "view";
const isReadOnly = mode === 'view'
const modalTitle =
mode === "create"
? "Yeni Teslimat Takibi"
: mode === "edit"
? "Teslimat Takibini Düzenle"
: "Teslimat Takibi Detayları";
mode === 'create'
? 'Yeni Teslimat Takibi'
: mode === 'edit'
? 'Teslimat Takibini Düzenle'
: 'Teslimat Takibi Detayları'
return (
<div className="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
@ -156,10 +140,7 @@ const DeliveryTrackingModal: React.FC<DeliveryTrackingModalProps> = ({
<FaTruck className="mr-2 text-blue-600" />
{modalTitle}
</h3>
<button
onClick={onClose}
className="text-gray-400 hover:text-gray-600 p-1"
>
<button onClick={onClose} className="text-gray-400 hover:text-gray-600 p-1">
<FaTimes />
</button>
</div>
@ -174,13 +155,11 @@ const DeliveryTrackingModal: React.FC<DeliveryTrackingModalProps> = ({
</h4>
<div>
<label className="block text-sm font-medium text-gray-700">
Giriş Numarası
</label>
<label className="block text-sm font-medium text-gray-700">Giriş Numarası</label>
<input
type="text"
name="receiptNumber"
value={formData.receiptNumber || ""}
value={formData.receiptNumber || ''}
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"
@ -189,13 +168,11 @@ const DeliveryTrackingModal: React.FC<DeliveryTrackingModalProps> = ({
</div>
<div>
<label className="block text-sm font-medium text-gray-700">
Sipariş ID
</label>
<label className="block text-sm font-medium text-gray-700">Sipariş ID</label>
<input
type="text"
name="orderId"
value={formData.orderId || ""}
value={formData.orderId || ''}
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"
@ -213,10 +190,8 @@ const DeliveryTrackingModal: React.FC<DeliveryTrackingModalProps> = ({
name="receiptDate"
value={
formData.receiptDate
? new Date(formData.receiptDate)
.toISOString()
.split("T")[0]
: ""
? new Date(formData.receiptDate).toISOString().split('T')[0]
: ''
}
onChange={handleInputChange}
readOnly={isReadOnly}
@ -225,13 +200,11 @@ const DeliveryTrackingModal: React.FC<DeliveryTrackingModalProps> = ({
</div>
<div>
<label className="block text-sm font-medium text-gray-700">
Teslim Alan
</label>
<label className="block text-sm font-medium text-gray-700">Teslim Alan</label>
<input
type="text"
name="receivedBy"
value={formData.receivedBy || ""}
value={formData.receivedBy || ''}
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"
@ -246,7 +219,7 @@ const DeliveryTrackingModal: React.FC<DeliveryTrackingModalProps> = ({
</label>
<select
name="warehouseId"
value={formData.warehouseId || ""}
value={formData.warehouseId || ''}
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"
@ -261,9 +234,7 @@ const DeliveryTrackingModal: React.FC<DeliveryTrackingModalProps> = ({
</div>
<div>
<label className="block text-sm font-medium text-gray-700">
Durum
</label>
<label className="block text-sm font-medium text-gray-700">Durum</label>
<select
name="status"
value={formData.status || ReceiptStatusEnum.Pending}
@ -273,20 +244,16 @@ const DeliveryTrackingModal: React.FC<DeliveryTrackingModalProps> = ({
>
<option value={ReceiptStatusEnum.Pending}>Beklemede</option>
<option value={ReceiptStatusEnum.InProgress}>İşlemde</option>
<option value={ReceiptStatusEnum.Completed}>
Tamamlandı
</option>
<option value={ReceiptStatusEnum.Completed}>Tamamlandı</option>
<option value={ReceiptStatusEnum.OnHold}>Bekletildi</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700">
Notlar
</label>
<label className="block text-sm font-medium text-gray-700">Notlar</label>
<textarea
name="notes"
value={formData.notes || ""}
value={formData.notes || ''}
onChange={handleInputChange}
readOnly={isReadOnly}
rows={2}
@ -315,14 +282,9 @@ const DeliveryTrackingModal: React.FC<DeliveryTrackingModalProps> = ({
<div className="space-y-2 max-h-72 overflow-y-auto p-1">
{formData.items?.map((item, index) => (
<div
key={item.id}
className="border rounded-lg p-2 bg-gray-50"
>
<div key={item.id} className="border rounded-lg p-2 bg-gray-50">
<div className="flex items-center justify-between mb-2">
<span className="text-sm font-medium text-gray-700">
Kalem {index + 1}
</span>
<span className="text-sm font-medium text-gray-700">Kalem {index + 1}</span>
{!isReadOnly && (
<button
type="button"
@ -342,13 +304,7 @@ const DeliveryTrackingModal: React.FC<DeliveryTrackingModalProps> = ({
<input
type="text"
value={item.materialId}
onChange={(e) =>
updateReceiptItem(
index,
"materialId",
e.target.value
)
}
onChange={(e) => updateReceiptItem(index, 'materialId', e.target.value)}
readOnly={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"
/>
@ -361,13 +317,7 @@ const DeliveryTrackingModal: React.FC<DeliveryTrackingModalProps> = ({
<input
type="text"
value={item.orderItemId}
onChange={(e) =>
updateReceiptItem(
index,
"orderItemId",
e.target.value
)
}
onChange={(e) => updateReceiptItem(index, 'orderItemId', e.target.value)}
readOnly={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"
/>
@ -383,8 +333,8 @@ const DeliveryTrackingModal: React.FC<DeliveryTrackingModalProps> = ({
onChange={(e) =>
updateReceiptItem(
index,
"receivedQuantity",
parseFloat(e.target.value) || 0
'receivedQuantity',
parseFloat(e.target.value) || 0,
)
}
readOnly={isReadOnly}
@ -404,8 +354,8 @@ const DeliveryTrackingModal: React.FC<DeliveryTrackingModalProps> = ({
onChange={(e) =>
updateReceiptItem(
index,
"acceptedQuantity",
parseFloat(e.target.value) || 0
'acceptedQuantity',
parseFloat(e.target.value) || 0,
)
}
readOnly={isReadOnly}
@ -425,8 +375,8 @@ const DeliveryTrackingModal: React.FC<DeliveryTrackingModalProps> = ({
onChange={(e) =>
updateReceiptItem(
index,
"rejectedQuantity",
parseFloat(e.target.value) || 0
'rejectedQuantity',
parseFloat(e.target.value) || 0,
)
}
readOnly={isReadOnly}
@ -445,25 +395,17 @@ const DeliveryTrackingModal: React.FC<DeliveryTrackingModalProps> = ({
onChange={(e) =>
updateReceiptItem(
index,
"qualityStatus",
e.target.value as QualityStatusEnum
'qualityStatus',
e.target.value as QualityStatusEnum,
)
}
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={QualityStatusEnum.Pending}>
Beklemede
</option>
<option value={QualityStatusEnum.Approved}>
Kabul
</option>
<option value={QualityStatusEnum.Rejected}>
Red
</option>
<option value={QualityStatusEnum.Conditional}>
Koşullu
</option>
<option value={QualityStatusEnum.Pending}>Beklemede</option>
<option value={QualityStatusEnum.Approved}>Kabul</option>
<option value={QualityStatusEnum.Rejected}>Red</option>
<option value={QualityStatusEnum.Conditional}>Koşullu</option>
</select>
</div>
@ -473,14 +415,8 @@ const DeliveryTrackingModal: React.FC<DeliveryTrackingModalProps> = ({
</label>
<input
type="text"
value={item.lotNumber || ""}
onChange={(e) =>
updateReceiptItem(
index,
"lotNumber",
e.target.value
)
}
value={item.lotNumber || ''}
onChange={(e) => updateReceiptItem(index, 'lotNumber', e.target.value)}
readOnly={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"
/>
@ -494,18 +430,14 @@ const DeliveryTrackingModal: React.FC<DeliveryTrackingModalProps> = ({
type="date"
value={
item.expiryDate
? new Date(item.expiryDate)
.toISOString()
.split("T")[0]
: ""
? new Date(item.expiryDate).toISOString().split('T')[0]
: ''
}
onChange={(e) =>
updateReceiptItem(
index,
"expiryDate",
e.target.value
? new Date(e.target.value)
: undefined
'expiryDate',
e.target.value ? new Date(e.target.value) : undefined,
)
}
readOnly={isReadOnly}
@ -532,9 +464,9 @@ const DeliveryTrackingModal: React.FC<DeliveryTrackingModalProps> = ({
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"}
{mode === 'view' ? 'Kapat' : 'İptal'}
</button>
{mode !== "view" && (
{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"
@ -547,7 +479,7 @@ const DeliveryTrackingModal: React.FC<DeliveryTrackingModalProps> = ({
</form>
</div>
</div>
);
};
)
}
export default DeliveryTrackingModal;
export default DeliveryTrackingModal

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -13,6 +13,7 @@ import {
} from 'react-icons/fa'
import { mockMaterialGroups, populateParentGroups } from '../../../mocks/mockMaterialGroups'
import { MmMaterialGroup } from '../../../types/mm'
import { Container } from '@/components/shared'
interface MaterialGroupNode extends MmMaterialGroup {
children: MaterialGroupNode[]
@ -98,240 +99,247 @@ const MaterialGroups: React.FC = () => {
}, [filteredGroups])
return (
<div className="space-y-3 py-2">
{/* Header */}
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3">
{/* Title & Description */}
<div>
<h2 className="text-2xl font-bold text-gray-900">Malzeme Grupları</h2>
<p className="text-gray-600">Malzeme gruplarını yönetin</p>
</div>
{/* Header Actions */}
<div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-1.5">
{/* View Mode Toggle */}
<div className="flex bg-gray-100 rounded-lg p-1">
<button
onClick={() => setViewMode('list')}
className={`p-1.5 rounded-md transition-colors ${
viewMode === 'list'
? 'bg-white text-blue-600 shadow-sm'
: 'text-gray-600 hover:text-gray-900'
}`}
title="Liste Görünümü"
>
<FaList className="w-4 h-4" />
</button>
<button
onClick={() => setViewMode('card')}
className={`p-1.5 rounded-md transition-colors ${
viewMode === 'card'
? 'bg-white text-blue-600 shadow-sm'
: 'text-gray-600 hover:text-gray-900'
}`}
title="Kart Görünümü"
>
<FaTh className="w-4 h-4" />
</button>
<button
onClick={() => setViewMode('tree')}
className={`p-1.5 rounded-md transition-colors ${
viewMode === 'tree'
? 'bg-white text-blue-600 shadow-sm'
: 'text-gray-600 hover:text-gray-900'
}`}
title="Ağaç Görünümü"
>
<FaSitemap className="w-4 h-4" />
</button>
<Container>
<div className="space-y-2">
{/* Header */}
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3">
{/* Title & Description */}
<div>
<h2 className="text-2xl font-bold text-gray-900">Malzeme Grupları</h2>
<p className="text-gray-600">Malzeme gruplarını yönetin</p>
</div>
{/* Search Box */}
<div className="relative">
<FaSearch
size={16}
className="absolute left-2.5 top-1/2 transform -translate-y-1/2 text-gray-400"
/>
<input
type="text"
placeholder="Malzeme grubu ara..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-8 pr-3 py-1 w-full sm:w-56 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm"
/>
{/* Header Actions */}
<div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-1.5">
{/* View Mode Toggle */}
<div className="flex bg-gray-100 rounded-lg p-1">
<button
onClick={() => setViewMode('list')}
className={`p-1.5 rounded-md transition-colors ${
viewMode === 'list'
? 'bg-white text-blue-600 shadow-sm'
: 'text-gray-600 hover:text-gray-900'
}`}
title="Liste Görünümü"
>
<FaList className="w-4 h-4" />
</button>
<button
onClick={() => setViewMode('card')}
className={`p-1.5 rounded-md transition-colors ${
viewMode === 'card'
? 'bg-white text-blue-600 shadow-sm'
: 'text-gray-600 hover:text-gray-900'
}`}
title="Kart Görünümü"
>
<FaTh className="w-4 h-4" />
</button>
<button
onClick={() => setViewMode('tree')}
className={`p-1.5 rounded-md transition-colors ${
viewMode === 'tree'
? 'bg-white text-blue-600 shadow-sm'
: 'text-gray-600 hover:text-gray-900'
}`}
title="Ağaç Görünümü"
>
<FaSitemap className="w-4 h-4" />
</button>
</div>
{/* Search Box */}
<div className="relative">
<FaSearch
size={16}
className="absolute left-2.5 top-1/2 transform -translate-y-1/2 text-gray-400"
/>
<input
type="text"
placeholder="Malzeme grubu ara..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-8 pr-3 py-1 w-full sm:w-56 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm"
/>
</div>
{/* Add New Group */}
<button
onClick={handleAdd}
className="flex items-center px-2.5 py-1 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors"
>
<FaPlus size={14} className="mr-2" />
Yeni Grup
</button>
</div>
{/* Add New Group */}
<button
onClick={handleAdd}
className="flex items-center px-2.5 py-1 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors"
>
<FaPlus size={14} className="mr-2" />
Yeni Grup
</button>
</div>
</div>
{viewMode === 'list' && (
<div className="bg-white shadow-sm rounded-lg overflow-hidden">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th className="px-3 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Grup Kodu
</th>
<th className="px-3 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Grup Adı
</th>
<th className="px-3 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Üst Grup
</th>
<th className="px-3 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
ıklama
</th>
<th className="px-3 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Durum
</th>
<th className="px-3 py-1.5 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">
İşlemler
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{filteredGroups.map((group) => (
<tr key={group.id} className="hover:bg-gray-50 text-xs">
<td className="px-3 py-1.5 whitespace-nowrap">
<span className="text-sm font-mono font-medium text-gray-900">
{viewMode === 'list' && (
<div className="bg-white shadow-sm rounded-lg overflow-hidden">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th className="px-3 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Grup Kodu
</th>
<th className="px-3 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Grup Adı
</th>
<th className="px-3 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Üst Grup
</th>
<th className="px-3 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
ıklama
</th>
<th className="px-3 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Durum
</th>
<th className="px-3 py-1.5 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">
İşlemler
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{filteredGroups.map((group) => (
<tr key={group.id} className="hover:bg-gray-50 text-xs">
<td className="px-3 py-1.5 whitespace-nowrap">
<span className="text-sm font-mono font-medium text-gray-900">
{group.code}
</span>
</td>
<td className="px-3 py-1.5 whitespace-nowrap">
<div className="text-sm font-medium text-gray-900">{group.name}</div>
</td>
<td className="px-3 py-1.5">
<div className="text-sm text-gray-900">
{group.parentGroup ? group.parentGroup.name : '-'}
</div>
</td>
<td className="px-3 py-1.5">
<div className="text-sm text-gray-900">{group.description}</div>
</td>
<td className="px-3 py-1.5 whitespace-nowrap">
<span
className={`px-2 py-0.5 text-xs font-medium rounded-full ${
group.isActive ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
}`}
>
{group.isActive ? 'Aktif' : 'Pasif'}
</span>
</td>
<td className="px-3 py-1.5 whitespace-nowrap text-right text-sm font-medium">
<div className="flex items-center justify-end space-x-2">
<button
onClick={() => handleEdit(group)}
className="text-blue-600 hover:text-blue-900"
>
<FaEdit className="h-4 w-4" />
</button>
<button
onClick={() => handleDelete(group)}
className="text-red-600 hover:text-red-900"
>
<FaTrash className="h-4 w-4" />
</button>
</div>
</td>
</tr>
))}
</tbody>
</table>
{filteredGroups.length === 0 && (
<div className="text-center py-12">
<p className="text-gray-500">Malzeme grubu bulunamadı</p>
</div>
)}
</div>
)}
{viewMode === 'card' && (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-2">
{filteredGroups.map((group) => (
<div
key={group.id}
className="bg-white shadow-sm rounded-lg p-2.5 hover:shadow-md transition-shadow"
>
<div className="flex items-start justify-between mb-3">
<div className="flex-1">
<span className="text-xs font-mono font-medium text-blue-600 bg-blue-50 px-2 py-1 rounded">
{group.code}
</span>
</td>
<td className="px-3 py-1.5 whitespace-nowrap">
<div className="text-sm font-medium text-gray-900">{group.name}</div>
</td>
<td className="px-3 py-1.5">
<div className="text-sm text-gray-900">
{group.parentGroup ? group.parentGroup.name : '-'}
</div>
</td>
<td className="px-3 py-1.5">
<div className="text-sm text-gray-900">{group.description}</div>
</td>
<td className="px-3 py-1.5 whitespace-nowrap">
<span
className={`px-2 py-0.5 text-xs font-medium rounded-full ${
group.isActive ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
}`}
</div>
<div className="flex items-center space-x-1">
<button
onClick={() => handleEdit(group)}
className="text-blue-600 hover:text-blue-900 p-1"
title="Düzenle"
>
{group.isActive ? 'Aktif' : 'Pasif'}
</span>
</td>
<td className="px-3 py-1.5 whitespace-nowrap text-right text-sm font-medium">
<div className="flex items-center justify-end space-x-2">
<button
onClick={() => handleEdit(group)}
className="text-blue-600 hover:text-blue-900"
>
<FaEdit className="h-4 w-4" />
</button>
<button
onClick={() => handleDelete(group)}
className="text-red-600 hover:text-red-900"
>
<FaTrash className="h-4 w-4" />
</button>
</div>
</td>
</tr>
))}
</tbody>
</table>
{filteredGroups.length === 0 && (
<div className="text-center py-12">
<p className="text-gray-500">Malzeme grubu bulunamadı</p>
</div>
)}
</div>
)}
{viewMode === 'card' && (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-2">
{filteredGroups.map((group) => (
<div
key={group.id}
className="bg-white shadow-sm rounded-lg p-2.5 hover:shadow-md transition-shadow"
>
<div className="flex items-start justify-between mb-3">
<div className="flex-1">
<span className="text-xs font-mono font-medium text-blue-600 bg-blue-50 px-2 py-1 rounded">
{group.code}
</span>
<FaEdit className="h-3 w-3" />
</button>
<button
onClick={() => handleDelete(group)}
className="text-red-600 hover:text-red-900 p-1"
title="Sil"
>
<FaTrash className="h-3 w-3" />
</button>
</div>
</div>
<div className="flex items-center space-x-1">
<button
onClick={() => handleEdit(group)}
className="text-blue-600 hover:text-blue-900 p-1"
title="Düzenle"
>
<FaEdit className="h-3 w-3" />
</button>
<button
onClick={() => handleDelete(group)}
className="text-red-600 hover:text-red-900 p-1"
title="Sil"
>
<FaTrash className="h-3 w-3" />
</button>
</div>
</div>
<h3 className="font-medium text-gray-900 mb-1.5 text-sm">{group.name}</h3>
<h3 className="font-medium text-gray-900 mb-1.5 text-sm">{group.name}</h3>
{group.description && (
<p className="text-sm text-gray-600 mb-3 line-clamp-2">{group.description}</p>
)}
<h3 className="text-gray-900 mb-3 text-sm">
{group.parentGroupId && (
<span className="text-sm text-gray-600">
<FaAngleRight className="inline text-gray-600 mb-1" />
{group.parentGroup?.name}
</span>
{group.description && (
<p className="text-sm text-gray-600 mb-3 line-clamp-2">{group.description}</p>
)}
</h3>
<div className="flex items-center justify-between">
<span
className={`px-2 py-0.5 text-xs font-medium rounded-full ${
group.isActive ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
}`}
>
{group.isActive ? 'Aktif' : 'Pasif'}
</span>
<h3 className="text-gray-900 mb-3 text-sm">
{group.parentGroupId && (
<span className="text-sm text-gray-600">
<FaAngleRight className="inline text-gray-600 mb-1" />
{group.parentGroup?.name}
</span>
)}
</h3>
<div className="flex items-center justify-between">
<span
className={`px-2 py-0.5 text-xs font-medium rounded-full ${
group.isActive ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
}`}
>
{group.isActive ? 'Aktif' : 'Pasif'}
</span>
</div>
</div>
</div>
))}
))}
{filteredGroups.length === 0 && (
<div className="col-span-full text-center py-12">
<p className="text-gray-500">Malzeme grubu bulunamadı</p>
</div>
)}
</div>
)}
{filteredGroups.length === 0 && (
<div className="col-span-full text-center py-12">
<p className="text-gray-500">Malzeme grubu bulunamadı</p>
</div>
)}
</div>
)}
{viewMode === 'tree' && (
<div className="bg-white shadow-sm rounded-lg p-3">
{groupTree.map((node) => (
<GroupTreeNode key={node.id} node={node} onEdit={handleEdit} onDelete={handleDelete} />
))}
{groupTree.length === 0 && (
<div className="text-center py-12">
<p className="text-gray-500">Ağaç görünümü için uygun malzeme grubu bulunamadı.</p>
</div>
)}
</div>
)}
{viewMode === 'tree' && (
<div className="bg-white shadow-sm rounded-lg p-3">
{groupTree.map((node) => (
<GroupTreeNode
key={node.id}
node={node}
onEdit={handleEdit}
onDelete={handleDelete}
/>
))}
{groupTree.length === 0 && (
<div className="text-center py-12">
<p className="text-gray-500">Ağaç görünümü için uygun malzeme grubu bulunamadı.</p>
</div>
)}
</div>
)}
</div>
{/* Modal */}
{isModalOpen && (
@ -341,7 +349,7 @@ const MaterialGroups: React.FC = () => {
onClose={() => setIsModalOpen(false)}
/>
)}
</div>
</Container>
)
}

File diff suppressed because it is too large Load diff

View file

@ -1,56 +1,47 @@
import React, { useState } from "react";
import {
FaPlus,
FaEdit,
FaTrash,
FaSearch,
FaTh,
FaList,
} from "react-icons/fa";
import { MmMaterialType, MaterialTypeEnum } from "../../../types/mm";
import { mockMaterialTypes } from "../../../mocks/mockMaterialTypes";
import { getMaterialTypeDisplay } from "../../../utils/erp";
import React, { useState } from 'react'
import { FaPlus, FaEdit, FaTrash, FaSearch, FaTh, FaList } from 'react-icons/fa'
import { MmMaterialType, MaterialTypeEnum } from '../../../types/mm'
import { mockMaterialTypes } from '../../../mocks/mockMaterialTypes'
import { getMaterialTypeDisplay } from '../../../utils/erp'
import { Container } from '@/components/shared'
const MaterialTypes: React.FC = () => {
const [searchTerm, setSearchTerm] = useState("");
const [isModalOpen, setIsModalOpen] = useState(false);
const [editingType, setEditingType] = useState<MmMaterialType | null>(null);
const [viewMode, setViewMode] = useState<"list" | "card">("list");
const [searchTerm, setSearchTerm] = useState('')
const [isModalOpen, setIsModalOpen] = useState(false)
const [editingType, setEditingType] = useState<MmMaterialType | null>(null)
const [viewMode, setViewMode] = useState<'list' | 'card'>('list')
// Mock data - gerçek uygulamada API'den gelecek
const [materialTypes, setMaterialTypes] =
useState<MmMaterialType[]>(mockMaterialTypes);
const [materialTypes, setMaterialTypes] = useState<MmMaterialType[]>(mockMaterialTypes)
const filteredTypes = materialTypes.filter(
(type) =>
type.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
type.description?.toLowerCase().includes(searchTerm.toLowerCase())
);
type.description?.toLowerCase().includes(searchTerm.toLowerCase()),
)
const handleAdd = () => {
setEditingType(null);
setIsModalOpen(true);
};
setEditingType(null)
setIsModalOpen(true)
}
const handleEdit = (type: MmMaterialType) => {
setEditingType(type);
setIsModalOpen(true);
};
setEditingType(type)
setIsModalOpen(true)
}
const handleDelete = (type: MmMaterialType) => {
if (
window.confirm(`${type.name} türünü silmek istediğinizden emin misiniz?`)
) {
setMaterialTypes((prev) => prev.filter((t) => t.id !== type.id));
if (window.confirm(`${type.name} türünü silmek istediğinizden emin misiniz?`)) {
setMaterialTypes((prev) => prev.filter((t) => t.id !== type.id))
}
};
}
const handleSave = (formData: Partial<MmMaterialType>) => {
if (editingType) {
// Update existing type
setMaterialTypes((prev) =>
prev.map((t) => (t.id === editingType.id ? { ...t, ...formData } : t))
);
prev.map((t) => (t.id === editingType.id ? { ...t, ...formData } : t)),
)
} else {
// Add new type
const newType: MmMaterialType = {
@ -59,218 +50,208 @@ const MaterialTypes: React.FC = () => {
name: formData.name!,
description: formData.description,
isActive: formData.isActive ?? true,
className: "",
};
setMaterialTypes((prev) => [...prev, newType]);
className: '',
}
setMaterialTypes((prev) => [...prev, newType])
}
setIsModalOpen(false);
};
setIsModalOpen(false)
}
return (
<div className="space-y-3 py-2">
{/* Header */}
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3">
{/* Title & Description */}
<div>
<h2 className="text-2xl font-bold text-gray-900">Malzeme Türleri</h2>
<p className="text-gray-600">Malzeme türlerini yönetin</p>
</div>
{/* Header Actions */}
<div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-1.5">
{/* View Mode Toggle */}
<div className="flex bg-gray-100 rounded-lg p-1">
<button
onClick={() => setViewMode("list")}
className={`p-1.5 rounded-md transition-colors ${
viewMode === "list"
? "bg-white text-blue-600 shadow-sm"
: "text-gray-600 hover:text-gray-900"
}`}
title="Liste Görünümü"
>
<FaList className="w-4 h-4" />
</button>
<button
onClick={() => setViewMode("card")}
className={`p-1.5 rounded-md transition-colors ${
viewMode === "card"
? "bg-white text-blue-600 shadow-sm"
: "text-gray-600 hover:text-gray-900"
}`}
title="Kart Görünümü"
>
<FaTh className="w-4 h-4" />
</button>
<Container>
<div className="space-y-2">
{/* Header */}
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3">
{/* Title & Description */}
<div>
<h2 className="text-2xl font-bold text-gray-900">Malzeme Türleri</h2>
<p className="text-gray-600">Malzeme türlerini yönetin</p>
</div>
{/* Search Box */}
<div className="relative">
<FaSearch
size={16}
className="absolute left-2.5 top-1/2 transform -translate-y-1/2 text-gray-400"
/>
<input
type="text"
placeholder="Malzeme türü ara..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-8 pr-3 py-1 w-full sm:w-56 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm"
/>
{/* Header Actions */}
<div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-1.5">
{/* View Mode Toggle */}
<div className="flex bg-gray-100 rounded-lg p-1">
<button
onClick={() => setViewMode('list')}
className={`p-1.5 rounded-md transition-colors ${
viewMode === 'list'
? 'bg-white text-blue-600 shadow-sm'
: 'text-gray-600 hover:text-gray-900'
}`}
title="Liste Görünümü"
>
<FaList className="w-4 h-4" />
</button>
<button
onClick={() => setViewMode('card')}
className={`p-1.5 rounded-md transition-colors ${
viewMode === 'card'
? 'bg-white text-blue-600 shadow-sm'
: 'text-gray-600 hover:text-gray-900'
}`}
title="Kart Görünümü"
>
<FaTh className="w-4 h-4" />
</button>
</div>
{/* Search Box */}
<div className="relative">
<FaSearch
size={16}
className="absolute left-2.5 top-1/2 transform -translate-y-1/2 text-gray-400"
/>
<input
type="text"
placeholder="Malzeme türü ara..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-8 pr-3 py-1 w-full sm:w-56 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm"
/>
</div>
{/* Add New Type */}
<button
onClick={handleAdd}
className="flex items-center px-2.5 py-1 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors"
>
<FaPlus size={14} className="mr-2" />
Yeni Tür
</button>
</div>
{/* Add New Type */}
<button
onClick={handleAdd}
className="flex items-center px-2.5 py-1 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors"
>
<FaPlus size={14} className="mr-2" />
Yeni Tür
</button>
</div>
</div>
{/* Types Display */}
{viewMode === "list" ? (
<div className="bg-white shadow-sm rounded-lg overflow-hidden">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th className="px-3 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Kod
</th>
<th className="px-3 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Ad
</th>
<th className="px-3 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
ıklama
</th>
<th className="px-3 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Durum
</th>
<th className="px-3 py-1.5 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">
İşlemler
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{filteredTypes.map((type) => (
<tr key={type.id} className="hover:bg-gray-50 text-xs">
<td className="px-3 py-1.5 whitespace-nowrap">
<span className="text-sm font-mono font-medium text-gray-900">
{/* Types Display */}
{viewMode === 'list' ? (
<div className="bg-white shadow-sm rounded-lg overflow-hidden">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th className="px-3 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Kod
</th>
<th className="px-3 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Ad
</th>
<th className="px-3 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
ıklama
</th>
<th className="px-3 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Durum
</th>
<th className="px-3 py-1.5 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">
İşlemler
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{filteredTypes.map((type) => (
<tr key={type.id} className="hover:bg-gray-50 text-xs">
<td className="px-3 py-1.5 whitespace-nowrap">
<span className="text-sm font-mono font-medium text-gray-900">
{getMaterialTypeDisplay(type.code)}
</span>
</td>
<td className="px-3 py-1.5 whitespace-nowrap">
<div className="text-sm font-medium text-gray-900">{type.name}</div>
</td>
<td className="px-3 py-1.5">
<div className="text-sm text-gray-900">{type.description}</div>
</td>
<td className="px-3 py-1.5 whitespace-nowrap">
<span
className={`px-2 py-0.5 text-xs font-medium rounded-full ${
type.isActive ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
}`}
>
{type.isActive ? 'Aktif' : 'Pasif'}
</span>
</td>
<td className="px-3 py-1.5 whitespace-nowrap text-right text-sm font-medium">
<div className="flex items-center justify-end space-x-2">
<button
onClick={() => handleEdit(type)}
className="text-blue-600 hover:text-blue-900"
>
<FaEdit className="h-4 w-4" />
</button>
<button
onClick={() => handleDelete(type)}
className="text-red-600 hover:text-red-900"
>
<FaTrash className="h-4 w-4" />
</button>
</div>
</td>
</tr>
))}
</tbody>
</table>
{filteredTypes.length === 0 && (
<div className="text-center py-12">
<p className="text-gray-500">Malzeme türü bulunamadı</p>
</div>
)}
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-2">
{filteredTypes.map((type) => (
<div
key={type.id}
className="bg-white shadow-sm rounded-lg p-2.5 hover:shadow-md transition-shadow"
>
<div className="flex items-start justify-between mb-3">
<div className="flex-1">
<span className="text-xs font-mono font-medium text-blue-600 bg-blue-50 px-2 py-1 rounded">
{getMaterialTypeDisplay(type.code)}
</span>
</td>
<td className="px-3 py-1.5 whitespace-nowrap">
<div className="text-sm font-medium text-gray-900">
{type.name}
</div>
</td>
<td className="px-3 py-1.5">
<div className="text-sm text-gray-900">
{type.description}
</div>
</td>
<td className="px-3 py-1.5 whitespace-nowrap">
<span
className={`px-2 py-0.5 text-xs font-medium rounded-full ${
type.isActive
? "bg-green-100 text-green-800"
: "bg-red-100 text-red-800"
}`}
</div>
<div className="flex items-center space-x-1">
<button
onClick={() => handleEdit(type)}
className="text-blue-600 hover:text-blue-900 p-1"
title="Düzenle"
>
{type.isActive ? "Aktif" : "Pasif"}
</span>
</td>
<td className="px-3 py-1.5 whitespace-nowrap text-right text-sm font-medium">
<div className="flex items-center justify-end space-x-2">
<button
onClick={() => handleEdit(type)}
className="text-blue-600 hover:text-blue-900"
>
<FaEdit className="h-4 w-4" />
</button>
<button
onClick={() => handleDelete(type)}
className="text-red-600 hover:text-red-900"
>
<FaTrash className="h-4 w-4" />
</button>
</div>
</td>
</tr>
))}
</tbody>
</table>
<FaEdit className="h-3 w-3" />
</button>
<button
onClick={() => handleDelete(type)}
className="text-red-600 hover:text-red-900 p-1"
title="Sil"
>
<FaTrash className="h-3 w-3" />
</button>
</div>
</div>
{filteredTypes.length === 0 && (
<div className="text-center py-12">
<p className="text-gray-500">Malzeme türü bulunamadı</p>
</div>
)}
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-2">
{filteredTypes.map((type) => (
<div
key={type.id}
className="bg-white shadow-sm rounded-lg p-2.5 hover:shadow-md transition-shadow"
>
<div className="flex items-start justify-between mb-3">
<div className="flex-1">
<span className="text-xs font-mono font-medium text-blue-600 bg-blue-50 px-2 py-1 rounded">
{getMaterialTypeDisplay(type.code)}
<h3 className="font-medium text-gray-900 mb-1.5 text-sm">{type.name}</h3>
{type.description && (
<p className="text-sm text-gray-600 mb-3 line-clamp-2">{type.description}</p>
)}
<div className="flex items-center justify-between">
<span
className={`px-2 py-0.5 text-xs font-medium rounded-full ${
type.isActive ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
}`}
>
{type.isActive ? 'Aktif' : 'Pasif'}
</span>
</div>
<div className="flex items-center space-x-1">
<button
onClick={() => handleEdit(type)}
className="text-blue-600 hover:text-blue-900 p-1"
title="Düzenle"
>
<FaEdit className="h-3 w-3" />
</button>
<button
onClick={() => handleDelete(type)}
className="text-red-600 hover:text-red-900 p-1"
title="Sil"
>
<FaTrash className="h-3 w-3" />
</button>
</div>
</div>
))}
<h3 className="font-medium text-gray-900 mb-1.5 text-sm">
{type.name}
</h3>
{type.description && (
<p className="text-sm text-gray-600 mb-3 line-clamp-2">
{type.description}
</p>
)}
<div className="flex items-center justify-between">
<span
className={`px-2 py-0.5 text-xs font-medium rounded-full ${
type.isActive
? "bg-green-100 text-green-800"
: "bg-red-100 text-red-800"
}`}
>
{type.isActive ? "Aktif" : "Pasif"}
</span>
{filteredTypes.length === 0 && (
<div className="col-span-full text-center py-12">
<p className="text-gray-500">Malzeme türü bulunamadı</p>
</div>
</div>
))}
{filteredTypes.length === 0 && (
<div className="col-span-full text-center py-12">
<p className="text-gray-500">Malzeme türü bulunamadı</p>
</div>
)}
</div>
)}
)}
</div>
)}
</div>
{/* Modal */}
{isModalOpen && (
@ -280,47 +261,41 @@ const MaterialTypes: React.FC = () => {
onClose={() => setIsModalOpen(false)}
/>
)}
</div>
);
};
interface MaterialTypeModalProps {
type?: MmMaterialType | null;
onSave: (data: Partial<MmMaterialType>) => void;
onClose: () => void;
</Container>
)
}
const MaterialTypeModal: React.FC<MaterialTypeModalProps> = ({
type,
onSave,
onClose,
}) => {
interface MaterialTypeModalProps {
type?: MmMaterialType | null
onSave: (data: Partial<MmMaterialType>) => void
onClose: () => void
}
const MaterialTypeModal: React.FC<MaterialTypeModalProps> = ({ type, onSave, onClose }) => {
const [formData, setFormData] = useState({
code: type?.code || MaterialTypeEnum.RawMaterial,
name: type?.name || "",
description: type?.description || "",
name: type?.name || '',
description: type?.description || '',
isActive: type?.isActive ?? true,
});
})
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
onSave(formData);
};
e.preventDefault()
onSave(formData)
}
return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50">
<div className="bg-white rounded-lg w-full max-w-xs">
<div className="px-3 py-2 border-b border-gray-200">
<h3 className="text-sm font-medium text-gray-900">
{type ? "Malzeme Türünü Düzenle" : "Yeni Malzeme Türü"}
{type ? 'Malzeme Türünü Düzenle' : 'Yeni Malzeme Türü'}
</h3>
</div>
<form onSubmit={handleSubmit} className="p-3 space-y-2">
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">
Tür Kodu
</label>
<label className="block text-xs font-medium text-gray-700 mb-1">Tür Kodu</label>
<select
value={formData.code}
onChange={(e) =>
@ -335,38 +310,28 @@ const MaterialTypeModal: React.FC<MaterialTypeModalProps> = ({
<option value={MaterialTypeEnum.RawMaterial}>Hammadde</option>
<option value={MaterialTypeEnum.SemiFinished}>Yarı Mamul</option>
<option value={MaterialTypeEnum.Finished}>Mamul</option>
<option value={MaterialTypeEnum.Consumable}>
Sarf Malzemesi
</option>
<option value={MaterialTypeEnum.Consumable}>Sarf Malzemesi</option>
<option value={MaterialTypeEnum.Service}>Hizmet</option>
<option value={MaterialTypeEnum.Spare}>Yedek Parça</option>
</select>
</div>
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">
Ad
</label>
<label className="block text-xs font-medium text-gray-700 mb-1">Ad</label>
<input
type="text"
value={formData.name}
onChange={(e) =>
setFormData({ ...formData, name: e.target.value })
}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
className="w-full px-2 py-1 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
required
/>
</div>
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">
ıklama
</label>
<label className="block text-xs font-medium text-gray-700 mb-1">ıklama</label>
<textarea
value={formData.description}
onChange={(e) =>
setFormData({ ...formData, description: e.target.value })
}
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
className="w-full px-2 py-1 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
rows={3}
/>
@ -377,9 +342,7 @@ const MaterialTypeModal: React.FC<MaterialTypeModalProps> = ({
type="checkbox"
id="isActive"
checked={formData.isActive}
onChange={(e) =>
setFormData({ ...formData, isActive: e.target.checked })
}
onChange={(e) => setFormData({ ...formData, isActive: e.target.checked })}
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
/>
<label htmlFor="isActive" className="ml-2 text-sm text-gray-700">
@ -405,7 +368,7 @@ const MaterialTypeModal: React.FC<MaterialTypeModalProps> = ({
</form>
</div>
</div>
);
};
)
}
export default MaterialTypes;
export default MaterialTypes

View file

@ -1,5 +1,5 @@
import React, { useState } from "react";
import { useNavigate } from "react-router-dom";
import React, { useState } from 'react'
import { useNavigate } from 'react-router-dom'
import {
FaPlus,
FaSearch,
@ -14,516 +14,464 @@ import {
FaCheckCircle,
FaTimesCircle,
FaExclamationTriangle,
} from "react-icons/fa";
import { OrderStatusEnum, MmPurchaseOrder } from "../../../types/mm";
import { mockPurchaseOrders } from "../../../mocks/mockPurchaseOrders";
import Widget from "../../../components/common/Widget";
} from 'react-icons/fa'
import { OrderStatusEnum, MmPurchaseOrder } from '../../../types/mm'
import { mockPurchaseOrders } from '../../../mocks/mockPurchaseOrders'
import Widget from '../../../components/common/Widget'
import {
getOrderStatusColor,
getOrderStatusIcon,
getOrderStatusText,
getRequestTypeText,
} from "../../../utils/erp";
} from '../../../utils/erp'
import { Container } from '@/components/shared'
const OrderManagement: React.FC = () => {
const navigate = useNavigate();
const [searchTerm, setSearchTerm] = useState("");
const [statusFilter, setStatusFilter] = useState<OrderStatusEnum | "all">(
"all"
);
const [selectedOrders, setSelectedOrders] = useState<string[]>([]);
const [showBulkModal, setShowBulkModal] = useState(false);
const [bulkModalType, setBulkModalType] = useState<"approval" | "status">(
"approval"
);
const navigate = useNavigate()
const [searchTerm, setSearchTerm] = useState('')
const [statusFilter, setStatusFilter] = useState<OrderStatusEnum | 'all'>('all')
const [selectedOrders, setSelectedOrders] = useState<string[]>([])
const [showBulkModal, setShowBulkModal] = useState(false)
const [bulkModalType, setBulkModalType] = useState<'approval' | 'status'>('approval')
// Mock data - replace with actual API calls
const [orders, setOrders] = useState<MmPurchaseOrder[]>(mockPurchaseOrders);
const [orders, setOrders] = useState<MmPurchaseOrder[]>(mockPurchaseOrders)
const filteredOrders = orders.filter((order) => {
const matchesSearch =
order.orderNumber.toLowerCase().includes(searchTerm.toLowerCase()) ||
order.supplier?.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
order.requestTitle.toLowerCase().includes(searchTerm.toLowerCase());
const matchesStatus =
statusFilter === "all" || order.status === statusFilter;
return matchesSearch && matchesStatus;
});
order.requestTitle.toLowerCase().includes(searchTerm.toLowerCase())
const matchesStatus = statusFilter === 'all' || order.status === statusFilter
return matchesSearch && matchesStatus
})
const getStatusText = (status: OrderStatusEnum) => getOrderStatusText(status);
const getStatusText = (status: OrderStatusEnum) => getOrderStatusText(status)
const getStatusColor = (status: OrderStatusEnum): string =>
getOrderStatusColor(status);
const getStatusColor = (status: OrderStatusEnum): string => getOrderStatusColor(status)
const getStatusIcon = (status: OrderStatusEnum): JSX.Element =>
getOrderStatusIcon(status);
const getStatusIcon = (status: OrderStatusEnum): JSX.Element => getOrderStatusIcon(status)
const getDeliveryProgress = (order: MmPurchaseOrder) => {
const totalQuantity = order.items.reduce(
(sum, item) => sum + item.quantity,
0
);
const deliveredQuantity = order.items.reduce(
(sum, item) => sum + item.deliveredQuantity,
0
);
return totalQuantity > 0
? Math.round((deliveredQuantity / totalQuantity) * 100)
: 0;
};
const totalQuantity = order.items.reduce((sum, item) => sum + item.quantity, 0)
const deliveredQuantity = order.items.reduce((sum, item) => sum + item.deliveredQuantity, 0)
return totalQuantity > 0 ? Math.round((deliveredQuantity / totalQuantity) * 100) : 0
}
const isOverdue = (order: MmPurchaseOrder) => {
const today = new Date();
const today = new Date()
return (
order.expectedDeliveryDate < today &&
order.status !== OrderStatusEnum.Delivered &&
order.status !== OrderStatusEnum.Completed
);
};
)
}
const getDaysUntilDelivery = (order: MmPurchaseOrder) => {
const today = new Date();
const diffTime = order.expectedDeliveryDate.getTime() - today.getTime();
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
return diffDays;
};
const today = new Date()
const diffTime = order.expectedDeliveryDate.getTime() - today.getTime()
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
return diffDays
}
const handleAddOrder = () => {
navigate("/admin/supplychain/orders/new");
};
navigate('/admin/supplychain/orders/new')
}
const handleEdit = (order: MmPurchaseOrder) => {
navigate(`/admin/supplychain/orders/edit/${order.id}`);
};
navigate(`/admin/supplychain/orders/edit/${order.id}`)
}
const handleView = (order: MmPurchaseOrder) => {
navigate(`/admin/supplychain/orders/view/${order.id}`);
};
navigate(`/admin/supplychain/orders/view/${order.id}`)
}
const handleSelectOrder = (orderId: string) => {
setSelectedOrders((prev) =>
prev.includes(orderId)
? prev.filter((id) => id !== orderId)
: [...prev, orderId]
);
};
prev.includes(orderId) ? prev.filter((id) => id !== orderId) : [...prev, orderId],
)
}
const handleBulkApproval = () => {
setBulkModalType("approval");
setShowBulkModal(true);
};
setBulkModalType('approval')
setShowBulkModal(true)
}
const handleBulkStatusUpdate = () => {
setBulkModalType("status");
setShowBulkModal(true);
};
setBulkModalType('status')
setShowBulkModal(true)
}
const processBulkApproval = () => {
const updatedOrders = orders.map((order) => {
if (selectedOrders.includes(order.id)) {
return { ...order, status: OrderStatusEnum.Approved };
return { ...order, status: OrderStatusEnum.Approved }
}
return order;
});
setOrders(updatedOrders);
setSelectedOrders([]);
setShowBulkModal(false);
};
return order
})
setOrders(updatedOrders)
setSelectedOrders([])
setShowBulkModal(false)
}
const processBulkStatusUpdate = (newStatus: OrderStatusEnum) => {
const updatedOrders = orders.map((order) => {
if (selectedOrders.includes(order.id)) {
return { ...order, status: newStatus };
return { ...order, status: newStatus }
}
return order;
});
setOrders(updatedOrders);
setSelectedOrders([]);
setShowBulkModal(false);
};
return order
})
setOrders(updatedOrders)
setSelectedOrders([])
setShowBulkModal(false)
}
return (
<div className="space-y-3 pt-2">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h2 className="text-2xl font-bold text-gray-900">
Satınalma Siparişleri
</h2>
<p className="text-gray-600">
Satınalma siparişlerini oluşturun ve takip edin
</p>
<Container>
<div className="space-y-2">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h2 className="text-2xl font-bold text-gray-900">Satınalma Siparişleri</h2>
<p className="text-gray-600">Satınalma siparişlerini oluşturun ve takip edin</p>
</div>
<button
onClick={handleAddOrder}
className="bg-blue-600 text-white px-3 py-1.5 text-sm rounded-lg hover:bg-blue-700 flex items-center space-x-1.5"
>
<FaPlus className="w-4 h-4" />
<span>Yeni Sipariş</span>
</button>
</div>
<button
onClick={handleAddOrder}
className="bg-blue-600 text-white px-3 py-1.5 text-sm rounded-lg hover:bg-blue-700 flex items-center space-x-1.5"
>
<FaPlus className="w-4 h-4" />
<span>Yeni Sipariş</span>
</button>
</div>
{/* Summary Cards */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<Widget
title="Toplam Sipariş"
value={orders.length}
color="blue"
icon="FaShoppingCart"
/>
{/* Summary Cards */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<Widget title="Toplam Sipariş" value={orders.length} color="blue" icon="FaShoppingCart" />
<Widget
title="Bekleyen"
value={
orders.filter((o) =>
[OrderStatusEnum.Pending, OrderStatusEnum.Confirmed].includes(
o.status
)
).length
}
color="orange"
icon="FaClock"
/>
<Widget
title="Bekleyen"
value={
orders.filter((o) =>
[OrderStatusEnum.Pending, OrderStatusEnum.Confirmed].includes(o.status),
).length
}
color="orange"
icon="FaClock"
/>
<Widget
title="Teslim Edildi"
value={
orders.filter((o) => o.status === OrderStatusEnum.Delivered).length
}
color="green"
icon="FaTruck"
/>
<Widget
title="Teslim Edildi"
value={orders.filter((o) => o.status === OrderStatusEnum.Delivered).length}
color="green"
icon="FaTruck"
/>
<Widget
title="Toplam Tutar"
value={`${orders
.reduce((sum, order) => sum + order.totalAmount, 0)
.toLocaleString()}`}
color="purple"
icon="FaDollarSign"
/>
</div>
{/* Filters */}
<div className="flex space-x-2">
<div className="flex-1 relative">
<FaSearch className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-5 h-5" />
<input
type="text"
placeholder="Sipariş 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"
<Widget
title="Toplam Tutar"
value={`${orders.reduce((sum, order) => sum + order.totalAmount, 0).toLocaleString()}`}
color="purple"
icon="FaDollarSign"
/>
</div>
<div className="relative">
<FaFilter className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-5 h-5" />
<select
value={statusFilter}
onChange={(e) =>
setStatusFilter(e.target.value as OrderStatusEnum | "all")
}
className="pl-10 pr-4 py-1.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
>
<option value="all">Tüm Durumlar</option>
<option value={OrderStatusEnum.Draft}>Taslak</option>
<option value={OrderStatusEnum.Pending}>Beklemede</option>
<option value={OrderStatusEnum.Approved}>Onaylandı</option>
<option value={OrderStatusEnum.Sent}>Gönderildi</option>
<option value={OrderStatusEnum.Confirmed}>Onaylandı</option>
<option value={OrderStatusEnum.PartiallyDelivered}>
Kısmi Teslim
</option>
<option value={OrderStatusEnum.Delivered}>Teslim Edildi</option>
<option value={OrderStatusEnum.Completed}>Tamamlandı</option>
<option value={OrderStatusEnum.Cancelled}>İptal Edildi</option>
</select>
{/* Filters */}
<div className="flex space-x-2">
<div className="flex-1 relative">
<FaSearch className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-5 h-5" />
<input
type="text"
placeholder="Sipariş 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="relative">
<FaFilter className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-5 h-5" />
<select
value={statusFilter}
onChange={(e) => setStatusFilter(e.target.value as OrderStatusEnum | 'all')}
className="pl-10 pr-4 py-1.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
>
<option value="all">Tüm Durumlar</option>
<option value={OrderStatusEnum.Draft}>Taslak</option>
<option value={OrderStatusEnum.Pending}>Beklemede</option>
<option value={OrderStatusEnum.Approved}>Onaylandı</option>
<option value={OrderStatusEnum.Sent}>Gönderildi</option>
<option value={OrderStatusEnum.Confirmed}>Onaylandı</option>
<option value={OrderStatusEnum.PartiallyDelivered}>Kısmi Teslim</option>
<option value={OrderStatusEnum.Delivered}>Teslim Edildi</option>
<option value={OrderStatusEnum.Completed}>Tamamlandı</option>
<option value={OrderStatusEnum.Cancelled}>İptal Edildi</option>
</select>
</div>
</div>
</div>
{/* Orders List */}
<div className="grid grid-cols-1 gap-4">
{filteredOrders.map((order) => (
<div
key={order.id}
className="bg-white rounded-lg shadow-md border border-gray-200 p-4 hover:shadow-lg transition-shadow"
>
<div className="flex items-start justify-between mb-3">
<div className="flex-1">
<div className="flex items-center space-x-3 mb-2">
<input
type="checkbox"
checked={selectedOrders.includes(order.id)}
onChange={() => handleSelectOrder(order.id)}
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
/>
<h3 className="text-base font-semibold text-gray-900">
{order.orderNumber}
</h3>
<span
className={`px-2 py-1 rounded-full text-xs font-medium flex items-center space-x-1 ${getStatusColor(
order.status
)}`}
>
{getStatusIcon(order.status)}
<span>{getStatusText(order.status)}</span>
</span>
{isOverdue(order) && (
<span className="bg-red-100 text-red-800 text-xs px-2 py-1 rounded flex items-center">
<FaExclamationTriangle className="w-3 h-3 mr-1" />
Gecikme
{/* Orders List */}
<div className="grid grid-cols-1 gap-4">
{filteredOrders.map((order) => (
<div
key={order.id}
className="bg-white rounded-lg shadow-md border border-gray-200 p-4 hover:shadow-lg transition-shadow"
>
<div className="flex items-start justify-between mb-3">
<div className="flex-1">
<div className="flex items-center space-x-3 mb-2">
<input
type="checkbox"
checked={selectedOrders.includes(order.id)}
onChange={() => handleSelectOrder(order.id)}
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
/>
<h3 className="text-base font-semibold text-gray-900">{order.orderNumber}</h3>
<span
className={`px-2 py-1 rounded-full text-xs font-medium flex items-center space-x-1 ${getStatusColor(
order.status,
)}`}
>
{getStatusIcon(order.status)}
<span>{getStatusText(order.status)}</span>
</span>
)}
</div>
<p className="text-gray-600 mb-1">{order.requestTitle}</p>
<p className="text-sm text-gray-500">
{order.supplier?.name} {" "}
{getRequestTypeText(order.requestType)}
</p>
</div>
<div className="flex space-x-1">
<button
onClick={() => handleView(order)}
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={() => handleEdit(order)}
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 className="p-1.5 text-gray-400 hover:text-red-600 hover:bg-red-50 rounded-md transition-colors">
<FaTrash className="w-4 h-4" />
</button>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-3 mb-3">
<div>
<span className="text-sm text-gray-500">Toplam Tutar</span>
<p className="font-semibold text-base text-gray-900">
{order.totalAmount.toLocaleString()}
</p>
</div>
<div>
<span className="text-sm text-gray-500">Sipariş Tarihi</span>
<p className="font-medium text-gray-900">
{order.orderDate.toLocaleDateString("tr-TR")}
</p>
</div>
<div>
<span className="text-sm text-gray-500">Teslim Tarihi</span>
<p
className={`font-medium ${
isOverdue(order)
? "text-red-600"
: getDaysUntilDelivery(order) <= 3
? "text-orange-600"
: "text-gray-900"
}`}
>
{order.expectedDeliveryDate.toLocaleDateString("tr-TR")}
{!isOverdue(order) &&
getDaysUntilDelivery(order) <= 7 &&
getDaysUntilDelivery(order) > 0 && (
<span className="text-xs text-orange-600 ml-1">
({getDaysUntilDelivery(order)} gün)
{isOverdue(order) && (
<span className="bg-red-100 text-red-800 text-xs px-2 py-1 rounded flex items-center">
<FaExclamationTriangle className="w-3 h-3 mr-1" />
Gecikme
</span>
)}
</p>
</div>
</div>
{/* Delivery Progress */}
<div className="mb-3">
<div className="flex items-center justify-between mb-2">
<span className="text-sm text-gray-500">Teslimat Durumu</span>
<span className="text-sm font-medium text-gray-900">
{getDeliveryProgress(order)}%
</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-2">
<div
className="bg-blue-600 h-2 rounded-full transition-all duration-300"
style={{ width: `${getDeliveryProgress(order)}%` }}
></div>
</div>
</div>
{/* Items Summary */}
<div className="grid grid-cols-3 gap-3 mb-3 text-sm">
<div className="text-center">
<div className="flex items-center justify-center mb-1">
<FaBox className="w-4 h-4 text-gray-400 mr-1" />
<span className="font-medium">{order.items.length}</span>
</div>
<p className="text-gray-600 mb-1">{order.requestTitle}</p>
<p className="text-sm text-gray-500">
{order.supplier?.name} {getRequestTypeText(order.requestType)}
</p>
</div>
<span className="text-gray-500">Kalem</span>
</div>
<div className="text-center">
<div className="flex items-center justify-center mb-1">
<FaCheckCircle className="w-4 h-4 text-green-400 mr-1" />
<span className="font-medium">
{order.items.reduce(
(sum, item) => sum + item.deliveredQuantity,
0
)}
</span>
<div className="flex space-x-1">
<button
onClick={() => handleView(order)}
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={() => handleEdit(order)}
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 className="p-1.5 text-gray-400 hover:text-red-600 hover:bg-red-50 rounded-md transition-colors">
<FaTrash className="w-4 h-4" />
</button>
</div>
<span className="text-gray-500">Teslim</span>
</div>
<div className="text-center">
<div className="flex items-center justify-center mb-1">
<FaClock className="w-4 h-4 text-orange-400 mr-1" />
<span className="font-medium">
{order.items.reduce(
(sum, item) => sum + item.remainingQuantity,
0
)}
</span>
</div>
<span className="text-gray-500">Kalan</span>
</div>
</div>
{/* Items Details */}
<div className="mt-3">
<div className="mb-2">
<h4 className="text-sm font-medium text-gray-900 mb-2">
Sipariş Kalemleri
</h4>
<div className="space-y-2">
{order.items.map((item) => (
<div key={item.id} className="bg-gray-50 p-2 rounded-lg">
<div className="flex items-center justify-between mb-1.5">
<div className="flex-1">
<p className="font-medium text-gray-900">
{item.material?.name || item.description}
</p>
<p className="text-sm text-gray-600">
Malzeme Kodu:{" "}
{item.material?.code || item.materialId} | Miktar:{" "}
{item.quantity} {item.unit} | Birim Fiyat:
{item.unitPrice.toLocaleString()}
</p>
</div>
<div className="text-right">
<p className="font-semibold text-gray-900">
{item.totalPrice.toLocaleString()}
</p>
<div className="text-xs text-gray-500">
<span className="text-green-600">
Teslim: {item.deliveredQuantity}
</span>{" "}
|
<span className="text-orange-600">
Kalan: {item.remainingQuantity}
</span>
</div>
</div>
</div>
{item.deliveryDate && (
<div className="text-xs text-gray-500">
Teslim Tarihi:{" "}
{item.deliveryDate.toLocaleDateString("tr-TR")}
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-3 mb-3">
<div>
<span className="text-sm text-gray-500">Toplam Tutar</span>
<p className="font-semibold text-base text-gray-900">
{order.totalAmount.toLocaleString()}
</p>
</div>
<div>
<span className="text-sm text-gray-500">Sipariş Tarihi</span>
<p className="font-medium text-gray-900">
{order.orderDate.toLocaleDateString('tr-TR')}
</p>
</div>
<div>
<span className="text-sm text-gray-500">Teslim Tarihi</span>
<p
className={`font-medium ${
isOverdue(order)
? 'text-red-600'
: getDaysUntilDelivery(order) <= 3
? 'text-orange-600'
: 'text-gray-900'
}`}
>
{order.expectedDeliveryDate.toLocaleDateString('tr-TR')}
{!isOverdue(order) &&
getDaysUntilDelivery(order) <= 7 &&
getDaysUntilDelivery(order) > 0 && (
<span className="text-xs text-orange-600 ml-1">
({getDaysUntilDelivery(order)} gün)
</span>
)}
</div>
))}
</p>
</div>
</div>
</div>
<div className="border-t border-gray-100 pt-3">
<div className="flex items-center justify-between text-sm">
<div className="flex items-center space-x-4">
<div className="flex items-center space-x-1">
<FaCalendar className="w-4 h-4 text-gray-400" />
<span className="text-gray-600">
{order.creationTime.toLocaleDateString("tr-TR")}
{/* Delivery Progress */}
<div className="mb-3">
<div className="flex items-center justify-between mb-2">
<span className="text-sm text-gray-500">Teslimat Durumu</span>
<span className="text-sm font-medium text-gray-900">
{getDeliveryProgress(order)}%
</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-2">
<div
className="bg-blue-600 h-2 rounded-full transition-all duration-300"
style={{ width: `${getDeliveryProgress(order)}%` }}
></div>
</div>
</div>
{/* Items Summary */}
<div className="grid grid-cols-3 gap-3 mb-3 text-sm">
<div className="text-center">
<div className="flex items-center justify-center mb-1">
<FaBox className="w-4 h-4 text-gray-400 mr-1" />
<span className="font-medium">{order.items.length}</span>
</div>
<span className="text-gray-500">Kalem</span>
</div>
<div className="text-center">
<div className="flex items-center justify-center mb-1">
<FaCheckCircle className="w-4 h-4 text-green-400 mr-1" />
<span className="font-medium">
{order.items.reduce((sum, item) => sum + item.deliveredQuantity, 0)}
</span>
</div>
<span className="text-gray-500">Teslim</span>
</div>
{order.approvedBy && (
<div className="flex items-center space-x-1">
<FaCheckCircle className="w-4 h-4 text-green-400" />
<span className="text-gray-600">
Onaylayan: {order.approvedBy}
<div className="text-center">
<div className="flex items-center justify-center mb-1">
<FaClock className="w-4 h-4 text-orange-400 mr-1" />
<span className="font-medium">
{order.items.reduce((sum, item) => sum + item.remainingQuantity, 0)}
</span>
</div>
<span className="text-gray-500">Kalan</span>
</div>
</div>
{/* Items Details */}
<div className="mt-3">
<div className="mb-2">
<h4 className="text-sm font-medium text-gray-900 mb-2">Sipariş Kalemleri</h4>
<div className="space-y-2">
{order.items.map((item) => (
<div key={item.id} className="bg-gray-50 p-2 rounded-lg">
<div className="flex items-center justify-between mb-1.5">
<div className="flex-1">
<p className="font-medium text-gray-900">
{item.material?.name || item.description}
</p>
<p className="text-sm text-gray-600">
Malzeme Kodu: {item.material?.code || item.materialId} | Miktar:{' '}
{item.quantity} {item.unit} | Birim Fiyat:
{item.unitPrice.toLocaleString()}
</p>
</div>
<div className="text-right">
<p className="font-semibold text-gray-900">
{item.totalPrice.toLocaleString()}
</p>
<div className="text-xs text-gray-500">
<span className="text-green-600">
Teslim: {item.deliveredQuantity}
</span>{' '}
|
<span className="text-orange-600">
Kalan: {item.remainingQuantity}
</span>
</div>
</div>
</div>
{item.deliveryDate && (
<div className="text-xs text-gray-500">
Teslim Tarihi: {item.deliveryDate.toLocaleDateString('tr-TR')}
</div>
)}
</div>
))}
</div>
</div>
</div>
<div className="border-t border-gray-100 pt-3">
<div className="flex items-center justify-between text-sm">
<div className="flex items-center space-x-4">
<div className="flex items-center space-x-1">
<FaCalendar className="w-4 h-4 text-gray-400" />
<span className="text-gray-600">
{order.creationTime.toLocaleDateString('tr-TR')}
</span>
</div>
</div>
{order.approvedBy && (
<div className="flex items-center space-x-1">
<FaCheckCircle className="w-4 h-4 text-green-400" />
<span className="text-gray-600">Onaylayan: {order.approvedBy}</span>
</div>
)}
</div>
{order.notes && (
<div className="mt-2 p-2 bg-gray-50 rounded text-sm text-gray-600">
<strong>Not:</strong> {order.notes}
</div>
)}
</div>
</div>
))}
</div>
{order.notes && (
<div className="mt-2 p-2 bg-gray-50 rounded text-sm text-gray-600">
<strong>Not:</strong> {order.notes}
</div>
)}
{filteredOrders.length === 0 && (
<div className="text-center py-8">
<FaShoppingCart className="w-16 h-16 text-gray-400 mx-auto mb-4" />
<h3 className="text-lg font-medium text-gray-900 mb-2">Sipariş bulunamadı</h3>
<p className="text-gray-500 mb-3">
Arama kriterlerinizi değiştirin veya yeni bir sipariş oluşturun.
</p>
<button
onClick={handleAddOrder}
className="bg-blue-600 text-white px-3 py-1.5 rounded-lg hover:bg-blue-700"
>
Yeni Sipariş Oluştur
</button>
</div>
)}
{/* Bulk Actions */}
{selectedOrders.length > 0 && (
<div className="fixed bottom-6 left-1/2 transform -translate-x-1/2 bg-white rounded-lg shadow-lg border border-gray-200 p-3">
<div className="flex items-center space-x-3">
<span className="text-sm text-gray-600">{selectedOrders.length} sipariş seçildi</span>
<div className="flex space-x-2">
<button
onClick={handleBulkApproval}
className="bg-green-600 text-white px-2.5 py-1.5 rounded text-sm hover:bg-green-700"
>
Toplu Onay
</button>
<button
onClick={handleBulkStatusUpdate}
className="bg-blue-600 text-white px-2.5 py-1.5 rounded text-sm hover:bg-blue-700"
>
Durum Güncelle
</button>
<button
onClick={() => setSelectedOrders([])}
className="bg-gray-600 text-white px-2.5 py-1.5 rounded text-sm hover:bg-gray-700"
>
Temizle
</button>
</div>
</div>
</div>
))}
)}
</div>
{filteredOrders.length === 0 && (
<div className="text-center py-8">
<FaShoppingCart className="w-16 h-16 text-gray-400 mx-auto mb-4" />
<h3 className="text-lg font-medium text-gray-900 mb-2">
Sipariş bulunamadı
</h3>
<p className="text-gray-500 mb-3">
Arama kriterlerinizi değiştirin veya yeni bir sipariş oluşturun.
</p>
<button
onClick={handleAddOrder}
className="bg-blue-600 text-white px-3 py-1.5 rounded-lg hover:bg-blue-700"
>
Yeni Sipariş Oluştur
</button>
</div>
)}
{/* Bulk Actions */}
{selectedOrders.length > 0 && (
<div className="fixed bottom-6 left-1/2 transform -translate-x-1/2 bg-white rounded-lg shadow-lg border border-gray-200 p-3">
<div className="flex items-center space-x-3">
<span className="text-sm text-gray-600">
{selectedOrders.length} sipariş seçildi
</span>
<div className="flex space-x-2">
<button
onClick={handleBulkApproval}
className="bg-green-600 text-white px-2.5 py-1.5 rounded text-sm hover:bg-green-700"
>
Toplu Onay
</button>
<button
onClick={handleBulkStatusUpdate}
className="bg-blue-600 text-white px-2.5 py-1.5 rounded text-sm hover:bg-blue-700"
>
Durum Güncelle
</button>
<button
onClick={() => setSelectedOrders([])}
className="bg-gray-600 text-white px-2.5 py-1.5 rounded text-sm hover:bg-gray-700"
>
Temizle
</button>
</div>
</div>
</div>
)}
{/* Bulk Action Modals */}
{showBulkModal && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg p-4 w-full max-w-2xl mx-4">
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold">
{bulkModalType === "approval" && "Toplu Onay İşlemi"}
{bulkModalType === "status" && "Toplu Durum Güncelleme"}
{bulkModalType === 'approval' && 'Toplu Onay İşlemi'}
{bulkModalType === 'status' && 'Toplu Durum Güncelleme'}
</h3>
<button
onClick={() => setShowBulkModal(false)}
@ -533,7 +481,7 @@ const OrderManagement: React.FC = () => {
</button>
</div>
{bulkModalType === "approval" && (
{bulkModalType === 'approval' && (
<div className="space-y-3">
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-3">
<div className="flex items-center">
@ -545,9 +493,7 @@ const OrderManagement: React.FC = () => {
</div>
<div className="space-y-2">
<h4 className="font-medium text-gray-900">
Seçili Siparişler:
</h4>
<h4 className="font-medium text-gray-900">Seçili Siparişler:</h4>
<div className="max-h-40 overflow-y-auto">
{orders
.filter((order) => selectedOrders.includes(order.id))
@ -559,7 +505,7 @@ const OrderManagement: React.FC = () => {
<span className="text-sm">{order.orderNumber}</span>
<span
className={`text-xs px-2 py-1 rounded-full ${getStatusColor(
order.status
order.status,
)}`}
>
{getStatusText(order.status)}
@ -586,7 +532,7 @@ const OrderManagement: React.FC = () => {
</div>
)}
{bulkModalType === "status" && (
{bulkModalType === 'status' && (
<div className="space-y-3">
<div className="bg-blue-50 border border-blue-200 rounded-lg p-3">
<div className="flex items-center">
@ -604,9 +550,9 @@ const OrderManagement: React.FC = () => {
<select
className="w-full border border-gray-300 rounded-lg px-3 py-2 focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
onChange={(e) => {
const newStatus = e.target.value as OrderStatusEnum;
const newStatus = e.target.value as OrderStatusEnum
if (newStatus) {
processBulkStatusUpdate(newStatus);
processBulkStatusUpdate(newStatus)
}
}}
defaultValue=""
@ -616,25 +562,15 @@ const OrderManagement: React.FC = () => {
<option value={OrderStatusEnum.Approved}>Onaylandı</option>
<option value={OrderStatusEnum.Sent}>Gönderildi</option>
<option value={OrderStatusEnum.Confirmed}>Onaylandı</option>
<option value={OrderStatusEnum.PartiallyDelivered}>
Kısmi Teslim
</option>
<option value={OrderStatusEnum.Delivered}>
Teslim Edildi
</option>
<option value={OrderStatusEnum.Completed}>
Tamamlandı
</option>
<option value={OrderStatusEnum.Cancelled}>
İptal Edildi
</option>
<option value={OrderStatusEnum.PartiallyDelivered}>Kısmi Teslim</option>
<option value={OrderStatusEnum.Delivered}>Teslim Edildi</option>
<option value={OrderStatusEnum.Completed}>Tamamlandı</option>
<option value={OrderStatusEnum.Cancelled}>İptal Edildi</option>
</select>
</div>
<div className="space-y-2">
<h4 className="font-medium text-gray-900">
Seçili Siparişler:
</h4>
<h4 className="font-medium text-gray-900">Seçili Siparişler:</h4>
<div className="max-h-40 overflow-y-auto">
{orders
.filter((order) => selectedOrders.includes(order.id))
@ -646,7 +582,7 @@ const OrderManagement: React.FC = () => {
<span className="text-sm">{order.orderNumber}</span>
<span
className={`text-xs px-2 py-1 rounded-full ${getStatusColor(
order.status
order.status,
)}`}
>
{getStatusText(order.status)}
@ -669,8 +605,8 @@ const OrderManagement: React.FC = () => {
</div>
</div>
)}
</div>
);
};
</Container>
)
}
export default OrderManagement;
export default OrderManagement

View file

@ -1,5 +1,5 @@
import React, { useEffect, useState } from "react";
import { useParams, useNavigate } from "react-router-dom";
import React, { useEffect, useState } from 'react'
import { useParams, useNavigate } from 'react-router-dom'
import {
FaArrowLeft,
FaSave,
@ -11,85 +11,76 @@ import {
FaTrash,
FaMapMarkerAlt,
FaUser,
} from "react-icons/fa";
import {
MmPurchaseOrder,
OrderStatusEnum,
MmPurchaseOrderItem,
} from "../../../types/mm";
import { mockMaterials } from "../../../mocks/mockMaterials";
import { mockBusinessParties } from "../../../mocks/mockBusinessParties";
import { mockPurchaseOrders } from "../../../mocks/mockPurchaseOrders";
import { Address, PaymentTerms } from "../../../types/common";
} from 'react-icons/fa'
import { MmPurchaseOrder, OrderStatusEnum, MmPurchaseOrderItem } from '../../../types/mm'
import { mockMaterials } from '../../../mocks/mockMaterials'
import { mockBusinessParties } from '../../../mocks/mockBusinessParties'
import { mockPurchaseOrders } from '../../../mocks/mockPurchaseOrders'
import { Address, PaymentTerms } from '../../../types/common'
import { Container } from '@/components/shared'
const OrderManagementForm: React.FC = () => {
const { id } = useParams<{ id: string }>();
const navigate = useNavigate();
const isEdit = id !== undefined && id !== "new";
const isView = window.location.pathname.includes("/view/");
const { id } = useParams<{ id: string }>()
const navigate = useNavigate()
const isEdit = id !== undefined && id !== 'new'
const isView = window.location.pathname.includes('/view/')
// Yeni sipariş için başlangıç şablonu
const newOrderDefaults: Partial<MmPurchaseOrder> = {
orderNumber: "",
supplierId: "",
orderNumber: '',
supplierId: '',
orderDate: new Date(),
deliveryDate: new Date(),
status: OrderStatusEnum.Draft,
paymentTerms: PaymentTerms.Net30,
currency: "TRY",
currency: 'TRY',
exchangeRate: 1,
subtotal: 0,
taxAmount: 0,
totalAmount: 0,
items: [],
deliveryAddress: {
street: "",
city: "",
state: "",
postalCode: "",
country: "Türkiye",
street: '',
city: '',
state: '',
postalCode: '',
country: 'Türkiye',
},
terms: "",
notes: "",
terms: '',
notes: '',
receipts: [],
};
}
// İlk state (isEdit vs new)
const [formData, setFormData] = useState<Partial<MmPurchaseOrder>>(() => {
if (isEdit) {
const po = mockPurchaseOrders.find((o) => o.id === id);
return { ...po };
const po = mockPurchaseOrders.find((o) => o.id === id)
return { ...po }
}
return { ...newOrderDefaults };
});
return { ...newOrderDefaults }
})
// id değişirse formu güncelle
useEffect(() => {
if (isEdit) {
const po = mockPurchaseOrders.find((o) => o.id === id);
setFormData(po ? { ...po } : { ...newOrderDefaults });
const po = mockPurchaseOrders.find((o) => o.id === id)
setFormData(po ? { ...po } : { ...newOrderDefaults })
} else {
setFormData({ ...newOrderDefaults });
setFormData({ ...newOrderDefaults })
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [id, isEdit]);
}, [id, isEdit])
const handleInputChange = (
e: React.ChangeEvent<
HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement
>
e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>,
) => {
const { name, value, type } = e.target;
const { name, value, type } = e.target
setFormData((prev) => ({
...prev,
[name]:
type === "number"
? parseFloat(value) || 0
: type === "date"
? new Date(value)
: value,
}));
};
type === 'number' ? parseFloat(value) || 0 : type === 'date' ? new Date(value) : value,
}))
}
const handleAddressChange = (field: keyof Address, value: string) => {
setFormData((prev) => ({
@ -98,15 +89,15 @@ const OrderManagementForm: React.FC = () => {
...prev.deliveryAddress!,
[field]: value,
},
}));
};
}))
}
const addOrderItem = () => {
const newItem: MmPurchaseOrderItem = {
id: `item-${Date.now()}`,
orderId: formData.id || "",
materialId: "",
description: "",
orderId: formData.id || '',
materialId: '',
description: '',
quantity: 0,
unitPrice: 0,
totalPrice: 0,
@ -114,91 +105,88 @@ const OrderManagementForm: React.FC = () => {
receivedQuantity: 0,
deliveredQuantity: 0,
remainingQuantity: 0,
unit: "",
};
unit: '',
}
setFormData((prev) => ({
...prev,
items: [...(prev.items || []), newItem],
}));
};
}))
}
const removeOrderItem = (index: number) => {
setFormData((prev) => ({
...prev,
items: prev.items?.filter((_, i) => i !== index) || [],
}));
calculateTotals();
};
}))
calculateTotals()
}
const updateOrderItem = (
index: number,
field: keyof MmPurchaseOrderItem,
value: string | number | Date | undefined
value: string | number | Date | undefined,
) => {
setFormData((prev) => {
const updatedItems =
prev.items?.map((item, i) => {
if (i === index) {
const updatedItem = { ...item, [field]: value };
const updatedItem = { ...item, [field]: value }
// Auto-calculate total amount when quantity or unit price changes
if (field === "quantity" || field === "unitPrice") {
updatedItem.totalPrice =
(updatedItem.quantity || 0) * (updatedItem.unitPrice || 0);
if (field === 'quantity' || field === 'unitPrice') {
updatedItem.totalPrice = (updatedItem.quantity || 0) * (updatedItem.unitPrice || 0)
updatedItem.remainingQuantity =
updatedItem.quantity - (updatedItem.receivedQuantity || 0);
updatedItem.quantity - (updatedItem.receivedQuantity || 0)
}
return updatedItem;
return updatedItem
}
return item;
}) || [];
return item
}) || []
return {
...prev,
items: updatedItems,
};
});
}
})
// Recalculate totals
setTimeout(calculateTotals, 0);
};
setTimeout(calculateTotals, 0)
}
const calculateTotals = () => {
const subtotal =
formData.items?.reduce((sum, item) => sum + (item.totalPrice || 0), 0) ||
0;
const taxAmount = subtotal * 0.18; // %18 KDV
const totalAmount = subtotal + taxAmount;
const subtotal = formData.items?.reduce((sum, item) => sum + (item.totalPrice || 0), 0) || 0
const taxAmount = subtotal * 0.18 // %18 KDV
const totalAmount = subtotal + taxAmount
setFormData((prev) => ({
...prev,
subtotal,
taxAmount,
totalAmount,
}));
};
}))
}
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
e.preventDefault()
// TODO: Implement save logic
console.log("Saving purchase order:", formData);
navigate("/admin/supplychain/orders");
};
console.log('Saving purchase order:', formData)
navigate('/admin/supplychain/orders')
}
const handleCancel = () => {
navigate("/admin/supplychain/orders");
};
navigate('/admin/supplychain/orders')
}
const isReadOnly = isView;
const isReadOnly = isView
const pageTitle = isEdit
? "Satınalma Siparişini Düzenle"
? 'Satınalma Siparişini Düzenle'
: isView
? "Satınalma Siparişi Detayları"
: "Yeni Satınalma Siparişi";
? 'Satınalma Siparişi Detayları'
: 'Yeni Satınalma Siparişi'
return (
<div className="min-h-screen bg-gray-50 py-4">
<div className="mx-auto">
<Container>
<div className="space-y-2">
{/* Header */}
<div className="bg-white rounded-lg shadow-md p-2 mb-2">
<div className="flex items-center justify-between">
@ -217,7 +205,7 @@ const OrderManagementForm: React.FC = () => {
className="px-3 py-1.5 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50 flex items-center"
>
<FaTimes className="mr-2" />
{isView ? "Kapat" : "İptal"}
{isView ? 'Kapat' : 'İptal'}
</button>
{!isView && (
<button
@ -251,7 +239,7 @@ const OrderManagementForm: React.FC = () => {
<input
type="text"
name="orderNumber"
value={formData.orderNumber || ""}
value={formData.orderNumber || ''}
onChange={handleInputChange}
readOnly={isReadOnly || isEdit}
className="mt-1 block w-full border border-gray-300 rounded-md px-2.5 py-1.5 focus:outline-none focus:ring-1 focus:ring-blue-500"
@ -265,7 +253,7 @@ const OrderManagementForm: React.FC = () => {
</label>
<select
name="supplierId"
value={formData.supplierId || ""}
value={formData.supplierId || ''}
onChange={handleInputChange}
disabled={isReadOnly}
className="mt-1 block w-full border border-gray-300 rounded-md px-2.5 py-1.5 focus:outline-none focus:ring-1 focus:ring-blue-500"
@ -290,10 +278,8 @@ const OrderManagementForm: React.FC = () => {
name="orderDate"
value={
formData.orderDate
? new Date(formData.orderDate)
.toISOString()
.split("T")[0]
: ""
? new Date(formData.orderDate).toISOString().split('T')[0]
: ''
}
onChange={handleInputChange}
readOnly={isReadOnly}
@ -311,10 +297,8 @@ const OrderManagementForm: React.FC = () => {
name="deliveryDate"
value={
formData.deliveryDate
? new Date(formData.deliveryDate)
.toISOString()
.split("T")[0]
: ""
? new Date(formData.deliveryDate).toISOString().split('T')[0]
: ''
}
onChange={handleInputChange}
readOnly={isReadOnly}
@ -345,12 +329,10 @@ const OrderManagementForm: React.FC = () => {
</div>
<div>
<label className="block text-sm font-medium text-gray-700">
Para Birimi
</label>
<label className="block text-sm font-medium text-gray-700">Para Birimi</label>
<select
name="currency"
value={formData.currency || "TRY"}
value={formData.currency || 'TRY'}
onChange={handleInputChange}
disabled={isReadOnly}
className="mt-1 block w-full border border-gray-300 rounded-md px-2.5 py-1.5 focus:outline-none focus:ring-1 focus:ring-blue-500"
@ -367,7 +349,7 @@ const OrderManagementForm: React.FC = () => {
</label>
<textarea
name="terms"
value={formData.terms || ""}
value={formData.terms || ''}
onChange={handleInputChange}
readOnly={isReadOnly}
rows={2}
@ -376,12 +358,10 @@ const OrderManagementForm: React.FC = () => {
</div>
<div className="md:col-span-2">
<label className="block text-sm font-medium text-gray-700">
Notlar
</label>
<label className="block text-sm font-medium text-gray-700">Notlar</label>
<textarea
name="notes"
value={formData.notes || ""}
value={formData.notes || ''}
onChange={handleInputChange}
readOnly={isReadOnly}
rows={1}
@ -400,75 +380,55 @@ const OrderManagementForm: React.FC = () => {
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
<div className="md:col-span-2">
<label className="block text-sm font-medium text-gray-700">
Adres
</label>
<label className="block text-sm font-medium text-gray-700">Adres</label>
<input
type="text"
value={formData.deliveryAddress?.street || ""}
onChange={(e) =>
handleAddressChange("street", e.target.value)
}
value={formData.deliveryAddress?.street || ''}
onChange={(e) => handleAddressChange('street', e.target.value)}
readOnly={isReadOnly}
className="mt-1 block w-full border border-gray-300 rounded-md px-2.5 py-1.5 focus:outline-none focus:ring-1 focus:ring-blue-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700">
Şehir
</label>
<label className="block text-sm font-medium text-gray-700">Şehir</label>
<input
type="text"
value={formData.deliveryAddress?.city || ""}
onChange={(e) =>
handleAddressChange("city", e.target.value)
}
value={formData.deliveryAddress?.city || ''}
onChange={(e) => handleAddressChange('city', e.target.value)}
readOnly={isReadOnly}
className="mt-1 block w-full border border-gray-300 rounded-md px-2.5 py-1.5 focus:outline-none focus:ring-1 focus:ring-blue-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700">
İl/Bölge
</label>
<label className="block text-sm font-medium text-gray-700">İl/Bölge</label>
<input
type="text"
value={formData.deliveryAddress?.state || ""}
onChange={(e) =>
handleAddressChange("state", e.target.value)
}
value={formData.deliveryAddress?.state || ''}
onChange={(e) => handleAddressChange('state', e.target.value)}
readOnly={isReadOnly}
className="mt-1 block w-full border border-gray-300 rounded-md px-2.5 py-1.5 focus:outline-none focus:ring-1 focus:ring-blue-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700">
Posta Kodu
</label>
<label className="block text-sm font-medium text-gray-700">Posta Kodu</label>
<input
type="text"
value={formData.deliveryAddress?.postalCode || ""}
onChange={(e) =>
handleAddressChange("postalCode", e.target.value)
}
value={formData.deliveryAddress?.postalCode || ''}
onChange={(e) => handleAddressChange('postalCode', e.target.value)}
readOnly={isReadOnly}
className="mt-1 block w-full border border-gray-300 rounded-md px-2.5 py-1.5 focus:outline-none focus:ring-1 focus:ring-blue-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700">
Ülke
</label>
<label className="block text-sm font-medium text-gray-700">Ülke</label>
<input
type="text"
value={formData.deliveryAddress?.country || ""}
onChange={(e) =>
handleAddressChange("country", e.target.value)
}
value={formData.deliveryAddress?.country || ''}
onChange={(e) => handleAddressChange('country', e.target.value)}
readOnly={isReadOnly}
className="mt-1 block w-full border border-gray-300 rounded-md px-2.5 py-1.5 focus:outline-none focus:ring-1 focus:ring-blue-500"
/>
@ -479,9 +439,7 @@ const OrderManagementForm: React.FC = () => {
{/* Sipariş Kalemleri */}
<div className="bg-white rounded-lg shadow-md p-4">
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-medium text-gray-900">
Sipariş Kalemleri
</h3>
<h3 className="text-lg font-medium text-gray-900">Sipariş Kalemleri</h3>
{!isReadOnly && (
<button
type="button"
@ -496,14 +454,9 @@ const OrderManagementForm: React.FC = () => {
<div className="space-y-3">
{formData.items?.map((item, index) => (
<div
key={item.id}
className="border rounded-lg p-3 bg-gray-50"
>
<div key={item.id} className="border rounded-lg p-3 bg-gray-50">
<div className="flex items-center justify-between mb-2">
<span className="text-sm font-medium text-gray-700">
Kalem {index + 1}
</span>
<span className="text-sm font-medium text-gray-700">Kalem {index + 1}</span>
{!isReadOnly && (
<button
type="button"
@ -517,18 +470,10 @@ const OrderManagementForm: React.FC = () => {
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-2">
<div>
<label className="block text-xs font-medium text-gray-600">
Malzeme
</label>
<label className="block text-xs font-medium text-gray-600">Malzeme</label>
<select
value={item.materialId}
onChange={(e) =>
updateOrderItem(
index,
"materialId",
e.target.value
)
}
onChange={(e) => updateOrderItem(index, 'materialId', e.target.value)}
disabled={isReadOnly}
className="mt-1 block w-full text-sm border border-gray-300 rounded-md px-2 py-1.5 focus:outline-none focus:ring-1 focus:ring-blue-500"
>
@ -548,31 +493,19 @@ const OrderManagementForm: React.FC = () => {
<input
type="text"
value={item.description}
onChange={(e) =>
updateOrderItem(
index,
"description",
e.target.value
)
}
onChange={(e) => updateOrderItem(index, 'description', e.target.value)}
readOnly={isReadOnly}
className="mt-1 block w-full text-sm border border-gray-300 rounded-md px-2 py-1.5 focus:outline-none focus:ring-1 focus:ring-blue-500"
/>
</div>
<div>
<label className="block text-xs font-medium text-gray-600">
Miktar
</label>
<label className="block text-xs font-medium text-gray-600">Miktar</label>
<input
type="number"
value={item.quantity}
onChange={(e) =>
updateOrderItem(
index,
"quantity",
parseFloat(e.target.value) || 0
)
updateOrderItem(index, 'quantity', parseFloat(e.target.value) || 0)
}
readOnly={isReadOnly}
min="0"
@ -589,11 +522,7 @@ const OrderManagementForm: React.FC = () => {
type="number"
value={item.unitPrice}
onChange={(e) =>
updateOrderItem(
index,
"unitPrice",
parseFloat(e.target.value) || 0
)
updateOrderItem(index, 'unitPrice', parseFloat(e.target.value) || 0)
}
readOnly={isReadOnly}
min="0"
@ -622,17 +551,11 @@ const OrderManagementForm: React.FC = () => {
type="date"
value={
item.deliveryDate
? new Date(item.deliveryDate)
.toISOString()
.split("T")[0]
: ""
? new Date(item.deliveryDate).toISOString().split('T')[0]
: ''
}
onChange={(e) =>
updateOrderItem(
index,
"deliveryDate",
new Date(e.target.value)
)
updateOrderItem(index, 'deliveryDate', new Date(e.target.value))
}
readOnly={isReadOnly}
className="mt-1 block w-full text-sm border border-gray-300 rounded-md px-2 py-1.5 focus:outline-none focus:ring-1 focus:ring-blue-500"
@ -649,8 +572,8 @@ const OrderManagementForm: React.FC = () => {
onChange={(e) =>
updateOrderItem(
index,
"receivedQuantity",
parseFloat(e.target.value) || 0
'receivedQuantity',
parseFloat(e.target.value) || 0,
)
}
readOnly={isReadOnly}
@ -678,13 +601,9 @@ const OrderManagementForm: React.FC = () => {
</label>
<input
type="text"
value={item.specifications || ""}
value={item.specifications || ''}
onChange={(e) =>
updateOrderItem(
index,
"specifications",
e.target.value
)
updateOrderItem(index, 'specifications', e.target.value)
}
readOnly={isReadOnly}
className="mt-1 block w-full text-sm border border-gray-300 rounded-md px-2 py-1.5 focus:outline-none focus:ring-1 focus:ring-blue-500"
@ -697,13 +616,9 @@ const OrderManagementForm: React.FC = () => {
</label>
<input
type="text"
value={item.qualityRequirements || ""}
value={item.qualityRequirements || ''}
onChange={(e) =>
updateOrderItem(
index,
"qualityRequirements",
e.target.value
)
updateOrderItem(index, 'qualityRequirements', e.target.value)
}
readOnly={isReadOnly}
className="mt-1 block w-full text-sm border border-gray-300 rounded-md px-2 py-1.5 focus:outline-none focus:ring-1 focus:ring-blue-500"
@ -726,15 +641,11 @@ const OrderManagementForm: React.FC = () => {
<div className="space-y-4">
{/* Durum ve Tutar */}
<div className="bg-white rounded-lg shadow-md p-4">
<h3 className="text-lg font-medium text-gray-900 mb-3">
Durum Bilgileri
</h3>
<h3 className="text-lg font-medium text-gray-900 mb-3">Durum Bilgileri</h3>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700">
Durum
</label>
<label className="block text-sm font-medium text-gray-700">Durum</label>
<select
name="status"
value={formData.status || OrderStatusEnum.Draft}
@ -744,29 +655,17 @@ const OrderManagementForm: React.FC = () => {
>
<option value={OrderStatusEnum.Draft}>Taslak</option>
<option value={OrderStatusEnum.Sent}>Gönderildi</option>
<option value={OrderStatusEnum.Confirmed}>
Onaylandı
</option>
<option value={OrderStatusEnum.PartiallyReceived}>
Kısmi Teslim
</option>
<option value={OrderStatusEnum.Received}>
Teslim Alındı
</option>
<option value={OrderStatusEnum.Invoiced}>
Faturalandı
</option>
<option value={OrderStatusEnum.Confirmed}>Onaylandı</option>
<option value={OrderStatusEnum.PartiallyReceived}>Kısmi Teslim</option>
<option value={OrderStatusEnum.Received}>Teslim Alındı</option>
<option value={OrderStatusEnum.Invoiced}>Faturalandı</option>
<option value={OrderStatusEnum.Closed}>Kapatıldı</option>
<option value={OrderStatusEnum.Cancelled}>
İptal Edildi
</option>
<option value={OrderStatusEnum.Cancelled}>İptal Edildi</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700">
Döviz Kuru
</label>
<label className="block text-sm font-medium text-gray-700">Döviz Kuru</label>
<input
type="number"
name="exchangeRate"
@ -805,12 +704,9 @@ const OrderManagementForm: React.FC = () => {
<div className="border-t pt-2">
<div className="flex justify-between">
<span className="text-lg font-medium text-gray-900">
Genel Toplam:
</span>
<span className="text-lg font-medium text-gray-900">Genel Toplam:</span>
<span className="text-lg font-bold text-green-600">
{formData.totalAmount?.toLocaleString()}{" "}
{formData.currency}
{formData.totalAmount?.toLocaleString()} {formData.currency}
</span>
</div>
</div>
@ -820,27 +716,18 @@ const OrderManagementForm: React.FC = () => {
{/* Sipariş Geçmişi (sadece görüntüleme) */}
{isView && formData.receipts && formData.receipts.length > 0 && (
<div className="bg-white rounded-lg shadow-md p-4">
<h3 className="text-base font-medium text-gray-900 mb-3">
Teslimat Geçmişi
</h3>
<h3 className="text-base font-medium text-gray-900 mb-3">Teslimat Geçmişi</h3>
<div className="space-y-2">
{formData.receipts.map((receipt) => (
<div
key={receipt.id}
className="border-l-4 border-green-500 pl-4"
>
<div key={receipt.id} className="border-l-4 border-green-500 pl-4">
<div className="text-sm font-medium text-gray-900">
{receipt.receiptNumber}
</div>
<div className="text-xs text-gray-500">
{new Date(receipt.receiptDate).toLocaleDateString(
"tr-TR"
)}
</div>
<div className="text-xs text-gray-600">
Durumu: {receipt.status}
{new Date(receipt.receiptDate).toLocaleDateString('tr-TR')}
</div>
<div className="text-xs text-gray-600">Durumu: {receipt.status}</div>
</div>
))}
</div>
@ -850,8 +737,8 @@ const OrderManagementForm: React.FC = () => {
</div>
</form>
</div>
</div>
);
};
</Container>
)
}
export default OrderManagementForm;
export default OrderManagementForm

View file

@ -1,5 +1,5 @@
import React, { useEffect, useState } from "react";
import { useParams, useNavigate } from "react-router-dom";
import React, { useEffect, useState } from 'react'
import { useParams, useNavigate } from 'react-router-dom'
import {
FaArrowLeft,
FaSave,
@ -9,135 +9,127 @@ import {
FaExclamationTriangle,
FaPlus,
FaTrash,
} from "react-icons/fa";
} from 'react-icons/fa'
import {
MmPurchaseRequest,
RequestTypeEnum,
RequestStatusEnum,
MmPurchaseRequestItem,
} from "../../../types/mm";
import { mockMaterials } from "../../../mocks/mockMaterials";
import { mockPurchaseRequests } from "../../../mocks/mockPurchaseRequests";
import { PriorityEnum } from "../../../types/common";
} from '../../../types/mm'
import { mockMaterials } from '../../../mocks/mockMaterials'
import { mockPurchaseRequests } from '../../../mocks/mockPurchaseRequests'
import { PriorityEnum } from '../../../types/common'
import { Container } from '@/components/shared'
const PurchaseRequestForm: React.FC = () => {
const { id } = useParams<{ id: string }>();
const navigate = useNavigate();
const isEdit = id !== undefined && id !== "new";
const isView = window.location.pathname.includes("/view/");
const { id } = useParams<{ id: string }>()
const navigate = useNavigate()
const isEdit = id !== undefined && id !== 'new'
const isView = window.location.pathname.includes('/view/')
// Yeni kayıt için başlangıç şablonu
const newRequestDefaults: Partial<MmPurchaseRequest> = {
requestNumber: "",
requestNumber: '',
requestType: RequestTypeEnum.Material,
description: "",
department: "",
requestedBy: "",
description: '',
department: '',
requestedBy: '',
requestDate: new Date(),
requiredDate: new Date(),
priority: PriorityEnum.Normal,
status: RequestStatusEnum.Draft,
currency: "TRY",
currency: 'TRY',
items: [],
approvals: [],
attachments: [],
comments: [],
};
}
// isEdit & id'ye göre ilk state
const [formData, setFormData] = useState<Partial<MmPurchaseRequest>>(() => {
if (isEdit) {
const pr = mockPurchaseRequests.find((a) => a.id === id); // DİKKAT: === kullan
return { ...pr };
const pr = mockPurchaseRequests.find((a) => a.id === id) // DİKKAT: === kullan
return { ...pr }
}
// new
return { ...newRequestDefaults };
});
return { ...newRequestDefaults }
})
useEffect(() => {
if (isEdit) {
const pr = mockPurchaseRequests.find((a) => a.id === id);
setFormData({ ...pr });
const pr = mockPurchaseRequests.find((a) => a.id === id)
setFormData({ ...pr })
} else {
setFormData({ ...newRequestDefaults });
setFormData({ ...newRequestDefaults })
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [id, isEdit]);
}, [id, isEdit])
const handleInputChange = (
e: React.ChangeEvent<
HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement
>
e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>,
) => {
const { name, value, type } = e.target;
const { name, value, type } = e.target
setFormData((prev) => ({
...prev,
[name]:
type === "number"
? parseFloat(value) || 0
: type === "date"
? new Date(value)
: value,
}));
};
type === 'number' ? parseFloat(value) || 0 : type === 'date' ? new Date(value) : value,
}))
}
const addRequestItem = () => {
const newItem: MmPurchaseRequestItem = {
id: `item-${Date.now()}`,
requestId: formData.id || "",
requestId: formData.id || '',
quantity: 0,
unit: "",
unit: '',
isUrgent: false,
};
}
setFormData((prev) => ({
...prev,
items: [...(prev.items || []), newItem],
}));
};
}))
}
const removeRequestItem = (index: number) => {
setFormData((prev) => ({
...prev,
items: prev.items?.filter((_, i) => i !== index) || [],
}));
};
}))
}
const updateRequestItem = (
index: number,
field: keyof MmPurchaseRequestItem,
value: string | number | boolean | undefined
value: string | number | boolean | undefined,
) => {
setFormData((prev) => ({
...prev,
items:
prev.items?.map((item, i) =>
i === index ? { ...item, [field]: value } : item
) || [],
}));
};
items: prev.items?.map((item, i) => (i === index ? { ...item, [field]: value } : item)) || [],
}))
}
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
e.preventDefault()
// TODO: Implement save logic
console.log("Saving purchase request:", formData);
navigate("/admin/supplychain/requests");
};
console.log('Saving purchase request:', formData)
navigate('/admin/supplychain/requests')
}
const handleCancel = () => {
navigate("/admin/supplychain/requests");
};
navigate('/admin/supplychain/requests')
}
const isReadOnly = isView;
const isReadOnly = isView
const pageTitle = isEdit
? "Satınalma Talebini Düzenle"
? 'Satınalma Talebini Düzenle'
: isView
? "Satınalma Talebi Detayları"
: "Yeni Satınalma Talebi";
? 'Satınalma Talebi Detayları'
: 'Yeni Satınalma Talebi'
return (
<div className="min-h-screen bg-gray-50 py-4">
<div className="mx-auto">
<Container>
<div className="space-y-2">
{/* Header */}
<div className="bg-white rounded-lg shadow-md p-2 mb-2">
<div className="flex items-center justify-between">
@ -156,7 +148,7 @@ const PurchaseRequestForm: React.FC = () => {
className="px-3 py-1.5 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50 flex items-center"
>
<FaTimes className="mr-2" />
{isView ? "Kapat" : "İptal"}
{isView ? 'Kapat' : 'İptal'}
</button>
{!isView && (
<button
@ -189,7 +181,7 @@ const PurchaseRequestForm: React.FC = () => {
<input
type="text"
name="requestNumber"
value={formData.requestNumber || ""}
value={formData.requestNumber || ''}
onChange={handleInputChange}
readOnly={isReadOnly || isEdit}
className="mt-1 block w-full border border-gray-300 rounded-md px-2.5 py-1.5 focus:outline-none focus:ring-1 focus:ring-blue-500"
@ -197,9 +189,7 @@ const PurchaseRequestForm: React.FC = () => {
</div>
<div>
<label className="block text-sm font-medium text-gray-700">
Talep Tipi
</label>
<label className="block text-sm font-medium text-gray-700">Talep Tipi</label>
<select
name="requestType"
value={formData.requestType || RequestTypeEnum.Material}
@ -209,20 +199,16 @@ const PurchaseRequestForm: React.FC = () => {
>
<option value={RequestTypeEnum.Material}>Malzeme</option>
<option value={RequestTypeEnum.Service}>Hizmet</option>
<option value={RequestTypeEnum.WorkCenter}>
İş Merkezi
</option>
<option value={RequestTypeEnum.WorkCenter}>İş Merkezi</option>
<option value={RequestTypeEnum.Maintenance}>Bakım</option>
</select>
</div>
<div className="md:col-span-2">
<label className="block text-sm font-medium text-gray-700">
ıklama
</label>
<label className="block text-sm font-medium text-gray-700">ıklama</label>
<textarea
name="description"
value={formData.description || ""}
value={formData.description || ''}
onChange={handleInputChange}
readOnly={isReadOnly}
rows={2}
@ -232,13 +218,11 @@ const PurchaseRequestForm: React.FC = () => {
</div>
<div>
<label className="block text-sm font-medium text-gray-700">
Departman
</label>
<label className="block text-sm font-medium text-gray-700">Departman</label>
<input
type="text"
name="department"
value={formData.department || ""}
value={formData.department || ''}
onChange={handleInputChange}
readOnly={isReadOnly}
className="mt-1 block w-full border border-gray-300 rounded-md px-2.5 py-1.5 focus:outline-none focus:ring-1 focus:ring-blue-500"
@ -247,13 +231,11 @@ const PurchaseRequestForm: React.FC = () => {
</div>
<div>
<label className="block text-sm font-medium text-gray-700">
Talep Eden
</label>
<label className="block text-sm font-medium text-gray-700">Talep Eden</label>
<input
type="text"
name="requestedBy"
value={formData.requestedBy || ""}
value={formData.requestedBy || ''}
onChange={handleInputChange}
readOnly={isReadOnly}
className="mt-1 block w-full border border-gray-300 rounded-md px-2.5 py-1.5 focus:outline-none focus:ring-1 focus:ring-blue-500"
@ -271,10 +253,8 @@ const PurchaseRequestForm: React.FC = () => {
name="requestDate"
value={
formData.requestDate
? new Date(formData.requestDate)
.toISOString()
.split("T")[0]
: ""
? new Date(formData.requestDate).toISOString().split('T')[0]
: ''
}
onChange={handleInputChange}
readOnly={isReadOnly}
@ -292,10 +272,8 @@ const PurchaseRequestForm: React.FC = () => {
name="requiredDate"
value={
formData.requiredDate
? new Date(formData.requiredDate)
.toISOString()
.split("T")[0]
: ""
? new Date(formData.requiredDate).toISOString().split('T')[0]
: ''
}
onChange={handleInputChange}
readOnly={isReadOnly}
@ -323,12 +301,10 @@ const PurchaseRequestForm: React.FC = () => {
</div>
<div>
<label className="block text-sm font-medium text-gray-700">
Para Birimi
</label>
<label className="block text-sm font-medium text-gray-700">Para Birimi</label>
<select
name="currency"
value={formData.currency || "TRY"}
value={formData.currency || 'TRY'}
onChange={handleInputChange}
disabled={isReadOnly}
className="mt-1 block w-full border border-gray-300 rounded-md px-2.5 py-1.5 focus:outline-none focus:ring-1 focus:ring-blue-500"
@ -344,9 +320,7 @@ const PurchaseRequestForm: React.FC = () => {
{/* Talep Kalemleri */}
<div className="bg-white rounded-lg shadow-md p-4">
<div className="flex items-center justify-between mb-3">
<h3 className="text-base font-medium text-gray-900">
Talep Kalemleri
</h3>
<h3 className="text-base font-medium text-gray-900">Talep Kalemleri</h3>
{!isReadOnly && (
<button
type="button"
@ -361,14 +335,9 @@ const PurchaseRequestForm: React.FC = () => {
<div className="space-y-3">
{formData.items?.map((item, index) => (
<div
key={item.id}
className="border rounded-lg p-3 bg-gray-50"
>
<div key={item.id} className="border rounded-lg p-3 bg-gray-50">
<div className="flex items-center justify-between mb-3">
<span className="text-sm font-medium text-gray-700">
Kalem {index + 1}
</span>
<span className="text-sm font-medium text-gray-700">Kalem {index + 1}</span>
{!isReadOnly && (
<button
type="button"
@ -382,18 +351,10 @@ const PurchaseRequestForm: React.FC = () => {
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-2">
<div>
<label className="block text-xs font-medium text-gray-600">
Malzeme
</label>
<label className="block text-xs font-medium text-gray-600">Malzeme</label>
<select
value={item.materialId || ""}
onChange={(e) =>
updateRequestItem(
index,
"materialId",
e.target.value
)
}
value={item.materialId || ''}
onChange={(e) => updateRequestItem(index, 'materialId', e.target.value)}
disabled={isReadOnly}
className="mt-1 block w-full text-sm border border-gray-300 rounded-md px-2 py-1.5 focus:outline-none focus:ring-1 focus:ring-blue-500"
>
@ -412,13 +373,9 @@ const PurchaseRequestForm: React.FC = () => {
</label>
<input
type="text"
value={item.serviceDescription || ""}
value={item.serviceDescription || ''}
onChange={(e) =>
updateRequestItem(
index,
"serviceDescription",
e.target.value
)
updateRequestItem(index, 'serviceDescription', e.target.value)
}
readOnly={isReadOnly}
className="mt-1 block w-full text-sm border border-gray-300 rounded-md px-2 py-1.5 focus:outline-none focus:ring-1 focus:ring-blue-500"
@ -426,18 +383,12 @@ const PurchaseRequestForm: React.FC = () => {
</div>
<div>
<label className="block text-xs font-medium text-gray-600">
Miktar
</label>
<label className="block text-xs font-medium text-gray-600">Miktar</label>
<input
type="number"
value={item.quantity}
onChange={(e) =>
updateRequestItem(
index,
"quantity",
parseFloat(e.target.value) || 0
)
updateRequestItem(index, 'quantity', parseFloat(e.target.value) || 0)
}
readOnly={isReadOnly}
min="0"
@ -447,15 +398,11 @@ const PurchaseRequestForm: React.FC = () => {
</div>
<div>
<label className="block text-xs font-medium text-gray-600">
Birim
</label>
<label className="block text-xs font-medium text-gray-600">Birim</label>
<input
type="text"
value={item.unit}
onChange={(e) =>
updateRequestItem(index, "unit", e.target.value)
}
onChange={(e) => updateRequestItem(index, 'unit', e.target.value)}
readOnly={isReadOnly}
className="mt-1 block w-full text-sm border border-gray-300 rounded-md px-2 py-1.5 focus:outline-none focus:ring-1 focus:ring-blue-500"
/>
@ -467,12 +414,12 @@ const PurchaseRequestForm: React.FC = () => {
</label>
<input
type="number"
value={item.estimatedPrice || ""}
value={item.estimatedPrice || ''}
onChange={(e) =>
updateRequestItem(
index,
"estimatedPrice",
parseFloat(e.target.value) || undefined
'estimatedPrice',
parseFloat(e.target.value) || undefined,
)
}
readOnly={isReadOnly}
@ -488,11 +435,7 @@ const PurchaseRequestForm: React.FC = () => {
type="checkbox"
checked={item.isUrgent}
onChange={(e) =>
updateRequestItem(
index,
"isUrgent",
e.target.checked
)
updateRequestItem(index, 'isUrgent', e.target.checked)
}
disabled={isReadOnly}
className="mr-1"
@ -507,13 +450,9 @@ const PurchaseRequestForm: React.FC = () => {
</label>
<input
type="text"
value={item.specification || ""}
value={item.specification || ''}
onChange={(e) =>
updateRequestItem(
index,
"specification",
e.target.value
)
updateRequestItem(index, 'specification', e.target.value)
}
readOnly={isReadOnly}
className="mt-1 block w-full text-sm border border-gray-300 rounded-md px-2 py-1.5 focus:outline-none focus:ring-1 focus:ring-blue-500"
@ -521,18 +460,12 @@ const PurchaseRequestForm: React.FC = () => {
</div>
<div className="md:col-span-2 lg:col-span-3">
<label className="block text-xs font-medium text-gray-600">
Gerekçe
</label>
<label className="block text-xs font-medium text-gray-600">Gerekçe</label>
<input
type="text"
value={item.justification || ""}
value={item.justification || ''}
onChange={(e) =>
updateRequestItem(
index,
"justification",
e.target.value
)
updateRequestItem(index, 'justification', e.target.value)
}
readOnly={isReadOnly}
className="mt-1 block w-full text-sm border border-gray-300 rounded-md px-2 py-1.5 focus:outline-none focus:ring-1 focus:ring-blue-500"
@ -554,15 +487,11 @@ const PurchaseRequestForm: React.FC = () => {
{/* Yan Panel - Durum ve Onaylar */}
<div className="space-y-4">
<div className="bg-white rounded-lg shadow-md p-4">
<h3 className="text-base font-medium text-gray-900 mb-3">
Durum Bilgileri
</h3>
<h3 className="text-base font-medium text-gray-900 mb-3">Durum Bilgileri</h3>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700">
Durum
</label>
<label className="block text-sm font-medium text-gray-700">Durum</label>
<select
name="status"
value={formData.status || RequestStatusEnum.Draft}
@ -571,21 +500,11 @@ const PurchaseRequestForm: React.FC = () => {
className="mt-1 block w-full border border-gray-300 rounded-md px-2.5 py-1.5 focus:outline-none focus:ring-1 focus:ring-blue-500"
>
<option value={RequestStatusEnum.Draft}>Taslak</option>
<option value={RequestStatusEnum.Submitted}>
Gönderildi
</option>
<option value={RequestStatusEnum.InReview}>
İncelemede
</option>
<option value={RequestStatusEnum.Approved}>
Onaylandı
</option>
<option value={RequestStatusEnum.Rejected}>
Reddedildi
</option>
<option value={RequestStatusEnum.Cancelled}>
İptal Edildi
</option>
<option value={RequestStatusEnum.Submitted}>Gönderildi</option>
<option value={RequestStatusEnum.InReview}>İncelemede</option>
<option value={RequestStatusEnum.Approved}>Onaylandı</option>
<option value={RequestStatusEnum.Rejected}>Reddedildi</option>
<option value={RequestStatusEnum.Cancelled}>İptal Edildi</option>
</select>
</div>
@ -595,8 +514,7 @@ const PurchaseRequestForm: React.FC = () => {
Toplam Tutar
</label>
<div className="mt-1 text-base font-semibold text-green-600">
{formData.totalAmount?.toLocaleString()}{" "}
{formData.currency}
{formData.totalAmount?.toLocaleString()} {formData.currency}
</div>
</div>
)}
@ -604,58 +522,48 @@ const PurchaseRequestForm: React.FC = () => {
</div>
{/* Onay Geçmişi (sadece görüntüleme) */}
{isView &&
formData.approvals &&
formData.approvals.length > 0 && (
<div className="bg-white rounded-lg shadow-md p-4">
<h3 className="text-base font-medium text-gray-900 mb-3">
Onay Geçmişi
</h3>
{isView && formData.approvals && formData.approvals.length > 0 && (
<div className="bg-white rounded-lg shadow-md p-4">
<h3 className="text-base font-medium text-gray-900 mb-3">Onay Geçmişi</h3>
<div className="space-y-3">
{formData.approvals.map((approval) => (
<div
key={approval.id}
className="border-l-4 border-blue-500 pl-4"
>
<div className="text-sm font-medium text-gray-900">
{approval.approverName}
</div>
<div className="text-xs text-gray-500">
{approval.approvalLevel} - Seviye{" "}
{approval.sequence}
</div>
<div
className={`text-xs mt-1 ${
approval.status === "APPROVED"
? "text-green-600"
: approval.status === "REJECTED"
? "text-red-600"
: "text-yellow-600"
}`}
>
{approval.status === "APPROVED"
? "Onaylandı"
: approval.status === "REJECTED"
? "Reddedildi"
: "Beklemede"}
</div>
{approval.comments && (
<div className="text-xs text-gray-600 mt-1">
{approval.comments}
</div>
)}
<div className="space-y-3">
{formData.approvals.map((approval) => (
<div key={approval.id} className="border-l-4 border-blue-500 pl-4">
<div className="text-sm font-medium text-gray-900">
{approval.approverName}
</div>
))}
</div>
<div className="text-xs text-gray-500">
{approval.approvalLevel} - Seviye {approval.sequence}
</div>
<div
className={`text-xs mt-1 ${
approval.status === 'APPROVED'
? 'text-green-600'
: approval.status === 'REJECTED'
? 'text-red-600'
: 'text-yellow-600'
}`}
>
{approval.status === 'APPROVED'
? 'Onaylandı'
: approval.status === 'REJECTED'
? 'Reddedildi'
: 'Beklemede'}
</div>
{approval.comments && (
<div className="text-xs text-gray-600 mt-1">{approval.comments}</div>
)}
</div>
))}
</div>
)}
</div>
)}
</div>
</div>
</form>
</div>
</div>
);
};
</Container>
)
}
export default PurchaseRequestForm;
export default PurchaseRequestForm

View file

@ -21,6 +21,7 @@ import {
getRequestStatusColor,
getRequestStatusText,
} from '../../../utils/erp'
import { Container } from '@/components/shared'
const PurchaseRequests: React.FC = () => {
const navigate = useNavigate()
@ -60,228 +61,232 @@ const PurchaseRequests: React.FC = () => {
}
return (
<div className="space-y-3 pt-2">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h2 className="text-2xl font-bold text-gray-900">Satınalma Talepleri</h2>
<p className="text-gray-600">Malzeme, hizmet ve merkezi taleplerini yönetin</p>
<Container>
<div className="space-y-2">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h2 className="text-2xl font-bold text-gray-900">Satınalma Talepleri</h2>
<p className="text-gray-600">Malzeme, hizmet ve merkezi taleplerini yönetin</p>
</div>
<button
onClick={handleAddNew}
className="bg-blue-600 text-white px-3 py-1.5 text-sm rounded-lg hover:bg-blue-700 flex items-center space-x-1.5"
>
<FaPlus className="w-4 h-4" />
<span>Yeni Talep</span>
</button>
</div>
<button
onClick={handleAddNew}
className="bg-blue-600 text-white px-3 py-1.5 text-sm rounded-lg hover:bg-blue-700 flex items-center space-x-1.5"
>
<FaPlus className="w-4 h-4" />
<span>Yeni Talep</span>
</button>
</div>
{/* Filters */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
<div className="relative">
<FaSearch className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-5 h-5" />
<input
type="text"
placeholder="Talep 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"
/>
{/* Filters */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
<div className="relative">
<FaSearch className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-5 h-5" />
<input
type="text"
placeholder="Talep 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>
<select
value={filterStatus}
onChange={(e) => setFilterStatus(e.target.value as RequestStatusEnum | 'ALL')}
className="px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
>
<option value="ALL">Tüm Durumlar</option>
<option value={RequestStatusEnum.Draft}>Taslak</option>
<option value={RequestStatusEnum.Submitted}>Gönderildi</option>
<option value={RequestStatusEnum.InReview}>İnceleniyor</option>
<option value={RequestStatusEnum.Approved}>Onaylandı</option>
<option value={RequestStatusEnum.Rejected}>Reddedildi</option>
</select>
<select
value={filterType}
onChange={(e) => setFilterType(e.target.value as RequestTypeEnum | 'ALL')}
className="px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
>
<option value="ALL">Tüm Türler</option>
<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>
<select
value={filterStatus}
onChange={(e) => setFilterStatus(e.target.value as RequestStatusEnum | 'ALL')}
className="px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
>
<option value="ALL">Tüm Durumlar</option>
<option value={RequestStatusEnum.Draft}>Taslak</option>
<option value={RequestStatusEnum.Submitted}>Gönderildi</option>
<option value={RequestStatusEnum.InReview}>İnceleniyor</option>
<option value={RequestStatusEnum.Approved}>Onaylandı</option>
<option value={RequestStatusEnum.Rejected}>Reddedildi</option>
</select>
<select
value={filterType}
onChange={(e) => setFilterType(e.target.value as RequestTypeEnum | 'ALL')}
className="px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
>
<option value="ALL">Tüm Türler</option>
<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 className="space-y-4">
{/* Request List */}
<div className="space-y-3 pt-2">
<h3 className="text-base font-semibold text-gray-900">Talep Listesi</h3>
{filteredRequests.map((request) => (
<div
key={request.id}
className="bg-white rounded-lg shadow-md border border-gray-200 p-3 hover:shadow-lg transition-shadow cursor-pointer"
onClick={() => handleViewDetails(request)}
>
<div className="flex items-start justify-between mb-2">
<div className="flex-1">
<div className="flex items-center space-x-2 mb-1.5">
<FaFileAlt className="w-5 h-5 text-gray-600" />
<h4 className="text-lg font-semibold text-gray-900">{request.requestNumber}</h4>
<span
className={`px-2 py-1 rounded-full text-xs font-medium ${getRequestTypeColor(
request.requestType,
)}`}
>
{getRequestTypeText(request.requestType)}
</span>
<span
className={`px-2 py-1 rounded-full text-xs font-medium ${getRequestStatusColor(
request.status,
)}`}
>
{getRequestStatusText(request.status)}
</span>
</div>
<p className="text-sm text-gray-600 mb-1">{request.description}</p>
<div className="flex items-center space-x-4 text-sm text-gray-500">
<span className="flex items-center">
<FaUser className="w-4 h-4 mr-1" />
{request.requestedBy}
</span>
<span className="flex items-center">
<FaCalendar className="w-4 h-4 mr-1" />
{request.requestDate.toLocaleDateString('tr-TR')}
</span>
</div>
</div>
<div className="flex space-x-2">
<button
onClick={(e) => {
e.stopPropagation()
handleViewDetails(request)
}}
className="p-1.5 text-gray-400 hover:text-green-600 hover:bg-green-50 rounded-md transition-colors"
title="Görüntüle"
>
<FaEye className="w-4 h-4" />
</button>
<button
onClick={(e) => {
e.stopPropagation()
handleEdit(request)
}}
className="p-1.5 text-gray-400 hover:text-blue-600 hover:bg-blue-50 rounded-md transition-colors"
title="Düzenle"
>
<FaEdit className="w-4 h-4" />
</button>
</div>
</div>
<div className="grid grid-cols-2 gap-3 text-sm mb-2">
<div className="flex items-center justify-between">
<span className="text-gray-500">Departman:</span>
<span className="font-medium">{request.department}</span>
</div>
<div className="flex items-center justify-between">
<span className="text-gray-500">Öncelik:</span>
<span className={`font-medium ${getPriorityColor(request.priority)}`}>
{getPriorityText(request.priority)}
</span>
</div>
<div className="flex items-center justify-between">
<span className="text-gray-500">Tutar:</span>
<span className="font-medium">
{request.totalAmount
? `${request.totalAmount.toLocaleString()}`
: 'Belirtilmemiş'}
</span>
</div>
<div className="flex items-center justify-between">
<span className="text-gray-500">Gerekli Tarih:</span>
<span className="font-medium">
{request.requiredDate.toLocaleDateString('tr-TR')}
</span>
</div>
</div>
{/* Urgency Warning */}
{getDaysUntilRequired(request.requiredDate) <= 3 &&
request.status !== RequestStatusEnum.Completed && (
<div className="flex items-center space-x-2 text-red-600 bg-red-50 px-2.5 py-1.5 rounded-lg mb-2">
<FaExclamationTriangle className="w-4 h-4" />
<span className="text-sm">
{getDaysUntilRequired(request.requiredDate) <= 0
? 'Gerekli tarih geçti!'
: `${getDaysUntilRequired(request.requiredDate)} gün kaldı`}
</span>
</div>
)}
{/* Items Details */}
<div className="mt-3 mb-3">
<h4 className="text-sm font-medium text-gray-900 mb-1.5">Talep Kalemleri</h4>
<div className="space-y-2">
{request.items.map((item) => (
<div key={item.id} className="bg-gray-50 p-2 rounded-lg">
<div className="flex items-center justify-between mb-1.5">
<div className="flex-1">
<p className="font-medium text-gray-900">
{item.specification || 'Malzeme'}
</p>
<p className="text-sm text-gray-600">
Malzeme ID: {item.materialId} | Miktar: {item.quantity} {item.unit}
{item.estimatedPrice && (
<> | Tahmini Fiyat: {item.estimatedPrice.toLocaleString()}</>
)}
</p>
</div>
{item.isUrgent && (
<span className="bg-red-100 text-red-800 text-xs px-2 py-1 rounded-full">
Acil
</span>
)}
</div>
{item.justification && (
<div className="text-xs text-gray-500">Gerekçe: {item.justification}</div>
)}
<div className="space-y-4">
{/* Request List */}
<div className="space-y-3 pt-2">
<h3 className="text-base font-semibold text-gray-900">Talep Listesi</h3>
{filteredRequests.map((request) => (
<div
key={request.id}
className="bg-white rounded-lg shadow-md border border-gray-200 p-3 hover:shadow-lg transition-shadow cursor-pointer"
onClick={() => handleViewDetails(request)}
>
<div className="flex items-start justify-between mb-2">
<div className="flex-1">
<div className="flex items-center space-x-2 mb-1.5">
<FaFileAlt className="w-5 h-5 text-gray-600" />
<h4 className="text-lg font-semibold text-gray-900">
{request.requestNumber}
</h4>
<span
className={`px-2 py-1 rounded-full text-xs font-medium ${getRequestTypeColor(
request.requestType,
)}`}
>
{getRequestTypeText(request.requestType)}
</span>
<span
className={`px-2 py-1 rounded-full text-xs font-medium ${getRequestStatusColor(
request.status,
)}`}
>
{getRequestStatusText(request.status)}
</span>
</div>
))}
<p className="text-sm text-gray-600 mb-1">{request.description}</p>
<div className="flex items-center space-x-4 text-sm text-gray-500">
<span className="flex items-center">
<FaUser className="w-4 h-4 mr-1" />
{request.requestedBy}
</span>
<span className="flex items-center">
<FaCalendar className="w-4 h-4 mr-1" />
{request.requestDate.toLocaleDateString('tr-TR')}
</span>
</div>
</div>
<div className="flex space-x-2">
<button
onClick={(e) => {
e.stopPropagation()
handleViewDetails(request)
}}
className="p-1.5 text-gray-400 hover:text-green-600 hover:bg-green-50 rounded-md transition-colors"
title="Görüntüle"
>
<FaEye className="w-4 h-4" />
</button>
<button
onClick={(e) => {
e.stopPropagation()
handleEdit(request)
}}
className="p-1.5 text-gray-400 hover:text-blue-600 hover:bg-blue-50 rounded-md transition-colors"
title="Düzenle"
>
<FaEdit className="w-4 h-4" />
</button>
</div>
</div>
</div>
{/* Approval Status */}
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<span className="text-sm text-gray-500">Onaylar:</span>
<div className="flex space-x-1">
{request.approvals.map((approval) => (
<div key={approval.id} className="flex items-center space-x-1">
{getApprovalStatusIcon(approval.status)}
<div className="grid grid-cols-2 gap-3 text-sm mb-2">
<div className="flex items-center justify-between">
<span className="text-gray-500">Departman:</span>
<span className="font-medium">{request.department}</span>
</div>
<div className="flex items-center justify-between">
<span className="text-gray-500">Öncelik:</span>
<span className={`font-medium ${getPriorityColor(request.priority)}`}>
{getPriorityText(request.priority)}
</span>
</div>
<div className="flex items-center justify-between">
<span className="text-gray-500">Tutar:</span>
<span className="font-medium">
{request.totalAmount
? `${request.totalAmount.toLocaleString()}`
: 'Belirtilmemiş'}
</span>
</div>
<div className="flex items-center justify-between">
<span className="text-gray-500">Gerekli Tarih:</span>
<span className="font-medium">
{request.requiredDate.toLocaleDateString('tr-TR')}
</span>
</div>
</div>
{/* Urgency Warning */}
{getDaysUntilRequired(request.requiredDate) <= 3 &&
request.status !== RequestStatusEnum.Completed && (
<div className="flex items-center space-x-2 text-red-600 bg-red-50 px-2.5 py-1.5 rounded-lg mb-2">
<FaExclamationTriangle className="w-4 h-4" />
<span className="text-sm">
{getDaysUntilRequired(request.requiredDate) <= 0
? 'Gerekli tarih geçti!'
: `${getDaysUntilRequired(request.requiredDate)} gün kaldı`}
</span>
</div>
)}
{/* Items Details */}
<div className="mt-3 mb-3">
<h4 className="text-sm font-medium text-gray-900 mb-1.5">Talep Kalemleri</h4>
<div className="space-y-2">
{request.items.map((item) => (
<div key={item.id} className="bg-gray-50 p-2 rounded-lg">
<div className="flex items-center justify-between mb-1.5">
<div className="flex-1">
<p className="font-medium text-gray-900">
{item.specification || 'Malzeme'}
</p>
<p className="text-sm text-gray-600">
Malzeme ID: {item.materialId} | Miktar: {item.quantity} {item.unit}
{item.estimatedPrice && (
<> | Tahmini Fiyat: {item.estimatedPrice.toLocaleString()}</>
)}
</p>
</div>
{item.isUrgent && (
<span className="bg-red-100 text-red-800 text-xs px-2 py-1 rounded-full">
Acil
</span>
)}
</div>
{item.justification && (
<div className="text-xs text-gray-500">Gerekçe: {item.justification}</div>
)}
</div>
))}
</div>
</div>
<div className="flex items-center space-x-2">
<span className="text-xs text-gray-400">{request.items.length} kalem</span>
{/* Approval Status */}
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<span className="text-sm text-gray-500">Onaylar:</span>
<div className="flex space-x-1">
{request.approvals.map((approval) => (
<div key={approval.id} className="flex items-center space-x-1">
{getApprovalStatusIcon(approval.status)}
</div>
))}
</div>
</div>
<div className="flex items-center space-x-2">
<span className="text-xs text-gray-400">{request.items.length} kalem</span>
</div>
</div>
</div>
</div>
))}
))}
{filteredRequests.length === 0 && (
<div className="text-center py-6">
<FaFileAlt className="w-12 h-12 text-gray-400 mx-auto mb-4" />
<h3 className="text-lg font-medium text-gray-900 mb-2">Talep bulunamadı</h3>
<p className="text-gray-500">
Arama kriterlerinizi değiştirin veya yeni bir talep ekleyin.
</p>
</div>
)}
{filteredRequests.length === 0 && (
<div className="text-center py-6">
<FaFileAlt className="w-12 h-12 text-gray-400 mx-auto mb-4" />
<h3 className="text-lg font-medium text-gray-900 mb-2">Talep bulunamadı</h3>
<p className="text-gray-500">
Arama kriterlerinizi değiştirin veya yeni bir talep ekleyin.
</p>
</div>
)}
</div>
</div>
</div>
</div>
</Container>
)
}

View file

@ -1,6 +1,6 @@
import React, { useState } from "react";
import { Link } from "react-router-dom";
import { useQuery } from "@tanstack/react-query";
import React, { useState } from 'react'
import { Link } from 'react-router-dom'
import { useQuery } from '@tanstack/react-query'
import {
FaFileAlt,
FaPlus,
@ -14,64 +14,54 @@ import {
FaExclamationTriangle,
FaUser,
FaCalendar,
} from "react-icons/fa";
import classNames from "classnames";
import { RequisitionStatusEnum } from "../../../types/mm";
import dayjs from "dayjs";
import { mockPurchaseRequisitions } from "../../../mocks/mockPurchaseRequisitions";
import { PriorityEnum } from "../../../types/common";
} from 'react-icons/fa'
import classNames from 'classnames'
import { RequisitionStatusEnum } from '../../../types/mm'
import dayjs from 'dayjs'
import { mockPurchaseRequisitions } from '../../../mocks/mockPurchaseRequisitions'
import { PriorityEnum } from '../../../types/common'
import {
getPriorityColor,
getPriorityText,
getRequisitionStatusColor,
getRequisitionStatusIcon,
getRequisitionStatusText,
} from "../../../utils/erp";
} from '../../../utils/erp'
import { Container } from '@/components/shared'
const PurchaseRequisitionList: React.FC = () => {
const [searchTerm, setSearchTerm] = useState("");
const [filterStatus, setFilterStatus] = useState("all");
const [filterPriority, setFilterPriority] = useState("all");
const [showFilters, setShowFilters] = useState(false);
const [searchTerm, setSearchTerm] = useState('')
const [filterStatus, setFilterStatus] = useState('all')
const [filterPriority, setFilterPriority] = useState('all')
const [showFilters, setShowFilters] = useState(false)
const {
data: requisitions,
isLoading,
error,
} = useQuery({
queryKey: [
"purchase-requisitions",
searchTerm,
filterStatus,
filterPriority,
],
queryKey: ['purchase-requisitions', searchTerm, filterStatus, filterPriority],
queryFn: async () => {
await new Promise((resolve) => setTimeout(resolve, 500));
await new Promise((resolve) => setTimeout(resolve, 500))
return mockPurchaseRequisitions.filter((req) => {
const matchesSearch =
req.requisitionNumber
.toLowerCase()
.includes(searchTerm.toLowerCase()) ||
req.requisitionNumber.toLowerCase().includes(searchTerm.toLowerCase()) ||
req.description?.toLowerCase().includes(searchTerm.toLowerCase()) ||
req.requestedBy.toLowerCase().includes(searchTerm.toLowerCase());
const matchesStatus =
filterStatus === "all" || req.status === filterStatus;
const matchesPriority =
filterPriority === "all" || req.priority === filterPriority;
return matchesSearch && matchesStatus && matchesPriority;
});
req.requestedBy.toLowerCase().includes(searchTerm.toLowerCase())
const matchesStatus = filterStatus === 'all' || req.status === filterStatus
const matchesPriority = filterPriority === 'all' || req.priority === filterPriority
return matchesSearch && matchesStatus && matchesPriority
})
},
});
})
if (isLoading) {
return (
<div className="flex items-center justify-center py-12">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
<span className="ml-3 text-gray-600">
Satınalma talepleri yükleniyor...
</span>
<span className="ml-3 text-gray-600">Satınalma talepleri yükleniyor...</span>
</div>
);
)
}
if (error) {
@ -79,359 +69,332 @@ const PurchaseRequisitionList: React.FC = () => {
<div className="bg-red-50 border border-red-200 rounded-lg p-4">
<div className="flex items-center">
<FaExclamationTriangle className="h-5 w-5 text-red-600 mr-2" />
<span className="text-red-800">
Satınalma talepleri yüklenirken hata oluştu.
</span>
<span className="text-red-800">Satınalma talepleri yüklenirken hata oluştu.</span>
</div>
</div>
);
)
}
return (
<div className="space-y-3 pt-2">
{/* Header Actions */}
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<div className="flex items-center space-x-4">
<div className="relative">
<FaSearch
size={20}
className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"
/>
<input
type="text"
placeholder="Talep numarası veya açıklama..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-10 pr-4 py-1.5 w-80 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
<Container>
<div className="space-y-2">
{/* Header Actions */}
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<div className="flex items-center space-x-4">
<div className="relative">
<FaSearch
size={20}
className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"
/>
<input
type="text"
placeholder="Talep numarası veya açıklama..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-10 pr-4 py-1.5 w-80 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
</div>
<button
onClick={() => setShowFilters(!showFilters)}
className={classNames(
'flex items-center px-3 py-1.5 border rounded-lg transition-colors',
showFilters
? 'border-blue-500 bg-blue-50 text-blue-700'
: 'border-gray-300 bg-white text-gray-700 hover:bg-gray-50',
)}
>
<FaFilter size={16} className="mr-2" />
Filtreler
</button>
</div>
<button
onClick={() => setShowFilters(!showFilters)}
className={classNames(
"flex items-center px-3 py-1.5 border rounded-lg transition-colors",
showFilters
? "border-blue-500 bg-blue-50 text-blue-700"
: "border-gray-300 bg-white text-gray-700 hover:bg-gray-50"
)}
>
<FaFilter size={16} className="mr-2" />
Filtreler
</button>
</div>
<div className="flex items-center space-x-3">
<button
onClick={() => alert('Dışa aktarma özelliği yakında eklenecek')}
className="flex items-center px-3 py-1.5 border border-gray-300 bg-white text-gray-700 rounded-lg hover:bg-gray-50 transition-colors"
>
<FaDownload size={16} className="mr-2" />
Dışa Aktar
</button>
<div className="flex items-center space-x-3">
<button
onClick={() => alert("Dışa aktarma özelliği yakında eklenecek")}
className="flex items-center px-3 py-1.5 border border-gray-300 bg-white text-gray-700 rounded-lg hover:bg-gray-50 transition-colors"
>
<FaDownload size={16} className="mr-2" />
Dışa Aktar
</button>
<Link
to="/admin/supplychain/requisitions/new"
className="flex items-center px-3 py-1.5 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
>
<FaPlus size={16} className="mr-2" />
Yeni Talep
</Link>
</div>
</div>
{/* Filters Panel */}
{showFilters && (
<div className="bg-white border border-gray-200 rounded-lg p-3">
<div className="grid grid-cols-1 md:grid-cols-4 gap-3">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Durum
</label>
<select
value={filterStatus}
onChange={(e) => setFilterStatus(e.target.value)}
className="w-full border border-gray-300 rounded-lg px-3 py-1.5 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
>
<option value="all">Tümü</option>
<option value={RequisitionStatusEnum.Draft}>Taslak</option>
<option value={RequisitionStatusEnum.Submitted}>
Gönderildi
</option>
<option value={RequisitionStatusEnum.InApproval}>Onayda</option>
<option value={RequisitionStatusEnum.Approved}>
Onaylandı
</option>
<option value={RequisitionStatusEnum.Rejected}>
Reddedildi
</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Öncelik
</label>
<select
value={filterPriority}
onChange={(e) => setFilterPriority(e.target.value)}
className="w-full border border-gray-300 rounded-lg px-3 py-1.5 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
>
<option value="all">Tümü</option>
<option value={PriorityEnum.Low}>Düşük</option>
<option value={PriorityEnum.Normal}>Normal</option>
<option value={PriorityEnum.High}>Yüksek</option>
<option value={PriorityEnum.Urgent}>Acil</option>
</select>
</div>
<div className="flex items-end">
<button
onClick={() => {
setFilterStatus("all");
setFilterPriority("all");
setSearchTerm("");
}}
className="w-full px-3 py-1.5 border border-gray-300 bg-white text-gray-700 rounded-lg hover:bg-gray-50 transition-colors"
>
Filtreleri Temizle
</button>
</div>
</div>
</div>
)}
{/* Statistics Cards */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-3">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">Toplam Talep</p>
<p className="text-xl font-bold text-gray-900">
{requisitions?.length || 0}
</p>
</div>
<FaFileAlt className="h-8 w-8 text-blue-600" />
<Link
to="/admin/supplychain/requisitions/new"
className="flex items-center px-3 py-1.5 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
>
<FaPlus size={16} className="mr-2" />
Yeni Talep
</Link>
</div>
</div>
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-3">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">Onay Bekleyen</p>
<p className="text-xl font-bold text-yellow-600">
{requisitions?.filter(
(r) => r.status === RequisitionStatusEnum.InApproval
).length || 0}
</p>
</div>
<FaClock className="h-8 w-8 text-yellow-600" />
</div>
</div>
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-3">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">Onaylanan</p>
<p className="text-xl font-bold text-green-600">
{requisitions?.filter(
(r) => r.status === RequisitionStatusEnum.Approved
).length || 0}
</p>
</div>
<FaCheckCircle className="h-8 w-8 text-green-600" />
</div>
</div>
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-3">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">Acil Talepler</p>
<p className="text-xl font-bold text-red-600">
{requisitions?.filter((r) => r.priority === PriorityEnum.Urgent)
.length || 0}
</p>
</div>
<FaExclamationTriangle className="h-8 w-8 text-red-600" />
</div>
</div>
</div>
{/* Requisitions Table */}
<div className="bg-white rounded-lg shadow-sm border border-gray-200">
<div className="px-4 py-3 border-b border-gray-200">
<h2 className="text-xl font-bold text-gray-900">
Satınalma Talepleri
</h2>
</div>
<div className="overflow-x-auto">
<table className="w-full">
<thead className="bg-gray-50">
<tr>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Talep Bilgileri
</th>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Talep Eden
</th>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Öncelik / Durum
</th>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Tarihler
</th>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Tutar
</th>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Kalem Sayısı
</th>
<th className="px-4 py-2 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">
İşlemler
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{requisitions?.map((requisition) => (
<tr
key={requisition.id}
className="hover:bg-gray-50 transition-colors"
{/* Filters Panel */}
{showFilters && (
<div className="bg-white border border-gray-200 rounded-lg p-3">
<div className="grid grid-cols-1 md:grid-cols-4 gap-3">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Durum</label>
<select
value={filterStatus}
onChange={(e) => setFilterStatus(e.target.value)}
className="w-full border border-gray-300 rounded-lg px-3 py-1.5 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
>
<td className="px-4 py-3">
<div className="flex items-center">
<div className="flex-shrink-0 h-10 w-10">
<div className="h-10 w-10 rounded-lg bg-blue-100 flex items-center justify-center">
<FaFileAlt className="h-5 w-5 text-blue-600" />
</div>
</div>
<div className="ml-4">
<div className="text-sm font-medium text-gray-900">
{requisition.requisitionNumber}
</div>
<div className="text-sm text-gray-500 max-w-xs truncate">
{requisition.description}
</div>
</div>
</div>
</td>
<option value="all">Tümü</option>
<option value={RequisitionStatusEnum.Draft}>Taslak</option>
<option value={RequisitionStatusEnum.Submitted}>Gönderildi</option>
<option value={RequisitionStatusEnum.InApproval}>Onayda</option>
<option value={RequisitionStatusEnum.Approved}>Onaylandı</option>
<option value={RequisitionStatusEnum.Rejected}>Reddedildi</option>
</select>
</div>
<td className="px-4 py-3">
<div className="flex items-center text-sm">
<FaUser size={16} className="text-gray-400 mr-2" />
<div>
<div className="font-medium text-gray-900">
{requisition.requestedBy}
</div>
<div className="text-gray-500">
{requisition.departmentId}
</div>
</div>
</div>
</td>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Öncelik</label>
<select
value={filterPriority}
onChange={(e) => setFilterPriority(e.target.value)}
className="w-full border border-gray-300 rounded-lg px-3 py-1.5 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
>
<option value="all">Tümü</option>
<option value={PriorityEnum.Low}>Düşük</option>
<option value={PriorityEnum.Normal}>Normal</option>
<option value={PriorityEnum.High}>Yüksek</option>
<option value={PriorityEnum.Urgent}>Acil</option>
</select>
</div>
<td className="px-4 py-3">
<div className="space-y-2">
<div
className={classNames(
"text-sm font-medium",
getPriorityColor(requisition.priority)
)}
>
{getPriorityText(requisition.priority)}
</div>
<span
className={classNames(
"inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium",
getRequisitionStatusColor(requisition.status)
)}
>
{getRequisitionStatusIcon(requisition.status)}
<span className="ml-1">
{getRequisitionStatusText(requisition.status)}
</span>
</span>
</div>
</td>
<td className="px-4 py-3">
<div className="space-y-1">
<div className="flex items-center text-sm text-gray-900">
<FaCalendar size={14} className="mr-1" />
{dayjs(requisition.requestDate).format("DD.MM.YYYY")}
</div>
<div className="text-sm text-gray-500">
İhtiyaç:{" "}
{dayjs(requisition.requiredDate).format("DD.MM.YYYY")}
</div>
</div>
</td>
<td className="px-4 py-3">
<div className="text-sm font-medium text-gray-900">
{requisition.totalAmount.toLocaleString()}
</div>
<div className="text-sm text-gray-500">
{requisition.currency}
</div>
</td>
<td className="px-4 py-3">
<div className="text-sm font-medium text-gray-900">
{requisition.items.length} Kalem
</div>
</td>
<td className="px-4 py-3 text-right">
<div className="flex items-center justify-end space-x-2">
<Link
to={`/admin/supplychain/requisitions/${requisition.id}`}
className="p-1.5 text-gray-600 hover:text-blue-600 hover:bg-blue-50 rounded-lg transition-colors"
title="Detayları Görüntüle"
>
<FaEye size={16} />
</Link>
{requisition.status === RequisitionStatusEnum.Draft && (
<Link
to={`/admin/supplychain/requisitions/edit/${requisition.id}`}
className="p-1.5 text-gray-600 hover:text-yellow-600 hover:bg-yellow-50 rounded-lg transition-colors"
title="Düzenle"
>
<FaEdit size={16} />
</Link>
)}
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
{(!requisitions || requisitions.length === 0) && (
<div className="text-center py-10">
<FaFileAlt className="mx-auto h-12 w-12 text-gray-400" />
<h3 className="mt-2 text-sm font-medium text-gray-900">
Satınalma talebi bulunamadı
</h3>
<p className="mt-1 text-sm text-gray-500">
Yeni satınalma talebi oluşturarak başlayın.
</p>
<div className="mt-6">
<Link
to="/admin/supplychain/requisitions/new"
className="inline-flex items-center px-3 py-1.5 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
>
<FaPlus size={16} className="mr-2" />
Yeni Talep Oluştur
</Link>
<div className="flex items-end">
<button
onClick={() => {
setFilterStatus('all')
setFilterPriority('all')
setSearchTerm('')
}}
className="w-full px-3 py-1.5 border border-gray-300 bg-white text-gray-700 rounded-lg hover:bg-gray-50 transition-colors"
>
Filtreleri Temizle
</button>
</div>
</div>
</div>
)}
</div>
</div>
);
};
export default PurchaseRequisitionList;
{/* Statistics Cards */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-3">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">Toplam Talep</p>
<p className="text-xl font-bold text-gray-900">{requisitions?.length || 0}</p>
</div>
<FaFileAlt className="h-8 w-8 text-blue-600" />
</div>
</div>
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-3">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">Onay Bekleyen</p>
<p className="text-xl font-bold text-yellow-600">
{requisitions?.filter((r) => r.status === RequisitionStatusEnum.InApproval)
.length || 0}
</p>
</div>
<FaClock className="h-8 w-8 text-yellow-600" />
</div>
</div>
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-3">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">Onaylanan</p>
<p className="text-xl font-bold text-green-600">
{requisitions?.filter((r) => r.status === RequisitionStatusEnum.Approved)
.length || 0}
</p>
</div>
<FaCheckCircle className="h-8 w-8 text-green-600" />
</div>
</div>
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-3">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">Acil Talepler</p>
<p className="text-xl font-bold text-red-600">
{requisitions?.filter((r) => r.priority === PriorityEnum.Urgent).length || 0}
</p>
</div>
<FaExclamationTriangle className="h-8 w-8 text-red-600" />
</div>
</div>
</div>
{/* Requisitions Table */}
<div className="bg-white rounded-lg shadow-sm border border-gray-200">
<div className="px-4 py-3 border-b border-gray-200">
<h2 className="text-xl font-bold text-gray-900">Satınalma Talepleri</h2>
</div>
<div className="overflow-x-auto">
<table className="w-full">
<thead className="bg-gray-50">
<tr>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Talep Bilgileri
</th>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Talep Eden
</th>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Öncelik / Durum
</th>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Tarihler
</th>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Tutar
</th>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Kalem Sayısı
</th>
<th className="px-4 py-2 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">
İşlemler
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{requisitions?.map((requisition) => (
<tr key={requisition.id} className="hover:bg-gray-50 transition-colors">
<td className="px-4 py-3">
<div className="flex items-center">
<div className="flex-shrink-0 h-10 w-10">
<div className="h-10 w-10 rounded-lg bg-blue-100 flex items-center justify-center">
<FaFileAlt className="h-5 w-5 text-blue-600" />
</div>
</div>
<div className="ml-4">
<div className="text-sm font-medium text-gray-900">
{requisition.requisitionNumber}
</div>
<div className="text-sm text-gray-500 max-w-xs truncate">
{requisition.description}
</div>
</div>
</div>
</td>
<td className="px-4 py-3">
<div className="flex items-center text-sm">
<FaUser size={16} className="text-gray-400 mr-2" />
<div>
<div className="font-medium text-gray-900">{requisition.requestedBy}</div>
<div className="text-gray-500">{requisition.departmentId}</div>
</div>
</div>
</td>
<td className="px-4 py-3">
<div className="space-y-2">
<div
className={classNames(
'text-sm font-medium',
getPriorityColor(requisition.priority),
)}
>
{getPriorityText(requisition.priority)}
</div>
<span
className={classNames(
'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium',
getRequisitionStatusColor(requisition.status),
)}
>
{getRequisitionStatusIcon(requisition.status)}
<span className="ml-1">
{getRequisitionStatusText(requisition.status)}
</span>
</span>
</div>
</td>
<td className="px-4 py-3">
<div className="space-y-1">
<div className="flex items-center text-sm text-gray-900">
<FaCalendar size={14} className="mr-1" />
{dayjs(requisition.requestDate).format('DD.MM.YYYY')}
</div>
<div className="text-sm text-gray-500">
İhtiyaç: {dayjs(requisition.requiredDate).format('DD.MM.YYYY')}
</div>
</div>
</td>
<td className="px-4 py-3">
<div className="text-sm font-medium text-gray-900">
{requisition.totalAmount.toLocaleString()}
</div>
<div className="text-sm text-gray-500">{requisition.currency}</div>
</td>
<td className="px-4 py-3">
<div className="text-sm font-medium text-gray-900">
{requisition.items.length} Kalem
</div>
</td>
<td className="px-4 py-3 text-right">
<div className="flex items-center justify-end space-x-2">
<Link
to={`/admin/supplychain/requisitions/${requisition.id}`}
className="p-1.5 text-gray-600 hover:text-blue-600 hover:bg-blue-50 rounded-lg transition-colors"
title="Detayları Görüntüle"
>
<FaEye size={16} />
</Link>
{requisition.status === RequisitionStatusEnum.Draft && (
<Link
to={`/admin/supplychain/requisitions/edit/${requisition.id}`}
className="p-1.5 text-gray-600 hover:text-yellow-600 hover:bg-yellow-50 rounded-lg transition-colors"
title="Düzenle"
>
<FaEdit size={16} />
</Link>
)}
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
{(!requisitions || requisitions.length === 0) && (
<div className="text-center py-10">
<FaFileAlt className="mx-auto h-12 w-12 text-gray-400" />
<h3 className="mt-2 text-sm font-medium text-gray-900">
Satınalma talebi bulunamadı
</h3>
<p className="mt-1 text-sm text-gray-500">
Yeni satınalma talebi oluşturarak başlayın.
</p>
<div className="mt-6">
<Link
to="/admin/supplychain/requisitions/new"
className="inline-flex items-center px-3 py-1.5 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
>
<FaPlus size={16} className="mr-2" />
Yeni Talep Oluştur
</Link>
</div>
</div>
)}
</div>
</div>
</Container>
)
}
export default PurchaseRequisitionList

View file

@ -1,5 +1,5 @@
import React, { useState } from "react";
import { useParams, useNavigate } from "react-router-dom";
import React, { useState } from 'react'
import { useParams, useNavigate } from 'react-router-dom'
import {
FaArrowLeft,
FaSave,
@ -10,153 +10,139 @@ import {
FaPlus,
FaTrash,
FaPaperclip,
} from "react-icons/fa";
} from 'react-icons/fa'
import {
MmQuotation,
QuotationStatusEnum,
RequestTypeEnum,
MmQuotationItem,
MmAttachment,
} from "../../../types/mm";
import { mockMaterials } from "../../../mocks/mockMaterials";
import { mockBusinessParties } from "../../../mocks/mockBusinessParties";
} from '../../../types/mm'
import { mockMaterials } from '../../../mocks/mockMaterials'
import { mockBusinessParties } from '../../../mocks/mockBusinessParties'
import { Container } from '@/components/shared'
const QuotationForm: React.FC = () => {
const { id } = useParams<{ id: string }>();
const navigate = useNavigate();
const isEdit = id !== undefined && id !== "new";
const isView = window.location.pathname.includes("/view/");
const { id } = useParams<{ id: string }>()
const navigate = useNavigate()
const isEdit = id !== undefined && id !== 'new'
const isView = window.location.pathname.includes('/view/')
const [formData, setFormData] = useState<Partial<MmQuotation>>({
quotationNumber: isEdit ? `TEK-2024-${id}` : "",
requestId: "",
requestTitle: "",
quotationNumber: isEdit ? `TEK-2024-${id}` : '',
requestId: '',
requestTitle: '',
requestType: RequestTypeEnum.Material,
supplierId: "",
supplierId: '',
quotationDate: new Date(),
validUntil: new Date(),
status: QuotationStatusEnum.Draft,
totalAmount: 0,
currency: "TRY",
paymentTerms: "",
deliveryTerms: "",
currency: 'TRY',
paymentTerms: '',
deliveryTerms: '',
items: [],
attachments: [],
notes: "",
});
notes: '',
})
const handleInputChange = (
e: React.ChangeEvent<
HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement
>
e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>,
) => {
const { name, value, type } = e.target;
const { name, value, type } = e.target
setFormData((prev) => ({
...prev,
[name]:
type === "number"
? parseFloat(value) || 0
: type === "date"
? new Date(value)
: value,
}));
};
type === 'number' ? parseFloat(value) || 0 : type === 'date' ? new Date(value) : value,
}))
}
const addQuotationItem = () => {
const newItem: MmQuotationItem = {
id: `item-${Date.now()}`,
materialCode: "",
materialName: "",
description: "",
materialCode: '',
materialName: '',
description: '',
quantity: 0,
unit: "",
unit: '',
unitPrice: 0,
totalPrice: 0,
specifications: [],
};
}
setFormData((prev) => ({
...prev,
items: [...(prev.items || []), newItem],
}));
};
}))
}
const removeQuotationItem = (index: number) => {
setFormData((prev) => ({
...prev,
items: prev.items?.filter((_, i) => i !== index) || [],
}));
calculateTotal();
};
}))
calculateTotal()
}
const updateQuotationItem = (
index: number,
field: keyof MmQuotationItem,
value: string | number | string[] | undefined
value: string | number | string[] | undefined,
) => {
setFormData((prev) => {
const updatedItems =
prev.items?.map((item, i) => {
if (i === index) {
const updatedItem = { ...item, [field]: value };
const updatedItem = { ...item, [field]: value }
// Auto-calculate total price when quantity or unit price changes
if (field === "quantity" || field === "unitPrice") {
updatedItem.totalPrice =
(updatedItem.quantity || 0) * (updatedItem.unitPrice || 0);
if (field === 'quantity' || field === 'unitPrice') {
updatedItem.totalPrice = (updatedItem.quantity || 0) * (updatedItem.unitPrice || 0)
}
return updatedItem;
return updatedItem
}
return item;
}) || [];
return item
}) || []
return {
...prev,
items: updatedItems,
};
});
}
})
// Recalculate total amount
setTimeout(calculateTotal, 0);
};
setTimeout(calculateTotal, 0)
}
const calculateTotal = () => {
const total =
formData.items?.reduce((sum, item) => sum + (item.totalPrice || 0), 0) ||
0;
const total = formData.items?.reduce((sum, item) => sum + (item.totalPrice || 0), 0) || 0
setFormData((prev) => ({
...prev,
totalAmount: total,
}));
};
}))
}
const handleSpecificationsChange = (index: number, value: string) => {
const specifications = value
.split("\n")
.filter((spec) => spec.trim() !== "");
updateQuotationItem(index, "specifications", specifications);
};
const specifications = value.split('\n').filter((spec) => spec.trim() !== '')
updateQuotationItem(index, 'specifications', specifications)
}
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
e.preventDefault()
// TODO: Implement save logic
console.log("Saving quotation:", formData);
navigate("/admin/supplychain/quotations");
};
console.log('Saving quotation:', formData)
navigate('/admin/supplychain/quotations')
}
const handleCancel = () => {
navigate("/admin/supplychain/quotations");
};
navigate('/admin/supplychain/quotations')
}
const isReadOnly = isView;
const pageTitle = isEdit
? "Teklifi Düzenle"
: isView
? "Teklif Detayları"
: "Yeni Teklif";
const isReadOnly = isView
const pageTitle = isEdit ? 'Teklifi Düzenle' : isView ? 'Teklif Detayları' : 'Yeni Teklif'
return (
<div className="min-h-screen bg-gray-50 py-4">
<div className="mx-auto">
<Container>
<div className="space-y-2">
{/* Header */}
<div className="bg-white rounded-lg shadow-md p-2 mb-2">
<div className="flex items-center justify-between">
@ -175,7 +161,7 @@ const QuotationForm: React.FC = () => {
className="px-3 py-1.5 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50 flex items-center"
>
<FaTimes className="mr-2" />
{isView ? "Kapat" : "İptal"}
{isView ? 'Kapat' : 'İptal'}
</button>
{!isView && (
<button
@ -209,7 +195,7 @@ const QuotationForm: React.FC = () => {
<input
type="text"
name="quotationNumber"
value={formData.quotationNumber || ""}
value={formData.quotationNumber || ''}
onChange={handleInputChange}
readOnly={isReadOnly || isEdit}
className="mt-1 block w-full border border-gray-300 rounded-md px-2.5 py-1.5 focus:outline-none focus:ring-1 focus:ring-blue-500"
@ -217,13 +203,11 @@ const QuotationForm: React.FC = () => {
</div>
<div>
<label className="block text-sm font-medium text-gray-700">
Talep ID
</label>
<label className="block text-sm font-medium text-gray-700">Talep ID</label>
<input
type="text"
name="requestId"
value={formData.requestId || ""}
value={formData.requestId || ''}
onChange={handleInputChange}
readOnly={isReadOnly}
className="mt-1 block w-full border border-gray-300 rounded-md px-2.5 py-1.5 focus:outline-none focus:ring-1 focus:ring-blue-500"
@ -232,13 +216,11 @@ const QuotationForm: React.FC = () => {
</div>
<div>
<label className="block text-sm font-medium text-gray-700">
Talep Başlığı
</label>
<label className="block text-sm font-medium text-gray-700">Talep Başlığı</label>
<input
type="text"
name="requestTitle"
value={formData.requestTitle || ""}
value={formData.requestTitle || ''}
onChange={handleInputChange}
readOnly={isReadOnly}
className="mt-1 block w-full border border-gray-300 rounded-md px-2.5 py-1.5 focus:outline-none focus:ring-1 focus:ring-blue-500"
@ -247,9 +229,7 @@ const QuotationForm: React.FC = () => {
</div>
<div>
<label className="block text-sm font-medium text-gray-700">
Talep Tipi
</label>
<label className="block text-sm font-medium text-gray-700">Talep Tipi</label>
<select
name="requestType"
value={formData.requestType || RequestTypeEnum.Material}
@ -259,20 +239,16 @@ const QuotationForm: React.FC = () => {
>
<option value={RequestTypeEnum.Material}>Malzeme</option>
<option value={RequestTypeEnum.Service}>Hizmet</option>
<option value={RequestTypeEnum.WorkCenter}>
İş Merkezi
</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">
Tedarikçi
</label>
<label className="block text-sm font-medium text-gray-700">Tedarikçi</label>
<select
name="supplierId"
value={formData.supplierId || ""}
value={formData.supplierId || ''}
onChange={handleInputChange}
disabled={isReadOnly}
className="mt-1 block w-full border border-gray-300 rounded-md px-2.5 py-1.5 focus:outline-none focus:ring-1 focus:ring-blue-500"
@ -288,13 +264,11 @@ const QuotationForm: React.FC = () => {
</div>
<div>
<label className="block text-sm font-medium text-gray-700">
Tedarikçi Adı
</label>
<label className="block text-sm font-medium text-gray-700">Tedarikçi Adı</label>
<input
type="text"
name="supplierName"
value={formData.supplier?.name || ""}
value={formData.supplier?.name || ''}
onChange={handleInputChange}
readOnly={isReadOnly}
className="mt-1 block w-full border border-gray-300 rounded-md px-2.5 py-1.5 focus:outline-none focus:ring-1 focus:ring-blue-500"
@ -312,10 +286,8 @@ const QuotationForm: React.FC = () => {
name="quotationDate"
value={
formData.quotationDate
? new Date(formData.quotationDate)
.toISOString()
.split("T")[0]
: ""
? new Date(formData.quotationDate).toISOString().split('T')[0]
: ''
}
onChange={handleInputChange}
readOnly={isReadOnly}
@ -333,10 +305,8 @@ const QuotationForm: React.FC = () => {
name="validUntil"
value={
formData.validUntil
? new Date(formData.validUntil)
.toISOString()
.split("T")[0]
: ""
? new Date(formData.validUntil).toISOString().split('T')[0]
: ''
}
onChange={handleInputChange}
readOnly={isReadOnly}
@ -345,12 +315,10 @@ const QuotationForm: React.FC = () => {
</div>
<div>
<label className="block text-sm font-medium text-gray-700">
Para Birimi
</label>
<label className="block text-sm font-medium text-gray-700">Para Birimi</label>
<select
name="currency"
value={formData.currency || "TRY"}
value={formData.currency || 'TRY'}
onChange={handleInputChange}
disabled={isReadOnly}
className="mt-1 block w-full border border-gray-300 rounded-md px-2.5 py-1.5 focus:outline-none focus:ring-1 focus:ring-blue-500"
@ -368,7 +336,7 @@ const QuotationForm: React.FC = () => {
<input
type="text"
name="paymentTerms"
value={formData.paymentTerms || ""}
value={formData.paymentTerms || ''}
onChange={handleInputChange}
readOnly={isReadOnly}
placeholder="ör: 30 gün vadeli"
@ -383,7 +351,7 @@ const QuotationForm: React.FC = () => {
<input
type="text"
name="deliveryTerms"
value={formData.deliveryTerms || ""}
value={formData.deliveryTerms || ''}
onChange={handleInputChange}
readOnly={isReadOnly}
placeholder="ör: 15 gün içinde teslim"
@ -398,7 +366,7 @@ const QuotationForm: React.FC = () => {
<input
type="number"
name="deliveryTime"
value={formData.deliveryTime || ""}
value={formData.deliveryTime || ''}
onChange={handleInputChange}
readOnly={isReadOnly}
min="0"
@ -408,12 +376,10 @@ const QuotationForm: React.FC = () => {
</div>
<div className="mt-4">
<label className="block text-sm font-medium text-gray-700">
Notlar
</label>
<label className="block text-sm font-medium text-gray-700">Notlar</label>
<textarea
name="notes"
value={formData.notes || ""}
value={formData.notes || ''}
onChange={handleInputChange}
readOnly={isReadOnly}
rows={2}
@ -425,9 +391,7 @@ const QuotationForm: React.FC = () => {
{/* Teklif Kalemleri */}
<div className="bg-white rounded-lg shadow-md p-4">
<div className="flex items-center justify-between mb-3">
<h3 className="text-base font-medium text-gray-900">
Teklif Kalemleri
</h3>
<h3 className="text-base font-medium text-gray-900">Teklif Kalemleri</h3>
{!isReadOnly && (
<button
type="button"
@ -442,14 +406,9 @@ const QuotationForm: React.FC = () => {
<div className="space-y-3">
{formData.items?.map((item, index) => (
<div
key={item.id}
className="border rounded-lg p-3 bg-gray-50"
>
<div key={item.id} className="border rounded-lg p-3 bg-gray-50">
<div className="flex items-center justify-between mb-2">
<span className="text-sm font-medium text-gray-700">
Kalem {index + 1}
</span>
<span className="text-sm font-medium text-gray-700">Kalem {index + 1}</span>
{!isReadOnly && (
<button
type="button"
@ -463,17 +422,11 @@ const QuotationForm: React.FC = () => {
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-2">
<div>
<label className="block text-xs font-medium text-gray-600">
Malzeme
</label>
<label className="block text-xs font-medium text-gray-600">Malzeme</label>
<select
value={item.materialCode}
onChange={(e) =>
updateQuotationItem(
index,
"materialCode",
e.target.value
)
updateQuotationItem(index, 'materialCode', e.target.value)
}
disabled={isReadOnly}
className="mt-1 block w-full text-sm border border-gray-300 rounded-md px-2 py-1.5 focus:outline-none focus:ring-1 focus:ring-blue-500"
@ -495,11 +448,7 @@ const QuotationForm: React.FC = () => {
type="text"
value={item.materialName}
onChange={(e) =>
updateQuotationItem(
index,
"materialName",
e.target.value
)
updateQuotationItem(index, 'materialName', e.target.value)
}
readOnly={isReadOnly}
className="mt-1 block w-full text-sm border border-gray-300 rounded-md px-2 py-1.5 focus:outline-none focus:ring-1 focus:ring-blue-500"
@ -514,11 +463,7 @@ const QuotationForm: React.FC = () => {
type="text"
value={item.description}
onChange={(e) =>
updateQuotationItem(
index,
"description",
e.target.value
)
updateQuotationItem(index, 'description', e.target.value)
}
readOnly={isReadOnly}
className="mt-1 block w-full text-sm border border-gray-300 rounded-md px-2 py-1.5 focus:outline-none focus:ring-1 focus:ring-blue-500"
@ -526,17 +471,15 @@ const QuotationForm: React.FC = () => {
</div>
<div>
<label className="block text-xs font-medium text-gray-600">
Miktar
</label>
<label className="block text-xs font-medium text-gray-600">Miktar</label>
<input
type="number"
value={item.quantity}
onChange={(e) =>
updateQuotationItem(
index,
"quantity",
parseFloat(e.target.value) || 0
'quantity',
parseFloat(e.target.value) || 0,
)
}
readOnly={isReadOnly}
@ -547,15 +490,11 @@ const QuotationForm: React.FC = () => {
</div>
<div>
<label className="block text-xs font-medium text-gray-600">
Birim
</label>
<label className="block text-xs font-medium text-gray-600">Birim</label>
<input
type="text"
value={item.unit}
onChange={(e) =>
updateQuotationItem(index, "unit", e.target.value)
}
onChange={(e) => updateQuotationItem(index, 'unit', e.target.value)}
readOnly={isReadOnly}
className="mt-1 block w-full text-sm border border-gray-300 rounded-md px-2 py-1.5 focus:outline-none focus:ring-1 focus:ring-blue-500"
/>
@ -571,8 +510,8 @@ const QuotationForm: React.FC = () => {
onChange={(e) =>
updateQuotationItem(
index,
"unitPrice",
parseFloat(e.target.value) || 0
'unitPrice',
parseFloat(e.target.value) || 0,
)
}
readOnly={isReadOnly}
@ -600,12 +539,12 @@ const QuotationForm: React.FC = () => {
</label>
<input
type="number"
value={item.leadTime || ""}
value={item.leadTime || ''}
onChange={(e) =>
updateQuotationItem(
index,
"leadTime",
parseInt(e.target.value) || undefined
'leadTime',
parseInt(e.target.value) || undefined,
)
}
readOnly={isReadOnly}
@ -619,10 +558,8 @@ const QuotationForm: React.FC = () => {
Spesifikasyonlar (her satıra bir spesifikasyon)
</label>
<textarea
value={item.specifications?.join("\n") || ""}
onChange={(e) =>
handleSpecificationsChange(index, e.target.value)
}
value={item.specifications?.join('\n') || ''}
onChange={(e) => handleSpecificationsChange(index, e.target.value)}
readOnly={isReadOnly}
rows={1}
className="mt-1 block w-full text-sm border border-gray-300 rounded-md px-2 py-1.5 focus:outline-none focus:ring-1 focus:ring-blue-500"
@ -645,15 +582,11 @@ const QuotationForm: React.FC = () => {
<div className="space-y-4">
{/* Durum ve Tutar */}
<div className="bg-white rounded-lg shadow-md p-4">
<h3 className="text-base font-medium text-gray-900 mb-3">
Durum Bilgileri
</h3>
<h3 className="text-base font-medium text-gray-900 mb-3">Durum Bilgileri</h3>
<div className="space-y-3">
<div>
<label className="block text-sm font-medium text-gray-700">
Durum
</label>
<label className="block text-sm font-medium text-gray-700">Durum</label>
<select
name="status"
value={formData.status || QuotationStatusEnum.Draft}
@ -662,24 +595,12 @@ const QuotationForm: React.FC = () => {
className="mt-1 block w-full border border-gray-300 rounded-md px-2.5 py-1.5 focus:outline-none focus:ring-1 focus:ring-blue-500"
>
<option value={QuotationStatusEnum.Draft}>Taslak</option>
<option value={QuotationStatusEnum.Pending}>
Beklemede
</option>
<option value={QuotationStatusEnum.UnderReview}>
İncelemede
</option>
<option value={QuotationStatusEnum.Submitted}>
Gönderildi
</option>
<option value={QuotationStatusEnum.Approved}>
Onaylandı
</option>
<option value={QuotationStatusEnum.Rejected}>
Reddedildi
</option>
<option value={QuotationStatusEnum.Expired}>
Süresi Doldu
</option>
<option value={QuotationStatusEnum.Pending}>Beklemede</option>
<option value={QuotationStatusEnum.UnderReview}>İncelemede</option>
<option value={QuotationStatusEnum.Submitted}>Gönderildi</option>
<option value={QuotationStatusEnum.Approved}>Onaylandı</option>
<option value={QuotationStatusEnum.Rejected}>Reddedildi</option>
<option value={QuotationStatusEnum.Expired}>Süresi Doldu</option>
</select>
</div>
@ -689,8 +610,7 @@ const QuotationForm: React.FC = () => {
Toplam Tutar
</label>
<div className="mt-1 text-base font-semibold text-green-600">
{formData.totalAmount?.toLocaleString()}{" "}
{formData.currency}
{formData.totalAmount?.toLocaleString()} {formData.currency}
</div>
</div>
</div>
@ -699,15 +619,11 @@ const QuotationForm: React.FC = () => {
{/* Değerlendirme (sadece görüntüleme) */}
{isView && formData.evaluationScore && (
<div className="bg-white rounded-lg shadow-md p-4">
<h3 className="text-base font-medium text-gray-900 mb-3">
Değerlendirme
</h3>
<h3 className="text-base font-medium text-gray-900 mb-3">Değerlendirme</h3>
<div className="space-y-3">
<div>
<div className="text-sm font-medium text-gray-700">
Değerlendirme Puanı
</div>
<div className="text-sm font-medium text-gray-700">Değerlendirme Puanı</div>
<div className="text-base font-semibold text-blue-600">
{formData.evaluationScore}/100
</div>
@ -715,12 +631,8 @@ const QuotationForm: React.FC = () => {
{formData.evaluatedBy && (
<div>
<div className="text-sm font-medium text-gray-700">
Değerlendiren
</div>
<div className="text-sm text-gray-600">
{formData.evaluatedBy}
</div>
<div className="text-sm font-medium text-gray-700">Değerlendiren</div>
<div className="text-sm text-gray-600">{formData.evaluatedBy}</div>
</div>
)}
@ -773,8 +685,8 @@ const QuotationForm: React.FC = () => {
</div>
</form>
</div>
</div>
);
};
</Container>
)
}
export default QuotationForm;
export default QuotationForm

View file

@ -1,5 +1,5 @@
import React, { useState } from "react";
import { useNavigate } from "react-router-dom";
import React, { useState } from 'react'
import { useNavigate } from 'react-router-dom'
import {
FaPlus,
FaSearch,
@ -16,418 +16,386 @@ import {
FaClock,
FaCheckCircle,
FaTimes,
} from "react-icons/fa";
import { MmQuotation, QuotationStatusEnum } from "../../../types/mm";
import { mockQuotations } from "../../../mocks/mockQuotations";
} from 'react-icons/fa'
import { MmQuotation, QuotationStatusEnum } from '../../../types/mm'
import { mockQuotations } from '../../../mocks/mockQuotations'
import {
getQuotationStatusColor,
getQuotationStatusIcon,
getQuotationStatusText,
getRequestTypeText,
} from "../../../utils/erp";
} from '../../../utils/erp'
import { Container } from '@/components/shared'
const QuotationManagement: React.FC = () => {
const navigate = useNavigate();
const [searchTerm, setSearchTerm] = useState("");
const [statusFilter, setStatusFilter] = useState<QuotationStatusEnum | "all">(
"all"
);
const [showModal, setShowModal] = useState(false);
const [modalType, setModalType] = useState<"bulk" | "comparison">("bulk");
const [selectedQuotations, setSelectedQuotations] = useState<string[]>([]);
const navigate = useNavigate()
const [searchTerm, setSearchTerm] = useState('')
const [statusFilter, setStatusFilter] = useState<QuotationStatusEnum | 'all'>('all')
const [showModal, setShowModal] = useState(false)
const [modalType, setModalType] = useState<'bulk' | 'comparison'>('bulk')
const [selectedQuotations, setSelectedQuotations] = useState<string[]>([])
// Mock data - replace with actual API calls
const [quotations] = useState<MmQuotation[]>(mockQuotations);
const [quotations] = useState<MmQuotation[]>(mockQuotations)
const filteredQuotations = quotations.filter((quotation) => {
const matchesSearch =
quotation.quotationNumber
.toLowerCase()
.includes(searchTerm.toLowerCase()) ||
quotation.supplier?.name
.toLowerCase()
.includes(searchTerm.toLowerCase()) ||
quotation.requestTitle.toLowerCase().includes(searchTerm.toLowerCase());
const matchesStatus =
statusFilter === "all" || quotation.status === statusFilter;
return matchesSearch && matchesStatus;
});
quotation.quotationNumber.toLowerCase().includes(searchTerm.toLowerCase()) ||
quotation.supplier?.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
quotation.requestTitle.toLowerCase().includes(searchTerm.toLowerCase())
const matchesStatus = statusFilter === 'all' || quotation.status === statusFilter
return matchesSearch && matchesStatus
})
const isExpiringSoon = (date: Date) => {
const today = new Date();
const diffTime = date.getTime() - today.getTime();
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
return diffDays <= 7 && diffDays > 0;
};
const today = new Date()
const diffTime = date.getTime() - today.getTime()
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
return diffDays <= 7 && diffDays > 0
}
const isExpired = (date: Date) => {
const today = new Date();
return date < today;
};
const today = new Date()
return date < today
}
const handleAddQuotation = () => {
navigate("/admin/supplychain/quotations/new");
};
navigate('/admin/supplychain/quotations/new')
}
const handleBulkQuotation = () => {
if (selectedQuotations.length === 0) {
alert("Toplu işlem için en az 1 teklif seçmelisiniz.");
return;
alert('Toplu işlem için en az 1 teklif seçmelisiniz.')
return
}
setModalType("bulk");
setShowModal(true);
};
setModalType('bulk')
setShowModal(true)
}
const handleCompareQuotations = () => {
if (selectedQuotations.length < 2) {
alert("Karşılaştırma için en az 2 teklif seçmelisiniz.");
return;
alert('Karşılaştırma için en az 2 teklif seçmelisiniz.')
return
}
setModalType("comparison");
setShowModal(true);
};
setModalType('comparison')
setShowModal(true)
}
const handleEdit = (quotation: MmQuotation) => {
navigate(`/admin/supplychain/quotations/edit/${quotation.id}`);
};
navigate(`/admin/supplychain/quotations/edit/${quotation.id}`)
}
const handleView = (quotation: MmQuotation) => {
navigate(`/admin/supplychain/quotations/view/${quotation.id}`);
};
navigate(`/admin/supplychain/quotations/view/${quotation.id}`)
}
const handleSelectQuotation = (quotationId: string) => {
setSelectedQuotations((prev) =>
prev.includes(quotationId)
? prev.filter((id) => id !== quotationId)
: [...prev, quotationId]
);
};
prev.includes(quotationId) ? prev.filter((id) => id !== quotationId) : [...prev, quotationId],
)
}
const getTotalItems = (quotation: MmQuotation) => {
return quotation.items.reduce((sum, item) => sum + item.quantity, 0);
};
return quotation.items.reduce((sum, item) => sum + item.quantity, 0)
}
const getAverageLeadTime = (quotation: MmQuotation) => {
const totalLeadTime = quotation.items.reduce(
(sum, item) => sum + (item.leadTime || 0),
0
);
return Math.round(totalLeadTime / quotation.items.length);
};
const totalLeadTime = quotation.items.reduce((sum, item) => sum + (item.leadTime || 0), 0)
return Math.round(totalLeadTime / quotation.items.length)
}
return (
<div className="space-y-3 pt-2">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h2 className="text-2xl font-bold text-gray-900">Teklif Yönetimi</h2>
<p className="text-gray-600">
Tedarikçi tekliflerini yönetin ve karşılaştırın
</p>
<Container>
<div className="space-y-2">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h2 className="text-2xl font-bold text-gray-900">Teklif Yönetimi</h2>
<p className="text-gray-600">Tedarikçi tekliflerini yönetin ve karşılaştırın</p>
</div>
<div className="flex space-x-2">
<button
onClick={handleBulkQuotation}
className="bg-purple-600 text-white text-sm px-3 py-1.5 rounded-lg hover:bg-purple-700 flex items-center space-x-1.5"
>
<FaUsers className="w-4 h-4" />
<span>Toplu Teklif</span>
</button>
<button
onClick={handleCompareQuotations}
className="bg-green-600 text-white text-sm px-3 py-1.5 rounded-lg hover:bg-green-700 flex items-center space-x-1.5"
>
<FaArrowUp className="w-4 h-4" />
<span>Karşılaştır</span>
</button>
<button
onClick={handleAddQuotation}
className="bg-blue-600 text-white text-sm px-3 py-1.5 rounded-lg hover:bg-blue-700 flex items-center space-x-1.5"
>
<FaPlus className="w-4 h-4" />
<span>Yeni Teklif</span>
</button>
</div>
</div>
{/* Filters */}
<div className="flex space-x-2">
<button
onClick={handleBulkQuotation}
className="bg-purple-600 text-white text-sm px-3 py-1.5 rounded-lg hover:bg-purple-700 flex items-center space-x-1.5"
>
<FaUsers className="w-4 h-4" />
<span>Toplu Teklif</span>
</button>
<button
onClick={handleCompareQuotations}
className="bg-green-600 text-white text-sm px-3 py-1.5 rounded-lg hover:bg-green-700 flex items-center space-x-1.5"
>
<FaArrowUp className="w-4 h-4" />
<span>Karşılaştır</span>
</button>
<button
onClick={handleAddQuotation}
className="bg-blue-600 text-white text-sm px-3 py-1.5 rounded-lg hover:bg-blue-700 flex items-center space-x-1.5"
>
<FaPlus className="w-4 h-4" />
<span>Yeni Teklif</span>
</button>
<div className="flex-1 relative">
<FaSearch className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-5 h-5" />
<input
type="text"
placeholder="Teklif 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="relative">
<FaFilter className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-5 h-5" />
<select
value={statusFilter}
onChange={(e) => setStatusFilter(e.target.value as QuotationStatusEnum | 'all')}
className="pl-10 pr-4 py-1.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
>
<option value="all">Tüm Durumlar</option>
<option value={QuotationStatusEnum.Draft}>Taslak</option>
<option value={QuotationStatusEnum.Pending}>Beklemede</option>
<option value={QuotationStatusEnum.UnderReview}>İnceleme</option>
<option value={QuotationStatusEnum.Submitted}>Sunuldu</option>
<option value={QuotationStatusEnum.Approved}>Onaylandı</option>
<option value={QuotationStatusEnum.Rejected}>Reddedildi</option>
<option value={QuotationStatusEnum.Expired}>Süresi Doldu</option>
</select>
</div>
</div>
</div>
{/* Filters */}
<div className="flex space-x-2">
<div className="flex-1 relative">
<FaSearch className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-5 h-5" />
<input
type="text"
placeholder="Teklif 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="relative">
<FaFilter className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-5 h-5" />
<select
value={statusFilter}
onChange={(e) =>
setStatusFilter(e.target.value as QuotationStatusEnum | "all")
}
className="pl-10 pr-4 py-1.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
>
<option value="all">Tüm Durumlar</option>
<option value={QuotationStatusEnum.Draft}>Taslak</option>
<option value={QuotationStatusEnum.Pending}>Beklemede</option>
<option value={QuotationStatusEnum.UnderReview}>İnceleme</option>
<option value={QuotationStatusEnum.Submitted}>Sunuldu</option>
<option value={QuotationStatusEnum.Approved}>Onaylandı</option>
<option value={QuotationStatusEnum.Rejected}>Reddedildi</option>
<option value={QuotationStatusEnum.Expired}>Süresi Doldu</option>
</select>
</div>
</div>
{/* Quotation Cards */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
{filteredQuotations.map((quotation) => (
<div
key={quotation.id}
className="bg-white rounded-lg shadow-md border border-gray-200 p-4 hover:shadow-lg transition-shadow"
>
<div className="flex items-start justify-between mb-3">
<div className="flex-1">
<div className="flex items-center space-x-1.5 mb-1.5">
<input
type="checkbox"
checked={selectedQuotations.includes(quotation.id)}
onChange={() => handleSelectQuotation(quotation.id)}
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
/>
<h3 className="text-base font-semibold text-gray-900">
{quotation.quotationNumber}
</h3>
<span
className={`px-2 py-1 rounded-full text-xs font-medium flex items-center space-x-1 ${getQuotationStatusColor(
quotation.status
)}`}
{/* Quotation Cards */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
{filteredQuotations.map((quotation) => (
<div
key={quotation.id}
className="bg-white rounded-lg shadow-md border border-gray-200 p-4 hover:shadow-lg transition-shadow"
>
<div className="flex items-start justify-between mb-3">
<div className="flex-1">
<div className="flex items-center space-x-1.5 mb-1.5">
<input
type="checkbox"
checked={selectedQuotations.includes(quotation.id)}
onChange={() => handleSelectQuotation(quotation.id)}
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
/>
<h3 className="text-base font-semibold text-gray-900">
{quotation.quotationNumber}
</h3>
<span
className={`px-2 py-1 rounded-full text-xs font-medium flex items-center space-x-1 ${getQuotationStatusColor(
quotation.status,
)}`}
>
{getQuotationStatusIcon(quotation.status)}
<span>{getQuotationStatusText(quotation.status)}</span>
</span>
</div>
<p className="text-gray-600">{quotation.requestTitle}</p>
<p className="text-sm text-gray-500">
{quotation.supplier?.name} {getRequestTypeText(quotation.requestType)}
</p>
</div>
<div className="flex space-x-1">
<button
onClick={() => handleView(quotation)}
className="p-1.5 text-gray-400 hover:text-green-600 hover:bg-green-50 rounded-md transition-colors"
title="Görüntüle"
>
{getQuotationStatusIcon(quotation.status)}
<span>{getQuotationStatusText(quotation.status)}</span>
</span>
<FaEye className="w-4 h-4" />
</button>
<button
onClick={() => handleEdit(quotation)}
className="p-1.5 text-gray-400 hover:text-blue-600 hover:bg-blue-50 rounded-md transition-colors"
title="Düzenle"
>
<FaEdit className="w-4 h-4" />
</button>
<button className="p-1.5 text-gray-400 hover:text-green-600 hover:bg-green-50 rounded-md transition-colors">
<FaDownload className="w-4 h-4" />
</button>
<button className="p-1.5 text-gray-400 hover:text-red-600 hover:bg-red-50 rounded-md transition-colors">
<FaTrash className="w-4 h-4" />
</button>
</div>
<p className="text-gray-600">{quotation.requestTitle}</p>
<p className="text-sm text-gray-500">
{quotation.supplier?.name} {" "}
{getRequestTypeText(quotation.requestType)}
</p>
</div>
<div className="flex space-x-1">
<button
onClick={() => handleView(quotation)}
className="p-1.5 text-gray-400 hover:text-green-600 hover:bg-green-50 rounded-md transition-colors"
title="Görüntüle"
>
<FaEye className="w-4 h-4" />
</button>
<button
onClick={() => handleEdit(quotation)}
className="p-1.5 text-gray-400 hover:text-blue-600 hover:bg-blue-50 rounded-md transition-colors"
title="Düzenle"
>
<FaEdit className="w-4 h-4" />
</button>
<button className="p-1.5 text-gray-400 hover:text-green-600 hover:bg-green-50 rounded-md transition-colors">
<FaDownload className="w-4 h-4" />
</button>
<button className="p-1.5 text-gray-400 hover:text-red-600 hover:bg-red-50 rounded-md transition-colors">
<FaTrash className="w-4 h-4" />
</button>
</div>
</div>
<div className="grid grid-cols-2 gap-3 mb-3">
<div>
<span className="text-sm text-gray-500">Toplam Tutar</span>
<p className="font-semibold text-base text-gray-900">
{quotation.totalAmount.toLocaleString()}
</p>
</div>
<div>
<span className="text-sm text-gray-500">Geçerlilik</span>
<p
className={`font-medium ${
isExpired(quotation.validUntil)
? "text-red-600"
: isExpiringSoon(quotation.validUntil)
? "text-orange-600"
: "text-gray-900"
}`}
>
{quotation.validUntil.toLocaleDateString("tr-TR")}
{isExpiringSoon(quotation.validUntil) &&
!isExpired(quotation.validUntil) && (
<span className="text-xs text-orange-600 ml-1">
(Yakında)
</span>
<div className="grid grid-cols-2 gap-3 mb-3">
<div>
<span className="text-sm text-gray-500">Toplam Tutar</span>
<p className="font-semibold text-base text-gray-900">
{quotation.totalAmount.toLocaleString()}
</p>
</div>
<div>
<span className="text-sm text-gray-500">Geçerlilik</span>
<p
className={`font-medium ${
isExpired(quotation.validUntil)
? 'text-red-600'
: isExpiringSoon(quotation.validUntil)
? 'text-orange-600'
: 'text-gray-900'
}`}
>
{quotation.validUntil.toLocaleDateString('tr-TR')}
{isExpiringSoon(quotation.validUntil) && !isExpired(quotation.validUntil) && (
<span className="text-xs text-orange-600 ml-1">(Yakında)</span>
)}
{isExpired(quotation.validUntil) && (
<span className="text-xs text-red-600 ml-1">(Doldu)</span>
)}
</p>
{isExpired(quotation.validUntil) && (
<span className="text-xs text-red-600 ml-1">(Doldu)</span>
)}
</p>
</div>
</div>
</div>
<div className="grid grid-cols-3 gap-3 mb-3 text-sm">
<div className="text-center">
<div className="flex items-center justify-center mb-1">
<FaBox className="w-4 h-4 text-gray-400 mr-1" />
<span className="font-medium">{quotation.items.length}</span>
<div className="grid grid-cols-3 gap-3 mb-3 text-sm">
<div className="text-center">
<div className="flex items-center justify-center mb-1">
<FaBox className="w-4 h-4 text-gray-400 mr-1" />
<span className="font-medium">{quotation.items.length}</span>
</div>
<span className="text-gray-500">Kalem</span>
</div>
<span className="text-gray-500">Kalem</span>
</div>
<div className="text-center">
<div className="flex items-center justify-center mb-1">
<FaArrowUp className="w-4 h-4 text-gray-400 mr-1" />
<span className="font-medium">
{getTotalItems(quotation)}
</span>
<div className="text-center">
<div className="flex items-center justify-center mb-1">
<FaArrowUp className="w-4 h-4 text-gray-400 mr-1" />
<span className="font-medium">{getTotalItems(quotation)}</span>
</div>
<span className="text-gray-500">Adet</span>
</div>
<span className="text-gray-500">Adet</span>
</div>
<div className="text-center">
<div className="flex items-center justify-center mb-1">
<FaClock className="w-4 h-4 text-gray-400 mr-1" />
<span className="font-medium">
{getAverageLeadTime(quotation)}
</span>
<div className="text-center">
<div className="flex items-center justify-center mb-1">
<FaClock className="w-4 h-4 text-gray-400 mr-1" />
<span className="font-medium">{getAverageLeadTime(quotation)}</span>
</div>
<span className="text-gray-500">Gün</span>
</div>
<span className="text-gray-500">Gün</span>
</div>
</div>
{/* Items Details */}
<div className="mt-3 mb-3">
<h4 className="text-sm font-medium text-gray-900 mb-1.5">
Teklif Kalemleri
</h4>
<div className="space-y-1.5">
{quotation.items.map((item) => (
<div key={item.id} className="bg-gray-50 p-2 rounded-lg">
<div className="flex items-center justify-between mb-1.5">
<div className="flex-1">
<p className="font-medium text-gray-900">
{item.materialName}
</p>
<p className="text-sm text-gray-600">
Malzeme Kodu: {item.materialCode} | Miktar:{" "}
{item.quantity} {item.unit} | Birim Fiyat:
{item.unitPrice.toLocaleString()}
</p>
</div>
<div className="text-right">
<p className="font-semibold text-gray-900">
{item.totalPrice.toLocaleString()}
</p>
<div className="text-xs text-gray-500">
Teslimat: {item.leadTime} gün
{/* Items Details */}
<div className="mt-3 mb-3">
<h4 className="text-sm font-medium text-gray-900 mb-1.5">Teklif Kalemleri</h4>
<div className="space-y-1.5">
{quotation.items.map((item) => (
<div key={item.id} className="bg-gray-50 p-2 rounded-lg">
<div className="flex items-center justify-between mb-1.5">
<div className="flex-1">
<p className="font-medium text-gray-900">{item.materialName}</p>
<p className="text-sm text-gray-600">
Malzeme Kodu: {item.materialCode} | Miktar: {item.quantity} {item.unit}{' '}
| Birim Fiyat: {item.unitPrice.toLocaleString()}
</p>
</div>
<div className="text-right">
<p className="font-semibold text-gray-900">
{item.totalPrice.toLocaleString()}
</p>
<div className="text-xs text-gray-500">Teslimat: {item.leadTime} gün</div>
</div>
</div>
{item.description && (
<div className="text-xs text-gray-500 mb-1">{item.description}</div>
)}
{item.specifications && item.specifications.length > 0 && (
<div className="text-xs text-gray-500">
Özellikler: {item.specifications.join(', ')}
</div>
)}
</div>
{item.description && (
<div className="text-xs text-gray-500 mb-1">
{item.description}
</div>
)}
{item.specifications && item.specifications.length > 0 && (
<div className="text-xs text-gray-500">
Özellikler: {item.specifications.join(", ")}
</div>
)}
))}
</div>
</div>
<div className="border-t border-gray-100 pt-3">
<div className="flex items-center justify-between text-sm">
<div className="flex items-center space-x-2">
<FaCalendar className="w-4 h-4 text-gray-400" />
<span className="text-gray-600">
{quotation.quotationDate.toLocaleDateString('tr-TR')}
</span>
</div>
))}
<div className="flex items-center space-x-2">
<FaUsers className="w-4 h-4 text-gray-400" />
<span className="text-gray-600">{quotation.submittedBy}</span>
</div>
</div>
{quotation.notes && (
<div className="mt-2 p-2 bg-gray-50 rounded text-sm text-gray-600">
<strong>Not:</strong> {quotation.notes}
</div>
)}
{quotation.evaluationNotes && (
<div className="mt-2 p-2 bg-blue-50 rounded text-sm text-gray-600">
<strong>Değerlendirme:</strong> {quotation.evaluationNotes}
</div>
)}
</div>
</div>
))}
</div>
<div className="border-t border-gray-100 pt-3">
<div className="flex items-center justify-between text-sm">
<div className="flex items-center space-x-2">
<FaCalendar className="w-4 h-4 text-gray-400" />
<span className="text-gray-600">
{quotation.quotationDate.toLocaleDateString("tr-TR")}
</span>
</div>
<div className="flex items-center space-x-2">
<FaUsers className="w-4 h-4 text-gray-400" />
<span className="text-gray-600">{quotation.submittedBy}</span>
</div>
{filteredQuotations.length === 0 && (
<div className="text-center py-8">
<FaFileAlt className="w-12 h-12 text-gray-400 mx-auto mb-4" />
<h3 className="text-lg font-medium text-gray-900 mb-2">Teklif bulunamadı</h3>
<p className="text-gray-500 mb-3">
Arama kriterlerinizi değiştirin veya yeni bir teklif ekleyin.
</p>
<button
onClick={handleAddQuotation}
className="bg-blue-600 text-white px-3 py-1.5 rounded-lg hover:bg-blue-700"
>
Yeni Teklif Ekle
</button>
</div>
)}
{/* Bulk Actions */}
{selectedQuotations.length > 0 && (
<div className="fixed bottom-6 left-1/2 transform -translate-x-1/2 bg-white rounded-lg shadow-lg border border-gray-200 p-3">
<div className="flex items-center space-x-3">
<span className="text-sm text-gray-600">
{selectedQuotations.length} teklif seçildi
</span>
<div className="flex space-x-2">
<button
onClick={handleCompareQuotations}
className="bg-green-600 text-white px-2.5 py-1.5 rounded text-sm hover:bg-green-700"
>
Karşılaştır
</button>
<button className="bg-blue-600 text-white px-2.5 py-1.5 rounded text-sm hover:bg-blue-700">
Toplu İşlem
</button>
<button
onClick={() => setSelectedQuotations([])}
className="bg-gray-600 text-white px-2.5 py-1.5 rounded text-sm hover:bg-gray-700"
>
Temizle
</button>
</div>
{quotation.notes && (
<div className="mt-2 p-2 bg-gray-50 rounded text-sm text-gray-600">
<strong>Not:</strong> {quotation.notes}
</div>
)}
{quotation.evaluationNotes && (
<div className="mt-2 p-2 bg-blue-50 rounded text-sm text-gray-600">
<strong>Değerlendirme:</strong> {quotation.evaluationNotes}
</div>
)}
</div>
</div>
))}
)}
</div>
{filteredQuotations.length === 0 && (
<div className="text-center py-8">
<FaFileAlt className="w-12 h-12 text-gray-400 mx-auto mb-4" />
<h3 className="text-lg font-medium text-gray-900 mb-2">
Teklif bulunamadı
</h3>
<p className="text-gray-500 mb-3">
Arama kriterlerinizi değiştirin veya yeni bir teklif ekleyin.
</p>
<button
onClick={handleAddQuotation}
className="bg-blue-600 text-white px-3 py-1.5 rounded-lg hover:bg-blue-700"
>
Yeni Teklif Ekle
</button>
</div>
)}
{/* Bulk Actions */}
{selectedQuotations.length > 0 && (
<div className="fixed bottom-6 left-1/2 transform -translate-x-1/2 bg-white rounded-lg shadow-lg border border-gray-200 p-3">
<div className="flex items-center space-x-3">
<span className="text-sm text-gray-600">
{selectedQuotations.length} teklif seçildi
</span>
<div className="flex space-x-2">
<button
onClick={handleCompareQuotations}
className="bg-green-600 text-white px-2.5 py-1.5 rounded text-sm hover:bg-green-700"
>
Karşılaştır
</button>
<button className="bg-blue-600 text-white px-2.5 py-1.5 rounded text-sm hover:bg-blue-700">
Toplu İşlem
</button>
<button
onClick={() => setSelectedQuotations([])}
className="bg-gray-600 text-white px-2.5 py-1.5 rounded text-sm hover:bg-gray-700"
>
Temizle
</button>
</div>
</div>
</div>
)}
{/* Modal for Bulk/Comparison operations */}
{showModal && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg p-4 w-full max-w-6xl mx-4 max-h-[90vh] overflow-y-auto">
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold">
{modalType === "bulk" && "Toplu Teklif Talebi"}
{modalType === "comparison" && "Teklif Karşılaştırması"}
{modalType === 'bulk' && 'Toplu Teklif Talebi'}
{modalType === 'comparison' && 'Teklif Karşılaştırması'}
</h3>
<button
onClick={() => setShowModal(false)}
@ -437,13 +405,11 @@ const QuotationManagement: React.FC = () => {
</button>
</div>
{modalType === "comparison" && (
{modalType === 'comparison' && (
<div className="space-y-4">
{/* Comparison Summary */}
<div className="bg-gray-50 p-3 rounded-lg">
<h4 className="font-medium text-gray-900 mb-2">
Karşılaştırma Özeti
</h4>
<h4 className="font-medium text-gray-900 mb-2">Karşılaştırma Özeti</h4>
<div className="grid grid-cols-1 md:grid-cols-4 gap-3 text-sm">
<div>
<span className="text-gray-500">Toplam Teklif:</span>
@ -456,7 +422,7 @@ const QuotationManagement: React.FC = () => {
{Math.min(
...quotations
.filter((q) => selectedQuotations.includes(q.id))
.map((q) => q.totalAmount)
.map((q) => q.totalAmount),
).toLocaleString()}
</p>
</div>
@ -467,7 +433,7 @@ const QuotationManagement: React.FC = () => {
{Math.max(
...quotations
.filter((q) => selectedQuotations.includes(q.id))
.map((q) => q.totalAmount)
.map((q) => q.totalAmount),
).toLocaleString()}
</p>
</div>
@ -478,21 +444,17 @@ const QuotationManagement: React.FC = () => {
((Math.max(
...quotations
.filter((q) => selectedQuotations.includes(q.id))
.map((q) => q.totalAmount)
.map((q) => q.totalAmount),
) -
Math.min(
...quotations
.filter((q) =>
selectedQuotations.includes(q.id)
)
.map((q) => q.totalAmount)
.filter((q) => selectedQuotations.includes(q.id))
.map((q) => q.totalAmount),
)) /
Math.min(
...quotations
.filter((q) =>
selectedQuotations.includes(q.id)
)
.map((q) => q.totalAmount)
.filter((q) => selectedQuotations.includes(q.id))
.map((q) => q.totalAmount),
)) *
100
).toFixed(1)}
@ -524,52 +486,38 @@ const QuotationManagement: React.FC = () => {
</thead>
<tbody className="bg-white divide-y divide-gray-200">
<tr>
<td className="px-3 py-2 font-medium text-gray-900">
Teklif No
</td>
<td className="px-3 py-2 font-medium text-gray-900">Teklif No</td>
{quotations
.filter((q) => selectedQuotations.includes(q.id))
.map((quotation) => (
<td
key={quotation.id}
className="px-3 py-2 text-center text-gray-600"
>
<td key={quotation.id} className="px-3 py-2 text-center text-gray-600">
{quotation.quotationNumber}
</td>
))}
</tr>
<tr className="bg-gray-50">
<td className="px-3 py-2 font-medium text-gray-900">
Toplam Tutar
</td>
<td className="px-3 py-2 font-medium text-gray-900">Toplam Tutar</td>
{quotations
.filter((q) => selectedQuotations.includes(q.id))
.map((quotation) => (
<td
key={quotation.id}
className="px-3 py-2 text-center"
>
<td key={quotation.id} className="px-3 py-2 text-center">
<span
className={`font-semibold ${
quotation.totalAmount ===
Math.min(
...quotations
.filter((q) =>
selectedQuotations.includes(q.id)
)
.map((q) => q.totalAmount)
.filter((q) => selectedQuotations.includes(q.id))
.map((q) => q.totalAmount),
)
? "text-green-600"
? 'text-green-600'
: quotation.totalAmount ===
Math.max(
...quotations
.filter((q) =>
selectedQuotations.includes(q.id)
)
.map((q) => q.totalAmount)
)
? "text-red-600"
: "text-gray-900"
Math.max(
...quotations
.filter((q) => selectedQuotations.includes(q.id))
.map((q) => q.totalAmount),
)
? 'text-red-600'
: 'text-gray-900'
}`}
>
{quotation.totalAmount.toLocaleString()}
@ -578,34 +526,24 @@ const QuotationManagement: React.FC = () => {
))}
</tr>
<tr>
<td className="px-3 py-2 font-medium text-gray-900">
Geçerlilik Tarihi
</td>
<td className="px-3 py-2 font-medium text-gray-900">Geçerlilik Tarihi</td>
{quotations
.filter((q) => selectedQuotations.includes(q.id))
.map((quotation) => (
<td
key={quotation.id}
className="px-3 py-2 text-center text-gray-600"
>
{quotation.validUntil.toLocaleDateString("tr-TR")}
<td key={quotation.id} className="px-3 py-2 text-center text-gray-600">
{quotation.validUntil.toLocaleDateString('tr-TR')}
</td>
))}
</tr>
<tr className="bg-gray-50">
<td className="px-3 py-2 font-medium text-gray-900">
Durum
</td>
<td className="px-3 py-2 font-medium text-gray-900">Durum</td>
{quotations
.filter((q) => selectedQuotations.includes(q.id))
.map((quotation) => (
<td
key={quotation.id}
className="px-3 py-2 text-center"
>
<td key={quotation.id} className="px-3 py-2 text-center">
<span
className={`px-2 py-1 rounded-full text-xs font-medium ${getQuotationStatusColor(
quotation.status
quotation.status,
)}`}
>
{getQuotationStatusText(quotation.status)}
@ -614,16 +552,11 @@ const QuotationManagement: React.FC = () => {
))}
</tr>
<tr>
<td className="px-3 py-2 font-medium text-gray-900">
Kalem Sayısı
</td>
<td className="px-3 py-2 font-medium text-gray-900">Kalem Sayısı</td>
{quotations
.filter((q) => selectedQuotations.includes(q.id))
.map((quotation) => (
<td
key={quotation.id}
className="px-3 py-2 text-center text-gray-600"
>
<td key={quotation.id} className="px-3 py-2 text-center text-gray-600">
{quotation.items.length}
</td>
))}
@ -635,10 +568,7 @@ const QuotationManagement: React.FC = () => {
{quotations
.filter((q) => selectedQuotations.includes(q.id))
.map((quotation) => (
<td
key={quotation.id}
className="px-3 py-2 text-center text-gray-600"
>
<td key={quotation.id} className="px-3 py-2 text-center text-gray-600">
{getAverageLeadTime(quotation)} gün
</td>
))}
@ -671,7 +601,7 @@ const QuotationManagement: React.FC = () => {
</div>
)}
{modalType === "bulk" && (
{modalType === 'bulk' && (
<div className="space-y-6">
{/* Bulk Request Form */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
@ -711,9 +641,7 @@ const QuotationManagement: React.FC = () => {
>
{quotation.supplier?.name}
<button
onClick={() =>
handleSelectQuotation(quotation.id)
}
onClick={() => handleSelectQuotation(quotation.id)}
className="ml-2 text-blue-600 hover:text-blue-800"
>
<FaTimes className="w-3 h-3" />
@ -785,8 +713,8 @@ const QuotationManagement: React.FC = () => {
</div>
</div>
)}
</div>
);
};
</Container>
)
}
export default QuotationManagement;
export default QuotationManagement

View file

@ -1,15 +1,15 @@
import React, { useState, useEffect } from "react";
import { FaTimes, FaSave, FaCreditCard, FaStar } from "react-icons/fa";
import { SupplierCardTypeEnum, SupplierTypeEnum } from "../../../types/mm";
import { mockBusinessPartyNew } from "../../../mocks/mockBusinessParties";
import { BusinessParty, PartyType, PaymentTerms } from "../../../types/common";
import React, { useState, useEffect } from 'react'
import { FaTimes, FaSave, FaCreditCard, FaStar } from 'react-icons/fa'
import { SupplierCardTypeEnum, SupplierTypeEnum } from '../../../types/mm'
import { mockBusinessPartyNew } from '../../../mocks/mockBusinessParties'
import { BusinessParty, PartyType, PaymentTerms } from '../../../types/common'
interface SupplierCardModalProps {
isOpen: boolean;
onClose: () => void;
onSave: (supplierCard: BusinessParty) => void;
supplierCard?: BusinessParty | null;
mode: "create" | "view" | "edit";
isOpen: boolean
onClose: () => void
onSave: (supplierCard: BusinessParty) => void
supplierCard?: BusinessParty | null
mode: 'create' | 'view' | 'edit'
}
const SupplierCardModal: React.FC<SupplierCardModalProps> = ({
@ -19,50 +19,45 @@ const SupplierCardModal: React.FC<SupplierCardModalProps> = ({
supplierCard,
mode,
}) => {
const [formData, setFormData] =
useState<Partial<BusinessParty>>(mockBusinessPartyNew);
const [formData, setFormData] = useState<Partial<BusinessParty>>(mockBusinessPartyNew)
useEffect(() => {
if (mode === "create") {
setFormData(mockBusinessPartyNew);
if (mode === 'create') {
setFormData(mockBusinessPartyNew)
} else if (supplierCard) {
setFormData(supplierCard);
setFormData(supplierCard)
}
}, [supplierCard, mode]);
}, [supplierCard, mode])
const handleInputChange = (
e: React.ChangeEvent<
HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement
>
e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>,
) => {
const { name, value, type } = e.target;
const { name, value, type } = e.target
setFormData((prev) => ({
...prev,
[name]:
type === "number"
type === 'number'
? parseFloat(value) || 0
: type === "checkbox"
? (e.target as HTMLInputElement).checked
: value,
}));
};
: type === 'checkbox'
? (e.target as HTMLInputElement).checked
: value,
}))
}
const handleSpecialConditionsChange = (value: string) => {
const conditions = value
.split("\n")
.filter((condition) => condition.trim() !== "");
const conditions = value.split('\n').filter((condition) => condition.trim() !== '')
setFormData((prev) => ({
...prev,
specialConditions: conditions,
}));
};
}))
}
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (mode !== "view") {
e.preventDefault()
if (mode !== 'view') {
const newSupplierCard: BusinessParty = {
id: supplierCard?.id || `SC-${Date.now()}`,
code: formData.code || "",
code: formData.code || '',
cardNumber:
formData.cardNumber ||
`SC-${new Date().getFullYear()}-${Date.now().toString().slice(-3)}`,
@ -88,27 +83,27 @@ const SupplierCardModal: React.FC<SupplierCardModalProps> = ({
creationTime: supplierCard?.creationTime || new Date(),
lastModificationTime: new Date(),
supplierType: SupplierTypeEnum.Material,
name: "",
currency: "",
name: '',
currency: '',
certifications: [],
bankAccounts: [],
contacts: [],
partyType: PartyType.Supplier,
};
onSave(newSupplierCard);
}
onSave(newSupplierCard)
}
onClose();
};
onClose()
}
if (!isOpen) return null;
if (!isOpen) return null
const isReadOnly = mode === "view";
const isReadOnly = mode === 'view'
const modalTitle =
mode === "create"
? "Yeni Tedarikçi Kartı"
: mode === "edit"
? "Tedarikçi Kartını Düzenle"
: "Tedarikçi Kartı Detayları";
mode === 'create'
? 'Yeni Tedarikçi Kartı'
: mode === 'edit'
? 'Tedarikçi Kartını Düzenle'
: 'Tedarikçi Kartı Detayları'
return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
@ -118,10 +113,7 @@ const SupplierCardModal: React.FC<SupplierCardModalProps> = ({
<FaCreditCard className="mr-2 text-blue-600" />
{modalTitle}
</h3>
<button
onClick={onClose}
className="p-1 text-gray-400 hover:text-gray-600"
>
<button onClick={onClose} className="p-1 text-gray-400 hover:text-gray-600">
<FaTimes />
</button>
</div>
@ -130,18 +122,14 @@ const SupplierCardModal: React.FC<SupplierCardModalProps> = ({
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{/* Temel Bilgiler */}
<div className="space-y-3">
<h4 className="text-sm font-medium text-gray-900 border-b pb-1.5">
Temel Bilgiler
</h4>
<h4 className="text-sm font-medium text-gray-900 border-b pb-1.5">Temel Bilgiler</h4>
<div>
<label className="block text-sm font-medium text-gray-700">
Tedarikçi
</label>
<label className="block text-sm font-medium text-gray-700">Tedarikçi</label>
<input
type="text"
name="customerCode"
value={formData.code || ""}
value={formData.code || ''}
onChange={handleInputChange}
readOnly={isReadOnly}
className="mt-1 block w-full border border-gray-300 rounded-md px-2.5 py-1.5 text-sm focus:outline-none focus:ring-1 focus:ring-blue-500"
@ -150,13 +138,11 @@ const SupplierCardModal: React.FC<SupplierCardModalProps> = ({
</div>
<div>
<label className="block text-sm font-medium text-gray-700">
Kart Numarası
</label>
<label className="block text-sm font-medium text-gray-700">Kart Numarası</label>
<input
type="text"
name="cardNumber"
value={formData.cardNumber || ""}
value={formData.cardNumber || ''}
onChange={handleInputChange}
readOnly={isReadOnly}
className="mt-1 block w-full border border-gray-300 rounded-md px-2.5 py-1.5 text-sm focus:outline-none focus:ring-1 focus:ring-blue-500"
@ -164,9 +150,7 @@ const SupplierCardModal: React.FC<SupplierCardModalProps> = ({
</div>
<div>
<label className="block text-sm font-medium text-gray-700">
Kart Tipi
</label>
<label className="block text-sm font-medium text-gray-700">Kart Tipi</label>
<select
name="cardType"
value={formData.cardType || SupplierCardTypeEnum.Standard}
@ -174,16 +158,10 @@ const SupplierCardModal: React.FC<SupplierCardModalProps> = ({
disabled={isReadOnly}
className="mt-1 block w-full border border-gray-300 rounded-md px-2.5 py-1.5 text-sm focus:outline-none focus:ring-1 focus:ring-blue-500"
>
<option value={SupplierCardTypeEnum.Standard}>
Standart
</option>
<option value={SupplierCardTypeEnum.Standard}>Standart</option>
<option value={SupplierCardTypeEnum.Premium}>Premium</option>
<option value={SupplierCardTypeEnum.Strategic}>
Stratejik
</option>
<option value={SupplierCardTypeEnum.Preferred}>
Tercihli
</option>
<option value={SupplierCardTypeEnum.Strategic}>Stratejik</option>
<option value={SupplierCardTypeEnum.Preferred}>Tercihli</option>
</select>
</div>
@ -196,8 +174,8 @@ const SupplierCardModal: React.FC<SupplierCardModalProps> = ({
name="validFrom"
value={
formData.validFrom
? new Date(formData.validFrom).toISOString().split("T")[0]
: ""
? new Date(formData.validFrom).toISOString().split('T')[0]
: ''
}
onChange={handleInputChange}
readOnly={isReadOnly}
@ -206,16 +184,12 @@ const SupplierCardModal: React.FC<SupplierCardModalProps> = ({
</div>
<div>
<label className="block text-sm font-medium text-gray-700">
Geçerlilik Bitişi
</label>
<label className="block text-sm font-medium text-gray-700">Geçerlilik Bitişi</label>
<input
type="date"
name="validTo"
value={
formData.validTo
? new Date(formData.validTo).toISOString().split("T")[0]
: ""
formData.validTo ? new Date(formData.validTo).toISOString().split('T')[0] : ''
}
onChange={handleInputChange}
readOnly={isReadOnly}
@ -226,14 +200,10 @@ const SupplierCardModal: React.FC<SupplierCardModalProps> = ({
{/* Mali Bilgiler */}
<div className="space-y-3">
<h4 className="text-sm font-medium text-gray-900 border-b pb-1.5">
Mali Bilgiler
</h4>
<h4 className="text-sm font-medium text-gray-900 border-b pb-1.5">Mali Bilgiler</h4>
<div>
<label className="block text-sm font-medium text-gray-700">
Kredi Limiti (TL)
</label>
<label className="block text-sm font-medium text-gray-700">Kredi Limiti (TL)</label>
<input
type="number"
name="creditLimit"
@ -259,9 +229,7 @@ const SupplierCardModal: React.FC<SupplierCardModalProps> = ({
</div>
<div>
<label className="block text-sm font-medium text-gray-700">
Ödeme Koşulları
</label>
<label className="block text-sm font-medium text-gray-700">Ödeme Koşulları</label>
<select
name="paymentTerms"
value={formData.paymentTerms || PaymentTerms.Net30}
@ -281,9 +249,7 @@ const SupplierCardModal: React.FC<SupplierCardModalProps> = ({
</div>
<div>
<label className="block text-sm font-medium text-gray-700">
İndirim Oranı (%)
</label>
<label className="block text-sm font-medium text-gray-700">İndirim Oranı (%)</label>
<input
type="number"
name="discountRate"
@ -307,9 +273,7 @@ const SupplierCardModal: React.FC<SupplierCardModalProps> = ({
disabled={isReadOnly}
className="mr-2"
/>
<span className="text-sm font-medium text-gray-700">
Aktif
</span>
<span className="text-sm font-medium text-gray-700">Aktif</span>
</label>
</div>
</div>
@ -322,7 +286,7 @@ const SupplierCardModal: React.FC<SupplierCardModalProps> = ({
</h4>
<textarea
name="specialConditions"
value={formData.specialConditions?.join("\n") || ""}
value={formData.specialConditions?.join('\n') || ''}
onChange={(e) => handleSpecialConditionsChange(e.target.value)}
readOnly={isReadOnly}
rows={3}
@ -332,7 +296,7 @@ const SupplierCardModal: React.FC<SupplierCardModalProps> = ({
</div>
{/* Performans Metrikleri - Sadece görüntüleme modu */}
{mode === "view" && formData.performanceMetrics && (
{mode === 'view' && formData.performanceMetrics && (
<div className="mt-4">
<h4 className="text-sm font-medium text-gray-900 border-b pb-1.5 mb-3 flex items-center">
<FaStar className="mr-2 text-yellow-500" />
@ -343,17 +307,13 @@ const SupplierCardModal: React.FC<SupplierCardModalProps> = ({
<div className="text-2xl font-bold text-blue-600">
{formData.performanceMetrics.deliveryPerformance}%
</div>
<div className="text-sm text-gray-600">
Teslimat Performansı
</div>
<div className="text-sm text-gray-600">Teslimat Performansı</div>
</div>
<div className="text-center">
<div className="text-2xl font-bold text-green-600">
{formData.performanceMetrics.qualityRating}%
</div>
<div className="text-sm text-gray-600">
Kalite Değerlendirmesi
</div>
<div className="text-sm text-gray-600">Kalite Değerlendirmesi</div>
</div>
<div className="text-center">
<div className="text-2xl font-bold text-purple-600">
@ -390,9 +350,9 @@ const SupplierCardModal: React.FC<SupplierCardModalProps> = ({
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"}
{mode === 'view' ? 'Kapat' : 'İptal'}
</button>
{mode !== "view" && (
{mode !== 'view' && (
<button
type="submit"
className="px-3 py-1.5 bg-blue-600 text-white rounded-md text-sm hover:bg-blue-700 flex items-center"
@ -405,7 +365,7 @@ const SupplierCardModal: React.FC<SupplierCardModalProps> = ({
</form>
</div>
</div>
);
};
)
}
export default SupplierCardModal;
export default SupplierCardModal

View file

@ -1,4 +1,4 @@
import React, { useState } from "react";
import React, { useState } from 'react'
import {
FaPlus,
FaSearch,
@ -13,533 +13,502 @@ import {
FaTh,
FaList,
FaBuilding,
} from "react-icons/fa";
import SupplierCardModal from "./SupplierCardModal";
import { mockBusinessParties } from "../../../mocks/mockBusinessParties";
import { BusinessParty, PartyType } from "../../../types/common";
} from 'react-icons/fa'
import SupplierCardModal from './SupplierCardModal'
import { mockBusinessParties } from '../../../mocks/mockBusinessParties'
import { BusinessParty, PartyType } from '../../../types/common'
import {
getSupplierCardTypeColor,
getSupplierCardTypeText,
getPaymentTermsText,
} from "../../../utils/erp";
} from '../../../utils/erp'
import { Container } from '@/components/shared'
const SupplierCards: React.FC = () => {
const [searchTerm, setSearchTerm] = useState("");
const [showModal, setShowModal] = useState(false);
const [editingCard, setEditingCard] = useState<BusinessParty | null>(null);
const [modalMode, setModalMode] = useState<"create" | "view" | "edit">(
"create"
);
const [viewMode, setViewMode] = useState<"cards" | "list">("cards");
const [searchTerm, setSearchTerm] = useState('')
const [showModal, setShowModal] = useState(false)
const [editingCard, setEditingCard] = useState<BusinessParty | null>(null)
const [modalMode, setModalMode] = useState<'create' | 'view' | 'edit'>('create')
const [viewMode, setViewMode] = useState<'cards' | 'list'>('cards')
// Mock data - replace with actual API calls
const [supplierCards] = useState<BusinessParty[]>(mockBusinessParties);
const [supplierCards] = useState<BusinessParty[]>(mockBusinessParties)
const filteredCards = supplierCards
.filter((card) => card.partyType === PartyType.Supplier)
.filter(
(card) =>
card.cardNumber!.toLowerCase().includes(searchTerm.toLowerCase()) ||
card.id.toLowerCase().includes(searchTerm.toLowerCase())
);
card.id.toLowerCase().includes(searchTerm.toLowerCase()),
)
const getCreditUtilization = (card: BusinessParty) => {
return (card.currentBalance! / card.creditLimit) * 100;
};
return (card.currentBalance! / card.creditLimit) * 100
}
const getPerformanceColor = (score: number) => {
if (score >= 90) return "text-green-600";
if (score >= 80) return "text-yellow-600";
return "text-red-600";
};
if (score >= 90) return 'text-green-600'
if (score >= 80) return 'text-yellow-600'
return 'text-red-600'
}
const handleEdit = (card: BusinessParty) => {
setEditingCard(card);
setModalMode("edit");
setShowModal(true);
};
setEditingCard(card)
setModalMode('edit')
setShowModal(true)
}
const handleView = (card: BusinessParty) => {
setEditingCard(card);
setModalMode("view");
setShowModal(true);
};
setEditingCard(card)
setModalMode('view')
setShowModal(true)
}
const handleAddNew = () => {
setEditingCard(null);
setModalMode("create");
setShowModal(true);
};
setEditingCard(null)
setModalMode('create')
setShowModal(true)
}
const handleSaveCard = (card: BusinessParty) => {
// TODO: Implement save logic
console.log("Saving supplier card:", card);
setShowModal(false);
setEditingCard(null);
};
console.log('Saving supplier card:', card)
setShowModal(false)
setEditingCard(null)
}
const handleCloseModal = () => {
setShowModal(false);
setEditingCard(null);
};
setShowModal(false)
setEditingCard(null)
}
const isCardExpiring = (card: BusinessParty) => {
if (!card.validTo) return false;
if (!card.validTo) return false
const daysUntilExpiry = Math.ceil(
(card.validTo.getTime() - new Date().getTime()) / (1000 * 60 * 60 * 24)
);
return daysUntilExpiry <= 30;
};
(card.validTo.getTime() - new Date().getTime()) / (1000 * 60 * 60 * 24),
)
return daysUntilExpiry <= 30
}
return (
<div className="space-y-3 pt-2">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h2 className="text-2xl font-bold text-gray-900">Tedarikçiler</h2>
<p className="text-gray-600">
Tedarikçi kredi limitleri, özel şartlar ve performans metriklerini
yönetin
</p>
</div>
<div className="flex items-center space-x-2">
<div className="flex bg-gray-100 rounded-lg">
<Container>
<div className="space-y-2">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h2 className="text-2xl font-bold text-gray-900">Tedarikçiler</h2>
<p className="text-gray-600">
Tedarikçi kredi limitleri, özel şartlar ve performans metriklerini yönetin
</p>
</div>
<div className="flex items-center space-x-2">
<div className="flex bg-gray-100 rounded-lg">
<button
onClick={() => setViewMode('cards')}
className={`px-3 py-2 rounded-md text-sm font-medium transition-colors ${
viewMode === 'cards'
? 'bg-white text-gray-900 shadow-sm'
: 'text-gray-500 hover:text-gray-700'
}`}
>
<FaTh className="w-4 h-4 inline" />
</button>
<button
onClick={() => setViewMode('list')}
className={`px-3 py-2 rounded-md text-sm font-medium transition-colors ${
viewMode === 'list'
? 'bg-white text-gray-900 shadow-sm'
: 'text-gray-500 hover:text-gray-700'
}`}
>
<FaList className="w-4 h-4 inline" />
</button>
</div>
{/* View Toggle */}
<button
onClick={() => setViewMode("cards")}
className={`px-3 py-2 rounded-md text-sm font-medium transition-colors ${
viewMode === "cards"
? "bg-white text-gray-900 shadow-sm"
: "text-gray-500 hover:text-gray-700"
}`}
onClick={handleAddNew}
className="bg-blue-600 text-white px-3 py-1.5 text-sm rounded-lg hover:bg-blue-700 flex items-center space-x-1.5"
>
<FaTh className="w-4 h-4 inline" />
</button>
<button
onClick={() => setViewMode("list")}
className={`px-3 py-2 rounded-md text-sm font-medium transition-colors ${
viewMode === "list"
? "bg-white text-gray-900 shadow-sm"
: "text-gray-500 hover:text-gray-700"
}`}
>
<FaList className="w-4 h-4 inline" />
<FaPlus className="w-4 h-4" />
<span>Yeni Tedarikçi Kartı</span>
</button>
</div>
{/* View Toggle */}
<button
onClick={handleAddNew}
className="bg-blue-600 text-white px-3 py-1.5 text-sm rounded-lg hover:bg-blue-700 flex items-center space-x-1.5"
>
<FaPlus className="w-4 h-4" />
<span>Yeni Tedarikçi Kartı</span>
</button>
</div>
</div>
{/* Search Bar */}
<div className="relative">
<FaSearch className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-5 h-5" />
<input
type="text"
placeholder="Tedarikçi kartı 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>
{/* Content Area */}
{viewMode === "cards" ? (
/* Cards Grid */
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
{filteredCards.map((card) => (
<div
key={card.id}
className={`bg-white rounded-lg shadow-md border-l-4 p-4 hover:shadow-lg transition-shadow ${
getSupplierCardTypeColor(card.cardType!).split(" ").slice(-1)[0]
}`}
>
<div className="flex items-start justify-between mb-3">
<div className="flex-1">
<div className="flex items-center space-x-2 mb-1.5">
<FaCreditCard className="w-6 h-6 text-gray-600" />
<h3 className="text-lg font-semibold text-gray-900">
{card.cardNumber}
</h3>
<span
className={`px-3 py-1 rounded-full text-sm font-medium border ${getSupplierCardTypeColor(
card.cardType!
)}`}
>
{getSupplierCardTypeText(card.cardType!)}
</span>
</div>
<p className="text-sm text-gray-600 mb-1">
Tedarikçi: {card.code}
</p>
<div className="flex items-center space-x-4 text-sm text-gray-500">
<span>Ödeme: {getPaymentTermsText(card.paymentTerms)}</span>
{card.discountRate && (
<span className="text-green-600">
%{card.discountRate} indirim
</span>
)}
</div>
</div>
<div className="flex space-x-1">
<button
onClick={() => handleView(card)}
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={() => handleEdit(card)}
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
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>
{/* Warnings */}
{(isCardExpiring(card) || !card.isActive) && (
<div className="mb-3 space-y-2">
{!card.isActive && (
<div className="flex items-center space-x-2 text-red-600 bg-red-50 px-2.5 py-1.5 rounded-lg">
<FaExclamationCircle className="w-4 h-4" />
<span className="text-sm">Kart pasif durumda</span>
</div>
)}
{isCardExpiring(card) && (
<div className="flex items-center space-x-2 text-orange-600 bg-orange-50 px-2.5 py-1.5 rounded-lg">
<FaCalendar className="w-4 h-4" />
<span className="text-sm">
Kartın süresi 30 gün içinde dolacak
</span>
</div>
)}
</div>
)}
{/* Credit Information */}
<div className="mb-3">
<div className="flex items-center justify-between mb-1.5">
<span className="text-sm text-gray-600 flex items-center">
<FaDollarSign className="w-4 h-4 mr-1" />
Kredi Limiti
</span>
<span className="font-medium">
{card.creditLimit.toLocaleString()}
</span>
</div>
<div className="flex items-center justify-between mb-1.5">
<span className="text-sm text-gray-600">Kullanılan</span>
<span className="font-medium">
{card.currentBalance!.toLocaleString()}
</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-2">
<div
className={`h-2 rounded-full ${
getCreditUtilization(card) > 80
? "bg-red-500"
: getCreditUtilization(card) > 60
? "bg-yellow-500"
: "bg-green-500"
}`}
style={{ width: `${getCreditUtilization(card)}%` }}
></div>
</div>
<div className="flex justify-between text-xs text-gray-500 mt-1">
<span>
%{getCreditUtilization(card).toFixed(1)} kullanıldı
</span>
<span>
{(card.creditLimit - card.currentBalance!).toLocaleString()}{" "}
müsait
</span>
</div>
</div>
{/* Performance Metrics */}
<div className="mb-3">
<div className="flex items-center justify-between mb-1.5">
<span className="text-sm text-gray-600 flex items-center">
<FaStar className="w-4 h-4 mr-1" />
Genel Performans
</span>
<span
className={`font-bold text-lg ${getPerformanceColor(
card.performanceMetrics!.overallScore
)}`}
>
{card.performanceMetrics!.overallScore}/100
</span>
</div>
<div className="grid grid-cols-2 gap-2 text-xs">
<div className="flex justify-between">
<span className="text-gray-600">Teslimat:</span>
<span
className={getPerformanceColor(
card.performanceMetrics!.deliveryPerformance
)}
>
{card.performanceMetrics!.deliveryPerformance}%
</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">Kalite:</span>
<span
className={getPerformanceColor(
card.performanceMetrics!.qualityRating
)}
>
{card.performanceMetrics!.qualityRating}%
</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">Fiyat:</span>
<span
className={getPerformanceColor(
card.performanceMetrics!.priceCompetitiveness
)}
>
{card.performanceMetrics!.priceCompetitiveness}%
</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">Uyum:</span>
<span
className={getPerformanceColor(
card.performanceMetrics!.complianceRating
)}
>
{card.performanceMetrics!.complianceRating}%
</span>
</div>
</div>
</div>
{/* Special Conditions */}
{card.specialConditions && card.specialConditions.length > 0 && (
<div className="mb-3">
<h4 className="text-sm font-medium text-gray-900 mb-2">
Özel Şartlar:
</h4>
<div className="flex flex-wrap gap-1">
{card.specialConditions.map((condition, index) => (
<span
key={index}
className="px-2 py-1 bg-blue-50 text-blue-700 text-xs rounded-full"
>
{condition}
</span>
))}
</div>
</div>
)}
{/* Footer */}
<div className="flex items-center justify-between pt-3 border-t border-gray-100">
<div className="flex items-center space-x-2">
<span
className={`px-2 py-1 rounded-full text-xs font-medium ${
card.isActive
? "bg-green-100 text-green-800"
: "bg-red-100 text-red-800"
}`}
>
{card.isActive ? "Aktif" : "Pasif"}
</span>
<span className="text-xs text-gray-500">
Geçerli: {card.validFrom!.toLocaleDateString("tr-TR")} -{" "}
{card.validTo?.toLocaleDateString("tr-TR") || "Süresiz"}
</span>
</div>
{card.lastOrderDate && (
<span className="text-xs text-gray-400">
Son işlem: {card.lastOrderDate.toLocaleDateString("tr-TR")}
</span>
)}
</div>
</div>
))}
{/* Search Bar */}
<div className="relative">
<FaSearch className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-5 h-5" />
<input
type="text"
placeholder="Tedarikçi kartı 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>
) : (
/* List View */
<div className="bg-white rounded-lg shadow overflow-hidden">
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th className="px-2 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Tedarikçi
</th>
<th className="px-2 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Kart Tipi
</th>
<th className="px-2 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Kredi Limiti
</th>
<th className="px-2 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Kullanım Oranı
</th>
<th className="px-2 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Performans
</th>
<th className="px-2 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Ödeme Şartları
</th>
<th className="px-2 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Durum
</th>
<th className="px-2 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
İşlemler
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{filteredCards.map((card) => (
<tr key={card.id} className="hover:bg-gray-50 text-sm">
<td className="px-2 py-2 whitespace-nowrap">
<div className="flex items-center">
<div className="flex-shrink-0 h-10 w-10">
<div className="h-10 w-10 rounded-full bg-gray-300 flex items-center justify-center">
<FaBuilding className="w-5 h-5 text-gray-600" />
</div>
</div>
<div className="ml-4">
<div className="text-sm font-medium text-gray-900">
{card.cardNumber}
</div>
<div className="text-sm text-gray-500">
{card.code}
</div>
</div>
</div>
</td>
<td className="px-2 py-2 whitespace-nowrap">
{/* Content Area */}
{viewMode === 'cards' ? (
/* Cards Grid */
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
{filteredCards.map((card) => (
<div
key={card.id}
className={`bg-white rounded-lg shadow-md border-l-4 p-4 hover:shadow-lg transition-shadow ${
getSupplierCardTypeColor(card.cardType!).split(' ').slice(-1)[0]
}`}
>
<div className="flex items-start justify-between mb-3">
<div className="flex-1">
<div className="flex items-center space-x-2 mb-1.5">
<FaCreditCard className="w-6 h-6 text-gray-600" />
<h3 className="text-lg font-semibold text-gray-900">{card.cardNumber}</h3>
<span
className={`inline-flex px-2 py-1 text-xs font-semibold rounded-full ${getSupplierCardTypeColor(
card.cardType!
className={`px-3 py-1 rounded-full text-sm font-medium border ${getSupplierCardTypeColor(
card.cardType!,
)}`}
>
{getSupplierCardTypeText(card.cardType!)}
</span>
</td>
<td className="px-2 py-2 whitespace-nowrap text-sm text-gray-900">
{card.creditLimit.toLocaleString()}
</td>
<td className="px-2 py-2 whitespace-nowrap">
<div className="flex items-center">
<div className="w-16 bg-gray-200 rounded-full h-2 mr-2">
<div
className={`h-2 rounded-full ${
getCreditUtilization(card) > 80
? "bg-red-500"
: getCreditUtilization(card) > 60
? "bg-yellow-500"
: "bg-green-500"
}`}
style={{ width: `${getCreditUtilization(card)}%` }}
></div>
</div>
<span className="text-sm text-gray-700">
%{getCreditUtilization(card).toFixed(1)}
</span>
</div>
<p className="text-sm text-gray-600 mb-1">Tedarikçi: {card.code}</p>
<div className="flex items-center space-x-4 text-sm text-gray-500">
<span>Ödeme: {getPaymentTermsText(card.paymentTerms)}</span>
{card.discountRate && (
<span className="text-green-600">%{card.discountRate} indirim</span>
)}
</div>
</div>
<div className="flex space-x-1">
<button
onClick={() => handleView(card)}
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={() => handleEdit(card)}
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
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>
{/* Warnings */}
{(isCardExpiring(card) || !card.isActive) && (
<div className="mb-3 space-y-2">
{!card.isActive && (
<div className="flex items-center space-x-2 text-red-600 bg-red-50 px-2.5 py-1.5 rounded-lg">
<FaExclamationCircle className="w-4 h-4" />
<span className="text-sm">Kart pasif durumda</span>
</div>
</td>
<td className="px-2 py-2 whitespace-nowrap">
<div className="flex items-center">
<FaStar className="w-4 h-4 text-yellow-400 mr-1" />
)}
{isCardExpiring(card) && (
<div className="flex items-center space-x-2 text-orange-600 bg-orange-50 px-2.5 py-1.5 rounded-lg">
<FaCalendar className="w-4 h-4" />
<span className="text-sm">Kartın süresi 30 gün içinde dolacak</span>
</div>
)}
</div>
)}
{/* Credit Information */}
<div className="mb-3">
<div className="flex items-center justify-between mb-1.5">
<span className="text-sm text-gray-600 flex items-center">
<FaDollarSign className="w-4 h-4 mr-1" />
Kredi Limiti
</span>
<span className="font-medium">{card.creditLimit.toLocaleString()}</span>
</div>
<div className="flex items-center justify-between mb-1.5">
<span className="text-sm text-gray-600">Kullanılan</span>
<span className="font-medium">{card.currentBalance!.toLocaleString()}</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-2">
<div
className={`h-2 rounded-full ${
getCreditUtilization(card) > 80
? 'bg-red-500'
: getCreditUtilization(card) > 60
? 'bg-yellow-500'
: 'bg-green-500'
}`}
style={{ width: `${getCreditUtilization(card)}%` }}
></div>
</div>
<div className="flex justify-between text-xs text-gray-500 mt-1">
<span>%{getCreditUtilization(card).toFixed(1)} kullanıldı</span>
<span>
{(card.creditLimit - card.currentBalance!).toLocaleString()} müsait
</span>
</div>
</div>
{/* Performance Metrics */}
<div className="mb-3">
<div className="flex items-center justify-between mb-1.5">
<span className="text-sm text-gray-600 flex items-center">
<FaStar className="w-4 h-4 mr-1" />
Genel Performans
</span>
<span
className={`font-bold text-lg ${getPerformanceColor(
card.performanceMetrics!.overallScore,
)}`}
>
{card.performanceMetrics!.overallScore}/100
</span>
</div>
<div className="grid grid-cols-2 gap-2 text-xs">
<div className="flex justify-between">
<span className="text-gray-600">Teslimat:</span>
<span
className={getPerformanceColor(
card.performanceMetrics!.deliveryPerformance,
)}
>
{card.performanceMetrics!.deliveryPerformance}%
</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">Kalite:</span>
<span className={getPerformanceColor(card.performanceMetrics!.qualityRating)}>
{card.performanceMetrics!.qualityRating}%
</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">Fiyat:</span>
<span
className={getPerformanceColor(
card.performanceMetrics!.priceCompetitiveness,
)}
>
{card.performanceMetrics!.priceCompetitiveness}%
</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">Uyum:</span>
<span
className={getPerformanceColor(card.performanceMetrics!.complianceRating)}
>
{card.performanceMetrics!.complianceRating}%
</span>
</div>
</div>
</div>
{/* Special Conditions */}
{card.specialConditions && card.specialConditions.length > 0 && (
<div className="mb-3">
<h4 className="text-sm font-medium text-gray-900 mb-2">Özel Şartlar:</h4>
<div className="flex flex-wrap gap-1">
{card.specialConditions.map((condition, index) => (
<span
className={`text-sm font-medium ${getPerformanceColor(
card.performanceMetrics!.overallScore
key={index}
className="px-2 py-1 bg-blue-50 text-blue-700 text-xs rounded-full"
>
{condition}
</span>
))}
</div>
</div>
)}
{/* Footer */}
<div className="flex items-center justify-between pt-3 border-t border-gray-100">
<div className="flex items-center space-x-2">
<span
className={`px-2 py-1 rounded-full text-xs font-medium ${
card.isActive ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
}`}
>
{card.isActive ? 'Aktif' : 'Pasif'}
</span>
<span className="text-xs text-gray-500">
Geçerli: {card.validFrom!.toLocaleDateString('tr-TR')} -{' '}
{card.validTo?.toLocaleDateString('tr-TR') || 'Süresiz'}
</span>
</div>
{card.lastOrderDate && (
<span className="text-xs text-gray-400">
Son işlem: {card.lastOrderDate.toLocaleDateString('tr-TR')}
</span>
)}
</div>
</div>
))}
</div>
) : (
/* List View */
<div className="bg-white rounded-lg shadow overflow-hidden">
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th className="px-2 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Tedarikçi
</th>
<th className="px-2 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Kart Tipi
</th>
<th className="px-2 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Kredi Limiti
</th>
<th className="px-2 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Kullanım Oranı
</th>
<th className="px-2 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Performans
</th>
<th className="px-2 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Ödeme Şartları
</th>
<th className="px-2 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Durum
</th>
<th className="px-2 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
İşlemler
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{filteredCards.map((card) => (
<tr key={card.id} className="hover:bg-gray-50 text-sm">
<td className="px-2 py-2 whitespace-nowrap">
<div className="flex items-center">
<div className="flex-shrink-0 h-10 w-10">
<div className="h-10 w-10 rounded-full bg-gray-300 flex items-center justify-center">
<FaBuilding className="w-5 h-5 text-gray-600" />
</div>
</div>
<div className="ml-4">
<div className="text-sm font-medium text-gray-900">
{card.cardNumber}
</div>
<div className="text-sm text-gray-500">{card.code}</div>
</div>
</div>
</td>
<td className="px-2 py-2 whitespace-nowrap">
<span
className={`inline-flex px-2 py-1 text-xs font-semibold rounded-full ${getSupplierCardTypeColor(
card.cardType!,
)}`}
>
{card.performanceMetrics!.overallScore}/100
{getSupplierCardTypeText(card.cardType!)}
</span>
</div>
</td>
<td className="px-2 py-2 whitespace-nowrap text-sm text-gray-500">
{getPaymentTermsText(card.paymentTerms)}
</td>
<td className="px-2 py-2 whitespace-nowrap">
<div className="flex flex-col space-y-1">
<span
className={`inline-flex px-2 py-1 text-xs font-semibold rounded-full ${
card.isActive
? "bg-green-100 text-green-800"
: "bg-red-100 text-red-800"
}`}
>
{card.isActive ? "Aktif" : "Pasif"}
</span>
{(isCardExpiring(card) || !card.isActive) && (
<div className="flex items-center">
<FaExclamationCircle className="w-3 h-3 text-orange-500 mr-1" />
<span className="text-xs text-orange-600">
{!card.isActive ? "Pasif" : "Süre dolacak"}
</span>
</td>
<td className="px-2 py-2 whitespace-nowrap text-sm text-gray-900">
{card.creditLimit.toLocaleString()}
</td>
<td className="px-2 py-2 whitespace-nowrap">
<div className="flex items-center">
<div className="w-16 bg-gray-200 rounded-full h-2 mr-2">
<div
className={`h-2 rounded-full ${
getCreditUtilization(card) > 80
? 'bg-red-500'
: getCreditUtilization(card) > 60
? 'bg-yellow-500'
: 'bg-green-500'
}`}
style={{ width: `${getCreditUtilization(card)}%` }}
></div>
</div>
)}
</div>
</td>
<td className="px-2 py-2 whitespace-nowrap text-right text-sm font-medium">
<div className="flex space-x-2">
<button
onClick={() => handleView(card)}
className="text-gray-400 hover:text-blue-600 hover:bg-blue-50 p-1 rounded"
title="Görüntüle"
>
<FaEye className="w-4 h-4" />
</button>
<button
onClick={() => handleEdit(card)}
className="text-gray-400 hover:text-green-600 hover:bg-green-50 p-1 rounded"
title="Düzenle"
>
<FaEdit className="w-4 h-4" />
</button>
<button
className="text-gray-400 hover:text-red-600 hover:bg-red-50 p-1 rounded"
title="Sil"
>
<FaTrash className="w-4 h-4" />
</button>
</div>
</td>
</tr>
))}
</tbody>
</table>
<span className="text-sm text-gray-700">
%{getCreditUtilization(card).toFixed(1)}
</span>
</div>
</td>
<td className="px-2 py-2 whitespace-nowrap">
<div className="flex items-center">
<FaStar className="w-4 h-4 text-yellow-400 mr-1" />
<span
className={`text-sm font-medium ${getPerformanceColor(
card.performanceMetrics!.overallScore,
)}`}
>
{card.performanceMetrics!.overallScore}/100
</span>
</div>
</td>
<td className="px-2 py-2 whitespace-nowrap text-sm text-gray-500">
{getPaymentTermsText(card.paymentTerms)}
</td>
<td className="px-2 py-2 whitespace-nowrap">
<div className="flex flex-col space-y-1">
<span
className={`inline-flex px-2 py-1 text-xs font-semibold rounded-full ${
card.isActive
? 'bg-green-100 text-green-800'
: 'bg-red-100 text-red-800'
}`}
>
{card.isActive ? 'Aktif' : 'Pasif'}
</span>
{(isCardExpiring(card) || !card.isActive) && (
<div className="flex items-center">
<FaExclamationCircle className="w-3 h-3 text-orange-500 mr-1" />
<span className="text-xs text-orange-600">
{!card.isActive ? 'Pasif' : 'Süre dolacak'}
</span>
</div>
)}
</div>
</td>
<td className="px-2 py-2 whitespace-nowrap text-right text-sm font-medium">
<div className="flex space-x-2">
<button
onClick={() => handleView(card)}
className="text-gray-400 hover:text-blue-600 hover:bg-blue-50 p-1 rounded"
title="Görüntüle"
>
<FaEye className="w-4 h-4" />
</button>
<button
onClick={() => handleEdit(card)}
className="text-gray-400 hover:text-green-600 hover:bg-green-50 p-1 rounded"
title="Düzenle"
>
<FaEdit className="w-4 h-4" />
</button>
<button
className="text-gray-400 hover:text-red-600 hover:bg-red-50 p-1 rounded"
title="Sil"
>
<FaTrash className="w-4 h-4" />
</button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
)}
)}
{filteredCards.length === 0 && (
<div className="text-center py-8">
<FaCreditCard className="w-12 h-12 text-gray-400 mx-auto mb-4" />
<h3 className="text-lg font-medium text-gray-900 mb-2">
Tedarikçi kartı bulunamadı
</h3>
<p className="text-gray-500">
Arama kriterlerinizi değiştirin veya yeni bir tedarikçi kartı
ekleyin.
</p>
</div>
)}
{filteredCards.length === 0 && (
<div className="text-center py-8">
<FaCreditCard className="w-12 h-12 text-gray-400 mx-auto mb-4" />
<h3 className="text-lg font-medium text-gray-900 mb-2">Tedarikçi kartı bulunamadı</h3>
<p className="text-gray-500">
Arama kriterlerinizi değiştirin veya yeni bir tedarikçi kartı ekleyin.
</p>
</div>
)}
</div>
{/* Supplier Card Modal */}
<SupplierCardModal
@ -549,8 +518,8 @@ const SupplierCards: React.FC = () => {
supplierCard={editingCard}
mode={modalMode}
/>
</div>
);
};
</Container>
)
}
export default SupplierCards;
export default SupplierCards

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
import React, { useState } from "react";
import { Link } from "react-router-dom";
import { useQuery } from "@tanstack/react-query";
import React, { useState } from 'react'
import { Link } from 'react-router-dom'
import { useQuery } from '@tanstack/react-query'
import {
FaTruck,
FaPlus,
@ -17,45 +17,39 @@ import {
FaArrowUp,
FaUsers,
FaBuilding,
} from "react-icons/fa";
import classNames from "classnames";
import { SupplierTypeEnum } from "../../../types/mm";
import { mockBusinessParties } from "../../../mocks/mockBusinessParties";
import { PartyType } from "../../../types/common";
import {
getSupplierTypeColor,
getSupplierTypeText,
getRatingColor,
} from "../../../utils/erp";
} from 'react-icons/fa'
import classNames from 'classnames'
import { SupplierTypeEnum } from '../../../types/mm'
import { mockBusinessParties } from '../../../mocks/mockBusinessParties'
import { PartyType } from '../../../types/common'
import { getSupplierTypeColor, getSupplierTypeText, getRatingColor } from '../../../utils/erp'
import { Container } from '@/components/shared'
const SupplierList: React.FC = () => {
const [searchTerm, setSearchTerm] = useState("");
const [filterType, setFilterType] = useState("all");
const [showFilters, setShowFilters] = useState(false);
const [searchTerm, setSearchTerm] = useState('')
const [filterType, setFilterType] = useState('all')
const [showFilters, setShowFilters] = useState(false)
const {
data: suppliers,
isLoading,
error,
} = useQuery({
queryKey: ["suppliers", searchTerm, filterType],
queryKey: ['suppliers', searchTerm, filterType],
queryFn: async () => {
await new Promise((resolve) => setTimeout(resolve, 500));
await new Promise((resolve) => setTimeout(resolve, 500))
const filtredSuppliers = mockBusinessParties.filter(
(a) => a.partyType === PartyType.Supplier
);
const filtredSuppliers = mockBusinessParties.filter((a) => a.partyType === PartyType.Supplier)
return filtredSuppliers.filter((supplier) => {
const matchesSearch =
supplier.code.toLowerCase().includes(searchTerm.toLowerCase()) ||
supplier.name.toLowerCase().includes(searchTerm.toLowerCase());
const matchesType =
filterType === "all" || supplier.supplierType === filterType;
return matchesSearch && matchesType;
});
supplier.name.toLowerCase().includes(searchTerm.toLowerCase())
const matchesType = filterType === 'all' || supplier.supplierType === filterType
return matchesSearch && matchesType
})
},
});
})
if (isLoading) {
return (
@ -63,7 +57,7 @@ const SupplierList: React.FC = () => {
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
<span className="ml-3 text-gray-600">Tedarikçiler yükleniyor...</span>
</div>
);
)
}
if (error) {
@ -71,388 +65,350 @@ const SupplierList: React.FC = () => {
<div className="bg-red-50 border border-red-200 rounded-lg p-4">
<div className="flex items-center">
<FaExclamationTriangle className="h-5 w-5 text-red-600 mr-2" />
<span className="text-red-800">
Tedarikçiler yüklenirken hata oluştu.
</span>
<span className="text-red-800">Tedarikçiler yüklenirken hata oluştu.</span>
</div>
</div>
);
)
}
return (
<div className="space-y-3 pt-2">
{/* Header Actions */}
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<div className="flex items-center space-x-4">
<div className="relative">
<FaSearch
size={20}
className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"
/>
<input
type="text"
placeholder="Tedarikçi kodu veya firma adı..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-10 pr-4 py-1.5 w-80 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
<Container>
<div className="space-y-2">
{/* Header Actions */}
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<div className="flex items-center space-x-4">
<div className="relative">
<FaSearch
size={20}
className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"
/>
<input
type="text"
placeholder="Tedarikçi kodu veya firma adı..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-10 pr-4 py-1.5 w-80 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
</div>
<button
onClick={() => setShowFilters(!showFilters)}
className={classNames(
'flex items-center px-3 py-1.5 border rounded-lg transition-colors',
showFilters
? 'border-blue-500 bg-blue-50 text-blue-700'
: 'border-gray-300 bg-white text-gray-700 hover:bg-gray-50',
)}
>
<FaFilter size={16} className="mr-2" />
Filtreler
</button>
</div>
<button
onClick={() => setShowFilters(!showFilters)}
className={classNames(
"flex items-center px-3 py-1.5 border rounded-lg transition-colors",
showFilters
? "border-blue-500 bg-blue-50 text-blue-700"
: "border-gray-300 bg-white text-gray-700 hover:bg-gray-50"
)}
>
<FaFilter size={16} className="mr-2" />
Filtreler
</button>
</div>
<div className="flex items-center space-x-3">
<button
onClick={() => alert('Dışa aktarma özelliği yakında eklenecek')}
className="flex items-center px-3 py-1.5 border border-gray-300 bg-white text-gray-700 rounded-lg hover:bg-gray-50 transition-colors"
>
<FaDownload size={16} className="mr-2" />
Dışa Aktar
</button>
<div className="flex items-center space-x-3">
<button
onClick={() => alert("Dışa aktarma özelliği yakında eklenecek")}
className="flex items-center px-3 py-1.5 border border-gray-300 bg-white text-gray-700 rounded-lg hover:bg-gray-50 transition-colors"
>
<FaDownload size={16} className="mr-2" />
Dışa Aktar
</button>
<Link
to="/admin/supplychain/suppliers/new"
className="flex items-center px-3 py-1.5 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
>
<FaPlus size={16} className="mr-2" />
Yeni Tedarikçi
</Link>
</div>
</div>
{/* Filters Panel */}
{showFilters && (
<div className="bg-white border border-gray-200 rounded-lg p-3">
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Tedarikçi Türü
</label>
<select
value={filterType}
onChange={(e) => setFilterType(e.target.value)}
className="w-full border border-gray-300 rounded-lg px-3 py-1.5 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
>
<option value="all">Tümü</option>
<option value={SupplierTypeEnum.Material}>Malzeme</option>
<option value={SupplierTypeEnum.Service}>Hizmet</option>
<option value={SupplierTypeEnum.Both}>Karma</option>
</select>
</div>
<div className="flex items-end">
<button
onClick={() => {
setFilterType("all");
setSearchTerm("");
}}
className="w-full px-3 py-1.5 border border-gray-300 bg-white text-gray-700 rounded-lg hover:bg-gray-50 transition-colors"
>
Filtreleri Temizle
</button>
</div>
</div>
</div>
)}
{/* Statistics Cards */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-3">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">
Toplam Tedarikçi
</p>
<p className="text-xl font-bold text-gray-900">
{suppliers?.length || 0}
</p>
</div>
<FaBuilding className="h-8 w-8 text-blue-600" />
<Link
to="/admin/supplychain/suppliers/new"
className="flex items-center px-3 py-1.5 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
>
<FaPlus size={16} className="mr-2" />
Yeni Tedarikçi
</Link>
</div>
</div>
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-3">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">
Aktif Tedarikçi
</p>
<p className="text-xl font-bold text-green-600">
{suppliers?.filter((s) => s.isActive).length || 0}
</p>
</div>
<FaUsers className="h-8 w-8 text-green-600" />
</div>
</div>
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-3">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">Ortalama Puan</p>
<p className="text-xl font-bold text-yellow-600">
{suppliers?.length
? (
suppliers.reduce(
(acc, s) =>
acc + (s.performanceMetrics?.overallScore ?? 0),
0
) / suppliers.length
).toFixed(1)
: "0.0"}
</p>
</div>
<FaStar className="h-8 w-8 text-yellow-600" />
</div>
</div>
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-3">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">
Yüksek Performans
</p>
<p className="text-xl font-bold text-purple-600">
{suppliers?.filter(
(s) => (s.performanceMetrics?.overallScore ?? 0) >= 4.5
).length || 0}
</p>
</div>
<FaArrowUp className="h-8 w-8 text-purple-600" />
</div>
</div>
</div>
{/* Suppliers Table */}
<div className="bg-white rounded-lg shadow-sm border border-gray-200">
<div className="px-4 py-3 border-b border-gray-200">
<h2 className="text-xl font-bold text-gray-900">Tedarikçi Listesi</h2>
</div>
<div className="overflow-x-auto">
<table className="w-full">
<thead className="bg-gray-50">
<tr>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Tedarikçi Bilgileri
</th>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
İletişim
</th>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Tür / Konum
</th>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Performans
</th>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Kredi Limiti
</th>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Durum
</th>
<th className="px-4 py-2 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">
İşlemler
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{suppliers?.map((supplier) => (
<tr
key={supplier.id}
className="hover:bg-gray-50 transition-colors"
{/* Filters Panel */}
{showFilters && (
<div className="bg-white border border-gray-200 rounded-lg p-3">
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Tedarikçi Türü
</label>
<select
value={filterType}
onChange={(e) => setFilterType(e.target.value)}
className="w-full border border-gray-300 rounded-lg px-3 py-1.5 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
>
<td className="px-4 py-3">
<div className="flex items-center">
<div className="flex-shrink-0 h-10 w-10">
<div className="h-10 w-10 rounded-lg bg-blue-100 flex items-center justify-center">
<FaTruck className="h-5 w-5 text-blue-600" />
</div>
</div>
<div className="ml-4">
<div className="text-sm font-medium text-gray-900">
{supplier.code}
</div>
<div className="text-sm text-gray-500">
{supplier.name}
</div>
{supplier.taxNumber && (
<div className="text-xs text-gray-400 mt-1">
VKN: {supplier.taxNumber}
</div>
)}
</div>
</div>
</td>
<option value="all">Tümü</option>
<option value={SupplierTypeEnum.Material}>Malzeme</option>
<option value={SupplierTypeEnum.Service}>Hizmet</option>
<option value={SupplierTypeEnum.Both}>Karma</option>
</select>
</div>
<td className="px-4 py-3">
<div className="space-y-1">
{supplier.primaryContact && (
<div className="text-sm font-medium text-gray-900">
{supplier.primaryContact.fullName}
</div>
)}
{supplier.email && (
<div className="flex items-center text-sm text-gray-500">
<FaEnvelope size={14} className="mr-1" />
{supplier.email}
</div>
)}
{supplier.phone && (
<div className="flex items-center text-sm text-gray-500">
<FaPhone size={14} className="mr-1" />
{supplier.phone}
</div>
)}
</div>
</td>
<td className="px-4 py-3">
<div className="space-y-1">
<span
className={classNames(
"inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium",
getSupplierTypeColor(supplier.supplierType!)
)}
>
{getSupplierTypeText(supplier.supplierType!)}
</span>
<div className="flex items-center text-sm text-gray-500">
<FaMapMarkerAlt size={14} className="mr-1" />
{supplier.address?.city}, {supplier.address?.country}
</div>
</div>
</td>
<td className="px-4 py-3">
<div className="space-y-1">
<div className="flex items-center">
<FaStar
size={14}
className={classNames(
"mr-1",
getRatingColor(
supplier.performanceMetrics?.overallScore ?? 0
)
)}
/>
<span
className={classNames(
"text-sm font-medium",
getRatingColor(
supplier.performanceMetrics?.overallScore ?? 0
)
)}
>
{(
supplier.performanceMetrics?.overallScore ?? 0
).toFixed(1)}
</span>
</div>
<div className="text-xs text-gray-500">
K:
{(
supplier.performanceMetrics?.qualityRating ?? 0
).toFixed(1)}{" "}
T:
{(
supplier.performanceMetrics?.deliveryPerformance ?? 0
).toFixed(1)}{" "}
F:
{(
supplier.performanceMetrics?.priceCompetitiveness ?? 0
).toFixed(1)}
</div>
{supplier.certifications &&
supplier.certifications.length > 0 && (
<div className="text-xs text-blue-600">
{supplier.certifications.join(", ")}
</div>
)}
</div>
</td>
<td className="px-4 py-3">
<div className="text-sm font-medium text-gray-900">
{supplier.creditLimit.toLocaleString()}
</div>
<div className="text-sm text-gray-500">
{supplier.paymentTerms}
</div>
</td>
<td className="px-4 py-3">
<span
className={classNames(
"inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium",
supplier.isActive
? "bg-green-100 text-green-800"
: "bg-red-100 text-red-800"
)}
>
{supplier.isActive ? "Aktif" : "Pasif"}
</span>
</td>
<td className="px-4 py-3 text-right">
<div className="flex items-center justify-end space-x-2">
<Link
to={`/admin/supplychain/suppliers/${supplier.id}`}
className="p-1.5 text-gray-600 hover:text-blue-600 hover:bg-blue-50 rounded-lg transition-colors"
title="Detayları Görüntüle"
>
<FaEye size={16} />
</Link>
<Link
to={`/admin/supplychain/suppliers/edit/${supplier.id}`}
className="p-1.5 text-gray-600 hover:text-yellow-600 hover:bg-yellow-50 rounded-lg transition-colors"
title="Düzenle"
>
<FaEdit size={16} />
</Link>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
{(!suppliers || suppliers.length === 0) && (
<div className="text-center py-10">
<FaTruck className="mx-auto h-12 w-12 text-gray-400" />
<h3 className="mt-2 text-sm font-medium text-gray-900">
Tedarikçi bulunamadı
</h3>
<p className="mt-1 text-sm text-gray-500">
Yeni tedarikçi ekleyerek başlayın.
</p>
<div className="mt-6">
<Link
to="/admin/supplychain/suppliers/new"
className="inline-flex items-center px-3 py-1.5 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
>
<FaPlus size={16} className="mr-2" />
Yeni Tedarikçi Ekle
</Link>
<div className="flex items-end">
<button
onClick={() => {
setFilterType('all')
setSearchTerm('')
}}
className="w-full px-3 py-1.5 border border-gray-300 bg-white text-gray-700 rounded-lg hover:bg-gray-50 transition-colors"
>
Filtreleri Temizle
</button>
</div>
</div>
</div>
)}
</div>
</div>
);
};
export default SupplierList;
{/* Statistics Cards */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-3">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">Toplam Tedarikçi</p>
<p className="text-xl font-bold text-gray-900">{suppliers?.length || 0}</p>
</div>
<FaBuilding className="h-8 w-8 text-blue-600" />
</div>
</div>
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-3">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">Aktif Tedarikçi</p>
<p className="text-xl font-bold text-green-600">
{suppliers?.filter((s) => s.isActive).length || 0}
</p>
</div>
<FaUsers className="h-8 w-8 text-green-600" />
</div>
</div>
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-3">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">Ortalama Puan</p>
<p className="text-xl font-bold text-yellow-600">
{suppliers?.length
? (
suppliers.reduce(
(acc, s) => acc + (s.performanceMetrics?.overallScore ?? 0),
0,
) / suppliers.length
).toFixed(1)
: '0.0'}
</p>
</div>
<FaStar className="h-8 w-8 text-yellow-600" />
</div>
</div>
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-3">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">Yüksek Performans</p>
<p className="text-xl font-bold text-purple-600">
{suppliers?.filter((s) => (s.performanceMetrics?.overallScore ?? 0) >= 4.5)
.length || 0}
</p>
</div>
<FaArrowUp className="h-8 w-8 text-purple-600" />
</div>
</div>
</div>
{/* Suppliers Table */}
<div className="bg-white rounded-lg shadow-sm border border-gray-200">
<div className="px-4 py-3 border-b border-gray-200">
<h2 className="text-xl font-bold text-gray-900">Tedarikçi Listesi</h2>
</div>
<div className="overflow-x-auto">
<table className="w-full">
<thead className="bg-gray-50">
<tr>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Tedarikçi Bilgileri
</th>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
İletişim
</th>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Tür / Konum
</th>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Performans
</th>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Kredi Limiti
</th>
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Durum
</th>
<th className="px-4 py-2 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">
İşlemler
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{suppliers?.map((supplier) => (
<tr key={supplier.id} className="hover:bg-gray-50 transition-colors">
<td className="px-4 py-3">
<div className="flex items-center">
<div className="flex-shrink-0 h-10 w-10">
<div className="h-10 w-10 rounded-lg bg-blue-100 flex items-center justify-center">
<FaTruck className="h-5 w-5 text-blue-600" />
</div>
</div>
<div className="ml-4">
<div className="text-sm font-medium text-gray-900">{supplier.code}</div>
<div className="text-sm text-gray-500">{supplier.name}</div>
{supplier.taxNumber && (
<div className="text-xs text-gray-400 mt-1">
VKN: {supplier.taxNumber}
</div>
)}
</div>
</div>
</td>
<td className="px-4 py-3">
<div className="space-y-1">
{supplier.primaryContact && (
<div className="text-sm font-medium text-gray-900">
{supplier.primaryContact.fullName}
</div>
)}
{supplier.email && (
<div className="flex items-center text-sm text-gray-500">
<FaEnvelope size={14} className="mr-1" />
{supplier.email}
</div>
)}
{supplier.phone && (
<div className="flex items-center text-sm text-gray-500">
<FaPhone size={14} className="mr-1" />
{supplier.phone}
</div>
)}
</div>
</td>
<td className="px-4 py-3">
<div className="space-y-1">
<span
className={classNames(
'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium',
getSupplierTypeColor(supplier.supplierType!),
)}
>
{getSupplierTypeText(supplier.supplierType!)}
</span>
<div className="flex items-center text-sm text-gray-500">
<FaMapMarkerAlt size={14} className="mr-1" />
{supplier.address?.city}, {supplier.address?.country}
</div>
</div>
</td>
<td className="px-4 py-3">
<div className="space-y-1">
<div className="flex items-center">
<FaStar
size={14}
className={classNames(
'mr-1',
getRatingColor(supplier.performanceMetrics?.overallScore ?? 0),
)}
/>
<span
className={classNames(
'text-sm font-medium',
getRatingColor(supplier.performanceMetrics?.overallScore ?? 0),
)}
>
{(supplier.performanceMetrics?.overallScore ?? 0).toFixed(1)}
</span>
</div>
<div className="text-xs text-gray-500">
K:
{(supplier.performanceMetrics?.qualityRating ?? 0).toFixed(1)} T:
{(supplier.performanceMetrics?.deliveryPerformance ?? 0).toFixed(1)} F:
{(supplier.performanceMetrics?.priceCompetitiveness ?? 0).toFixed(1)}
</div>
{supplier.certifications && supplier.certifications.length > 0 && (
<div className="text-xs text-blue-600">
{supplier.certifications.join(', ')}
</div>
)}
</div>
</td>
<td className="px-4 py-3">
<div className="text-sm font-medium text-gray-900">
{supplier.creditLimit.toLocaleString()}
</div>
<div className="text-sm text-gray-500">{supplier.paymentTerms}</div>
</td>
<td className="px-4 py-3">
<span
className={classNames(
'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium',
supplier.isActive
? 'bg-green-100 text-green-800'
: 'bg-red-100 text-red-800',
)}
>
{supplier.isActive ? 'Aktif' : 'Pasif'}
</span>
</td>
<td className="px-4 py-3 text-right">
<div className="flex items-center justify-end space-x-2">
<Link
to={`/admin/supplychain/suppliers/${supplier.id}`}
className="p-1.5 text-gray-600 hover:text-blue-600 hover:bg-blue-50 rounded-lg transition-colors"
title="Detayları Görüntüle"
>
<FaEye size={16} />
</Link>
<Link
to={`/admin/supplychain/suppliers/edit/${supplier.id}`}
className="p-1.5 text-gray-600 hover:text-yellow-600 hover:bg-yellow-50 rounded-lg transition-colors"
title="Düzenle"
>
<FaEdit size={16} />
</Link>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
{(!suppliers || suppliers.length === 0) && (
<div className="text-center py-10">
<FaTruck className="mx-auto h-12 w-12 text-gray-400" />
<h3 className="mt-2 text-sm font-medium text-gray-900">Tedarikçi bulunamadı</h3>
<p className="mt-1 text-sm text-gray-500">Yeni tedarikçi ekleyerek başlayın.</p>
<div className="mt-6">
<Link
to="/admin/supplychain/suppliers/new"
className="inline-flex items-center px-3 py-1.5 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
>
<FaPlus size={16} className="mr-2" />
Yeni Tedarikçi Ekle
</Link>
</div>
</div>
)}
</div>
</div>
</Container>
)
}
export default SupplierList