Supply Management
This commit is contained in:
parent
4b7fd59e61
commit
811a575ff7
21 changed files with 6426 additions and 7554 deletions
|
|
@ -3270,16 +3270,6 @@
|
||||||
"RequiredPermissionName": null,
|
"RequiredPermissionName": null,
|
||||||
"IsDisabled": false
|
"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",
|
"ParentCode": "App.SupplyChain",
|
||||||
"Code": "App.SupplyChain.Quotations",
|
"Code": "App.SupplyChain.Quotations",
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,20 @@
|
||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from 'react'
|
||||||
import { FaTimes, FaSave, FaUsers, FaPlus, FaTrash } from "react-icons/fa";
|
import { FaTimes, FaSave, FaUsers, FaPlus, FaTrash } from 'react-icons/fa'
|
||||||
import {
|
import {
|
||||||
MmApprovalWorkflow,
|
MmApprovalWorkflow,
|
||||||
MmApprovalWorkflowLevel,
|
MmApprovalWorkflowLevel,
|
||||||
RequestTypeEnum,
|
RequestTypeEnum,
|
||||||
ApprovalLevelEnum,
|
ApprovalLevelEnum,
|
||||||
} from "../../../types/mm";
|
} from '../../../types/mm'
|
||||||
import MultiSelectEmployee from "../../../components/common/MultiSelectEmployee";
|
import MultiSelectEmployee from '../../../components/common/MultiSelectEmployee'
|
||||||
import { mockDepartments } from "../../../mocks/mockDepartments";
|
import { mockDepartments } from '../../../mocks/mockDepartments'
|
||||||
|
|
||||||
interface ApprovalWorkflowModalProps {
|
interface ApprovalWorkflowModalProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean
|
||||||
onClose: () => void;
|
onClose: () => void
|
||||||
onSave: (workflow: MmApprovalWorkflow) => void;
|
onSave: (workflow: MmApprovalWorkflow) => void
|
||||||
workflow?: MmApprovalWorkflow | null;
|
workflow?: MmApprovalWorkflow | null
|
||||||
mode: "create" | "view" | "edit";
|
mode: 'create' | 'view' | 'edit'
|
||||||
}
|
}
|
||||||
|
|
||||||
const ApprovalWorkflowModal: React.FC<ApprovalWorkflowModalProps> = ({
|
const ApprovalWorkflowModal: React.FC<ApprovalWorkflowModalProps> = ({
|
||||||
|
|
@ -25,119 +25,117 @@ const ApprovalWorkflowModal: React.FC<ApprovalWorkflowModalProps> = ({
|
||||||
mode,
|
mode,
|
||||||
}) => {
|
}) => {
|
||||||
const [formData, setFormData] = useState<Partial<MmApprovalWorkflow>>({
|
const [formData, setFormData] = useState<Partial<MmApprovalWorkflow>>({
|
||||||
name: "",
|
name: '',
|
||||||
departmentId: "",
|
departmentId: '',
|
||||||
requestType: RequestTypeEnum.Material,
|
requestType: RequestTypeEnum.Material,
|
||||||
amountThreshold: 0,
|
amountThreshold: 0,
|
||||||
approvalLevels: [],
|
approvalLevels: [],
|
||||||
isActive: true,
|
isActive: true,
|
||||||
});
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (mode === "create") {
|
if (mode === 'create') {
|
||||||
// Reset form to initial empty state for new workflow
|
// Reset form to initial empty state for new workflow
|
||||||
setFormData({
|
setFormData({
|
||||||
name: "",
|
name: '',
|
||||||
departmentId: "",
|
departmentId: '',
|
||||||
requestType: RequestTypeEnum.Material,
|
requestType: RequestTypeEnum.Material,
|
||||||
amountThreshold: 0,
|
amountThreshold: 0,
|
||||||
approvalLevels: [],
|
approvalLevels: [],
|
||||||
isActive: true,
|
isActive: true,
|
||||||
});
|
})
|
||||||
} else if (workflow) {
|
} else if (workflow) {
|
||||||
// Load existing workflow data for view/edit modes
|
// Load existing workflow data for view/edit modes
|
||||||
setFormData(workflow);
|
setFormData(workflow)
|
||||||
}
|
}
|
||||||
}, [workflow, mode]);
|
}, [workflow, mode])
|
||||||
|
|
||||||
const handleInputChange = (
|
const handleInputChange = (
|
||||||
e: React.ChangeEvent<
|
e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>,
|
||||||
HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement
|
|
||||||
>
|
|
||||||
) => {
|
) => {
|
||||||
const { name, value, type } = e.target;
|
const { name, value, type } = e.target
|
||||||
setFormData((prev) => ({
|
setFormData((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
[name]:
|
[name]:
|
||||||
type === "number"
|
type === 'number'
|
||||||
? parseFloat(value) || 0
|
? parseFloat(value) || 0
|
||||||
: type === "checkbox"
|
: type === 'checkbox'
|
||||||
? (e.target as HTMLInputElement).checked
|
? (e.target as HTMLInputElement).checked
|
||||||
: value,
|
: value,
|
||||||
}));
|
}))
|
||||||
};
|
}
|
||||||
|
|
||||||
const addApprovalLevel = () => {
|
const addApprovalLevel = () => {
|
||||||
const newLevel: MmApprovalWorkflowLevel = {
|
const newLevel: MmApprovalWorkflowLevel = {
|
||||||
id: `level-${Date.now()}`,
|
id: `level-${Date.now()}`,
|
||||||
workflowId: formData.id || "",
|
workflowId: formData.id || '',
|
||||||
level: ApprovalLevelEnum.Supervisor,
|
level: ApprovalLevelEnum.Supervisor,
|
||||||
approverUserIds: [],
|
approverUserIds: [],
|
||||||
approverNames: [],
|
approverNames: [],
|
||||||
sequence: (formData.approvalLevels?.length || 0) + 1,
|
sequence: (formData.approvalLevels?.length || 0) + 1,
|
||||||
isRequired: true,
|
isRequired: true,
|
||||||
isParallel: false,
|
isParallel: false,
|
||||||
};
|
}
|
||||||
|
|
||||||
setFormData((prev) => ({
|
setFormData((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
approvalLevels: [...(prev.approvalLevels || []), newLevel],
|
approvalLevels: [...(prev.approvalLevels || []), newLevel],
|
||||||
}));
|
}))
|
||||||
};
|
}
|
||||||
|
|
||||||
const removeApprovalLevel = (index: number) => {
|
const removeApprovalLevel = (index: number) => {
|
||||||
setFormData((prev) => ({
|
setFormData((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
approvalLevels: prev.approvalLevels?.filter((_, i) => i !== index) || [],
|
approvalLevels: prev.approvalLevels?.filter((_, i) => i !== index) || [],
|
||||||
}));
|
}))
|
||||||
};
|
}
|
||||||
|
|
||||||
const updateApprovalLevel = (
|
const updateApprovalLevel = (
|
||||||
index: number,
|
index: number,
|
||||||
field: keyof MmApprovalWorkflowLevel,
|
field: keyof MmApprovalWorkflowLevel,
|
||||||
value: string | number | boolean | string[] | undefined
|
value: string | number | boolean | string[] | undefined,
|
||||||
) => {
|
) => {
|
||||||
setFormData((prev) => ({
|
setFormData((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
approvalLevels:
|
approvalLevels:
|
||||||
prev.approvalLevels?.map((level, i) =>
|
prev.approvalLevels?.map((level, i) =>
|
||||||
i === index ? { ...level, [field]: value } : level
|
i === index ? { ...level, [field]: value } : level,
|
||||||
) || [],
|
) || [],
|
||||||
}));
|
}))
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleApproverNamesChange = (index: number, employees: string[]) => {
|
const handleApproverNamesChange = (index: number, employees: string[]) => {
|
||||||
updateApprovalLevel(index, "approverNames", employees);
|
updateApprovalLevel(index, 'approverNames', employees)
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleSubmit = (e: React.FormEvent) => {
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault()
|
||||||
if (mode !== "view") {
|
if (mode !== 'view') {
|
||||||
const newWorkflow: MmApprovalWorkflow = {
|
const newWorkflow: MmApprovalWorkflow = {
|
||||||
id: workflow?.id || `WF-${Date.now()}`,
|
id: workflow?.id || `WF-${Date.now()}`,
|
||||||
name: formData.name || "",
|
name: formData.name || '',
|
||||||
departmentId: formData.departmentId || "",
|
departmentId: formData.departmentId || '',
|
||||||
requestType: formData.requestType || RequestTypeEnum.Material,
|
requestType: formData.requestType || RequestTypeEnum.Material,
|
||||||
amountThreshold: formData.amountThreshold || 0,
|
amountThreshold: formData.amountThreshold || 0,
|
||||||
approvalLevels: formData.approvalLevels || [],
|
approvalLevels: formData.approvalLevels || [],
|
||||||
isActive: formData.isActive ?? true,
|
isActive: formData.isActive ?? true,
|
||||||
creationTime: workflow?.creationTime || new Date(),
|
creationTime: workflow?.creationTime || new Date(),
|
||||||
lastModificationTime: new Date(),
|
lastModificationTime: new Date(),
|
||||||
};
|
|
||||||
onSave(newWorkflow);
|
|
||||||
}
|
}
|
||||||
onClose();
|
onSave(newWorkflow)
|
||||||
};
|
}
|
||||||
|
onClose()
|
||||||
|
}
|
||||||
|
|
||||||
if (!isOpen) return null;
|
if (!isOpen) return null
|
||||||
|
|
||||||
const isReadOnly = mode === "view";
|
const isReadOnly = mode === 'view'
|
||||||
const modalTitle =
|
const modalTitle =
|
||||||
mode === "create"
|
mode === 'create'
|
||||||
? "Yeni Onay Süreci"
|
? 'Yeni Onay Süreci'
|
||||||
: mode === "edit"
|
: mode === 'edit'
|
||||||
? "Onay Sürecini Düzenle"
|
? 'Onay Sürecini Düzenle'
|
||||||
: "Onay Süreci Detayları";
|
: 'Onay Süreci Detayları'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
|
<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" />
|
<FaUsers className="mr-2 text-blue-600" />
|
||||||
{modalTitle}
|
{modalTitle}
|
||||||
</h3>
|
</h3>
|
||||||
<button
|
<button onClick={onClose} className="text-gray-400 hover:text-gray-600 p-1">
|
||||||
onClick={onClose}
|
|
||||||
className="text-gray-400 hover:text-gray-600 p-1"
|
|
||||||
>
|
|
||||||
<FaTimes />
|
<FaTimes />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -159,18 +154,14 @@ const ApprovalWorkflowModal: React.FC<ApprovalWorkflowModalProps> = ({
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
{/* Temel Bilgiler */}
|
{/* Temel Bilgiler */}
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<h4 className="text-base font-medium text-gray-900 border-b pb-1">
|
<h4 className="text-base font-medium text-gray-900 border-b pb-1">Temel Bilgiler</h4>
|
||||||
Temel Bilgiler
|
|
||||||
</h4>
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700">
|
<label className="block text-sm font-medium text-gray-700">Süreç Adı</label>
|
||||||
Süreç Adı
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="workflowName"
|
name="workflowName"
|
||||||
value={formData.name || ""}
|
value={formData.name || ''}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
readOnly={isReadOnly}
|
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"
|
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>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700">
|
<label className="block text-sm font-medium text-gray-700">Departman</label>
|
||||||
Departman
|
|
||||||
</label>
|
|
||||||
<select
|
<select
|
||||||
value={formData.departmentId || ""}
|
value={formData.departmentId || ''}
|
||||||
onChange={handleInputChange}
|
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"
|
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
|
required
|
||||||
|
|
@ -198,9 +187,7 @@ const ApprovalWorkflowModal: React.FC<ApprovalWorkflowModalProps> = ({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700">
|
<label className="block text-sm font-medium text-gray-700">Talep Tipi</label>
|
||||||
Talep Tipi
|
|
||||||
</label>
|
|
||||||
<select
|
<select
|
||||||
name="requestType"
|
name="requestType"
|
||||||
value={formData.requestType || RequestTypeEnum.Material}
|
value={formData.requestType || RequestTypeEnum.Material}
|
||||||
|
|
@ -216,9 +203,7 @@ const ApprovalWorkflowModal: React.FC<ApprovalWorkflowModalProps> = ({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700">
|
<label className="block text-sm font-medium text-gray-700">Tutar Eşiği (TL)</label>
|
||||||
Tutar Eşiği (TL)
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
name="amountThreshold"
|
name="amountThreshold"
|
||||||
|
|
@ -239,9 +224,7 @@ const ApprovalWorkflowModal: React.FC<ApprovalWorkflowModalProps> = ({
|
||||||
disabled={isReadOnly}
|
disabled={isReadOnly}
|
||||||
className="mr-2"
|
className="mr-2"
|
||||||
/>
|
/>
|
||||||
<span className="text-sm font-medium text-gray-700">
|
<span className="text-sm font-medium text-gray-700">Aktif</span>
|
||||||
Aktif
|
|
||||||
</span>
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -266,10 +249,7 @@ const ApprovalWorkflowModal: React.FC<ApprovalWorkflowModalProps> = ({
|
||||||
|
|
||||||
<div className="space-y-2 max-h-56 overflow-y-auto p-1">
|
<div className="space-y-2 max-h-56 overflow-y-auto p-1">
|
||||||
{formData.approvalLevels?.map((level, index) => (
|
{formData.approvalLevels?.map((level, index) => (
|
||||||
<div
|
<div key={level.id} className="border rounded-lg p-2 bg-gray-50">
|
||||||
key={level.id}
|
|
||||||
className="border rounded-lg p-2 bg-gray-50"
|
|
||||||
>
|
|
||||||
<div className="flex items-center justify-between mb-1">
|
<div className="flex items-center justify-between mb-1">
|
||||||
<span className="text-sm font-medium text-gray-700">
|
<span className="text-sm font-medium text-gray-700">
|
||||||
Seviye {level.sequence}
|
Seviye {level.sequence}
|
||||||
|
|
@ -292,30 +272,18 @@ const ApprovalWorkflowModal: React.FC<ApprovalWorkflowModalProps> = ({
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
value={level.level}
|
value={level.level}
|
||||||
onChange={(e) =>
|
onChange={(e) => updateApprovalLevel(index, 'level', e.target.value)}
|
||||||
updateApprovalLevel(index, "level", e.target.value)
|
|
||||||
}
|
|
||||||
disabled={isReadOnly}
|
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"
|
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}>
|
<option value={ApprovalLevelEnum.Supervisor}>Süpervizör</option>
|
||||||
Süpervizör
|
<option value={ApprovalLevelEnum.Manager}>Müdür</option>
|
||||||
</option>
|
<option value={ApprovalLevelEnum.Director}>Direktör</option>
|
||||||
<option value={ApprovalLevelEnum.Manager}>
|
<option value={ApprovalLevelEnum.GeneralManager}>Genel Müdür</option>
|
||||||
Müdür
|
|
||||||
</option>
|
|
||||||
<option value={ApprovalLevelEnum.Director}>
|
|
||||||
Direktör
|
|
||||||
</option>
|
|
||||||
<option value={ApprovalLevelEnum.GeneralManager}>
|
|
||||||
Genel Müdür
|
|
||||||
</option>
|
|
||||||
<option value={ApprovalLevelEnum.FinanceManager}>
|
<option value={ApprovalLevelEnum.FinanceManager}>
|
||||||
Mali İşler Müdürü
|
Mali İşler Müdürü
|
||||||
</option>
|
</option>
|
||||||
<option value={ApprovalLevelEnum.TechnicalManager}>
|
<option value={ApprovalLevelEnum.TechnicalManager}>Teknik Müdür</option>
|
||||||
Teknik Müdür
|
|
||||||
</option>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -325,9 +293,7 @@ const ApprovalWorkflowModal: React.FC<ApprovalWorkflowModalProps> = ({
|
||||||
</label>
|
</label>
|
||||||
<MultiSelectEmployee
|
<MultiSelectEmployee
|
||||||
selectedEmployees={level.approverNames || []}
|
selectedEmployees={level.approverNames || []}
|
||||||
onChange={(employees) =>
|
onChange={(employees) => handleApproverNamesChange(index, employees)}
|
||||||
handleApproverNamesChange(index, employees)
|
|
||||||
}
|
|
||||||
disabled={isReadOnly}
|
disabled={isReadOnly}
|
||||||
placeholder="Onaylayıcı seçin..."
|
placeholder="Onaylayıcı seçin..."
|
||||||
className="mt-1"
|
className="mt-1"
|
||||||
|
|
@ -340,12 +306,12 @@ const ApprovalWorkflowModal: React.FC<ApprovalWorkflowModalProps> = ({
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
value={level.timeoutDays || ""}
|
value={level.timeoutDays || ''}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
updateApprovalLevel(
|
updateApprovalLevel(
|
||||||
index,
|
index,
|
||||||
"timeoutDays",
|
'timeoutDays',
|
||||||
parseInt(e.target.value) || undefined
|
parseInt(e.target.value) || undefined,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
readOnly={isReadOnly}
|
readOnly={isReadOnly}
|
||||||
|
|
@ -360,11 +326,7 @@ const ApprovalWorkflowModal: React.FC<ApprovalWorkflowModalProps> = ({
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={level.isRequired}
|
checked={level.isRequired}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
updateApprovalLevel(
|
updateApprovalLevel(index, 'isRequired', e.target.checked)
|
||||||
index,
|
|
||||||
"isRequired",
|
|
||||||
e.target.checked
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
disabled={isReadOnly}
|
disabled={isReadOnly}
|
||||||
className="mr-1"
|
className="mr-1"
|
||||||
|
|
@ -376,11 +338,7 @@ const ApprovalWorkflowModal: React.FC<ApprovalWorkflowModalProps> = ({
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={level.isParallel}
|
checked={level.isParallel}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
updateApprovalLevel(
|
updateApprovalLevel(index, 'isParallel', e.target.checked)
|
||||||
index,
|
|
||||||
"isParallel",
|
|
||||||
e.target.checked
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
disabled={isReadOnly}
|
disabled={isReadOnly}
|
||||||
className="mr-1"
|
className="mr-1"
|
||||||
|
|
@ -408,9 +366,9 @@ const ApprovalWorkflowModal: React.FC<ApprovalWorkflowModalProps> = ({
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="px-3 py-1.5 border border-gray-300 rounded-md text-sm text-gray-700 hover:bg-gray-50"
|
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>
|
</button>
|
||||||
{mode !== "view" && (
|
{mode !== 'view' && (
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="px-3 py-1.5 bg-blue-600 text-white rounded-md hover:bg-blue-700 flex items-center text-sm"
|
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>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
export default ApprovalWorkflowModal;
|
export default ApprovalWorkflowModal
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useState } from "react";
|
import React, { useState } from 'react'
|
||||||
import {
|
import {
|
||||||
FaPlus,
|
FaPlus,
|
||||||
FaSearch,
|
FaSearch,
|
||||||
|
|
@ -10,94 +10,88 @@ import {
|
||||||
FaClock,
|
FaClock,
|
||||||
FaCheckCircle,
|
FaCheckCircle,
|
||||||
FaEye,
|
FaEye,
|
||||||
} from "react-icons/fa";
|
} from 'react-icons/fa'
|
||||||
import { MmApprovalWorkflow } from "../../../types/mm";
|
import { MmApprovalWorkflow } from '../../../types/mm'
|
||||||
import ApprovalWorkflowModal from "./ApprovalWorkflowModal";
|
import ApprovalWorkflowModal from './ApprovalWorkflowModal'
|
||||||
import { mockApprovalWorkflows } from "../../../mocks/mockApprovalWorkflows";
|
import { mockApprovalWorkflows } from '../../../mocks/mockApprovalWorkflows'
|
||||||
import {
|
import {
|
||||||
getApprovalLevelColor,
|
getApprovalLevelColor,
|
||||||
getApprovalLevelText,
|
getApprovalLevelText,
|
||||||
getRequestTypeColor,
|
getRequestTypeColor,
|
||||||
getRequestTypeText,
|
getRequestTypeText,
|
||||||
} from "../../../utils/erp";
|
} from '../../../utils/erp'
|
||||||
|
import { Container } from '@/components/shared'
|
||||||
|
|
||||||
const ApprovalWorkflows: React.FC = () => {
|
const ApprovalWorkflows: React.FC = () => {
|
||||||
const [searchTerm, setSearchTerm] = useState("");
|
const [searchTerm, setSearchTerm] = useState('')
|
||||||
const [showModal, setShowModal] = useState(false);
|
const [showModal, setShowModal] = useState(false)
|
||||||
const [editingWorkflow, setEditingWorkflow] =
|
const [editingWorkflow, setEditingWorkflow] = useState<MmApprovalWorkflow | null>(null)
|
||||||
useState<MmApprovalWorkflow | null>(null);
|
const [selectedWorkflow, setSelectedWorkflow] = useState<MmApprovalWorkflow | null>(null)
|
||||||
const [selectedWorkflow, setSelectedWorkflow] =
|
const [modalMode, setModalMode] = useState<'create' | 'view' | 'edit'>('create')
|
||||||
useState<MmApprovalWorkflow | null>(null);
|
|
||||||
const [modalMode, setModalMode] = useState<"create" | "view" | "edit">(
|
|
||||||
"create"
|
|
||||||
);
|
|
||||||
|
|
||||||
// Mock data - replace with actual API calls
|
// Mock data - replace with actual API calls
|
||||||
const [workflows] = useState<MmApprovalWorkflow[]>(mockApprovalWorkflows);
|
const [workflows] = useState<MmApprovalWorkflow[]>(mockApprovalWorkflows)
|
||||||
|
|
||||||
const filteredWorkflows = workflows.filter(
|
const filteredWorkflows = workflows.filter(
|
||||||
(workflow) =>
|
(workflow) =>
|
||||||
workflow.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
workflow.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||||
workflow.departmentId.toLowerCase().includes(searchTerm.toLowerCase())
|
workflow.departmentId.toLowerCase().includes(searchTerm.toLowerCase()),
|
||||||
);
|
)
|
||||||
|
|
||||||
const getTotalSteps = (workflow: MmApprovalWorkflow) => {
|
const getTotalSteps = (workflow: MmApprovalWorkflow) => {
|
||||||
return workflow.approvalLevels.length;
|
return workflow.approvalLevels.length
|
||||||
};
|
}
|
||||||
|
|
||||||
const getRequiredSteps = (workflow: MmApprovalWorkflow) => {
|
const getRequiredSteps = (workflow: MmApprovalWorkflow) => {
|
||||||
return workflow.approvalLevels.filter((level) => level.isRequired).length;
|
return workflow.approvalLevels.filter((level) => level.isRequired).length
|
||||||
};
|
}
|
||||||
|
|
||||||
const getMaxTimeout = (workflow: MmApprovalWorkflow) => {
|
const getMaxTimeout = (workflow: MmApprovalWorkflow) => {
|
||||||
return Math.max(
|
return Math.max(...workflow.approvalLevels.map((level) => level.timeoutDays || 0))
|
||||||
...workflow.approvalLevels.map((level) => level.timeoutDays || 0)
|
}
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleEdit = (workflow: MmApprovalWorkflow) => {
|
const handleEdit = (workflow: MmApprovalWorkflow) => {
|
||||||
setEditingWorkflow(workflow);
|
setEditingWorkflow(workflow)
|
||||||
setModalMode("edit");
|
setModalMode('edit')
|
||||||
setShowModal(true);
|
setShowModal(true)
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleView = (workflow: MmApprovalWorkflow) => {
|
const handleView = (workflow: MmApprovalWorkflow) => {
|
||||||
setEditingWorkflow(workflow);
|
setEditingWorkflow(workflow)
|
||||||
setModalMode("view");
|
setModalMode('view')
|
||||||
setShowModal(true);
|
setShowModal(true)
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleAddNew = () => {
|
const handleAddNew = () => {
|
||||||
setEditingWorkflow(null);
|
setEditingWorkflow(null)
|
||||||
setModalMode("create");
|
setModalMode('create')
|
||||||
setShowModal(true);
|
setShowModal(true)
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleViewDetails = (workflow: MmApprovalWorkflow) => {
|
const handleViewDetails = (workflow: MmApprovalWorkflow) => {
|
||||||
setSelectedWorkflow(workflow);
|
setSelectedWorkflow(workflow)
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleSaveWorkflow = (workflow: MmApprovalWorkflow) => {
|
const handleSaveWorkflow = (workflow: MmApprovalWorkflow) => {
|
||||||
// TODO: Implement save logic
|
// TODO: Implement save logic
|
||||||
console.log("Saving workflow:", workflow);
|
console.log('Saving workflow:', workflow)
|
||||||
setShowModal(false);
|
setShowModal(false)
|
||||||
setEditingWorkflow(null);
|
setEditingWorkflow(null)
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleCloseModal = () => {
|
const handleCloseModal = () => {
|
||||||
setShowModal(false);
|
setShowModal(false)
|
||||||
setEditingWorkflow(null);
|
setEditingWorkflow(null)
|
||||||
};
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-3 pt-2">
|
<Container>
|
||||||
|
<div className="space-y-2">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-2xl font-bold text-gray-900">Onay Süreçleri</h2>
|
<h2 className="text-2xl font-bold text-gray-900">Onay Süreçleri</h2>
|
||||||
<p className="text-gray-600">
|
<p className="text-gray-600">Departman bazlı satınalma onay süreçlerini yönetin</p>
|
||||||
Departman bazlı satınalma onay süreçlerini yönetin
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={handleAddNew}
|
onClick={handleAddNew}
|
||||||
|
|
@ -123,9 +117,7 @@ const ApprovalWorkflows: React.FC = () => {
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||||
{/* Workflow List */}
|
{/* Workflow List */}
|
||||||
<div className="space-y-3 pt-2">
|
<div className="space-y-3 pt-2">
|
||||||
<h3 className="text-base font-semibold text-gray-900">
|
<h3 className="text-base font-semibold text-gray-900">Onay Süreçleri</h3>
|
||||||
Onay Süreçleri
|
|
||||||
</h3>
|
|
||||||
{filteredWorkflows.map((workflow) => (
|
{filteredWorkflows.map((workflow) => (
|
||||||
<div
|
<div
|
||||||
key={workflow.id}
|
key={workflow.id}
|
||||||
|
|
@ -136,12 +128,10 @@ const ApprovalWorkflows: React.FC = () => {
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<div className="flex items-center space-x-2 mb-1">
|
<div className="flex items-center space-x-2 mb-1">
|
||||||
<FaCodeBranch className="w-4 h-4 text-gray-600" />
|
<FaCodeBranch className="w-4 h-4 text-gray-600" />
|
||||||
<h4 className="text-base font-semibold text-gray-900">
|
<h4 className="text-base font-semibold text-gray-900">{workflow.name}</h4>
|
||||||
{workflow.name}
|
|
||||||
</h4>
|
|
||||||
<span
|
<span
|
||||||
className={`px-2 py-1 rounded-full text-xs font-medium ${getRequestTypeColor(
|
className={`px-2 py-1 rounded-full text-xs font-medium ${getRequestTypeColor(
|
||||||
workflow.requestType
|
workflow.requestType,
|
||||||
)}`}
|
)}`}
|
||||||
>
|
>
|
||||||
{getRequestTypeText(workflow.requestType)}
|
{getRequestTypeText(workflow.requestType)}
|
||||||
|
|
@ -157,8 +147,8 @@ const ApprovalWorkflows: React.FC = () => {
|
||||||
<div className="flex space-x-1">
|
<div className="flex space-x-1">
|
||||||
<button
|
<button
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation()
|
||||||
handleView(workflow);
|
handleView(workflow)
|
||||||
}}
|
}}
|
||||||
className="p-1.5 text-gray-400 hover:text-blue-600 hover:bg-blue-50 rounded-md transition-colors"
|
className="p-1.5 text-gray-400 hover:text-blue-600 hover:bg-blue-50 rounded-md transition-colors"
|
||||||
title="Görüntüle"
|
title="Görüntüle"
|
||||||
|
|
@ -167,8 +157,8 @@ const ApprovalWorkflows: React.FC = () => {
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation()
|
||||||
handleEdit(workflow);
|
handleEdit(workflow)
|
||||||
}}
|
}}
|
||||||
className="p-1.5 text-gray-400 hover:text-green-600 hover:bg-green-50 rounded-md transition-colors"
|
className="p-1.5 text-gray-400 hover:text-green-600 hover:bg-green-50 rounded-md transition-colors"
|
||||||
title="Düzenle"
|
title="Düzenle"
|
||||||
|
|
@ -198,23 +188,19 @@ const ApprovalWorkflows: React.FC = () => {
|
||||||
<FaCheckCircle className="w-4 h-4 mr-1" />
|
<FaCheckCircle className="w-4 h-4 mr-1" />
|
||||||
Zorunlu Adım
|
Zorunlu Adım
|
||||||
</span>
|
</span>
|
||||||
<span className="font-medium">
|
<span className="font-medium">{getRequiredSteps(workflow)}</span>
|
||||||
{getRequiredSteps(workflow)}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<span className="text-gray-500 flex items-center">
|
<span className="text-gray-500 flex items-center">
|
||||||
<FaClock className="w-4 h-4 mr-1" />
|
<FaClock className="w-4 h-4 mr-1" />
|
||||||
Maks. Süre
|
Maks. Süre
|
||||||
</span>
|
</span>
|
||||||
<span className="font-medium">
|
<span className="font-medium">{getMaxTimeout(workflow)} gün</span>
|
||||||
{getMaxTimeout(workflow)} gün
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<span className="text-gray-500">Son Güncelleme</span>
|
<span className="text-gray-500">Son Güncelleme</span>
|
||||||
<span className="font-medium">
|
<span className="font-medium">
|
||||||
{workflow.lastModificationTime.toLocaleDateString("tr-TR")}
|
{workflow.lastModificationTime.toLocaleDateString('tr-TR')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -222,17 +208,13 @@ const ApprovalWorkflows: React.FC = () => {
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<span
|
<span
|
||||||
className={`px-2 py-1 rounded-full text-xs font-medium ${
|
className={`px-2 py-1 rounded-full text-xs font-medium ${
|
||||||
workflow.isActive
|
workflow.isActive ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
|
||||||
? "bg-green-100 text-green-800"
|
|
||||||
: "bg-red-100 text-red-800"
|
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{workflow.isActive ? "Aktif" : "Pasif"}
|
{workflow.isActive ? 'Aktif' : 'Pasif'}
|
||||||
</span>
|
</span>
|
||||||
<div className="flex items-center space-x-1">
|
<div className="flex items-center space-x-1">
|
||||||
{workflow.approvalLevels.some(
|
{workflow.approvalLevels.some((level) => level.isParallel) && (
|
||||||
(level) => level.isParallel
|
|
||||||
) && (
|
|
||||||
<span className="bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded flex items-center">
|
<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" />
|
<FaCodeBranch className="w-3 h-3 mr-1" />
|
||||||
Paralel Onay
|
Paralel Onay
|
||||||
|
|
@ -246,12 +228,9 @@ const ApprovalWorkflows: React.FC = () => {
|
||||||
{filteredWorkflows.length === 0 && (
|
{filteredWorkflows.length === 0 && (
|
||||||
<div className="text-center py-6">
|
<div className="text-center py-6">
|
||||||
<FaCodeBranch className="w-12 h-12 text-gray-400 mx-auto mb-4" />
|
<FaCodeBranch className="w-12 h-12 text-gray-400 mx-auto mb-4" />
|
||||||
<h3 className="text-base font-medium text-gray-900 mb-2">
|
<h3 className="text-base font-medium text-gray-900 mb-2">Onay süreci bulunamadı</h3>
|
||||||
Onay süreci bulunamadı
|
|
||||||
</h3>
|
|
||||||
<p className="text-sm text-gray-500">
|
<p className="text-sm text-gray-500">
|
||||||
Arama kriterlerinizi değiştirin veya yeni bir onay süreci
|
Arama kriterlerinizi değiştirin veya yeni bir onay süreci ekleyin.
|
||||||
ekleyin.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -259,9 +238,7 @@ const ApprovalWorkflows: React.FC = () => {
|
||||||
|
|
||||||
{/* Workflow Details */}
|
{/* Workflow Details */}
|
||||||
<div className="space-y-3 pt-2">
|
<div className="space-y-3 pt-2">
|
||||||
<h3 className="text-base font-semibold text-gray-900">
|
<h3 className="text-base font-semibold text-gray-900">Onay Adımları</h3>
|
||||||
Onay Adımları
|
|
||||||
</h3>
|
|
||||||
{selectedWorkflow ? (
|
{selectedWorkflow ? (
|
||||||
<div className="bg-white rounded-lg shadow-md border border-gray-200 p-3">
|
<div className="bg-white rounded-lg shadow-md border border-gray-200 p-3">
|
||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
|
|
@ -270,16 +247,9 @@ const ApprovalWorkflows: React.FC = () => {
|
||||||
</h4>
|
</h4>
|
||||||
<div className="grid grid-cols-2 gap-1 text-xs text-gray-600 mb-2">
|
<div className="grid grid-cols-2 gap-1 text-xs text-gray-600 mb-2">
|
||||||
<div>Departman: {selectedWorkflow.departmentId}</div>
|
<div>Departman: {selectedWorkflow.departmentId}</div>
|
||||||
<div>
|
<div>Tür: {getRequestTypeText(selectedWorkflow.requestType)}</div>
|
||||||
Tür: {getRequestTypeText(selectedWorkflow.requestType)}
|
<div>Tutar Limiti: ₺{selectedWorkflow.amountThreshold.toLocaleString()}</div>
|
||||||
</div>
|
<div>Durum: {selectedWorkflow.isActive ? 'Aktif' : 'Pasif'}</div>
|
||||||
<div>
|
|
||||||
Tutar Limiti: ₺
|
|
||||||
{selectedWorkflow.amountThreshold.toLocaleString()}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
Durum: {selectedWorkflow.isActive ? "Aktif" : "Pasif"}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -287,10 +257,7 @@ const ApprovalWorkflows: React.FC = () => {
|
||||||
{selectedWorkflow.approvalLevels
|
{selectedWorkflow.approvalLevels
|
||||||
.sort((a, b) => a.sequence - b.sequence)
|
.sort((a, b) => a.sequence - b.sequence)
|
||||||
.map((level, index) => (
|
.map((level, index) => (
|
||||||
<div
|
<div key={level.id} className="border border-gray-200 rounded-lg p-2">
|
||||||
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 justify-between mb-2">
|
||||||
<div className="flex items-center space-x-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">
|
<span className="bg-blue-100 text-blue-800 text-xs font-medium px-2 py-1 rounded">
|
||||||
|
|
@ -298,7 +265,7 @@ const ApprovalWorkflows: React.FC = () => {
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
className={`px-2 py-1 rounded-full text-xs font-medium ${getApprovalLevelColor(
|
className={`px-2 py-1 rounded-full text-xs font-medium ${getApprovalLevelColor(
|
||||||
level.level
|
level.level,
|
||||||
)}`}
|
)}`}
|
||||||
>
|
>
|
||||||
{getApprovalLevelText(level.level)}
|
{getApprovalLevelText(level.level)}
|
||||||
|
|
@ -323,15 +290,10 @@ const ApprovalWorkflows: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="text-xs">
|
<div className="text-xs">
|
||||||
<div className="font-medium text-gray-900 mb-1">
|
<div className="font-medium text-gray-900 mb-1">Onaylayanlar:</div>
|
||||||
Onaylayanlar:
|
|
||||||
</div>
|
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
{level.approverNames.map((name, idx) => (
|
{level.approverNames.map((name, idx) => (
|
||||||
<div
|
<div key={idx} className="flex items-center space-x-2">
|
||||||
key={idx}
|
|
||||||
className="flex items-center space-x-2"
|
|
||||||
>
|
|
||||||
<FaUsers className="w-4 h-4 text-gray-400" />
|
<FaUsers className="w-4 h-4 text-gray-400" />
|
||||||
<span className="text-gray-700">{name}</span>
|
<span className="text-gray-700">{name}</span>
|
||||||
<span className="text-xs text-gray-500">
|
<span className="text-xs text-gray-500">
|
||||||
|
|
@ -388,6 +350,7 @@ const ApprovalWorkflows: React.FC = () => {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Approval Workflow Modal */}
|
{/* Approval Workflow Modal */}
|
||||||
<ApprovalWorkflowModal
|
<ApprovalWorkflowModal
|
||||||
|
|
@ -397,8 +360,8 @@ const ApprovalWorkflows: React.FC = () => {
|
||||||
workflow={editingWorkflow}
|
workflow={editingWorkflow}
|
||||||
mode={modalMode}
|
mode={modalMode}
|
||||||
/>
|
/>
|
||||||
</div>
|
</Container>
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
export default ApprovalWorkflows;
|
export default ApprovalWorkflows
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useState } from "react";
|
import React, { useState } from 'react'
|
||||||
import {
|
import {
|
||||||
FaSearch,
|
FaSearch,
|
||||||
FaFilter,
|
FaFilter,
|
||||||
|
|
@ -9,14 +9,14 @@ import {
|
||||||
FaBox,
|
FaBox,
|
||||||
FaUser,
|
FaUser,
|
||||||
FaFileAlt,
|
FaFileAlt,
|
||||||
} from "react-icons/fa";
|
} from 'react-icons/fa'
|
||||||
import {
|
import {
|
||||||
DeliveryStatusEnum,
|
DeliveryStatusEnum,
|
||||||
MmDelivery,
|
MmDelivery,
|
||||||
MmGoodsReceipt,
|
MmGoodsReceipt,
|
||||||
ReceiptStatusEnum,
|
ReceiptStatusEnum,
|
||||||
QualityStatusEnum,
|
QualityStatusEnum,
|
||||||
} from "../../../types/mm";
|
} from '../../../types/mm'
|
||||||
import {
|
import {
|
||||||
getConditionColor,
|
getConditionColor,
|
||||||
getConditionText,
|
getConditionText,
|
||||||
|
|
@ -24,43 +24,34 @@ import {
|
||||||
getRequestTypeText,
|
getRequestTypeText,
|
||||||
getDeliveryStatusColor,
|
getDeliveryStatusColor,
|
||||||
getDeliveryStatusIcon,
|
getDeliveryStatusIcon,
|
||||||
} from "../../../utils/erp";
|
} from '../../../utils/erp'
|
||||||
import { mockDeliveries } from "../../../mocks/mockDeliveries";
|
import { mockDeliveries } from '../../../mocks/mockDeliveries'
|
||||||
import DeliveryTrackingModal from "./DeliveryTrackingModal";
|
import DeliveryTrackingModal from './DeliveryTrackingModal'
|
||||||
import Widget from "../../../components/common/Widget";
|
import Widget from '../../../components/common/Widget'
|
||||||
|
import { Container } from '@/components/shared'
|
||||||
|
|
||||||
const DeliveryTracking: React.FC = () => {
|
const DeliveryTracking: React.FC = () => {
|
||||||
const [searchTerm, setSearchTerm] = useState("");
|
const [searchTerm, setSearchTerm] = useState('')
|
||||||
const [statusFilter, setStatusFilter] = useState<DeliveryStatusEnum | "all">(
|
const [statusFilter, setStatusFilter] = useState<DeliveryStatusEnum | 'all'>('all')
|
||||||
"all"
|
const [selectedDelivery, setSelectedDelivery] = useState<MmDelivery | null>(null)
|
||||||
);
|
|
||||||
const [selectedDelivery, setSelectedDelivery] = useState<MmDelivery | null>(
|
|
||||||
null
|
|
||||||
);
|
|
||||||
|
|
||||||
// Modal state
|
// Modal state
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [isModalOpen, setIsModalOpen] = useState(false)
|
||||||
const [modalMode, setModalMode] = useState<"create" | "view" | "edit">(
|
const [modalMode, setModalMode] = useState<'create' | 'view' | 'edit'>('view')
|
||||||
"view"
|
const [selectedReceipt, setSelectedReceipt] = useState<MmGoodsReceipt | null>(null)
|
||||||
);
|
|
||||||
const [selectedReceipt, setSelectedReceipt] = useState<MmGoodsReceipt | null>(
|
|
||||||
null
|
|
||||||
);
|
|
||||||
|
|
||||||
// Mock data - replace with actual API calls
|
// Mock data - replace with actual API calls
|
||||||
const [deliveries] = useState<MmDelivery[]>(mockDeliveries);
|
const [deliveries] = useState<MmDelivery[]>(mockDeliveries)
|
||||||
|
|
||||||
// Convert Delivery to GoodsReceipt for modal compatibility
|
// Convert Delivery to GoodsReceipt for modal compatibility
|
||||||
const convertDeliveryToGoodsReceipt = (
|
const convertDeliveryToGoodsReceipt = (delivery: MmDelivery): MmGoodsReceipt => {
|
||||||
delivery: MmDelivery
|
|
||||||
): MmGoodsReceipt => {
|
|
||||||
return {
|
return {
|
||||||
id: delivery.id,
|
id: delivery.id,
|
||||||
receiptNumber: delivery.deliveryNumber,
|
receiptNumber: delivery.deliveryNumber,
|
||||||
orderId: delivery.orderNumber,
|
orderId: delivery.orderNumber,
|
||||||
receiptDate: delivery.actualDeliveryDate || delivery.expectedDeliveryDate,
|
receiptDate: delivery.actualDeliveryDate || delivery.expectedDeliveryDate,
|
||||||
receivedBy: delivery.receivedBy || "",
|
receivedBy: delivery.receivedBy || '',
|
||||||
warehouseId: "WH001", // Default warehouse, could be mapped from delivery
|
warehouseId: 'WH001', // Default warehouse, could be mapped from delivery
|
||||||
status:
|
status:
|
||||||
delivery.status === DeliveryStatusEnum.Delivered
|
delivery.status === DeliveryStatusEnum.Delivered
|
||||||
? ReceiptStatusEnum.Completed
|
? ReceiptStatusEnum.Completed
|
||||||
|
|
@ -74,100 +65,84 @@ const DeliveryTracking: React.FC = () => {
|
||||||
receivedQuantity: item.deliveredQuantity,
|
receivedQuantity: item.deliveredQuantity,
|
||||||
acceptedQuantity: item.deliveredQuantity,
|
acceptedQuantity: item.deliveredQuantity,
|
||||||
rejectedQuantity: 0,
|
rejectedQuantity: 0,
|
||||||
lotNumber: "",
|
lotNumber: '',
|
||||||
expiryDate: undefined,
|
expiryDate: undefined,
|
||||||
qualityStatus:
|
qualityStatus:
|
||||||
item.condition === "Good"
|
item.condition === 'Good'
|
||||||
? QualityStatusEnum.Approved
|
? QualityStatusEnum.Approved
|
||||||
: item.condition === "Damaged"
|
: item.condition === 'Damaged'
|
||||||
? QualityStatusEnum.Rejected
|
? QualityStatusEnum.Rejected
|
||||||
: QualityStatusEnum.Pending,
|
: QualityStatusEnum.Pending,
|
||||||
storageLocation: undefined,
|
storageLocation: undefined,
|
||||||
})),
|
})),
|
||||||
creationTime: new Date(),
|
creationTime: new Date(),
|
||||||
lastModificationTime: new Date(),
|
lastModificationTime: new Date(),
|
||||||
};
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleViewDelivery = (delivery: MmDelivery) => {
|
const handleViewDelivery = (delivery: MmDelivery) => {
|
||||||
const receipt = convertDeliveryToGoodsReceipt(delivery);
|
const receipt = convertDeliveryToGoodsReceipt(delivery)
|
||||||
setSelectedReceipt(receipt);
|
setSelectedReceipt(receipt)
|
||||||
setModalMode("view");
|
setModalMode('view')
|
||||||
setIsModalOpen(true);
|
setIsModalOpen(true)
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleEditDelivery = (delivery: MmDelivery) => {
|
const handleEditDelivery = (delivery: MmDelivery) => {
|
||||||
const receipt = convertDeliveryToGoodsReceipt(delivery);
|
const receipt = convertDeliveryToGoodsReceipt(delivery)
|
||||||
setSelectedReceipt(receipt);
|
setSelectedReceipt(receipt)
|
||||||
setModalMode("edit");
|
setModalMode('edit')
|
||||||
setIsModalOpen(true);
|
setIsModalOpen(true)
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleSaveReceipt = (receipt: MmGoodsReceipt) => {
|
const handleSaveReceipt = (receipt: MmGoodsReceipt) => {
|
||||||
// Handle saving the receipt
|
// Handle saving the receipt
|
||||||
// This would typically involve API calls to update the backend
|
// This would typically involve API calls to update the backend
|
||||||
console.log("Saving receipt:", receipt);
|
console.log('Saving receipt:', receipt)
|
||||||
setIsModalOpen(false);
|
setIsModalOpen(false)
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleCloseModal = () => {
|
const handleCloseModal = () => {
|
||||||
setIsModalOpen(false);
|
setIsModalOpen(false)
|
||||||
setSelectedReceipt(null);
|
setSelectedReceipt(null)
|
||||||
};
|
}
|
||||||
|
|
||||||
const filteredDeliveries = deliveries.filter((delivery) => {
|
const filteredDeliveries = deliveries.filter((delivery) => {
|
||||||
const matchesSearch =
|
const matchesSearch =
|
||||||
delivery.deliveryNumber
|
delivery.deliveryNumber.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||||
.toLowerCase()
|
|
||||||
.includes(searchTerm.toLowerCase()) ||
|
|
||||||
delivery.orderNumber.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
delivery.orderNumber.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||||
delivery.supplierName.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
delivery.supplierName.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||||
(delivery.trackingNumber &&
|
(delivery.trackingNumber &&
|
||||||
delivery.trackingNumber
|
delivery.trackingNumber.toLowerCase().includes(searchTerm.toLowerCase()))
|
||||||
.toLowerCase()
|
const matchesStatus = statusFilter === 'all' || delivery.status === statusFilter
|
||||||
.includes(searchTerm.toLowerCase()));
|
return matchesSearch && matchesStatus
|
||||||
const matchesStatus =
|
})
|
||||||
statusFilter === "all" || delivery.status === statusFilter;
|
|
||||||
return matchesSearch && matchesStatus;
|
|
||||||
});
|
|
||||||
|
|
||||||
const isDelayed = (delivery: MmDelivery) => {
|
const isDelayed = (delivery: MmDelivery) => {
|
||||||
const today = new Date();
|
const today = new Date()
|
||||||
return (
|
return delivery.expectedDeliveryDate < today && !delivery.actualDeliveryDate
|
||||||
delivery.expectedDeliveryDate < today && !delivery.actualDeliveryDate
|
}
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getDaysDelay = (delivery: MmDelivery) => {
|
const getDaysDelay = (delivery: MmDelivery) => {
|
||||||
if (!isDelayed(delivery)) return 0;
|
if (!isDelayed(delivery)) return 0
|
||||||
const today = new Date();
|
const today = new Date()
|
||||||
const diffTime = today.getTime() - delivery.expectedDeliveryDate.getTime();
|
const diffTime = today.getTime() - delivery.expectedDeliveryDate.getTime()
|
||||||
return Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
return Math.ceil(diffTime / (1000 * 60 * 60 * 24))
|
||||||
};
|
}
|
||||||
|
|
||||||
const getDeliveryProgress = (delivery: MmDelivery) => {
|
const getDeliveryProgress = (delivery: MmDelivery) => {
|
||||||
const totalQuantity = delivery.items.reduce(
|
const totalQuantity = delivery.items.reduce((sum, item) => sum + item.orderedQuantity, 0)
|
||||||
(sum, item) => sum + item.orderedQuantity,
|
const deliveredQuantity = delivery.items.reduce((sum, item) => sum + item.deliveredQuantity, 0)
|
||||||
0
|
return totalQuantity > 0 ? Math.round((deliveredQuantity / totalQuantity) * 100) : 0
|
||||||
);
|
}
|
||||||
const deliveredQuantity = delivery.items.reduce(
|
|
||||||
(sum, item) => sum + item.deliveredQuantity,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
return totalQuantity > 0
|
|
||||||
? Math.round((deliveredQuantity / totalQuantity) * 100)
|
|
||||||
: 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-3 pt-2">
|
<Container>
|
||||||
|
<div className="space-y-2">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-2xl font-bold text-gray-900">Teslimat Takibi</h2>
|
<h2 className="text-2xl font-bold text-gray-900">Teslimat Takibi</h2>
|
||||||
<p className="text-gray-600">
|
<p className="text-gray-600">Sipariş teslimatlarını takip edin ve yönetin</p>
|
||||||
Sipariş teslimatlarını takip edin ve yönetin
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -181,7 +156,7 @@ const DeliveryTracking: React.FC = () => {
|
||||||
DeliveryStatusEnum.InTransit,
|
DeliveryStatusEnum.InTransit,
|
||||||
DeliveryStatusEnum.OutForDelivery,
|
DeliveryStatusEnum.OutForDelivery,
|
||||||
DeliveryStatusEnum.Shipped,
|
DeliveryStatusEnum.Shipped,
|
||||||
].includes(d.status)
|
].includes(d.status),
|
||||||
).length
|
).length
|
||||||
}
|
}
|
||||||
color="blue"
|
color="blue"
|
||||||
|
|
@ -190,10 +165,7 @@ const DeliveryTracking: React.FC = () => {
|
||||||
|
|
||||||
<Widget
|
<Widget
|
||||||
title="Teslim Edildi"
|
title="Teslim Edildi"
|
||||||
value={
|
value={deliveries.filter((d) => d.status === DeliveryStatusEnum.Delivered).length}
|
||||||
deliveries.filter((d) => d.status === DeliveryStatusEnum.Delivered)
|
|
||||||
.length
|
|
||||||
}
|
|
||||||
color="green"
|
color="green"
|
||||||
icon="FaCheckCircle"
|
icon="FaCheckCircle"
|
||||||
/>
|
/>
|
||||||
|
|
@ -201,9 +173,7 @@ const DeliveryTracking: React.FC = () => {
|
||||||
<Widget
|
<Widget
|
||||||
title="Kısmi Teslim"
|
title="Kısmi Teslim"
|
||||||
value={
|
value={
|
||||||
deliveries.filter(
|
deliveries.filter((d) => d.status === DeliveryStatusEnum.PartiallyDelivered).length
|
||||||
(d) => d.status === DeliveryStatusEnum.PartiallyDelivered
|
|
||||||
).length
|
|
||||||
}
|
}
|
||||||
color="orange"
|
color="orange"
|
||||||
icon="FaExclamationTriangle"
|
icon="FaExclamationTriangle"
|
||||||
|
|
@ -212,9 +182,8 @@ const DeliveryTracking: React.FC = () => {
|
||||||
<Widget
|
<Widget
|
||||||
title="Gecikmeli"
|
title="Gecikmeli"
|
||||||
value={
|
value={
|
||||||
deliveries.filter(
|
deliveries.filter((d) => d.status === DeliveryStatusEnum.Delayed || isDelayed(d))
|
||||||
(d) => d.status === DeliveryStatusEnum.Delayed || isDelayed(d)
|
.length
|
||||||
).length
|
|
||||||
}
|
}
|
||||||
color="teal"
|
color="teal"
|
||||||
icon="FaClock"
|
icon="FaClock"
|
||||||
|
|
@ -237,9 +206,7 @@ const DeliveryTracking: React.FC = () => {
|
||||||
<FaFilter className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
|
<FaFilter className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
|
||||||
<select
|
<select
|
||||||
value={statusFilter}
|
value={statusFilter}
|
||||||
onChange={(e) =>
|
onChange={(e) => setStatusFilter(e.target.value as DeliveryStatusEnum | 'all')}
|
||||||
setStatusFilter(e.target.value as DeliveryStatusEnum | "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 text-sm"
|
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 text-sm"
|
||||||
>
|
>
|
||||||
<option value="all">Tüm Durumlar</option>
|
<option value="all">Tüm Durumlar</option>
|
||||||
|
|
@ -248,9 +215,7 @@ const DeliveryTracking: React.FC = () => {
|
||||||
<option value={DeliveryStatusEnum.InTransit}>Yolda</option>
|
<option value={DeliveryStatusEnum.InTransit}>Yolda</option>
|
||||||
<option value={DeliveryStatusEnum.OutForDelivery}>Dağıtımda</option>
|
<option value={DeliveryStatusEnum.OutForDelivery}>Dağıtımda</option>
|
||||||
<option value={DeliveryStatusEnum.Delivered}>Teslim Edildi</option>
|
<option value={DeliveryStatusEnum.Delivered}>Teslim Edildi</option>
|
||||||
<option value={DeliveryStatusEnum.PartiallyDelivered}>
|
<option value={DeliveryStatusEnum.PartiallyDelivered}>Kısmi Teslim</option>
|
||||||
Kısmi Teslim
|
|
||||||
</option>
|
|
||||||
<option value={DeliveryStatusEnum.Delayed}>Gecikmeli</option>
|
<option value={DeliveryStatusEnum.Delayed}>Gecikmeli</option>
|
||||||
<option value={DeliveryStatusEnum.Returned}>İade Edildi</option>
|
<option value={DeliveryStatusEnum.Returned}>İade Edildi</option>
|
||||||
<option value={DeliveryStatusEnum.Cancelled}>İptal Edildi</option>
|
<option value={DeliveryStatusEnum.Cancelled}>İptal Edildi</option>
|
||||||
|
|
@ -266,9 +231,7 @@ const DeliveryTracking: React.FC = () => {
|
||||||
<div
|
<div
|
||||||
key={delivery.id}
|
key={delivery.id}
|
||||||
className={`bg-white rounded-lg shadow-md border border-gray-200 p-3 hover:shadow-lg transition-shadow cursor-pointer ${
|
className={`bg-white rounded-lg shadow-md border border-gray-200 p-3 hover:shadow-lg transition-shadow cursor-pointer ${
|
||||||
selectedDelivery?.id === delivery.id
|
selectedDelivery?.id === delivery.id ? 'ring-2 ring-blue-500' : ''
|
||||||
? "ring-2 ring-blue-500"
|
|
||||||
: ""
|
|
||||||
}`}
|
}`}
|
||||||
onClick={() => setSelectedDelivery(delivery)}
|
onClick={() => setSelectedDelivery(delivery)}
|
||||||
>
|
>
|
||||||
|
|
@ -280,7 +243,7 @@ const DeliveryTracking: React.FC = () => {
|
||||||
</h4>
|
</h4>
|
||||||
<span
|
<span
|
||||||
className={`px-2 py-1 rounded-full text-xs font-medium flex items-center space-x-1 ${getDeliveryStatusColor(
|
className={`px-2 py-1 rounded-full text-xs font-medium flex items-center space-x-1 ${getDeliveryStatusColor(
|
||||||
delivery.status
|
delivery.status,
|
||||||
)}`}
|
)}`}
|
||||||
>
|
>
|
||||||
{getDeliveryStatusIcon(delivery.status)}
|
{getDeliveryStatusIcon(delivery.status)}
|
||||||
|
|
@ -292,12 +255,8 @@ const DeliveryTracking: React.FC = () => {
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-gray-600 mb-1">
|
<p className="text-xs text-gray-600 mb-1">Sipariş: {delivery.orderNumber}</p>
|
||||||
Sipariş: {delivery.orderNumber}
|
<p className="text-sm text-gray-600">{delivery.supplierName}</p>
|
||||||
</p>
|
|
||||||
<p className="text-sm text-gray-600">
|
|
||||||
{delivery.supplierName}
|
|
||||||
</p>
|
|
||||||
{delivery.trackingNumber && (
|
{delivery.trackingNumber && (
|
||||||
<p className="text-xs text-blue-600 mt-1">
|
<p className="text-xs text-blue-600 mt-1">
|
||||||
Takip No: {delivery.trackingNumber}
|
Takip No: {delivery.trackingNumber}
|
||||||
|
|
@ -327,17 +286,15 @@ const DeliveryTracking: React.FC = () => {
|
||||||
<span className="text-gray-500">Beklenen Tarih</span>
|
<span className="text-gray-500">Beklenen Tarih</span>
|
||||||
<p
|
<p
|
||||||
className={`font-medium ${
|
className={`font-medium ${
|
||||||
isDelayed(delivery) ? "text-red-600" : "text-gray-900"
|
isDelayed(delivery) ? 'text-red-600' : 'text-gray-900'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{delivery.expectedDeliveryDate.toLocaleDateString("tr-TR")}
|
{delivery.expectedDeliveryDate.toLocaleDateString('tr-TR')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span className="text-gray-500">Teslimat Durumu</span>
|
<span className="text-gray-500">Teslimat Durumu</span>
|
||||||
<p className="font-medium text-gray-900">
|
<p className="font-medium text-gray-900">{getDeliveryProgress(delivery)}%</p>
|
||||||
{getDeliveryProgress(delivery)}%
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -347,13 +304,12 @@ const DeliveryTracking: React.FC = () => {
|
||||||
<div
|
<div
|
||||||
className={`h-2 rounded-full transition-all duration-300 ${
|
className={`h-2 rounded-full transition-all duration-300 ${
|
||||||
delivery.status === DeliveryStatusEnum.Delivered
|
delivery.status === DeliveryStatusEnum.Delivered
|
||||||
? "bg-green-600"
|
? 'bg-green-600'
|
||||||
: delivery.status ===
|
: delivery.status === DeliveryStatusEnum.PartiallyDelivered
|
||||||
DeliveryStatusEnum.PartiallyDelivered
|
? 'bg-orange-600'
|
||||||
? "bg-orange-600"
|
|
||||||
: delivery.status === DeliveryStatusEnum.Delayed
|
: delivery.status === DeliveryStatusEnum.Delayed
|
||||||
? "bg-red-600"
|
? 'bg-red-600'
|
||||||
: "bg-blue-600"
|
: 'bg-blue-600'
|
||||||
}`}
|
}`}
|
||||||
style={{ width: `${getDeliveryProgress(delivery)}%` }}
|
style={{ width: `${getDeliveryProgress(delivery)}%` }}
|
||||||
></div>
|
></div>
|
||||||
|
|
@ -365,9 +321,7 @@ const DeliveryTracking: React.FC = () => {
|
||||||
<FaMapMarkerAlt className="w-3 h-3" />
|
<FaMapMarkerAlt className="w-3 h-3" />
|
||||||
<span>{getRequestTypeText(delivery.requestType)}</span>
|
<span>{getRequestTypeText(delivery.requestType)}</span>
|
||||||
</div>
|
</div>
|
||||||
{delivery.courierCompany && (
|
{delivery.courierCompany && <span>{delivery.courierCompany}</span>}
|
||||||
<span>{delivery.courierCompany}</span>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
@ -375,21 +329,15 @@ const DeliveryTracking: React.FC = () => {
|
||||||
{filteredDeliveries.length === 0 && (
|
{filteredDeliveries.length === 0 && (
|
||||||
<div className="text-center py-6">
|
<div className="text-center py-6">
|
||||||
<FaTruck className="w-12 h-12 text-gray-400 mx-auto mb-4" />
|
<FaTruck className="w-12 h-12 text-gray-400 mx-auto mb-4" />
|
||||||
<h3 className="text-base font-medium text-gray-900 mb-2">
|
<h3 className="text-base font-medium text-gray-900 mb-2">Teslimat bulunamadı</h3>
|
||||||
Teslimat bulunamadı
|
<p className="text-sm text-gray-500">Arama kriterlerinizi değiştirin.</p>
|
||||||
</h3>
|
|
||||||
<p className="text-sm text-gray-500">
|
|
||||||
Arama kriterlerinizi değiştirin.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Delivery Details */}
|
{/* Delivery Details */}
|
||||||
<div className="space-y-3 pt-2">
|
<div className="space-y-3 pt-2">
|
||||||
<h3 className="text-base font-semibold text-gray-900">
|
<h3 className="text-base font-semibold text-gray-900">Teslimat Detayları</h3>
|
||||||
Teslimat Detayları
|
|
||||||
</h3>
|
|
||||||
{selectedDelivery ? (
|
{selectedDelivery ? (
|
||||||
<div className="bg-white rounded-lg shadow-md border border-gray-200 p-4">
|
<div className="bg-white rounded-lg shadow-md border border-gray-200 p-4">
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
|
|
@ -399,45 +347,35 @@ const DeliveryTracking: React.FC = () => {
|
||||||
</h4>
|
</h4>
|
||||||
<span
|
<span
|
||||||
className={`px-3 py-1 rounded-full text-sm font-medium flex items-center space-x-1 ${getDeliveryStatusColor(
|
className={`px-3 py-1 rounded-full text-sm font-medium flex items-center space-x-1 ${getDeliveryStatusColor(
|
||||||
selectedDelivery.status
|
selectedDelivery.status,
|
||||||
)}`}
|
)}`}
|
||||||
>
|
>
|
||||||
{getDeliveryStatusIcon(selectedDelivery.status)}
|
{getDeliveryStatusIcon(selectedDelivery.status)}
|
||||||
<span>
|
<span>{getDeliveryStatusText(selectedDelivery.status)}</span>
|
||||||
{getDeliveryStatusText(selectedDelivery.status)}
|
|
||||||
</span>
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-3 text-xs mb-3">
|
<div className="grid grid-cols-2 gap-3 text-xs mb-3">
|
||||||
<div>
|
<div>
|
||||||
<span className="text-gray-500">Sipariş No:</span>
|
<span className="text-gray-500">Sipariş No:</span>
|
||||||
<p className="font-medium">
|
<p className="font-medium">{selectedDelivery.orderNumber}</p>
|
||||||
{selectedDelivery.orderNumber}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span className="text-gray-500">Tedarikçi:</span>
|
<span className="text-gray-500">Tedarikçi:</span>
|
||||||
<p className="font-medium">
|
<p className="font-medium">{selectedDelivery.supplierName}</p>
|
||||||
{selectedDelivery.supplierName}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span className="text-gray-500">Beklenen Tarih:</span>
|
<span className="text-gray-500">Beklenen Tarih:</span>
|
||||||
<p className="font-medium">
|
<p className="font-medium">
|
||||||
{selectedDelivery.expectedDeliveryDate.toLocaleDateString(
|
{selectedDelivery.expectedDeliveryDate.toLocaleDateString('tr-TR')}
|
||||||
"tr-TR"
|
|
||||||
)}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span className="text-gray-500">Gerçek Tarih:</span>
|
<span className="text-gray-500">Gerçek Tarih:</span>
|
||||||
<p className="font-medium">
|
<p className="font-medium">
|
||||||
{selectedDelivery.actualDeliveryDate
|
{selectedDelivery.actualDeliveryDate
|
||||||
? selectedDelivery.actualDeliveryDate.toLocaleDateString(
|
? selectedDelivery.actualDeliveryDate.toLocaleDateString('tr-TR')
|
||||||
"tr-TR"
|
: 'Henüz teslim edilmedi'}
|
||||||
)
|
|
||||||
: "Henüz teslim edilmedi"}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -446,17 +384,13 @@ const DeliveryTracking: React.FC = () => {
|
||||||
<div className="mb-3 p-2 bg-blue-50 rounded-lg text-xs">
|
<div className="mb-3 p-2 bg-blue-50 rounded-lg text-xs">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<span className="text-sm text-gray-600">
|
<span className="text-sm text-gray-600">Kargo Takip No:</span>
|
||||||
Kargo Takip No:
|
|
||||||
</span>
|
|
||||||
<p className="font-medium text-blue-900">
|
<p className="font-medium text-blue-900">
|
||||||
{selectedDelivery.trackingNumber}
|
{selectedDelivery.trackingNumber}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span className="text-sm text-gray-600">
|
<span className="text-sm text-gray-600">Kargo Şirketi:</span>
|
||||||
Kargo Şirketi:
|
|
||||||
</span>
|
|
||||||
<p className="font-medium text-blue-900">
|
<p className="font-medium text-blue-900">
|
||||||
{selectedDelivery.courierCompany}
|
{selectedDelivery.courierCompany}
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -481,36 +415,25 @@ const DeliveryTracking: React.FC = () => {
|
||||||
<FaUser className="w-4 h-4 mr-1" />
|
<FaUser className="w-4 h-4 mr-1" />
|
||||||
Teslim Alan:
|
Teslim Alan:
|
||||||
</span>
|
</span>
|
||||||
<p className="text-xs text-gray-900">
|
<p className="text-xs text-gray-900">{selectedDelivery.receivedBy}</p>
|
||||||
{selectedDelivery.receivedBy}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Items */}
|
{/* Items */}
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<h5 className="font-medium text-gray-900 mb-3">
|
<h5 className="font-medium text-gray-900 mb-3">Teslimat Kalemleri</h5>
|
||||||
Teslimat Kalemleri
|
|
||||||
</h5>
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{selectedDelivery.items.map((item) => (
|
{selectedDelivery.items.map((item) => (
|
||||||
<div
|
<div key={item.id} className="border border-gray-200 rounded-lg p-2">
|
||||||
key={item.id}
|
|
||||||
className="border border-gray-200 rounded-lg p-2"
|
|
||||||
>
|
|
||||||
<div className="flex items-start justify-between mb-2">
|
<div className="flex items-start justify-between mb-2">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<h6 className="font-medium text-gray-900">
|
<h6 className="font-medium text-gray-900">{item.material?.name}</h6>
|
||||||
{item.material?.name}
|
<p className="text-sm text-gray-600">{item.material?.id}</p>
|
||||||
</h6>
|
|
||||||
<p className="text-sm text-gray-600">
|
|
||||||
{item.material?.id}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
className={`px-2 py-1 rounded-full text-xs font-medium ${getConditionColor(
|
className={`px-2 py-1 rounded-full text-xs font-medium ${getConditionColor(
|
||||||
item.condition
|
item.condition,
|
||||||
)}`}
|
)}`}
|
||||||
>
|
>
|
||||||
{getConditionText(item.condition)}
|
{getConditionText(item.condition)}
|
||||||
|
|
@ -533,8 +456,7 @@ const DeliveryTracking: React.FC = () => {
|
||||||
<div>
|
<div>
|
||||||
<span className="text-gray-500">Kalan:</span>
|
<span className="text-gray-500">Kalan:</span>
|
||||||
<p className="font-medium">
|
<p className="font-medium">
|
||||||
{item.orderedQuantity - item.deliveredQuantity}{" "}
|
{item.orderedQuantity - item.deliveredQuantity} {item.unit}
|
||||||
{item.unit}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -551,12 +473,8 @@ const DeliveryTracking: React.FC = () => {
|
||||||
|
|
||||||
{selectedDelivery.notes && (
|
{selectedDelivery.notes && (
|
||||||
<div className="mb-3 p-2 bg-yellow-50 rounded-lg">
|
<div className="mb-3 p-2 bg-yellow-50 rounded-lg">
|
||||||
<span className="text-sm text-gray-600 font-medium">
|
<span className="text-sm text-gray-600 font-medium">Genel Notlar:</span>
|
||||||
Genel Notlar:
|
<p className="text-sm text-gray-900 mt-1">{selectedDelivery.notes}</p>
|
||||||
</span>
|
|
||||||
<p className="text-sm text-gray-900 mt-1">
|
|
||||||
{selectedDelivery.notes}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
@ -568,10 +486,7 @@ const DeliveryTracking: React.FC = () => {
|
||||||
</span>
|
</span>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
{selectedDelivery.attachments.map((attachment, index) => (
|
{selectedDelivery.attachments.map((attachment, index) => (
|
||||||
<div
|
<div key={index} className="flex items-center space-x-2 text-sm">
|
||||||
key={index}
|
|
||||||
className="flex items-center space-x-2 text-sm"
|
|
||||||
>
|
|
||||||
<FaFileAlt className="w-4 h-4 text-gray-400" />
|
<FaFileAlt className="w-4 h-4 text-gray-400" />
|
||||||
<span className="text-blue-600 hover:underline cursor-pointer">
|
<span className="text-blue-600 hover:underline cursor-pointer">
|
||||||
{attachment}
|
{attachment}
|
||||||
|
|
@ -595,6 +510,7 @@ const DeliveryTracking: React.FC = () => {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Delivery Tracking Modal */}
|
{/* Delivery Tracking Modal */}
|
||||||
<DeliveryTrackingModal
|
<DeliveryTrackingModal
|
||||||
|
|
@ -604,8 +520,8 @@ const DeliveryTracking: React.FC = () => {
|
||||||
receipt={selectedReceipt}
|
receipt={selectedReceipt}
|
||||||
mode={modalMode}
|
mode={modalMode}
|
||||||
/>
|
/>
|
||||||
</div>
|
</Container>
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
export default DeliveryTracking;
|
export default DeliveryTracking
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,18 @@
|
||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from 'react'
|
||||||
import {
|
import { FaTimes, FaSave, FaTruck, FaMapMarkerAlt, FaCalendar, FaBox } from 'react-icons/fa'
|
||||||
FaTimes,
|
|
||||||
FaSave,
|
|
||||||
FaTruck,
|
|
||||||
FaMapMarkerAlt,
|
|
||||||
FaCalendar,
|
|
||||||
FaBox,
|
|
||||||
} from "react-icons/fa";
|
|
||||||
import {
|
import {
|
||||||
MmGoodsReceipt,
|
MmGoodsReceipt,
|
||||||
ReceiptStatusEnum,
|
ReceiptStatusEnum,
|
||||||
QualityStatusEnum,
|
QualityStatusEnum,
|
||||||
MmGoodsReceiptItem,
|
MmGoodsReceiptItem,
|
||||||
} from "../../../types/mm";
|
} from '../../../types/mm'
|
||||||
|
|
||||||
interface DeliveryTrackingModalProps {
|
interface DeliveryTrackingModalProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean
|
||||||
onClose: () => void;
|
onClose: () => void
|
||||||
onSave: (receipt: MmGoodsReceipt) => void;
|
onSave: (receipt: MmGoodsReceipt) => void
|
||||||
receipt?: MmGoodsReceipt | null;
|
receipt?: MmGoodsReceipt | null
|
||||||
mode: "create" | "view" | "edit";
|
mode: 'create' | 'view' | 'edit'
|
||||||
}
|
}
|
||||||
|
|
||||||
const DeliveryTrackingModal: React.FC<DeliveryTrackingModalProps> = ({
|
const DeliveryTrackingModal: React.FC<DeliveryTrackingModalProps> = ({
|
||||||
|
|
@ -30,123 +23,114 @@ const DeliveryTrackingModal: React.FC<DeliveryTrackingModalProps> = ({
|
||||||
mode,
|
mode,
|
||||||
}) => {
|
}) => {
|
||||||
const [formData, setFormData] = useState<Partial<MmGoodsReceipt>>({
|
const [formData, setFormData] = useState<Partial<MmGoodsReceipt>>({
|
||||||
receiptNumber: "",
|
receiptNumber: '',
|
||||||
orderId: "",
|
orderId: '',
|
||||||
receiptDate: new Date(),
|
receiptDate: new Date(),
|
||||||
receivedBy: "",
|
receivedBy: '',
|
||||||
warehouseId: "",
|
warehouseId: '',
|
||||||
status: ReceiptStatusEnum.Pending,
|
status: ReceiptStatusEnum.Pending,
|
||||||
notes: "",
|
notes: '',
|
||||||
items: [],
|
items: [],
|
||||||
});
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (mode === "create") {
|
if (mode === 'create') {
|
||||||
// Reset form to initial empty state for new goods receipt
|
// Reset form to initial empty state for new goods receipt
|
||||||
setFormData({
|
setFormData({
|
||||||
receiptNumber: "",
|
receiptNumber: '',
|
||||||
orderId: "",
|
orderId: '',
|
||||||
receiptDate: new Date(),
|
receiptDate: new Date(),
|
||||||
receivedBy: "",
|
receivedBy: '',
|
||||||
warehouseId: "",
|
warehouseId: '',
|
||||||
status: ReceiptStatusEnum.Pending,
|
status: ReceiptStatusEnum.Pending,
|
||||||
notes: "",
|
notes: '',
|
||||||
items: [],
|
items: [],
|
||||||
});
|
})
|
||||||
} else if (receipt) {
|
} else if (receipt) {
|
||||||
// Load existing receipt data for view/edit modes
|
// Load existing receipt data for view/edit modes
|
||||||
setFormData(receipt);
|
setFormData(receipt)
|
||||||
}
|
}
|
||||||
}, [receipt, mode]);
|
}, [receipt, mode])
|
||||||
|
|
||||||
const handleInputChange = (
|
const handleInputChange = (
|
||||||
e: React.ChangeEvent<
|
e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>,
|
||||||
HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement
|
|
||||||
>
|
|
||||||
) => {
|
) => {
|
||||||
const { name, value, type } = e.target;
|
const { name, value, type } = e.target
|
||||||
setFormData((prev) => ({
|
setFormData((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
[name]:
|
[name]:
|
||||||
type === "number"
|
type === 'number' ? parseFloat(value) || 0 : type === 'date' ? new Date(value) : value,
|
||||||
? parseFloat(value) || 0
|
}))
|
||||||
: type === "date"
|
}
|
||||||
? new Date(value)
|
|
||||||
: value,
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
const addReceiptItem = () => {
|
const addReceiptItem = () => {
|
||||||
const newItem: MmGoodsReceiptItem = {
|
const newItem: MmGoodsReceiptItem = {
|
||||||
id: `item-${Date.now()}`,
|
id: `item-${Date.now()}`,
|
||||||
receiptId: formData.id || "",
|
receiptId: formData.id || '',
|
||||||
orderItemId: "",
|
orderItemId: '',
|
||||||
materialId: "",
|
materialId: '',
|
||||||
receivedQuantity: 0,
|
receivedQuantity: 0,
|
||||||
acceptedQuantity: 0,
|
acceptedQuantity: 0,
|
||||||
rejectedQuantity: 0,
|
rejectedQuantity: 0,
|
||||||
qualityStatus: QualityStatusEnum.Pending,
|
qualityStatus: QualityStatusEnum.Pending,
|
||||||
};
|
}
|
||||||
|
|
||||||
setFormData((prev) => ({
|
setFormData((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
items: [...(prev.items || []), newItem],
|
items: [...(prev.items || []), newItem],
|
||||||
}));
|
}))
|
||||||
};
|
}
|
||||||
|
|
||||||
const removeReceiptItem = (index: number) => {
|
const removeReceiptItem = (index: number) => {
|
||||||
setFormData((prev) => ({
|
setFormData((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
items: prev.items?.filter((_, i) => i !== index) || [],
|
items: prev.items?.filter((_, i) => i !== index) || [],
|
||||||
}));
|
}))
|
||||||
};
|
}
|
||||||
|
|
||||||
const updateReceiptItem = (
|
const updateReceiptItem = (
|
||||||
index: number,
|
index: number,
|
||||||
field: keyof MmGoodsReceiptItem,
|
field: keyof MmGoodsReceiptItem,
|
||||||
value: string | number | QualityStatusEnum | Date | undefined
|
value: string | number | QualityStatusEnum | Date | undefined,
|
||||||
) => {
|
) => {
|
||||||
setFormData((prev) => ({
|
setFormData((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
items:
|
items: prev.items?.map((item, i) => (i === index ? { ...item, [field]: value } : item)) || [],
|
||||||
prev.items?.map((item, i) =>
|
}))
|
||||||
i === index ? { ...item, [field]: value } : item
|
}
|
||||||
) || [],
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = (e: React.FormEvent) => {
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault()
|
||||||
if (mode !== "view") {
|
if (mode !== 'view') {
|
||||||
const newReceipt: MmGoodsReceipt = {
|
const newReceipt: MmGoodsReceipt = {
|
||||||
id: receipt?.id || `GR-${Date.now()}`,
|
id: receipt?.id || `GR-${Date.now()}`,
|
||||||
receiptNumber:
|
receiptNumber:
|
||||||
formData.receiptNumber ||
|
formData.receiptNumber ||
|
||||||
`GR-${new Date().getFullYear()}-${Date.now().toString().slice(-3)}`,
|
`GR-${new Date().getFullYear()}-${Date.now().toString().slice(-3)}`,
|
||||||
orderId: formData.orderId || "",
|
orderId: formData.orderId || '',
|
||||||
receiptDate: formData.receiptDate || new Date(),
|
receiptDate: formData.receiptDate || new Date(),
|
||||||
receivedBy: formData.receivedBy || "",
|
receivedBy: formData.receivedBy || '',
|
||||||
warehouseId: formData.warehouseId || "",
|
warehouseId: formData.warehouseId || '',
|
||||||
status: formData.status || ReceiptStatusEnum.Pending,
|
status: formData.status || ReceiptStatusEnum.Pending,
|
||||||
notes: formData.notes,
|
notes: formData.notes,
|
||||||
items: formData.items || [],
|
items: formData.items || [],
|
||||||
creationTime: receipt?.creationTime || new Date(),
|
creationTime: receipt?.creationTime || new Date(),
|
||||||
lastModificationTime: new Date(),
|
lastModificationTime: new Date(),
|
||||||
};
|
|
||||||
onSave(newReceipt);
|
|
||||||
}
|
}
|
||||||
onClose();
|
onSave(newReceipt)
|
||||||
};
|
}
|
||||||
|
onClose()
|
||||||
|
}
|
||||||
|
|
||||||
if (!isOpen) return null;
|
if (!isOpen) return null
|
||||||
|
|
||||||
const isReadOnly = mode === "view";
|
const isReadOnly = mode === 'view'
|
||||||
const modalTitle =
|
const modalTitle =
|
||||||
mode === "create"
|
mode === 'create'
|
||||||
? "Yeni Teslimat Takibi"
|
? 'Yeni Teslimat Takibi'
|
||||||
: mode === "edit"
|
: mode === 'edit'
|
||||||
? "Teslimat Takibini Düzenle"
|
? 'Teslimat Takibini Düzenle'
|
||||||
: "Teslimat Takibi Detayları";
|
: 'Teslimat Takibi Detayları'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
|
<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" />
|
<FaTruck className="mr-2 text-blue-600" />
|
||||||
{modalTitle}
|
{modalTitle}
|
||||||
</h3>
|
</h3>
|
||||||
<button
|
<button onClick={onClose} className="text-gray-400 hover:text-gray-600 p-1">
|
||||||
onClick={onClose}
|
|
||||||
className="text-gray-400 hover:text-gray-600 p-1"
|
|
||||||
>
|
|
||||||
<FaTimes />
|
<FaTimes />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -174,13 +155,11 @@ const DeliveryTrackingModal: React.FC<DeliveryTrackingModalProps> = ({
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700">
|
<label className="block text-sm font-medium text-gray-700">Giriş Numarası</label>
|
||||||
Giriş Numarası
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="receiptNumber"
|
name="receiptNumber"
|
||||||
value={formData.receiptNumber || ""}
|
value={formData.receiptNumber || ''}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
readOnly={isReadOnly}
|
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"
|
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>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700">
|
<label className="block text-sm font-medium text-gray-700">Sipariş ID</label>
|
||||||
Sipariş ID
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="orderId"
|
name="orderId"
|
||||||
value={formData.orderId || ""}
|
value={formData.orderId || ''}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
readOnly={isReadOnly}
|
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"
|
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"
|
name="receiptDate"
|
||||||
value={
|
value={
|
||||||
formData.receiptDate
|
formData.receiptDate
|
||||||
? new Date(formData.receiptDate)
|
? new Date(formData.receiptDate).toISOString().split('T')[0]
|
||||||
.toISOString()
|
: ''
|
||||||
.split("T")[0]
|
|
||||||
: ""
|
|
||||||
}
|
}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
readOnly={isReadOnly}
|
readOnly={isReadOnly}
|
||||||
|
|
@ -225,13 +200,11 @@ const DeliveryTrackingModal: React.FC<DeliveryTrackingModalProps> = ({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700">
|
<label className="block text-sm font-medium text-gray-700">Teslim Alan</label>
|
||||||
Teslim Alan
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="receivedBy"
|
name="receivedBy"
|
||||||
value={formData.receivedBy || ""}
|
value={formData.receivedBy || ''}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
readOnly={isReadOnly}
|
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"
|
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>
|
</label>
|
||||||
<select
|
<select
|
||||||
name="warehouseId"
|
name="warehouseId"
|
||||||
value={formData.warehouseId || ""}
|
value={formData.warehouseId || ''}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
disabled={isReadOnly}
|
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"
|
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>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700">
|
<label className="block text-sm font-medium text-gray-700">Durum</label>
|
||||||
Durum
|
|
||||||
</label>
|
|
||||||
<select
|
<select
|
||||||
name="status"
|
name="status"
|
||||||
value={formData.status || ReceiptStatusEnum.Pending}
|
value={formData.status || ReceiptStatusEnum.Pending}
|
||||||
|
|
@ -273,20 +244,16 @@ const DeliveryTrackingModal: React.FC<DeliveryTrackingModalProps> = ({
|
||||||
>
|
>
|
||||||
<option value={ReceiptStatusEnum.Pending}>Beklemede</option>
|
<option value={ReceiptStatusEnum.Pending}>Beklemede</option>
|
||||||
<option value={ReceiptStatusEnum.InProgress}>İşlemde</option>
|
<option value={ReceiptStatusEnum.InProgress}>İşlemde</option>
|
||||||
<option value={ReceiptStatusEnum.Completed}>
|
<option value={ReceiptStatusEnum.Completed}>Tamamlandı</option>
|
||||||
Tamamlandı
|
|
||||||
</option>
|
|
||||||
<option value={ReceiptStatusEnum.OnHold}>Bekletildi</option>
|
<option value={ReceiptStatusEnum.OnHold}>Bekletildi</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700">
|
<label className="block text-sm font-medium text-gray-700">Notlar</label>
|
||||||
Notlar
|
|
||||||
</label>
|
|
||||||
<textarea
|
<textarea
|
||||||
name="notes"
|
name="notes"
|
||||||
value={formData.notes || ""}
|
value={formData.notes || ''}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
readOnly={isReadOnly}
|
readOnly={isReadOnly}
|
||||||
rows={2}
|
rows={2}
|
||||||
|
|
@ -315,14 +282,9 @@ const DeliveryTrackingModal: React.FC<DeliveryTrackingModalProps> = ({
|
||||||
|
|
||||||
<div className="space-y-2 max-h-72 overflow-y-auto p-1">
|
<div className="space-y-2 max-h-72 overflow-y-auto p-1">
|
||||||
{formData.items?.map((item, index) => (
|
{formData.items?.map((item, index) => (
|
||||||
<div
|
<div key={item.id} className="border rounded-lg p-2 bg-gray-50">
|
||||||
key={item.id}
|
|
||||||
className="border rounded-lg p-2 bg-gray-50"
|
|
||||||
>
|
|
||||||
<div className="flex items-center justify-between mb-2">
|
<div className="flex items-center justify-between mb-2">
|
||||||
<span className="text-sm font-medium text-gray-700">
|
<span className="text-sm font-medium text-gray-700">Kalem {index + 1}</span>
|
||||||
Kalem {index + 1}
|
|
||||||
</span>
|
|
||||||
{!isReadOnly && (
|
{!isReadOnly && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|
@ -342,13 +304,7 @@ const DeliveryTrackingModal: React.FC<DeliveryTrackingModalProps> = ({
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={item.materialId}
|
value={item.materialId}
|
||||||
onChange={(e) =>
|
onChange={(e) => updateReceiptItem(index, 'materialId', e.target.value)}
|
||||||
updateReceiptItem(
|
|
||||||
index,
|
|
||||||
"materialId",
|
|
||||||
e.target.value
|
|
||||||
)
|
|
||||||
}
|
|
||||||
readOnly={isReadOnly}
|
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"
|
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
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={item.orderItemId}
|
value={item.orderItemId}
|
||||||
onChange={(e) =>
|
onChange={(e) => updateReceiptItem(index, 'orderItemId', e.target.value)}
|
||||||
updateReceiptItem(
|
|
||||||
index,
|
|
||||||
"orderItemId",
|
|
||||||
e.target.value
|
|
||||||
)
|
|
||||||
}
|
|
||||||
readOnly={isReadOnly}
|
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"
|
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) =>
|
onChange={(e) =>
|
||||||
updateReceiptItem(
|
updateReceiptItem(
|
||||||
index,
|
index,
|
||||||
"receivedQuantity",
|
'receivedQuantity',
|
||||||
parseFloat(e.target.value) || 0
|
parseFloat(e.target.value) || 0,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
readOnly={isReadOnly}
|
readOnly={isReadOnly}
|
||||||
|
|
@ -404,8 +354,8 @@ const DeliveryTrackingModal: React.FC<DeliveryTrackingModalProps> = ({
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
updateReceiptItem(
|
updateReceiptItem(
|
||||||
index,
|
index,
|
||||||
"acceptedQuantity",
|
'acceptedQuantity',
|
||||||
parseFloat(e.target.value) || 0
|
parseFloat(e.target.value) || 0,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
readOnly={isReadOnly}
|
readOnly={isReadOnly}
|
||||||
|
|
@ -425,8 +375,8 @@ const DeliveryTrackingModal: React.FC<DeliveryTrackingModalProps> = ({
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
updateReceiptItem(
|
updateReceiptItem(
|
||||||
index,
|
index,
|
||||||
"rejectedQuantity",
|
'rejectedQuantity',
|
||||||
parseFloat(e.target.value) || 0
|
parseFloat(e.target.value) || 0,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
readOnly={isReadOnly}
|
readOnly={isReadOnly}
|
||||||
|
|
@ -445,25 +395,17 @@ const DeliveryTrackingModal: React.FC<DeliveryTrackingModalProps> = ({
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
updateReceiptItem(
|
updateReceiptItem(
|
||||||
index,
|
index,
|
||||||
"qualityStatus",
|
'qualityStatus',
|
||||||
e.target.value as QualityStatusEnum
|
e.target.value as QualityStatusEnum,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
disabled={isReadOnly}
|
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"
|
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}>
|
<option value={QualityStatusEnum.Pending}>Beklemede</option>
|
||||||
Beklemede
|
<option value={QualityStatusEnum.Approved}>Kabul</option>
|
||||||
</option>
|
<option value={QualityStatusEnum.Rejected}>Red</option>
|
||||||
<option value={QualityStatusEnum.Approved}>
|
<option value={QualityStatusEnum.Conditional}>Koşullu</option>
|
||||||
Kabul
|
|
||||||
</option>
|
|
||||||
<option value={QualityStatusEnum.Rejected}>
|
|
||||||
Red
|
|
||||||
</option>
|
|
||||||
<option value={QualityStatusEnum.Conditional}>
|
|
||||||
Koşullu
|
|
||||||
</option>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -473,14 +415,8 @@ const DeliveryTrackingModal: React.FC<DeliveryTrackingModalProps> = ({
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={item.lotNumber || ""}
|
value={item.lotNumber || ''}
|
||||||
onChange={(e) =>
|
onChange={(e) => updateReceiptItem(index, 'lotNumber', e.target.value)}
|
||||||
updateReceiptItem(
|
|
||||||
index,
|
|
||||||
"lotNumber",
|
|
||||||
e.target.value
|
|
||||||
)
|
|
||||||
}
|
|
||||||
readOnly={isReadOnly}
|
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"
|
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"
|
type="date"
|
||||||
value={
|
value={
|
||||||
item.expiryDate
|
item.expiryDate
|
||||||
? new Date(item.expiryDate)
|
? new Date(item.expiryDate).toISOString().split('T')[0]
|
||||||
.toISOString()
|
: ''
|
||||||
.split("T")[0]
|
|
||||||
: ""
|
|
||||||
}
|
}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
updateReceiptItem(
|
updateReceiptItem(
|
||||||
index,
|
index,
|
||||||
"expiryDate",
|
'expiryDate',
|
||||||
e.target.value
|
e.target.value ? new Date(e.target.value) : undefined,
|
||||||
? new Date(e.target.value)
|
|
||||||
: undefined
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
readOnly={isReadOnly}
|
readOnly={isReadOnly}
|
||||||
|
|
@ -532,9 +464,9 @@ const DeliveryTrackingModal: React.FC<DeliveryTrackingModalProps> = ({
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="px-3 py-1.5 border border-gray-300 rounded-md text-sm text-gray-700 hover:bg-gray-50"
|
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>
|
</button>
|
||||||
{mode !== "view" && (
|
{mode !== 'view' && (
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="px-3 py-1.5 bg-blue-600 text-white rounded-md hover:bg-blue-700 flex items-center text-sm"
|
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>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
export default DeliveryTrackingModal;
|
export default DeliveryTrackingModal
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useState } from "react";
|
import React, { useState } from 'react'
|
||||||
import {
|
import {
|
||||||
FaBox,
|
FaBox,
|
||||||
FaFileAlt,
|
FaFileAlt,
|
||||||
|
|
@ -10,54 +10,54 @@ import {
|
||||||
FaArrowsAltH,
|
FaArrowsAltH,
|
||||||
FaWarehouse,
|
FaWarehouse,
|
||||||
FaSitemap,
|
FaSitemap,
|
||||||
} from "react-icons/fa";
|
} from 'react-icons/fa'
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from 'react-router-dom'
|
||||||
import { mockMaterials } from "../../../mocks/mockMaterials";
|
import { mockMaterials } from '../../../mocks/mockMaterials'
|
||||||
import classNames from "classnames";
|
import classNames from 'classnames'
|
||||||
import { InfoItem, InfoSection } from "../../../components/common/InfoSection";
|
import { InfoItem, InfoSection } from '../../../components/common/InfoSection'
|
||||||
|
import { Container } from '@/components/shared'
|
||||||
|
|
||||||
const MaterialCard: React.FC = () => {
|
const MaterialCard: React.FC = () => {
|
||||||
const { id } = useParams();
|
const { id } = useParams()
|
||||||
const material = mockMaterials.find((m) => m.id === id)!;
|
const material = mockMaterials.find((m) => m.id === id)!
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate()
|
||||||
|
|
||||||
const [activeTab, setActiveTab] = useState<
|
const [activeTab, setActiveTab] = useState<
|
||||||
| "basic"
|
| 'basic'
|
||||||
| "pricing"
|
| 'pricing'
|
||||||
| "specifications"
|
| 'specifications'
|
||||||
| "alternativeUnits"
|
| 'alternativeUnits'
|
||||||
| "suppliers"
|
| 'suppliers'
|
||||||
| "stockLevels"
|
| 'stockLevels'
|
||||||
| "variants"
|
| 'variants'
|
||||||
>("basic");
|
>('basic')
|
||||||
|
|
||||||
if (!material) {
|
if (!material) {
|
||||||
return (
|
return (
|
||||||
<div className="text-center py-10">
|
<div className="text-center py-10">
|
||||||
<h2 className="text-xl font-bold text-gray-700">Malzeme Bulunamadı</h2>
|
<h2 className="text-xl font-bold text-gray-700">Malzeme Bulunamadı</h2>
|
||||||
<p className="text-gray-500 mt-2">
|
<p className="text-gray-500 mt-2">Belirtilen ID ile eşleşen bir malzeme bulunamadı.</p>
|
||||||
Belirtilen ID ile eşleşen bir malzeme bulunamadı.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{ key: "basic", label: "Temel Bilgiler", icon: FaFileAlt },
|
{ key: 'basic', label: 'Temel Bilgiler', icon: FaFileAlt },
|
||||||
{ key: "pricing", label: "Fiyatlandırma", icon: FaDollarSign },
|
{ key: 'pricing', label: 'Fiyatlandırma', icon: FaDollarSign },
|
||||||
{ key: "specifications", label: "Özellikler", icon: FaCog },
|
{ key: 'specifications', label: 'Özellikler', icon: FaCog },
|
||||||
{
|
{
|
||||||
key: "alternativeUnits",
|
key: 'alternativeUnits',
|
||||||
label: "Alternatif Birimler",
|
label: 'Alternatif Birimler',
|
||||||
icon: FaArrowsAltH,
|
icon: FaArrowsAltH,
|
||||||
},
|
},
|
||||||
{ key: "suppliers", label: "Tedarikçiler", icon: FaTruck },
|
{ key: 'suppliers', label: 'Tedarikçiler', icon: FaTruck },
|
||||||
{ key: "stockLevels", label: "Stok Seviyeleri", icon: FaWarehouse },
|
{ key: 'stockLevels', label: 'Stok Seviyeleri', icon: FaWarehouse },
|
||||||
{ key: "variants", label: "Varyantlar", icon: FaSitemap },
|
{ key: 'variants', label: 'Varyantlar', icon: FaSitemap },
|
||||||
];
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="py-4 space-y-2">
|
<Container>
|
||||||
|
<div className="space-y-2">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-2.5">
|
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-2.5">
|
||||||
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-2">
|
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-2">
|
||||||
|
|
@ -66,26 +66,20 @@ const MaterialCard: React.FC = () => {
|
||||||
<FaBox className="w-6 h-6 text-blue-600" />
|
<FaBox className="w-6 h-6 text-blue-600" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-2xl font-bold text-gray-900">
|
<h2 className="text-2xl font-bold text-gray-900">{material.code}</h2>
|
||||||
{material.code}
|
|
||||||
</h2>
|
|
||||||
<p className="text-gray-600">{material.name}</p>
|
<p className="text-gray-600">{material.name}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center space-x-1.5">
|
<div className="flex items-center space-x-1.5">
|
||||||
<button
|
<button
|
||||||
onClick={() =>
|
onClick={() => navigate(`/admin/supplychain/materials/edit/${material.id}`)}
|
||||||
navigate(`/admin/supplychain/materials/edit/${material.id}`)
|
|
||||||
}
|
|
||||||
className="px-3 py-2 text-sm border border-gray-300 bg-white text-gray-700 rounded-md hover:bg-gray-50 transition-colors flex items-center"
|
className="px-3 py-2 text-sm border border-gray-300 bg-white text-gray-700 rounded-md hover:bg-gray-50 transition-colors flex items-center"
|
||||||
>
|
>
|
||||||
<FaEdit size={14} className="mr-1.5 inline" />
|
<FaEdit size={14} className="mr-1.5 inline" />
|
||||||
Düzenle
|
Düzenle
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() =>
|
onClick={() => navigate(`/admin/warehouse/movements/${material.id}`)}
|
||||||
navigate(`/admin/warehouse/movements/${material.id}`)
|
|
||||||
}
|
|
||||||
className="px-3 py-2 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors flex items-center"
|
className="px-3 py-2 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors flex items-center"
|
||||||
>
|
>
|
||||||
<FaEye size={14} className="mr-1.5 inline" />
|
<FaEye size={14} className="mr-1.5 inline" />
|
||||||
|
|
@ -101,7 +95,7 @@ const MaterialCard: React.FC = () => {
|
||||||
<div className="min-w-max">
|
<div className="min-w-max">
|
||||||
<nav className="flex space-x-4 px-3 -mb-px">
|
<nav className="flex space-x-4 px-3 -mb-px">
|
||||||
{tabs.map((tab) => {
|
{tabs.map((tab) => {
|
||||||
const Icon = tab.icon;
|
const Icon = tab.icon
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
key={tab.key}
|
key={tab.key}
|
||||||
|
|
@ -109,48 +103,39 @@ const MaterialCard: React.FC = () => {
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setActiveTab(
|
setActiveTab(
|
||||||
tab.key as
|
tab.key as
|
||||||
| "basic"
|
| 'basic'
|
||||||
| "pricing"
|
| 'pricing'
|
||||||
| "specifications"
|
| 'specifications'
|
||||||
| "alternativeUnits"
|
| 'alternativeUnits'
|
||||||
| "suppliers"
|
| 'suppliers'
|
||||||
| "stockLevels"
|
| 'stockLevels'
|
||||||
| "variants"
|
| 'variants',
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"flex items-center py-3 px-1 border-b-2 font-medium text-sm transition-colors whitespace-nowrap",
|
'flex items-center py-3 px-1 border-b-2 font-medium text-sm transition-colors whitespace-nowrap',
|
||||||
activeTab === tab.key
|
activeTab === tab.key
|
||||||
? "border-blue-500 text-blue-600"
|
? 'border-blue-500 text-blue-600'
|
||||||
: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300"
|
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Icon size={14} className="mr-2" />
|
<Icon size={14} className="mr-2" />
|
||||||
{tab.label}
|
{tab.label}
|
||||||
</button>
|
</button>
|
||||||
);
|
)
|
||||||
})}
|
})}
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="p-3">
|
<div className="p-3">
|
||||||
{activeTab === "basic" && (
|
{activeTab === 'basic' && (
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
<InfoSection title="Genel Bilgiler">
|
<InfoSection title="Genel Bilgiler">
|
||||||
<InfoItem
|
<InfoItem label="Malzeme Türü" value={material.materialType?.name || '-'} />
|
||||||
label="Malzeme Türü"
|
<InfoItem label="Malzeme Grubu" value={material.materialGroup?.name || '-'} />
|
||||||
value={material.materialType?.name || "-"}
|
<InfoItem label="Temel Birim" value={material.baseUnit?.code || '-'} />
|
||||||
/>
|
<InfoItem label="Barkod" value={material.barcode || '-'} />
|
||||||
<InfoItem
|
|
||||||
label="Malzeme Grubu"
|
|
||||||
value={material.materialGroup?.name || "-"}
|
|
||||||
/>
|
|
||||||
<InfoItem
|
|
||||||
label="Temel Birim"
|
|
||||||
value={material.baseUnit?.code || "-"}
|
|
||||||
/>
|
|
||||||
<InfoItem label="Barkod" value={material.barcode || "-"} />
|
|
||||||
</InfoSection>
|
</InfoSection>
|
||||||
|
|
||||||
<InfoSection title="Takip Bilgileri">
|
<InfoSection title="Takip Bilgileri">
|
||||||
|
|
@ -159,59 +144,46 @@ const MaterialCard: React.FC = () => {
|
||||||
value={material.trackingType}
|
value={material.trackingType}
|
||||||
isBadge
|
isBadge
|
||||||
badgeColor={
|
badgeColor={
|
||||||
material.trackingType === "Quantity"
|
material.trackingType === 'Quantity'
|
||||||
? "bg-green-100 text-green-800"
|
? 'bg-green-100 text-green-800'
|
||||||
: "bg-red-100 text-red-800"
|
: 'bg-red-100 text-red-800'
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<InfoItem
|
<InfoItem
|
||||||
label="Oluşturma Tarihi"
|
label="Oluşturma Tarihi"
|
||||||
value={new Date(material.creationTime).toLocaleDateString(
|
value={new Date(material.creationTime).toLocaleDateString('tr-TR')}
|
||||||
"tr-TR"
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
<InfoItem
|
<InfoItem
|
||||||
label="Son Değişiklik"
|
label="Son Değişiklik"
|
||||||
value={new Date(
|
value={new Date(material.lastModificationTime).toLocaleDateString('tr-TR')}
|
||||||
material.lastModificationTime
|
|
||||||
).toLocaleDateString("tr-TR")}
|
|
||||||
/>
|
/>
|
||||||
</InfoSection>
|
</InfoSection>
|
||||||
|
|
||||||
<InfoSection title="Durum">
|
<InfoSection title="Durum">
|
||||||
<InfoItem
|
<InfoItem
|
||||||
label="Aktiflik Durumu"
|
label="Aktiflik Durumu"
|
||||||
value={material.isActive ? "Aktif" : "Pasif"}
|
value={material.isActive ? 'Aktif' : 'Pasif'}
|
||||||
isBadge
|
isBadge
|
||||||
badgeColor={
|
badgeColor={
|
||||||
material.isActive
|
material.isActive ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
|
||||||
? "bg-green-100 text-green-800"
|
|
||||||
: "bg-red-100 text-red-800"
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<InfoItem
|
<InfoItem label="Açıklama" value={material.description || '-'} />
|
||||||
label="Açıklama"
|
|
||||||
value={material.description || "-"}
|
|
||||||
/>
|
|
||||||
</InfoSection>
|
</InfoSection>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{activeTab === "pricing" && (
|
{activeTab === 'pricing' && (
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
<InfoSection title="Fiyatlandırma">
|
<InfoSection title="Fiyatlandırma">
|
||||||
<InfoItem
|
<InfoItem
|
||||||
label="Maliyet Fiyatı"
|
label="Maliyet Fiyatı"
|
||||||
value={`${material.costPrice.toLocaleString("tr-TR")} ${
|
value={`${material.costPrice.toLocaleString('tr-TR')} ${material.currency}`}
|
||||||
material.currency
|
|
||||||
}`}
|
|
||||||
isBold
|
isBold
|
||||||
/>
|
/>
|
||||||
<InfoItem
|
<InfoItem
|
||||||
label="Satış Fiyatı"
|
label="Satış Fiyatı"
|
||||||
value={`${material.salesPrice.toLocaleString("tr-TR")} ${
|
value={`${material.salesPrice.toLocaleString('tr-TR')} ${material.currency}`}
|
||||||
material.currency
|
|
||||||
}`}
|
|
||||||
isBold
|
isBold
|
||||||
/>
|
/>
|
||||||
<InfoItem label="Para Birimi" value={material.currency} />
|
<InfoItem label="Para Birimi" value={material.currency} />
|
||||||
|
|
@ -219,10 +191,9 @@ const MaterialCard: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{activeTab === "alternativeUnits" && (
|
{activeTab === 'alternativeUnits' && (
|
||||||
<div>
|
<div>
|
||||||
{material.alternativeUnits &&
|
{material.alternativeUnits && material.alternativeUnits.length > 0 ? (
|
||||||
material.alternativeUnits.length > 0 ? (
|
|
||||||
<div className="bg-white overflow-hidden shadow rounded-lg">
|
<div className="bg-white overflow-hidden shadow rounded-lg">
|
||||||
<table className="min-w-full divide-y divide-gray-200">
|
<table className="min-w-full divide-y divide-gray-200">
|
||||||
<thead className="bg-gray-50">
|
<thead className="bg-gray-50">
|
||||||
|
|
@ -248,7 +219,7 @@ const MaterialCard: React.FC = () => {
|
||||||
{altUnit.conversionFactor}
|
{altUnit.conversionFactor}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-3 py-1.5 whitespace-nowrap text-gray-900">
|
<td className="px-3 py-1.5 whitespace-nowrap text-gray-900">
|
||||||
{altUnit.isDefault ? "Evet" : "Hayır"}
|
{altUnit.isDefault ? 'Evet' : 'Hayır'}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
|
|
@ -258,9 +229,7 @@ const MaterialCard: React.FC = () => {
|
||||||
) : (
|
) : (
|
||||||
<div className="text-center py-12">
|
<div className="text-center py-12">
|
||||||
<FaCog className="mx-auto h-12 w-12 text-gray-400" />
|
<FaCog className="mx-auto h-12 w-12 text-gray-400" />
|
||||||
<h3 className="mt-2 text-sm font-medium text-gray-900">
|
<h3 className="mt-2 text-sm font-medium text-gray-900">Alternatif birim</h3>
|
||||||
Alternatif birim
|
|
||||||
</h3>
|
|
||||||
<p className="mt-1 text-sm text-gray-500">
|
<p className="mt-1 text-sm text-gray-500">
|
||||||
Bu malzeme için henüz alternatif birim tanımlanmamış.
|
Bu malzeme için henüz alternatif birim tanımlanmamış.
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -269,7 +238,7 @@ const MaterialCard: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{activeTab === "specifications" && (
|
{activeTab === 'specifications' && (
|
||||||
<div>
|
<div>
|
||||||
{material.specifications && material.specifications.length > 0 ? (
|
{material.specifications && material.specifications.length > 0 ? (
|
||||||
<div className="bg-white overflow-hidden shadow rounded-lg">
|
<div className="bg-white overflow-hidden shadow rounded-lg">
|
||||||
|
|
@ -300,17 +269,17 @@ const MaterialCard: React.FC = () => {
|
||||||
{spec.specificationValue}
|
{spec.specificationValue}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-3 py-1.5 whitespace-nowrap text-gray-900">
|
<td className="px-3 py-1.5 whitespace-nowrap text-gray-900">
|
||||||
{spec.unitId || "-"}
|
{spec.unitId || '-'}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-3 py-1.5 whitespace-nowrap">
|
<td className="px-3 py-1.5 whitespace-nowrap">
|
||||||
<span
|
<span
|
||||||
className={`px-2 py-1 text-xs font-medium rounded-full ${
|
className={`px-2 py-1 text-xs font-medium rounded-full ${
|
||||||
spec.isRequired
|
spec.isRequired
|
||||||
? "bg-red-100 text-red-800"
|
? 'bg-red-100 text-red-800'
|
||||||
: "bg-gray-100 text-gray-800"
|
: 'bg-gray-100 text-gray-800'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{spec.isRequired ? "Zorunlu" : "İsteğe Bağlı"}
|
{spec.isRequired ? 'Zorunlu' : 'İsteğe Bağlı'}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
@ -321,9 +290,7 @@ const MaterialCard: React.FC = () => {
|
||||||
) : (
|
) : (
|
||||||
<div className="text-center py-12">
|
<div className="text-center py-12">
|
||||||
<FaCog className="mx-auto h-12 w-12 text-gray-400" />
|
<FaCog className="mx-auto h-12 w-12 text-gray-400" />
|
||||||
<h3 className="mt-2 text-sm font-medium text-gray-900">
|
<h3 className="mt-2 text-sm font-medium text-gray-900">Özellik bulunamadı</h3>
|
||||||
Özellik bulunamadı
|
|
||||||
</h3>
|
|
||||||
<p className="mt-1 text-sm text-gray-500">
|
<p className="mt-1 text-sm text-gray-500">
|
||||||
Bu malzeme için henüz özellik tanımlanmamış.
|
Bu malzeme için henüz özellik tanımlanmamış.
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -332,7 +299,7 @@ const MaterialCard: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{activeTab === "suppliers" && (
|
{activeTab === 'suppliers' && (
|
||||||
<div>
|
<div>
|
||||||
{material.suppliers && material.suppliers.length > 0 ? (
|
{material.suppliers && material.suppliers.length > 0 ? (
|
||||||
<div className="bg-white overflow-hidden shadow rounded-lg">
|
<div className="bg-white overflow-hidden shadow rounded-lg">
|
||||||
|
|
@ -366,7 +333,7 @@ const MaterialCard: React.FC = () => {
|
||||||
{item.supplier?.name}
|
{item.supplier?.name}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-3 py-1.5 whitespace-nowrap text-gray-900">
|
<td className="px-3 py-1.5 whitespace-nowrap text-gray-900">
|
||||||
{item.supplierMaterialCode || "-"}
|
{item.supplierMaterialCode || '-'}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-3 py-1.5 whitespace-nowrap text-gray-900">
|
<td className="px-3 py-1.5 whitespace-nowrap text-gray-900">
|
||||||
{item.leadTime}
|
{item.leadTime}
|
||||||
|
|
@ -375,18 +342,17 @@ const MaterialCard: React.FC = () => {
|
||||||
{item.minimumOrderQuantity}
|
{item.minimumOrderQuantity}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-3 py-1.5 whitespace-nowrap text-gray-900">
|
<td className="px-3 py-1.5 whitespace-nowrap text-gray-900">
|
||||||
{item.price.toLocaleString("tr-TR")}{" "}
|
{item.price.toLocaleString('tr-TR')} {material.currency}
|
||||||
{material.currency}
|
|
||||||
</td>
|
</td>
|
||||||
<td className="px-3 py-1.5 whitespace-nowrap">
|
<td className="px-3 py-1.5 whitespace-nowrap">
|
||||||
<span
|
<span
|
||||||
className={`px-2 py-1 text-xs font-medium rounded-full ${
|
className={`px-2 py-1 text-xs font-medium rounded-full ${
|
||||||
item.isPreferred
|
item.isPreferred
|
||||||
? "bg-green-100 text-green-800"
|
? 'bg-green-100 text-green-800'
|
||||||
: "bg-gray-100 text-gray-800"
|
: 'bg-gray-100 text-gray-800'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{item.isPreferred ? "Aktif" : "Pasif"}
|
{item.isPreferred ? 'Aktif' : 'Pasif'}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
@ -397,9 +363,7 @@ const MaterialCard: React.FC = () => {
|
||||||
) : (
|
) : (
|
||||||
<div className="text-center py-8 p-3">
|
<div className="text-center py-8 p-3">
|
||||||
<FaTruck className="mx-auto h-8 w-8 text-gray-400" />
|
<FaTruck className="mx-auto h-8 w-8 text-gray-400" />
|
||||||
<h3 className="mt-2 text-sm font-medium text-gray-900">
|
<h3 className="mt-2 text-sm font-medium text-gray-900">Tedarikçi Yönetimi</h3>
|
||||||
Tedarikçi Yönetimi
|
|
||||||
</h3>
|
|
||||||
<p className="mt-1 text-xs text-gray-500">
|
<p className="mt-1 text-xs text-gray-500">
|
||||||
Bu malzeme için henüz tedarikçi tanımlanmamış.
|
Bu malzeme için henüz tedarikçi tanımlanmamış.
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -408,7 +372,7 @@ const MaterialCard: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{activeTab === "stockLevels" && (
|
{activeTab === 'stockLevels' && (
|
||||||
<div>
|
<div>
|
||||||
{material.stockLevels && material.stockLevels.length > 0 ? (
|
{material.stockLevels && material.stockLevels.length > 0 ? (
|
||||||
<div className="bg-white overflow-hidden shadow rounded-lg">
|
<div className="bg-white overflow-hidden shadow rounded-lg">
|
||||||
|
|
@ -445,13 +409,13 @@ const MaterialCard: React.FC = () => {
|
||||||
{material.stockLevels.map((item) => (
|
{material.stockLevels.map((item) => (
|
||||||
<tr key={item.id} className="text-xs">
|
<tr key={item.id} className="text-xs">
|
||||||
<td className="px-3 py-1.5 whitespace-nowrap font-medium text-gray-900">
|
<td className="px-3 py-1.5 whitespace-nowrap font-medium text-gray-900">
|
||||||
{item.warehouse?.name || "-"}
|
{item.warehouse?.name || '-'}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-3 py-1.5 whitespace-nowrap text-gray-900">
|
<td className="px-3 py-1.5 whitespace-nowrap text-gray-900">
|
||||||
{item.zone?.name || "-"}
|
{item.zone?.name || '-'}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-3 py-1.5 whitespace-nowrap text-gray-900">
|
<td className="px-3 py-1.5 whitespace-nowrap text-gray-900">
|
||||||
{item.location?.name || "-"}
|
{item.location?.name || '-'}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-3 py-1.5 whitespace-nowrap text-gray-900">
|
<td className="px-3 py-1.5 whitespace-nowrap text-gray-900">
|
||||||
{item.minimumStock}
|
{item.minimumStock}
|
||||||
|
|
@ -459,9 +423,7 @@ const MaterialCard: React.FC = () => {
|
||||||
<td className="px-3 py-1.5 whitespace-nowrap text-gray-900">
|
<td className="px-3 py-1.5 whitespace-nowrap text-gray-900">
|
||||||
{item.maximumStock}
|
{item.maximumStock}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-3 py-1.5 whitespace-nowrap">
|
<td className="px-3 py-1.5 whitespace-nowrap">{item.reorderPoint}</td>
|
||||||
{item.reorderPoint}
|
|
||||||
</td>
|
|
||||||
<td className="px-3 py-1.5 whitespace-nowrap">
|
<td className="px-3 py-1.5 whitespace-nowrap">
|
||||||
{item.availableQuantity}
|
{item.availableQuantity}
|
||||||
</td>
|
</td>
|
||||||
|
|
@ -476,9 +438,7 @@ const MaterialCard: React.FC = () => {
|
||||||
) : (
|
) : (
|
||||||
<div className="text-center py-8 p-3">
|
<div className="text-center py-8 p-3">
|
||||||
<FaTruck className="mx-auto h-8 w-8 text-gray-400" />
|
<FaTruck className="mx-auto h-8 w-8 text-gray-400" />
|
||||||
<h3 className="mt-2 text-sm font-medium text-gray-900">
|
<h3 className="mt-2 text-sm font-medium text-gray-900">Stok Seviyeleri</h3>
|
||||||
Stok Seviyeleri
|
|
||||||
</h3>
|
|
||||||
<p className="mt-1 text-xs text-gray-500">
|
<p className="mt-1 text-xs text-gray-500">
|
||||||
Stok seviyeleri için henüz veri bulunmamaktadır.
|
Stok seviyeleri için henüz veri bulunmamaktadır.
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -487,7 +447,7 @@ const MaterialCard: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{activeTab === "variants" && (
|
{activeTab === 'variants' && (
|
||||||
<div>
|
<div>
|
||||||
{material.variants && material.variants.length > 0 ? (
|
{material.variants && material.variants.length > 0 ? (
|
||||||
<div className="bg-white overflow-hidden shadow rounded-lg">
|
<div className="bg-white overflow-hidden shadow rounded-lg">
|
||||||
|
|
@ -512,16 +472,16 @@ const MaterialCard: React.FC = () => {
|
||||||
{material.variants.map((item) => (
|
{material.variants.map((item) => (
|
||||||
<tr key={item.id} className="text-xs">
|
<tr key={item.id} className="text-xs">
|
||||||
<td className="px-3 py-1.5 whitespace-nowrap font-medium text-gray-900">
|
<td className="px-3 py-1.5 whitespace-nowrap font-medium text-gray-900">
|
||||||
{item.variantCode || "-"}
|
{item.variantCode || '-'}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-3 py-1.5 whitespace-nowrap text-gray-900">
|
<td className="px-3 py-1.5 whitespace-nowrap text-gray-900">
|
||||||
{item.variantName || "-"}
|
{item.variantName || '-'}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-3 py-1.5 whitespace-nowrap text-gray-900">
|
<td className="px-3 py-1.5 whitespace-nowrap text-gray-900">
|
||||||
{item.additionalCost.toLocaleString("tr-TR")}
|
{item.additionalCost.toLocaleString('tr-TR')}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-3 py-1.5 whitespace-nowrap text-gray-900">
|
<td className="px-3 py-1.5 whitespace-nowrap text-gray-900">
|
||||||
{item.isActive ? "Evet" : "Hayır"}
|
{item.isActive ? 'Evet' : 'Hayır'}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
|
|
@ -531,9 +491,7 @@ const MaterialCard: React.FC = () => {
|
||||||
) : (
|
) : (
|
||||||
<div className="text-center py-8 p-3">
|
<div className="text-center py-8 p-3">
|
||||||
<FaTruck className="mx-auto h-8 w-8 text-gray-400" />
|
<FaTruck className="mx-auto h-8 w-8 text-gray-400" />
|
||||||
<h3 className="mt-2 text-sm font-medium text-gray-900">
|
<h3 className="mt-2 text-sm font-medium text-gray-900">Varyantlar</h3>
|
||||||
Varyantlar
|
|
||||||
</h3>
|
|
||||||
<p className="mt-1 text-xs text-gray-500">
|
<p className="mt-1 text-xs text-gray-500">
|
||||||
Varyantlar için henüz veri bulunmamaktadır.
|
Varyantlar için henüz veri bulunmamaktadır.
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -544,7 +502,8 @@ const MaterialCard: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
</Container>
|
||||||
};
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export default MaterialCard;
|
export default MaterialCard
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -13,6 +13,7 @@ import {
|
||||||
} from 'react-icons/fa'
|
} from 'react-icons/fa'
|
||||||
import { mockMaterialGroups, populateParentGroups } from '../../../mocks/mockMaterialGroups'
|
import { mockMaterialGroups, populateParentGroups } from '../../../mocks/mockMaterialGroups'
|
||||||
import { MmMaterialGroup } from '../../../types/mm'
|
import { MmMaterialGroup } from '../../../types/mm'
|
||||||
|
import { Container } from '@/components/shared'
|
||||||
|
|
||||||
interface MaterialGroupNode extends MmMaterialGroup {
|
interface MaterialGroupNode extends MmMaterialGroup {
|
||||||
children: MaterialGroupNode[]
|
children: MaterialGroupNode[]
|
||||||
|
|
@ -98,7 +99,8 @@ const MaterialGroups: React.FC = () => {
|
||||||
}, [filteredGroups])
|
}, [filteredGroups])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-3 py-2">
|
<Container>
|
||||||
|
<div className="space-y-2">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3">
|
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3">
|
||||||
{/* Title & Description */}
|
{/* Title & Description */}
|
||||||
|
|
@ -323,7 +325,12 @@ const MaterialGroups: React.FC = () => {
|
||||||
{viewMode === 'tree' && (
|
{viewMode === 'tree' && (
|
||||||
<div className="bg-white shadow-sm rounded-lg p-3">
|
<div className="bg-white shadow-sm rounded-lg p-3">
|
||||||
{groupTree.map((node) => (
|
{groupTree.map((node) => (
|
||||||
<GroupTreeNode key={node.id} node={node} onEdit={handleEdit} onDelete={handleDelete} />
|
<GroupTreeNode
|
||||||
|
key={node.id}
|
||||||
|
node={node}
|
||||||
|
onEdit={handleEdit}
|
||||||
|
onDelete={handleDelete}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
{groupTree.length === 0 && (
|
{groupTree.length === 0 && (
|
||||||
<div className="text-center py-12">
|
<div className="text-center py-12">
|
||||||
|
|
@ -332,6 +339,7 @@ const MaterialGroups: React.FC = () => {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Modal */}
|
{/* Modal */}
|
||||||
{isModalOpen && (
|
{isModalOpen && (
|
||||||
|
|
@ -341,7 +349,7 @@ const MaterialGroups: React.FC = () => {
|
||||||
onClose={() => setIsModalOpen(false)}
|
onClose={() => setIsModalOpen(false)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</Container>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { useState, useEffect, useCallback } from "react";
|
import React, { useState, useEffect, useCallback } from 'react'
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from 'react-router-dom'
|
||||||
import {
|
import {
|
||||||
FaPlus,
|
FaPlus,
|
||||||
FaSearch,
|
FaSearch,
|
||||||
|
|
@ -11,220 +11,207 @@ import {
|
||||||
FaExclamationTriangle,
|
FaExclamationTriangle,
|
||||||
FaArchive,
|
FaArchive,
|
||||||
FaHeartbeat,
|
FaHeartbeat,
|
||||||
} from "react-icons/fa";
|
} from 'react-icons/fa'
|
||||||
import Widget from "../../../components/common/Widget";
|
import Widget from '../../../components/common/Widget'
|
||||||
import DataTable from "../../../components/common/DataTable";
|
import DataTable from '../../../components/common/DataTable'
|
||||||
import LoadingSpinner from "../../../components/common/LoadingSpinner";
|
import LoadingSpinner from '../../../components/common/LoadingSpinner'
|
||||||
import StatusBadge from "../../../components/common/StatusBadge";
|
import StatusBadge from '../../../components/common/StatusBadge'
|
||||||
import { MmMaterial } from "../../../types/mm";
|
import { MmMaterial } from '../../../types/mm'
|
||||||
import { mockMaterials } from "../../../mocks/mockMaterials";
|
import { mockMaterials } from '../../../mocks/mockMaterials'
|
||||||
|
import { Container } from '@/components/shared'
|
||||||
|
|
||||||
export interface MaterialSearchFilters {
|
export interface MaterialSearchFilters {
|
||||||
materialCode?: string;
|
materialCode?: string
|
||||||
description?: string;
|
description?: string
|
||||||
materialTypeId?: string;
|
materialTypeId?: string
|
||||||
materialGroupId?: string;
|
materialGroupId?: string
|
||||||
isActive?: boolean;
|
isActive?: boolean
|
||||||
hasStock?: boolean;
|
hasStock?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const MaterialList: React.FC = () => {
|
const MaterialList: React.FC = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate()
|
||||||
const [materials, setMaterials] = useState<MmMaterial[]>([]);
|
const [materials, setMaterials] = useState<MmMaterial[]>([])
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true)
|
||||||
const [searchFilters, setSearchFilters] = useState<MaterialSearchFilters>({});
|
const [searchFilters, setSearchFilters] = useState<MaterialSearchFilters>({})
|
||||||
const [selectedMaterials] = useState<string[]>([]);
|
const [selectedMaterials] = useState<string[]>([])
|
||||||
|
|
||||||
const [totalCount, setTotalCount] = useState(0);
|
const [totalCount, setTotalCount] = useState(0)
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1)
|
||||||
const [pageSize, setPageSize] = useState(10);
|
const [pageSize, setPageSize] = useState(10)
|
||||||
const [sortBy, setSortBy] = useState<string>("materialCode");
|
const [sortBy, setSortBy] = useState<string>('materialCode')
|
||||||
const [sortDirection, setSortDirection] = useState<"asc" | "desc">("asc");
|
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc')
|
||||||
|
|
||||||
const loadMaterials = useCallback(async () => {
|
const loadMaterials = useCallback(async () => {
|
||||||
setLoading(true);
|
setLoading(true)
|
||||||
try {
|
try {
|
||||||
// Simulate API call
|
// Simulate API call
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
await new Promise((resolve) => setTimeout(resolve, 1000))
|
||||||
|
|
||||||
// Apply filters
|
// Apply filters
|
||||||
let filteredMaterials = [...mockMaterials];
|
let filteredMaterials = [...mockMaterials]
|
||||||
|
|
||||||
if (searchFilters.materialCode) {
|
if (searchFilters.materialCode) {
|
||||||
filteredMaterials = filteredMaterials.filter((m) =>
|
filteredMaterials = filteredMaterials.filter((m) =>
|
||||||
m.code
|
m.code.toLowerCase().includes(searchFilters.materialCode!.toLowerCase()),
|
||||||
.toLowerCase()
|
)
|
||||||
.includes(searchFilters.materialCode!.toLowerCase())
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (searchFilters.description) {
|
if (searchFilters.description) {
|
||||||
filteredMaterials = filteredMaterials.filter((m) =>
|
filteredMaterials = filteredMaterials.filter((m) =>
|
||||||
m.name
|
m.name.toLowerCase().includes(searchFilters.description!.toLowerCase()),
|
||||||
.toLowerCase()
|
)
|
||||||
.includes(searchFilters.description!.toLowerCase())
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (searchFilters.isActive !== undefined) {
|
if (searchFilters.isActive !== undefined) {
|
||||||
filteredMaterials = filteredMaterials.filter(
|
filteredMaterials = filteredMaterials.filter((m) => m.isActive === searchFilters.isActive)
|
||||||
(m) => m.isActive === searchFilters.isActive
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (searchFilters.hasStock !== undefined) {
|
if (searchFilters.hasStock !== undefined) {
|
||||||
if (searchFilters.hasStock) {
|
if (searchFilters.hasStock) {
|
||||||
filteredMaterials = filteredMaterials.filter((m) => m.totalStock > 0);
|
filteredMaterials = filteredMaterials.filter((m) => m.totalStock > 0)
|
||||||
} else {
|
} else {
|
||||||
filteredMaterials = filteredMaterials.filter(
|
filteredMaterials = filteredMaterials.filter((m) => m.totalStock === 0)
|
||||||
(m) => m.totalStock === 0
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply sorting
|
// Apply sorting
|
||||||
filteredMaterials.sort((a, b) => {
|
filteredMaterials.sort((a, b) => {
|
||||||
let aValue: string | number;
|
let aValue: string | number
|
||||||
let bValue: string | number;
|
let bValue: string | number
|
||||||
|
|
||||||
switch (sortBy) {
|
switch (sortBy) {
|
||||||
case "materialCode":
|
case 'materialCode':
|
||||||
aValue = a.code;
|
aValue = a.code
|
||||||
bValue = b.code;
|
bValue = b.code
|
||||||
break;
|
break
|
||||||
case "description":
|
case 'description':
|
||||||
aValue = a.name;
|
aValue = a.name
|
||||||
bValue = b.name;
|
bValue = b.name
|
||||||
break;
|
break
|
||||||
case "materialType":
|
case 'materialType':
|
||||||
aValue = a.materialType?.name || "";
|
aValue = a.materialType?.name || ''
|
||||||
bValue = b.materialType?.name || "";
|
bValue = b.materialType?.name || ''
|
||||||
break;
|
break
|
||||||
case "materialGroupName":
|
case 'materialGroupName':
|
||||||
aValue = a.materialGroup?.name || "";
|
aValue = a.materialGroup?.name || ''
|
||||||
bValue = b.materialGroup?.name || "";
|
bValue = b.materialGroup?.name || ''
|
||||||
break;
|
break
|
||||||
case "baseUnitName":
|
case 'baseUnitName':
|
||||||
aValue = a.baseUnit?.name || "";
|
aValue = a.baseUnit?.name || ''
|
||||||
bValue = b.baseUnit?.name || "";
|
bValue = b.baseUnit?.name || ''
|
||||||
break;
|
break
|
||||||
case "costPrice":
|
case 'costPrice':
|
||||||
aValue = a.costPrice;
|
aValue = a.costPrice
|
||||||
bValue = b.costPrice;
|
bValue = b.costPrice
|
||||||
break;
|
break
|
||||||
case "salesPrice":
|
case 'salesPrice':
|
||||||
aValue = a.salesPrice;
|
aValue = a.salesPrice
|
||||||
bValue = b.salesPrice;
|
bValue = b.salesPrice
|
||||||
break;
|
break
|
||||||
case "totalStock":
|
case 'totalStock':
|
||||||
aValue = a.totalStock;
|
aValue = a.totalStock
|
||||||
bValue = b.totalStock;
|
bValue = b.totalStock
|
||||||
break;
|
break
|
||||||
case "isActive":
|
case 'isActive':
|
||||||
aValue = a.isActive ? 1 : 0;
|
aValue = a.isActive ? 1 : 0
|
||||||
bValue = b.isActive ? 1 : 0;
|
bValue = b.isActive ? 1 : 0
|
||||||
break;
|
break
|
||||||
default:
|
default:
|
||||||
aValue = "";
|
aValue = ''
|
||||||
bValue = "";
|
bValue = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sortDirection === "asc") {
|
if (sortDirection === 'asc') {
|
||||||
return aValue > bValue ? 1 : -1;
|
return aValue > bValue ? 1 : -1
|
||||||
} else {
|
} else {
|
||||||
return aValue < bValue ? 1 : -1;
|
return aValue < bValue ? 1 : -1
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
setTotalCount(filteredMaterials.length);
|
setTotalCount(filteredMaterials.length)
|
||||||
const startIndex = (currentPage - 1) * pageSize;
|
const startIndex = (currentPage - 1) * pageSize
|
||||||
const endIndex = startIndex + pageSize;
|
const endIndex = startIndex + pageSize
|
||||||
setMaterials(filteredMaterials.slice(startIndex, endIndex));
|
setMaterials(filteredMaterials.slice(startIndex, endIndex))
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error loading materials:", error);
|
console.error('Error loading materials:', error)
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false)
|
||||||
}
|
}
|
||||||
}, [searchFilters, sortBy, sortDirection, currentPage, pageSize]);
|
}, [searchFilters, sortBy, sortDirection, currentPage, pageSize])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadMaterials();
|
loadMaterials()
|
||||||
}, [loadMaterials]);
|
}, [loadMaterials])
|
||||||
|
|
||||||
const handleSearch = (field: keyof MaterialSearchFilters, value: string) => {
|
const handleSearch = (field: keyof MaterialSearchFilters, value: string) => {
|
||||||
setSearchFilters((prev) => ({
|
setSearchFilters((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
[field]: value || undefined,
|
[field]: value || undefined,
|
||||||
}));
|
}))
|
||||||
setCurrentPage(1);
|
setCurrentPage(1)
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleSort = (key: string) => {
|
const handleSort = (key: string) => {
|
||||||
if (sortBy === key) {
|
if (sortBy === key) {
|
||||||
setSortDirection((prev) => (prev === "asc" ? "desc" : "asc"));
|
setSortDirection((prev) => (prev === 'asc' ? 'desc' : 'asc'))
|
||||||
} else {
|
} else {
|
||||||
setSortBy(key);
|
setSortBy(key)
|
||||||
setSortDirection("asc");
|
setSortDirection('asc')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
const handleView = (material: MmMaterial) => {
|
const handleView = (material: MmMaterial) => {
|
||||||
navigate(`/admin/supplychain/materials/detail/${material.id}`);
|
navigate(`/admin/supplychain/materials/detail/${material.id}`)
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleEdit = (material: MmMaterial) => {
|
const handleEdit = (material: MmMaterial) => {
|
||||||
navigate(`/admin/supplychain/materials/edit/${material.id}`);
|
navigate(`/admin/supplychain/materials/edit/${material.id}`)
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleDelete = async (material: MmMaterial) => {
|
const handleDelete = async (material: MmMaterial) => {
|
||||||
if (
|
if (window.confirm(`"${material.name}" malzemesini silmek istediğinizden emin misiniz?`)) {
|
||||||
window.confirm(
|
|
||||||
`"${material.name}" malzemesini silmek istediğinizden emin misiniz?`
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
// Implement delete logic
|
// Implement delete logic
|
||||||
console.log("Deleting material:", material.id);
|
console.log('Deleting material:', material.id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
const handleBulkDelete = async () => {
|
const handleBulkDelete = async () => {
|
||||||
if (selectedMaterials.length === 0) return;
|
if (selectedMaterials.length === 0) return
|
||||||
|
|
||||||
if (
|
if (
|
||||||
window.confirm(
|
window.confirm(`${selectedMaterials.length} malzemeyi silmek istediğinizden emin misiniz?`)
|
||||||
`${selectedMaterials.length} malzemeyi silmek istediğinizden emin misiniz?`
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
// Implement bulk delete logic
|
// Implement bulk delete logic
|
||||||
console.log("Bulk deleting materials:", selectedMaterials);
|
console.log('Bulk deleting materials:', selectedMaterials)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
const handleExport = () => {
|
const handleExport = () => {
|
||||||
// Implement export logic
|
// Implement export logic
|
||||||
console.log("Exporting materials with filters:", searchFilters);
|
console.log('Exporting materials with filters:', searchFilters)
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleImport = () => {
|
const handleImport = () => {
|
||||||
// Implement import logic
|
// Implement import logic
|
||||||
};
|
}
|
||||||
|
|
||||||
const getStockStatusColor = (stock: number): string => {
|
const getStockStatusColor = (stock: number): string => {
|
||||||
if (stock === 0) return "text-red-600";
|
if (stock === 0) return 'text-red-600'
|
||||||
if (stock < 100) return "text-yellow-600";
|
if (stock < 100) return 'text-yellow-600'
|
||||||
return "text-green-600";
|
return 'text-green-600'
|
||||||
};
|
}
|
||||||
|
|
||||||
const getStockStatusIcon = (stock: number) => {
|
const getStockStatusIcon = (stock: number) => {
|
||||||
if (stock === 0) return <FaExclamationTriangle className="w-4 h-4" />;
|
if (stock === 0) return <FaExclamationTriangle className="w-4 h-4" />
|
||||||
if (stock < 100) return <FaHeartbeat className="w-4 h-4" />;
|
if (stock < 100) return <FaHeartbeat className="w-4 h-4" />
|
||||||
return <FaArchive className="w-4 h-4" />;
|
return <FaArchive className="w-4 h-4" />
|
||||||
};
|
}
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
key: "materialCode",
|
key: 'materialCode',
|
||||||
header: "Malzeme Kodu",
|
header: 'Malzeme Kodu',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
render: (item: MmMaterial) => (
|
render: (item: MmMaterial) => (
|
||||||
<div
|
<div
|
||||||
|
|
@ -236,8 +223,8 @@ const MaterialList: React.FC = () => {
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "materialName",
|
key: 'materialName',
|
||||||
header: "Maleme Adı",
|
header: 'Maleme Adı',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
render: (item: MmMaterial) => (
|
render: (item: MmMaterial) => (
|
||||||
<div className="max-w-xs truncate" title={item.name}>
|
<div className="max-w-xs truncate" title={item.name}>
|
||||||
|
|
@ -246,14 +233,14 @@ const MaterialList: React.FC = () => {
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "materialTypeName",
|
key: 'materialTypeName',
|
||||||
header: "Malzeme Tipi",
|
header: 'Malzeme Tipi',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
render: (item: MmMaterial) => item.materialType?.name || "-",
|
render: (item: MmMaterial) => item.materialType?.name || '-',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "materialGroupName",
|
key: 'materialGroupName',
|
||||||
header: "Malzeme Grubu",
|
header: 'Malzeme Grubu',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
render: (item: MmMaterial) => (
|
render: (item: MmMaterial) => (
|
||||||
<div className="max-w-xs truncate" title={item.materialGroup?.name}>
|
<div className="max-w-xs truncate" title={item.materialGroup?.name}>
|
||||||
|
|
@ -262,71 +249,69 @@ const MaterialList: React.FC = () => {
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "baseUnitName",
|
key: 'baseUnitName',
|
||||||
header: "Birim",
|
header: 'Birim',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
render: (item: MmMaterial) => item.baseUnit?.code || "-",
|
render: (item: MmMaterial) => item.baseUnit?.code || '-',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "costPrice",
|
key: 'costPrice',
|
||||||
header: "Maliyet",
|
header: 'Maliyet',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
render: (item: MmMaterial) => (
|
render: (item: MmMaterial) => (
|
||||||
<div className="text-right">
|
<div className="text-right">
|
||||||
{item.costPrice.toLocaleString("tr-TR", {
|
{item.costPrice.toLocaleString('tr-TR', {
|
||||||
minimumFractionDigits: 2,
|
minimumFractionDigits: 2,
|
||||||
maximumFractionDigits: 2,
|
maximumFractionDigits: 2,
|
||||||
})}{" "}
|
})}{' '}
|
||||||
{item.currency}
|
{item.currency}
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "salesPrice",
|
key: 'salesPrice',
|
||||||
header: "Satış Fiyatı",
|
header: 'Satış Fiyatı',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
render: (item: MmMaterial) => (
|
render: (item: MmMaterial) => (
|
||||||
<div className="text-right">
|
<div className="text-right">
|
||||||
{item.salesPrice.toLocaleString("tr-TR", {
|
{item.salesPrice.toLocaleString('tr-TR', {
|
||||||
minimumFractionDigits: 2,
|
minimumFractionDigits: 2,
|
||||||
maximumFractionDigits: 2,
|
maximumFractionDigits: 2,
|
||||||
})}{" "}
|
})}{' '}
|
||||||
{item.currency}
|
{item.currency}
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "totalStock",
|
key: 'totalStock',
|
||||||
header: "Stok",
|
header: 'Stok',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
render: (item: MmMaterial) => (
|
render: (item: MmMaterial) => (
|
||||||
<div
|
<div
|
||||||
className={`text-right flex items-center justify-end space-x-1 ${getStockStatusColor(
|
className={`text-right flex items-center justify-end space-x-1 ${getStockStatusColor(
|
||||||
item.totalStock
|
item.totalStock,
|
||||||
)}`}
|
)}`}
|
||||||
>
|
>
|
||||||
{getStockStatusIcon(item.totalStock)}
|
{getStockStatusIcon(item.totalStock)}
|
||||||
<span>
|
<span>
|
||||||
{item.totalStock.toLocaleString("tr-TR", {
|
{item.totalStock.toLocaleString('tr-TR', {
|
||||||
minimumFractionDigits: 2,
|
minimumFractionDigits: 2,
|
||||||
maximumFractionDigits: 2,
|
maximumFractionDigits: 2,
|
||||||
})}{" "}
|
})}{' '}
|
||||||
{item.baseUnit?.code}
|
{item.baseUnit?.code}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "isActive",
|
key: 'isActive',
|
||||||
header: "Durum",
|
header: 'Durum',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
render: (item: MmMaterial) => (
|
render: (item: MmMaterial) => <StatusBadge status={item.isActive ? 'active' : 'inactive'} />,
|
||||||
<StatusBadge status={item.isActive ? "active" : "inactive"} />
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "actions",
|
key: 'actions',
|
||||||
header: "İşlemler",
|
header: 'İşlemler',
|
||||||
render: (item: MmMaterial) => (
|
render: (item: MmMaterial) => (
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<button
|
<button
|
||||||
|
|
@ -353,22 +338,21 @@ const MaterialList: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
];
|
]
|
||||||
|
|
||||||
if (loading && materials.length === 0) {
|
if (loading && materials.length === 0) {
|
||||||
return <LoadingSpinner />;
|
return <LoadingSpinner />
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-3 py-2">
|
<Container>
|
||||||
|
<div className="space-y-2">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3">
|
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3">
|
||||||
{/* Title & Description */}
|
{/* Title & Description */}
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-2xl font-bold text-gray-900">Malzeme Listesi</h2>
|
<h2 className="text-2xl font-bold text-gray-900">Malzeme Listesi</h2>
|
||||||
<p className="text-gray-600">
|
<p className="text-gray-600">Toplam {totalCount} malzeme bulundu</p>
|
||||||
Toplam {totalCount} malzeme bulundu
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Header Actions */}
|
{/* Header Actions */}
|
||||||
|
|
@ -390,7 +374,7 @@ const MaterialList: React.FC = () => {
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate("/admin/supplychain/materials/new")}
|
onClick={() => navigate('/admin/supplychain/materials/new')}
|
||||||
className="flex items-center px-2.5 py-1 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors"
|
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" />
|
<FaPlus size={14} className="mr-2" />
|
||||||
|
|
@ -401,12 +385,7 @@ const MaterialList: React.FC = () => {
|
||||||
|
|
||||||
{/* Stat Widgets */}
|
{/* Stat Widgets */}
|
||||||
<div className="w-full grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-2">
|
<div className="w-full grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-2">
|
||||||
<Widget
|
<Widget title="Toplam Malzeme" value={totalCount} color="blue" icon="FaArchive" />
|
||||||
title="Toplam Malzeme"
|
|
||||||
value={totalCount}
|
|
||||||
color="blue"
|
|
||||||
icon="FaArchive"
|
|
||||||
/>
|
|
||||||
<Widget
|
<Widget
|
||||||
title="Aktif Malzeme"
|
title="Aktif Malzeme"
|
||||||
value={materials.filter((m) => m.isActive).length}
|
value={materials.filter((m) => m.isActive).length}
|
||||||
|
|
@ -442,8 +421,8 @@ const MaterialList: React.FC = () => {
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Malzeme kodu ara..."
|
placeholder="Malzeme kodu ara..."
|
||||||
className="pl-8 pr-3 py-1 w-full border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm"
|
className="pl-8 pr-3 py-1 w-full border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm"
|
||||||
value={searchFilters.materialCode || ""}
|
value={searchFilters.materialCode || ''}
|
||||||
onChange={(e) => handleSearch("materialCode", e.target.value)}
|
onChange={(e) => handleSearch('materialCode', e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -453,8 +432,8 @@ const MaterialList: React.FC = () => {
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Açıklama ara..."
|
placeholder="Açıklama ara..."
|
||||||
className="px-3 py-1 w-full border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm"
|
className="px-3 py-1 w-full border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm"
|
||||||
value={searchFilters.description || ""}
|
value={searchFilters.description || ''}
|
||||||
onChange={(e) => handleSearch("description", e.target.value)}
|
onChange={(e) => handleSearch('description', e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -463,16 +442,14 @@ const MaterialList: React.FC = () => {
|
||||||
<select
|
<select
|
||||||
className="block w-full px-3 py-1 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm"
|
className="block w-full px-3 py-1 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm"
|
||||||
value={
|
value={
|
||||||
searchFilters.isActive === undefined
|
searchFilters.isActive === undefined ? '' : searchFilters.isActive.toString()
|
||||||
? ""
|
|
||||||
: searchFilters.isActive.toString()
|
|
||||||
}
|
}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const value = e.target.value;
|
const value = e.target.value
|
||||||
setSearchFilters((prev) => ({
|
setSearchFilters((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
isActive: value === "" ? undefined : value === "true",
|
isActive: value === '' ? undefined : value === 'true',
|
||||||
}));
|
}))
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<option value="">Durum (Tümü)</option>
|
<option value="">Durum (Tümü)</option>
|
||||||
|
|
@ -486,16 +463,14 @@ const MaterialList: React.FC = () => {
|
||||||
<select
|
<select
|
||||||
className="block w-full px-3 py-1 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm"
|
className="block w-full px-3 py-1 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm"
|
||||||
value={
|
value={
|
||||||
searchFilters.hasStock === undefined
|
searchFilters.hasStock === undefined ? '' : searchFilters.hasStock.toString()
|
||||||
? ""
|
|
||||||
: searchFilters.hasStock.toString()
|
|
||||||
}
|
}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const value = e.target.value;
|
const value = e.target.value
|
||||||
setSearchFilters((prev) => ({
|
setSearchFilters((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
hasStock: value === "" ? undefined : value === "true",
|
hasStock: value === '' ? undefined : value === 'true',
|
||||||
}));
|
}))
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<option value="">Stok Durumu (Tümü)</option>
|
<option value="">Stok Durumu (Tümü)</option>
|
||||||
|
|
@ -510,8 +485,8 @@ const MaterialList: React.FC = () => {
|
||||||
{/* Reset */}
|
{/* Reset */}
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSearchFilters({});
|
setSearchFilters({})
|
||||||
setCurrentPage(1);
|
setCurrentPage(1)
|
||||||
}}
|
}}
|
||||||
className="flex items-center px-2.5 py-1 border border-gray-300 rounded-md text-sm text-gray-700 bg-white hover:bg-gray-50 transition-colors"
|
className="flex items-center px-2.5 py-1 border border-gray-300 rounded-md text-sm text-gray-700 bg-white hover:bg-gray-50 transition-colors"
|
||||||
>
|
>
|
||||||
|
|
@ -565,9 +540,7 @@ const MaterialList: React.FC = () => {
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setCurrentPage(
|
setCurrentPage(Math.min(Math.ceil(totalCount / pageSize), currentPage + 1))
|
||||||
Math.min(Math.ceil(totalCount / pageSize), currentPage + 1)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
disabled={currentPage === Math.ceil(totalCount / pageSize)}
|
disabled={currentPage === Math.ceil(totalCount / pageSize)}
|
||||||
className="ml-3 relative inline-flex items-center px-2.5 py-1 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
|
className="ml-3 relative inline-flex items-center px-2.5 py-1 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
|
@ -578,24 +551,22 @@ const MaterialList: React.FC = () => {
|
||||||
<div className="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
|
<div className="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm text-gray-700">
|
<p className="text-sm text-gray-700">
|
||||||
<span className="font-medium">
|
<span className="font-medium">{(currentPage - 1) * pageSize + 1}</span>
|
||||||
{(currentPage - 1) * pageSize + 1}
|
{' - '}
|
||||||
</span>
|
|
||||||
{" - "}
|
|
||||||
<span className="font-medium">
|
<span className="font-medium">
|
||||||
{Math.min(currentPage * pageSize, totalCount)}
|
{Math.min(currentPage * pageSize, totalCount)}
|
||||||
</span>
|
</span>
|
||||||
{" / "}
|
{' / '}
|
||||||
<span className="font-medium">{totalCount}</span>
|
<span className="font-medium">{totalCount}</span>
|
||||||
{" kayıt"}
|
{' kayıt'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<select
|
<select
|
||||||
value={pageSize}
|
value={pageSize}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setPageSize(Number(e.target.value));
|
setPageSize(Number(e.target.value))
|
||||||
setCurrentPage(1);
|
setCurrentPage(1)
|
||||||
}}
|
}}
|
||||||
className="border border-gray-300 rounded-md text-sm px-2 py-0.5 focus:ring-blue-500 focus:border-blue-500"
|
className="border border-gray-300 rounded-md text-sm px-2 py-0.5 focus:ring-blue-500 focus:border-blue-500"
|
||||||
>
|
>
|
||||||
|
|
@ -612,16 +583,13 @@ const MaterialList: React.FC = () => {
|
||||||
>
|
>
|
||||||
Önceki
|
Önceki
|
||||||
</button>
|
</button>
|
||||||
{Array.from(
|
{Array.from({ length: Math.ceil(totalCount / pageSize) }, (_, i) => i + 1)
|
||||||
{ length: Math.ceil(totalCount / pageSize) },
|
|
||||||
(_, i) => i + 1
|
|
||||||
)
|
|
||||||
.filter((page) => {
|
.filter((page) => {
|
||||||
return (
|
return (
|
||||||
page === 1 ||
|
page === 1 ||
|
||||||
page === Math.ceil(totalCount / pageSize) ||
|
page === Math.ceil(totalCount / pageSize) ||
|
||||||
Math.abs(page - currentPage) <= 2
|
Math.abs(page - currentPage) <= 2
|
||||||
);
|
)
|
||||||
})
|
})
|
||||||
.map((page, index, array) => {
|
.map((page, index, array) => {
|
||||||
if (index > 0 && array[index - 1] !== page - 1) {
|
if (index > 0 && array[index - 1] !== page - 1) {
|
||||||
|
|
@ -637,13 +605,13 @@ const MaterialList: React.FC = () => {
|
||||||
onClick={() => setCurrentPage(page)}
|
onClick={() => setCurrentPage(page)}
|
||||||
className={`relative inline-flex items-center px-2.5 py-1 border text-sm font-medium ${
|
className={`relative inline-flex items-center px-2.5 py-1 border text-sm font-medium ${
|
||||||
currentPage === page
|
currentPage === page
|
||||||
? "z-10 bg-blue-50 border-blue-500 text-blue-600"
|
? 'z-10 bg-blue-50 border-blue-500 text-blue-600'
|
||||||
: "bg-white border-gray-300 text-gray-500 hover:bg-gray-50"
|
: 'bg-white border-gray-300 text-gray-500 hover:bg-gray-50'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{page}
|
{page}
|
||||||
</button>,
|
</button>,
|
||||||
];
|
]
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
|
|
@ -651,22 +619,17 @@ const MaterialList: React.FC = () => {
|
||||||
onClick={() => setCurrentPage(page)}
|
onClick={() => setCurrentPage(page)}
|
||||||
className={`relative inline-flex items-center px-2.5 py-1 border text-sm font-medium ${
|
className={`relative inline-flex items-center px-2.5 py-1 border text-sm font-medium ${
|
||||||
currentPage === page
|
currentPage === page
|
||||||
? "z-10 bg-blue-50 border-blue-500 text-blue-600"
|
? 'z-10 bg-blue-50 border-blue-500 text-blue-600'
|
||||||
: "bg-white border-gray-300 text-gray-500 hover:bg-gray-50"
|
: 'bg-white border-gray-300 text-gray-500 hover:bg-gray-50'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{page}
|
{page}
|
||||||
</button>
|
</button>
|
||||||
);
|
)
|
||||||
})}
|
})}
|
||||||
<button
|
<button
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setCurrentPage(
|
setCurrentPage(Math.min(Math.ceil(totalCount / pageSize), currentPage + 1))
|
||||||
Math.min(
|
|
||||||
Math.ceil(totalCount / pageSize),
|
|
||||||
currentPage + 1
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
disabled={currentPage === Math.ceil(totalCount / pageSize)}
|
disabled={currentPage === Math.ceil(totalCount / pageSize)}
|
||||||
className="relative inline-flex items-center px-2 py-1 rounded-r-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
|
className="relative inline-flex items-center px-2 py-1 rounded-r-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
|
@ -680,7 +643,8 @@ const MaterialList: React.FC = () => {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
</Container>
|
||||||
};
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export default MaterialList;
|
export default MaterialList
|
||||||
|
|
|
||||||
|
|
@ -1,56 +1,47 @@
|
||||||
import React, { useState } from "react";
|
import React, { useState } from 'react'
|
||||||
import {
|
import { FaPlus, FaEdit, FaTrash, FaSearch, FaTh, FaList } from 'react-icons/fa'
|
||||||
FaPlus,
|
import { MmMaterialType, MaterialTypeEnum } from '../../../types/mm'
|
||||||
FaEdit,
|
import { mockMaterialTypes } from '../../../mocks/mockMaterialTypes'
|
||||||
FaTrash,
|
import { getMaterialTypeDisplay } from '../../../utils/erp'
|
||||||
FaSearch,
|
import { Container } from '@/components/shared'
|
||||||
FaTh,
|
|
||||||
FaList,
|
|
||||||
} from "react-icons/fa";
|
|
||||||
import { MmMaterialType, MaterialTypeEnum } from "../../../types/mm";
|
|
||||||
import { mockMaterialTypes } from "../../../mocks/mockMaterialTypes";
|
|
||||||
import { getMaterialTypeDisplay } from "../../../utils/erp";
|
|
||||||
|
|
||||||
const MaterialTypes: React.FC = () => {
|
const MaterialTypes: React.FC = () => {
|
||||||
const [searchTerm, setSearchTerm] = useState("");
|
const [searchTerm, setSearchTerm] = useState('')
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [isModalOpen, setIsModalOpen] = useState(false)
|
||||||
const [editingType, setEditingType] = useState<MmMaterialType | null>(null);
|
const [editingType, setEditingType] = useState<MmMaterialType | null>(null)
|
||||||
const [viewMode, setViewMode] = useState<"list" | "card">("list");
|
const [viewMode, setViewMode] = useState<'list' | 'card'>('list')
|
||||||
|
|
||||||
// Mock data - gerçek uygulamada API'den gelecek
|
// Mock data - gerçek uygulamada API'den gelecek
|
||||||
const [materialTypes, setMaterialTypes] =
|
const [materialTypes, setMaterialTypes] = useState<MmMaterialType[]>(mockMaterialTypes)
|
||||||
useState<MmMaterialType[]>(mockMaterialTypes);
|
|
||||||
|
|
||||||
const filteredTypes = materialTypes.filter(
|
const filteredTypes = materialTypes.filter(
|
||||||
(type) =>
|
(type) =>
|
||||||
type.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
type.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||||
type.description?.toLowerCase().includes(searchTerm.toLowerCase())
|
type.description?.toLowerCase().includes(searchTerm.toLowerCase()),
|
||||||
);
|
)
|
||||||
|
|
||||||
const handleAdd = () => {
|
const handleAdd = () => {
|
||||||
setEditingType(null);
|
setEditingType(null)
|
||||||
setIsModalOpen(true);
|
setIsModalOpen(true)
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleEdit = (type: MmMaterialType) => {
|
const handleEdit = (type: MmMaterialType) => {
|
||||||
setEditingType(type);
|
setEditingType(type)
|
||||||
setIsModalOpen(true);
|
setIsModalOpen(true)
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleDelete = (type: MmMaterialType) => {
|
const handleDelete = (type: MmMaterialType) => {
|
||||||
if (
|
if (window.confirm(`${type.name} türünü silmek istediğinizden emin misiniz?`)) {
|
||||||
window.confirm(`${type.name} türünü silmek istediğinizden emin misiniz?`)
|
setMaterialTypes((prev) => prev.filter((t) => t.id !== type.id))
|
||||||
) {
|
}
|
||||||
setMaterialTypes((prev) => prev.filter((t) => t.id !== type.id));
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
const handleSave = (formData: Partial<MmMaterialType>) => {
|
const handleSave = (formData: Partial<MmMaterialType>) => {
|
||||||
if (editingType) {
|
if (editingType) {
|
||||||
// Update existing type
|
// Update existing type
|
||||||
setMaterialTypes((prev) =>
|
setMaterialTypes((prev) =>
|
||||||
prev.map((t) => (t.id === editingType.id ? { ...t, ...formData } : t))
|
prev.map((t) => (t.id === editingType.id ? { ...t, ...formData } : t)),
|
||||||
);
|
)
|
||||||
} else {
|
} else {
|
||||||
// Add new type
|
// Add new type
|
||||||
const newType: MmMaterialType = {
|
const newType: MmMaterialType = {
|
||||||
|
|
@ -59,15 +50,16 @@ const MaterialTypes: React.FC = () => {
|
||||||
name: formData.name!,
|
name: formData.name!,
|
||||||
description: formData.description,
|
description: formData.description,
|
||||||
isActive: formData.isActive ?? true,
|
isActive: formData.isActive ?? true,
|
||||||
className: "",
|
className: '',
|
||||||
};
|
}
|
||||||
setMaterialTypes((prev) => [...prev, newType]);
|
setMaterialTypes((prev) => [...prev, newType])
|
||||||
|
}
|
||||||
|
setIsModalOpen(false)
|
||||||
}
|
}
|
||||||
setIsModalOpen(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-3 py-2">
|
<Container>
|
||||||
|
<div className="space-y-2">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3">
|
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3">
|
||||||
{/* Title & Description */}
|
{/* Title & Description */}
|
||||||
|
|
@ -81,22 +73,22 @@ const MaterialTypes: React.FC = () => {
|
||||||
{/* View Mode Toggle */}
|
{/* View Mode Toggle */}
|
||||||
<div className="flex bg-gray-100 rounded-lg p-1">
|
<div className="flex bg-gray-100 rounded-lg p-1">
|
||||||
<button
|
<button
|
||||||
onClick={() => setViewMode("list")}
|
onClick={() => setViewMode('list')}
|
||||||
className={`p-1.5 rounded-md transition-colors ${
|
className={`p-1.5 rounded-md transition-colors ${
|
||||||
viewMode === "list"
|
viewMode === 'list'
|
||||||
? "bg-white text-blue-600 shadow-sm"
|
? 'bg-white text-blue-600 shadow-sm'
|
||||||
: "text-gray-600 hover:text-gray-900"
|
: 'text-gray-600 hover:text-gray-900'
|
||||||
}`}
|
}`}
|
||||||
title="Liste Görünümü"
|
title="Liste Görünümü"
|
||||||
>
|
>
|
||||||
<FaList className="w-4 h-4" />
|
<FaList className="w-4 h-4" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setViewMode("card")}
|
onClick={() => setViewMode('card')}
|
||||||
className={`p-1.5 rounded-md transition-colors ${
|
className={`p-1.5 rounded-md transition-colors ${
|
||||||
viewMode === "card"
|
viewMode === 'card'
|
||||||
? "bg-white text-blue-600 shadow-sm"
|
? 'bg-white text-blue-600 shadow-sm'
|
||||||
: "text-gray-600 hover:text-gray-900"
|
: 'text-gray-600 hover:text-gray-900'
|
||||||
}`}
|
}`}
|
||||||
title="Kart Görünümü"
|
title="Kart Görünümü"
|
||||||
>
|
>
|
||||||
|
|
@ -131,7 +123,7 @@ const MaterialTypes: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Types Display */}
|
{/* Types Display */}
|
||||||
{viewMode === "list" ? (
|
{viewMode === 'list' ? (
|
||||||
<div className="bg-white shadow-sm rounded-lg overflow-hidden">
|
<div className="bg-white shadow-sm rounded-lg overflow-hidden">
|
||||||
<table className="min-w-full divide-y divide-gray-200">
|
<table className="min-w-full divide-y divide-gray-200">
|
||||||
<thead className="bg-gray-50">
|
<thead className="bg-gray-50">
|
||||||
|
|
@ -162,24 +154,18 @@ const MaterialTypes: React.FC = () => {
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-3 py-1.5 whitespace-nowrap">
|
<td className="px-3 py-1.5 whitespace-nowrap">
|
||||||
<div className="text-sm font-medium text-gray-900">
|
<div className="text-sm font-medium text-gray-900">{type.name}</div>
|
||||||
{type.name}
|
|
||||||
</div>
|
|
||||||
</td>
|
</td>
|
||||||
<td className="px-3 py-1.5">
|
<td className="px-3 py-1.5">
|
||||||
<div className="text-sm text-gray-900">
|
<div className="text-sm text-gray-900">{type.description}</div>
|
||||||
{type.description}
|
|
||||||
</div>
|
|
||||||
</td>
|
</td>
|
||||||
<td className="px-3 py-1.5 whitespace-nowrap">
|
<td className="px-3 py-1.5 whitespace-nowrap">
|
||||||
<span
|
<span
|
||||||
className={`px-2 py-0.5 text-xs font-medium rounded-full ${
|
className={`px-2 py-0.5 text-xs font-medium rounded-full ${
|
||||||
type.isActive
|
type.isActive ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
|
||||||
? "bg-green-100 text-green-800"
|
|
||||||
: "bg-red-100 text-red-800"
|
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{type.isActive ? "Aktif" : "Pasif"}
|
{type.isActive ? 'Aktif' : 'Pasif'}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-3 py-1.5 whitespace-nowrap text-right text-sm font-medium">
|
<td className="px-3 py-1.5 whitespace-nowrap text-right text-sm font-medium">
|
||||||
|
|
@ -240,25 +226,19 @@ const MaterialTypes: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 className="font-medium text-gray-900 mb-1.5 text-sm">
|
<h3 className="font-medium text-gray-900 mb-1.5 text-sm">{type.name}</h3>
|
||||||
{type.name}
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
{type.description && (
|
{type.description && (
|
||||||
<p className="text-sm text-gray-600 mb-3 line-clamp-2">
|
<p className="text-sm text-gray-600 mb-3 line-clamp-2">{type.description}</p>
|
||||||
{type.description}
|
|
||||||
</p>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<span
|
<span
|
||||||
className={`px-2 py-0.5 text-xs font-medium rounded-full ${
|
className={`px-2 py-0.5 text-xs font-medium rounded-full ${
|
||||||
type.isActive
|
type.isActive ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
|
||||||
? "bg-green-100 text-green-800"
|
|
||||||
: "bg-red-100 text-red-800"
|
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{type.isActive ? "Aktif" : "Pasif"}
|
{type.isActive ? 'Aktif' : 'Pasif'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -271,6 +251,7 @@ const MaterialTypes: React.FC = () => {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Modal */}
|
{/* Modal */}
|
||||||
{isModalOpen && (
|
{isModalOpen && (
|
||||||
|
|
@ -280,47 +261,41 @@ const MaterialTypes: React.FC = () => {
|
||||||
onClose={() => setIsModalOpen(false)}
|
onClose={() => setIsModalOpen(false)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</Container>
|
||||||
);
|
)
|
||||||
};
|
|
||||||
|
|
||||||
interface MaterialTypeModalProps {
|
|
||||||
type?: MmMaterialType | null;
|
|
||||||
onSave: (data: Partial<MmMaterialType>) => void;
|
|
||||||
onClose: () => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const MaterialTypeModal: React.FC<MaterialTypeModalProps> = ({
|
interface MaterialTypeModalProps {
|
||||||
type,
|
type?: MmMaterialType | null
|
||||||
onSave,
|
onSave: (data: Partial<MmMaterialType>) => void
|
||||||
onClose,
|
onClose: () => void
|
||||||
}) => {
|
}
|
||||||
|
|
||||||
|
const MaterialTypeModal: React.FC<MaterialTypeModalProps> = ({ type, onSave, onClose }) => {
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
code: type?.code || MaterialTypeEnum.RawMaterial,
|
code: type?.code || MaterialTypeEnum.RawMaterial,
|
||||||
name: type?.name || "",
|
name: type?.name || '',
|
||||||
description: type?.description || "",
|
description: type?.description || '',
|
||||||
isActive: type?.isActive ?? true,
|
isActive: type?.isActive ?? true,
|
||||||
});
|
})
|
||||||
|
|
||||||
const handleSubmit = (e: React.FormEvent) => {
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault()
|
||||||
onSave(formData);
|
onSave(formData)
|
||||||
};
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50">
|
<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="bg-white rounded-lg w-full max-w-xs">
|
||||||
<div className="px-3 py-2 border-b border-gray-200">
|
<div className="px-3 py-2 border-b border-gray-200">
|
||||||
<h3 className="text-sm font-medium text-gray-900">
|
<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>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form onSubmit={handleSubmit} className="p-3 space-y-2">
|
<form onSubmit={handleSubmit} className="p-3 space-y-2">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-medium text-gray-700 mb-1">
|
<label className="block text-xs font-medium text-gray-700 mb-1">Tür Kodu</label>
|
||||||
Tür Kodu
|
|
||||||
</label>
|
|
||||||
<select
|
<select
|
||||||
value={formData.code}
|
value={formData.code}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
|
|
@ -335,38 +310,28 @@ const MaterialTypeModal: React.FC<MaterialTypeModalProps> = ({
|
||||||
<option value={MaterialTypeEnum.RawMaterial}>Hammadde</option>
|
<option value={MaterialTypeEnum.RawMaterial}>Hammadde</option>
|
||||||
<option value={MaterialTypeEnum.SemiFinished}>Yarı Mamul</option>
|
<option value={MaterialTypeEnum.SemiFinished}>Yarı Mamul</option>
|
||||||
<option value={MaterialTypeEnum.Finished}>Mamul</option>
|
<option value={MaterialTypeEnum.Finished}>Mamul</option>
|
||||||
<option value={MaterialTypeEnum.Consumable}>
|
<option value={MaterialTypeEnum.Consumable}>Sarf Malzemesi</option>
|
||||||
Sarf Malzemesi
|
|
||||||
</option>
|
|
||||||
<option value={MaterialTypeEnum.Service}>Hizmet</option>
|
<option value={MaterialTypeEnum.Service}>Hizmet</option>
|
||||||
<option value={MaterialTypeEnum.Spare}>Yedek Parça</option>
|
<option value={MaterialTypeEnum.Spare}>Yedek Parça</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-medium text-gray-700 mb-1">
|
<label className="block text-xs font-medium text-gray-700 mb-1">Ad</label>
|
||||||
Ad
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={formData.name}
|
value={formData.name}
|
||||||
onChange={(e) =>
|
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||||
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"
|
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
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-medium text-gray-700 mb-1">
|
<label className="block text-xs font-medium text-gray-700 mb-1">Açıklama</label>
|
||||||
Açıklama
|
|
||||||
</label>
|
|
||||||
<textarea
|
<textarea
|
||||||
value={formData.description}
|
value={formData.description}
|
||||||
onChange={(e) =>
|
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
||||||
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"
|
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}
|
rows={3}
|
||||||
/>
|
/>
|
||||||
|
|
@ -377,9 +342,7 @@ const MaterialTypeModal: React.FC<MaterialTypeModalProps> = ({
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
id="isActive"
|
id="isActive"
|
||||||
checked={formData.isActive}
|
checked={formData.isActive}
|
||||||
onChange={(e) =>
|
onChange={(e) => setFormData({ ...formData, isActive: e.target.checked })}
|
||||||
setFormData({ ...formData, isActive: e.target.checked })
|
|
||||||
}
|
|
||||||
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
|
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">
|
<label htmlFor="isActive" className="ml-2 text-sm text-gray-700">
|
||||||
|
|
@ -405,7 +368,7 @@ const MaterialTypeModal: React.FC<MaterialTypeModalProps> = ({
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
export default MaterialTypes;
|
export default MaterialTypes
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { useState } from "react";
|
import React, { useState } from 'react'
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from 'react-router-dom'
|
||||||
import {
|
import {
|
||||||
FaPlus,
|
FaPlus,
|
||||||
FaSearch,
|
FaSearch,
|
||||||
|
|
@ -14,145 +14,126 @@ import {
|
||||||
FaCheckCircle,
|
FaCheckCircle,
|
||||||
FaTimesCircle,
|
FaTimesCircle,
|
||||||
FaExclamationTriangle,
|
FaExclamationTriangle,
|
||||||
} from "react-icons/fa";
|
} from 'react-icons/fa'
|
||||||
import { OrderStatusEnum, MmPurchaseOrder } from "../../../types/mm";
|
import { OrderStatusEnum, MmPurchaseOrder } from '../../../types/mm'
|
||||||
import { mockPurchaseOrders } from "../../../mocks/mockPurchaseOrders";
|
import { mockPurchaseOrders } from '../../../mocks/mockPurchaseOrders'
|
||||||
import Widget from "../../../components/common/Widget";
|
import Widget from '../../../components/common/Widget'
|
||||||
import {
|
import {
|
||||||
getOrderStatusColor,
|
getOrderStatusColor,
|
||||||
getOrderStatusIcon,
|
getOrderStatusIcon,
|
||||||
getOrderStatusText,
|
getOrderStatusText,
|
||||||
getRequestTypeText,
|
getRequestTypeText,
|
||||||
} from "../../../utils/erp";
|
} from '../../../utils/erp'
|
||||||
|
import { Container } from '@/components/shared'
|
||||||
|
|
||||||
const OrderManagement: React.FC = () => {
|
const OrderManagement: React.FC = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate()
|
||||||
const [searchTerm, setSearchTerm] = useState("");
|
const [searchTerm, setSearchTerm] = useState('')
|
||||||
const [statusFilter, setStatusFilter] = useState<OrderStatusEnum | "all">(
|
const [statusFilter, setStatusFilter] = useState<OrderStatusEnum | 'all'>('all')
|
||||||
"all"
|
const [selectedOrders, setSelectedOrders] = useState<string[]>([])
|
||||||
);
|
const [showBulkModal, setShowBulkModal] = useState(false)
|
||||||
const [selectedOrders, setSelectedOrders] = useState<string[]>([]);
|
const [bulkModalType, setBulkModalType] = useState<'approval' | 'status'>('approval')
|
||||||
const [showBulkModal, setShowBulkModal] = useState(false);
|
|
||||||
const [bulkModalType, setBulkModalType] = useState<"approval" | "status">(
|
|
||||||
"approval"
|
|
||||||
);
|
|
||||||
|
|
||||||
// Mock data - replace with actual API calls
|
// 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 filteredOrders = orders.filter((order) => {
|
||||||
const matchesSearch =
|
const matchesSearch =
|
||||||
order.orderNumber.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
order.orderNumber.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||||
order.supplier?.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
order.supplier?.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||||
order.requestTitle.toLowerCase().includes(searchTerm.toLowerCase());
|
order.requestTitle.toLowerCase().includes(searchTerm.toLowerCase())
|
||||||
const matchesStatus =
|
const matchesStatus = statusFilter === 'all' || order.status === statusFilter
|
||||||
statusFilter === "all" || order.status === statusFilter;
|
return matchesSearch && matchesStatus
|
||||||
return matchesSearch && matchesStatus;
|
})
|
||||||
});
|
|
||||||
|
|
||||||
const getStatusText = (status: OrderStatusEnum) => getOrderStatusText(status);
|
const getStatusText = (status: OrderStatusEnum) => getOrderStatusText(status)
|
||||||
|
|
||||||
const getStatusColor = (status: OrderStatusEnum): string =>
|
const getStatusColor = (status: OrderStatusEnum): string => getOrderStatusColor(status)
|
||||||
getOrderStatusColor(status);
|
|
||||||
|
|
||||||
const getStatusIcon = (status: OrderStatusEnum): JSX.Element =>
|
const getStatusIcon = (status: OrderStatusEnum): JSX.Element => getOrderStatusIcon(status)
|
||||||
getOrderStatusIcon(status);
|
|
||||||
|
|
||||||
const getDeliveryProgress = (order: MmPurchaseOrder) => {
|
const getDeliveryProgress = (order: MmPurchaseOrder) => {
|
||||||
const totalQuantity = order.items.reduce(
|
const totalQuantity = order.items.reduce((sum, item) => sum + item.quantity, 0)
|
||||||
(sum, item) => sum + item.quantity,
|
const deliveredQuantity = order.items.reduce((sum, item) => sum + item.deliveredQuantity, 0)
|
||||||
0
|
return totalQuantity > 0 ? Math.round((deliveredQuantity / totalQuantity) * 100) : 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 isOverdue = (order: MmPurchaseOrder) => {
|
||||||
const today = new Date();
|
const today = new Date()
|
||||||
return (
|
return (
|
||||||
order.expectedDeliveryDate < today &&
|
order.expectedDeliveryDate < today &&
|
||||||
order.status !== OrderStatusEnum.Delivered &&
|
order.status !== OrderStatusEnum.Delivered &&
|
||||||
order.status !== OrderStatusEnum.Completed
|
order.status !== OrderStatusEnum.Completed
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
const getDaysUntilDelivery = (order: MmPurchaseOrder) => {
|
const getDaysUntilDelivery = (order: MmPurchaseOrder) => {
|
||||||
const today = new Date();
|
const today = new Date()
|
||||||
const diffTime = order.expectedDeliveryDate.getTime() - today.getTime();
|
const diffTime = order.expectedDeliveryDate.getTime() - today.getTime()
|
||||||
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
|
||||||
return diffDays;
|
return diffDays
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleAddOrder = () => {
|
const handleAddOrder = () => {
|
||||||
navigate("/admin/supplychain/orders/new");
|
navigate('/admin/supplychain/orders/new')
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleEdit = (order: MmPurchaseOrder) => {
|
const handleEdit = (order: MmPurchaseOrder) => {
|
||||||
navigate(`/admin/supplychain/orders/edit/${order.id}`);
|
navigate(`/admin/supplychain/orders/edit/${order.id}`)
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleView = (order: MmPurchaseOrder) => {
|
const handleView = (order: MmPurchaseOrder) => {
|
||||||
navigate(`/admin/supplychain/orders/view/${order.id}`);
|
navigate(`/admin/supplychain/orders/view/${order.id}`)
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleSelectOrder = (orderId: string) => {
|
const handleSelectOrder = (orderId: string) => {
|
||||||
setSelectedOrders((prev) =>
|
setSelectedOrders((prev) =>
|
||||||
prev.includes(orderId)
|
prev.includes(orderId) ? prev.filter((id) => id !== orderId) : [...prev, orderId],
|
||||||
? prev.filter((id) => id !== orderId)
|
)
|
||||||
: [...prev, orderId]
|
}
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleBulkApproval = () => {
|
const handleBulkApproval = () => {
|
||||||
setBulkModalType("approval");
|
setBulkModalType('approval')
|
||||||
setShowBulkModal(true);
|
setShowBulkModal(true)
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleBulkStatusUpdate = () => {
|
const handleBulkStatusUpdate = () => {
|
||||||
setBulkModalType("status");
|
setBulkModalType('status')
|
||||||
setShowBulkModal(true);
|
setShowBulkModal(true)
|
||||||
};
|
}
|
||||||
|
|
||||||
const processBulkApproval = () => {
|
const processBulkApproval = () => {
|
||||||
const updatedOrders = orders.map((order) => {
|
const updatedOrders = orders.map((order) => {
|
||||||
if (selectedOrders.includes(order.id)) {
|
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 processBulkStatusUpdate = (newStatus: OrderStatusEnum) => {
|
||||||
const updatedOrders = orders.map((order) => {
|
const updatedOrders = orders.map((order) => {
|
||||||
if (selectedOrders.includes(order.id)) {
|
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 (
|
return (
|
||||||
<div className="space-y-3 pt-2">
|
<Container>
|
||||||
|
<div className="space-y-2">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-2xl font-bold text-gray-900">
|
<h2 className="text-2xl font-bold text-gray-900">Satınalma Siparişleri</h2>
|
||||||
Satınalma Siparişleri
|
<p className="text-gray-600">Satınalma siparişlerini oluşturun ve takip edin</p>
|
||||||
</h2>
|
|
||||||
<p className="text-gray-600">
|
|
||||||
Satınalma siparişlerini oluşturun ve takip edin
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={handleAddOrder}
|
onClick={handleAddOrder}
|
||||||
|
|
@ -165,20 +146,13 @@ const OrderManagement: React.FC = () => {
|
||||||
|
|
||||||
{/* Summary Cards */}
|
{/* Summary Cards */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||||
<Widget
|
<Widget title="Toplam Sipariş" value={orders.length} color="blue" icon="FaShoppingCart" />
|
||||||
title="Toplam Sipariş"
|
|
||||||
value={orders.length}
|
|
||||||
color="blue"
|
|
||||||
icon="FaShoppingCart"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Widget
|
<Widget
|
||||||
title="Bekleyen"
|
title="Bekleyen"
|
||||||
value={
|
value={
|
||||||
orders.filter((o) =>
|
orders.filter((o) =>
|
||||||
[OrderStatusEnum.Pending, OrderStatusEnum.Confirmed].includes(
|
[OrderStatusEnum.Pending, OrderStatusEnum.Confirmed].includes(o.status),
|
||||||
o.status
|
|
||||||
)
|
|
||||||
).length
|
).length
|
||||||
}
|
}
|
||||||
color="orange"
|
color="orange"
|
||||||
|
|
@ -187,18 +161,14 @@ const OrderManagement: React.FC = () => {
|
||||||
|
|
||||||
<Widget
|
<Widget
|
||||||
title="Teslim Edildi"
|
title="Teslim Edildi"
|
||||||
value={
|
value={orders.filter((o) => o.status === OrderStatusEnum.Delivered).length}
|
||||||
orders.filter((o) => o.status === OrderStatusEnum.Delivered).length
|
|
||||||
}
|
|
||||||
color="green"
|
color="green"
|
||||||
icon="FaTruck"
|
icon="FaTruck"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Widget
|
<Widget
|
||||||
title="Toplam Tutar"
|
title="Toplam Tutar"
|
||||||
value={`₺${orders
|
value={`₺${orders.reduce((sum, order) => sum + order.totalAmount, 0).toLocaleString()}`}
|
||||||
.reduce((sum, order) => sum + order.totalAmount, 0)
|
|
||||||
.toLocaleString()}`}
|
|
||||||
color="purple"
|
color="purple"
|
||||||
icon="FaDollarSign"
|
icon="FaDollarSign"
|
||||||
/>
|
/>
|
||||||
|
|
@ -220,9 +190,7 @@ const OrderManagement: React.FC = () => {
|
||||||
<FaFilter className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-5 h-5" />
|
<FaFilter className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-5 h-5" />
|
||||||
<select
|
<select
|
||||||
value={statusFilter}
|
value={statusFilter}
|
||||||
onChange={(e) =>
|
onChange={(e) => setStatusFilter(e.target.value as OrderStatusEnum | 'all')}
|
||||||
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"
|
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="all">Tüm Durumlar</option>
|
||||||
|
|
@ -231,9 +199,7 @@ const OrderManagement: React.FC = () => {
|
||||||
<option value={OrderStatusEnum.Approved}>Onaylandı</option>
|
<option value={OrderStatusEnum.Approved}>Onaylandı</option>
|
||||||
<option value={OrderStatusEnum.Sent}>Gönderildi</option>
|
<option value={OrderStatusEnum.Sent}>Gönderildi</option>
|
||||||
<option value={OrderStatusEnum.Confirmed}>Onaylandı</option>
|
<option value={OrderStatusEnum.Confirmed}>Onaylandı</option>
|
||||||
<option value={OrderStatusEnum.PartiallyDelivered}>
|
<option value={OrderStatusEnum.PartiallyDelivered}>Kısmi Teslim</option>
|
||||||
Kısmi Teslim
|
|
||||||
</option>
|
|
||||||
<option value={OrderStatusEnum.Delivered}>Teslim Edildi</option>
|
<option value={OrderStatusEnum.Delivered}>Teslim Edildi</option>
|
||||||
<option value={OrderStatusEnum.Completed}>Tamamlandı</option>
|
<option value={OrderStatusEnum.Completed}>Tamamlandı</option>
|
||||||
<option value={OrderStatusEnum.Cancelled}>İptal Edildi</option>
|
<option value={OrderStatusEnum.Cancelled}>İptal Edildi</option>
|
||||||
|
|
@ -257,12 +223,10 @@ const OrderManagement: React.FC = () => {
|
||||||
onChange={() => handleSelectOrder(order.id)}
|
onChange={() => handleSelectOrder(order.id)}
|
||||||
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
||||||
/>
|
/>
|
||||||
<h3 className="text-base font-semibold text-gray-900">
|
<h3 className="text-base font-semibold text-gray-900">{order.orderNumber}</h3>
|
||||||
{order.orderNumber}
|
|
||||||
</h3>
|
|
||||||
<span
|
<span
|
||||||
className={`px-2 py-1 rounded-full text-xs font-medium flex items-center space-x-1 ${getStatusColor(
|
className={`px-2 py-1 rounded-full text-xs font-medium flex items-center space-x-1 ${getStatusColor(
|
||||||
order.status
|
order.status,
|
||||||
)}`}
|
)}`}
|
||||||
>
|
>
|
||||||
{getStatusIcon(order.status)}
|
{getStatusIcon(order.status)}
|
||||||
|
|
@ -277,8 +241,7 @@ const OrderManagement: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
<p className="text-gray-600 mb-1">{order.requestTitle}</p>
|
<p className="text-gray-600 mb-1">{order.requestTitle}</p>
|
||||||
<p className="text-sm text-gray-500">
|
<p className="text-sm text-gray-500">
|
||||||
{order.supplier?.name} •{" "}
|
{order.supplier?.name} • {getRequestTypeText(order.requestType)}
|
||||||
{getRequestTypeText(order.requestType)}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex space-x-1">
|
<div className="flex space-x-1">
|
||||||
|
|
@ -312,7 +275,7 @@ const OrderManagement: React.FC = () => {
|
||||||
<div>
|
<div>
|
||||||
<span className="text-sm text-gray-500">Sipariş Tarihi</span>
|
<span className="text-sm text-gray-500">Sipariş Tarihi</span>
|
||||||
<p className="font-medium text-gray-900">
|
<p className="font-medium text-gray-900">
|
||||||
{order.orderDate.toLocaleDateString("tr-TR")}
|
{order.orderDate.toLocaleDateString('tr-TR')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -320,13 +283,13 @@ const OrderManagement: React.FC = () => {
|
||||||
<p
|
<p
|
||||||
className={`font-medium ${
|
className={`font-medium ${
|
||||||
isOverdue(order)
|
isOverdue(order)
|
||||||
? "text-red-600"
|
? 'text-red-600'
|
||||||
: getDaysUntilDelivery(order) <= 3
|
: getDaysUntilDelivery(order) <= 3
|
||||||
? "text-orange-600"
|
? 'text-orange-600'
|
||||||
: "text-gray-900"
|
: 'text-gray-900'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{order.expectedDeliveryDate.toLocaleDateString("tr-TR")}
|
{order.expectedDeliveryDate.toLocaleDateString('tr-TR')}
|
||||||
{!isOverdue(order) &&
|
{!isOverdue(order) &&
|
||||||
getDaysUntilDelivery(order) <= 7 &&
|
getDaysUntilDelivery(order) <= 7 &&
|
||||||
getDaysUntilDelivery(order) > 0 && (
|
getDaysUntilDelivery(order) > 0 && (
|
||||||
|
|
@ -367,10 +330,7 @@ const OrderManagement: React.FC = () => {
|
||||||
<div className="flex items-center justify-center mb-1">
|
<div className="flex items-center justify-center mb-1">
|
||||||
<FaCheckCircle className="w-4 h-4 text-green-400 mr-1" />
|
<FaCheckCircle className="w-4 h-4 text-green-400 mr-1" />
|
||||||
<span className="font-medium">
|
<span className="font-medium">
|
||||||
{order.items.reduce(
|
{order.items.reduce((sum, item) => sum + item.deliveredQuantity, 0)}
|
||||||
(sum, item) => sum + item.deliveredQuantity,
|
|
||||||
0
|
|
||||||
)}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-gray-500">Teslim</span>
|
<span className="text-gray-500">Teslim</span>
|
||||||
|
|
@ -379,10 +339,7 @@ const OrderManagement: React.FC = () => {
|
||||||
<div className="flex items-center justify-center mb-1">
|
<div className="flex items-center justify-center mb-1">
|
||||||
<FaClock className="w-4 h-4 text-orange-400 mr-1" />
|
<FaClock className="w-4 h-4 text-orange-400 mr-1" />
|
||||||
<span className="font-medium">
|
<span className="font-medium">
|
||||||
{order.items.reduce(
|
{order.items.reduce((sum, item) => sum + item.remainingQuantity, 0)}
|
||||||
(sum, item) => sum + item.remainingQuantity,
|
|
||||||
0
|
|
||||||
)}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-gray-500">Kalan</span>
|
<span className="text-gray-500">Kalan</span>
|
||||||
|
|
@ -392,9 +349,7 @@ const OrderManagement: React.FC = () => {
|
||||||
{/* Items Details */}
|
{/* Items Details */}
|
||||||
<div className="mt-3">
|
<div className="mt-3">
|
||||||
<div className="mb-2">
|
<div className="mb-2">
|
||||||
<h4 className="text-sm font-medium text-gray-900 mb-2">
|
<h4 className="text-sm font-medium text-gray-900 mb-2">Sipariş Kalemleri</h4>
|
||||||
Sipariş Kalemleri
|
|
||||||
</h4>
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{order.items.map((item) => (
|
{order.items.map((item) => (
|
||||||
<div key={item.id} className="bg-gray-50 p-2 rounded-lg">
|
<div key={item.id} className="bg-gray-50 p-2 rounded-lg">
|
||||||
|
|
@ -404,8 +359,7 @@ const OrderManagement: React.FC = () => {
|
||||||
{item.material?.name || item.description}
|
{item.material?.name || item.description}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm text-gray-600">
|
<p className="text-sm text-gray-600">
|
||||||
Malzeme Kodu:{" "}
|
Malzeme Kodu: {item.material?.code || item.materialId} | Miktar:{' '}
|
||||||
{item.material?.code || item.materialId} | Miktar:{" "}
|
|
||||||
{item.quantity} {item.unit} | Birim Fiyat: ₺
|
{item.quantity} {item.unit} | Birim Fiyat: ₺
|
||||||
{item.unitPrice.toLocaleString()}
|
{item.unitPrice.toLocaleString()}
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -417,7 +371,7 @@ const OrderManagement: React.FC = () => {
|
||||||
<div className="text-xs text-gray-500">
|
<div className="text-xs text-gray-500">
|
||||||
<span className="text-green-600">
|
<span className="text-green-600">
|
||||||
Teslim: {item.deliveredQuantity}
|
Teslim: {item.deliveredQuantity}
|
||||||
</span>{" "}
|
</span>{' '}
|
||||||
|
|
|
|
||||||
<span className="text-orange-600">
|
<span className="text-orange-600">
|
||||||
Kalan: {item.remainingQuantity}
|
Kalan: {item.remainingQuantity}
|
||||||
|
|
@ -427,8 +381,7 @@ const OrderManagement: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
{item.deliveryDate && (
|
{item.deliveryDate && (
|
||||||
<div className="text-xs text-gray-500">
|
<div className="text-xs text-gray-500">
|
||||||
Teslim Tarihi:{" "}
|
Teslim Tarihi: {item.deliveryDate.toLocaleDateString('tr-TR')}
|
||||||
{item.deliveryDate.toLocaleDateString("tr-TR")}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -443,16 +396,14 @@ const OrderManagement: React.FC = () => {
|
||||||
<div className="flex items-center space-x-1">
|
<div className="flex items-center space-x-1">
|
||||||
<FaCalendar className="w-4 h-4 text-gray-400" />
|
<FaCalendar className="w-4 h-4 text-gray-400" />
|
||||||
<span className="text-gray-600">
|
<span className="text-gray-600">
|
||||||
{order.creationTime.toLocaleDateString("tr-TR")}
|
{order.creationTime.toLocaleDateString('tr-TR')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{order.approvedBy && (
|
{order.approvedBy && (
|
||||||
<div className="flex items-center space-x-1">
|
<div className="flex items-center space-x-1">
|
||||||
<FaCheckCircle className="w-4 h-4 text-green-400" />
|
<FaCheckCircle className="w-4 h-4 text-green-400" />
|
||||||
<span className="text-gray-600">
|
<span className="text-gray-600">Onaylayan: {order.approvedBy}</span>
|
||||||
Onaylayan: {order.approvedBy}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -470,9 +421,7 @@ const OrderManagement: React.FC = () => {
|
||||||
{filteredOrders.length === 0 && (
|
{filteredOrders.length === 0 && (
|
||||||
<div className="text-center py-8">
|
<div className="text-center py-8">
|
||||||
<FaShoppingCart className="w-16 h-16 text-gray-400 mx-auto mb-4" />
|
<FaShoppingCart className="w-16 h-16 text-gray-400 mx-auto mb-4" />
|
||||||
<h3 className="text-lg font-medium text-gray-900 mb-2">
|
<h3 className="text-lg font-medium text-gray-900 mb-2">Sipariş bulunamadı</h3>
|
||||||
Sipariş bulunamadı
|
|
||||||
</h3>
|
|
||||||
<p className="text-gray-500 mb-3">
|
<p className="text-gray-500 mb-3">
|
||||||
Arama kriterlerinizi değiştirin veya yeni bir sipariş oluşturun.
|
Arama kriterlerinizi değiştirin veya yeni bir sipariş oluşturun.
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -489,9 +438,7 @@ const OrderManagement: React.FC = () => {
|
||||||
{selectedOrders.length > 0 && (
|
{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="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">
|
<div className="flex items-center space-x-3">
|
||||||
<span className="text-sm text-gray-600">
|
<span className="text-sm text-gray-600">{selectedOrders.length} sipariş seçildi</span>
|
||||||
{selectedOrders.length} sipariş seçildi
|
|
||||||
</span>
|
|
||||||
<div className="flex space-x-2">
|
<div className="flex space-x-2">
|
||||||
<button
|
<button
|
||||||
onClick={handleBulkApproval}
|
onClick={handleBulkApproval}
|
||||||
|
|
@ -515,6 +462,7 @@ const OrderManagement: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Bulk Action Modals */}
|
{/* Bulk Action Modals */}
|
||||||
{showBulkModal && (
|
{showBulkModal && (
|
||||||
|
|
@ -522,8 +470,8 @@ const OrderManagement: React.FC = () => {
|
||||||
<div className="bg-white rounded-lg p-4 w-full max-w-2xl mx-4">
|
<div className="bg-white rounded-lg p-4 w-full max-w-2xl mx-4">
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-4">
|
||||||
<h3 className="text-lg font-semibold">
|
<h3 className="text-lg font-semibold">
|
||||||
{bulkModalType === "approval" && "Toplu Onay İşlemi"}
|
{bulkModalType === 'approval' && 'Toplu Onay İşlemi'}
|
||||||
{bulkModalType === "status" && "Toplu Durum Güncelleme"}
|
{bulkModalType === 'status' && 'Toplu Durum Güncelleme'}
|
||||||
</h3>
|
</h3>
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowBulkModal(false)}
|
onClick={() => setShowBulkModal(false)}
|
||||||
|
|
@ -533,7 +481,7 @@ const OrderManagement: React.FC = () => {
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{bulkModalType === "approval" && (
|
{bulkModalType === 'approval' && (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-3">
|
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-3">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
|
|
@ -545,9 +493,7 @@ const OrderManagement: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<h4 className="font-medium text-gray-900">
|
<h4 className="font-medium text-gray-900">Seçili Siparişler:</h4>
|
||||||
Seçili Siparişler:
|
|
||||||
</h4>
|
|
||||||
<div className="max-h-40 overflow-y-auto">
|
<div className="max-h-40 overflow-y-auto">
|
||||||
{orders
|
{orders
|
||||||
.filter((order) => selectedOrders.includes(order.id))
|
.filter((order) => selectedOrders.includes(order.id))
|
||||||
|
|
@ -559,7 +505,7 @@ const OrderManagement: React.FC = () => {
|
||||||
<span className="text-sm">{order.orderNumber}</span>
|
<span className="text-sm">{order.orderNumber}</span>
|
||||||
<span
|
<span
|
||||||
className={`text-xs px-2 py-1 rounded-full ${getStatusColor(
|
className={`text-xs px-2 py-1 rounded-full ${getStatusColor(
|
||||||
order.status
|
order.status,
|
||||||
)}`}
|
)}`}
|
||||||
>
|
>
|
||||||
{getStatusText(order.status)}
|
{getStatusText(order.status)}
|
||||||
|
|
@ -586,7 +532,7 @@ const OrderManagement: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{bulkModalType === "status" && (
|
{bulkModalType === 'status' && (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="bg-blue-50 border border-blue-200 rounded-lg p-3">
|
<div className="bg-blue-50 border border-blue-200 rounded-lg p-3">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
|
|
@ -604,9 +550,9 @@ const OrderManagement: React.FC = () => {
|
||||||
<select
|
<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"
|
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) => {
|
onChange={(e) => {
|
||||||
const newStatus = e.target.value as OrderStatusEnum;
|
const newStatus = e.target.value as OrderStatusEnum
|
||||||
if (newStatus) {
|
if (newStatus) {
|
||||||
processBulkStatusUpdate(newStatus);
|
processBulkStatusUpdate(newStatus)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
defaultValue=""
|
defaultValue=""
|
||||||
|
|
@ -616,25 +562,15 @@ const OrderManagement: React.FC = () => {
|
||||||
<option value={OrderStatusEnum.Approved}>Onaylandı</option>
|
<option value={OrderStatusEnum.Approved}>Onaylandı</option>
|
||||||
<option value={OrderStatusEnum.Sent}>Gönderildi</option>
|
<option value={OrderStatusEnum.Sent}>Gönderildi</option>
|
||||||
<option value={OrderStatusEnum.Confirmed}>Onaylandı</option>
|
<option value={OrderStatusEnum.Confirmed}>Onaylandı</option>
|
||||||
<option value={OrderStatusEnum.PartiallyDelivered}>
|
<option value={OrderStatusEnum.PartiallyDelivered}>Kısmi Teslim</option>
|
||||||
Kısmi Teslim
|
<option value={OrderStatusEnum.Delivered}>Teslim Edildi</option>
|
||||||
</option>
|
<option value={OrderStatusEnum.Completed}>Tamamlandı</option>
|
||||||
<option value={OrderStatusEnum.Delivered}>
|
<option value={OrderStatusEnum.Cancelled}>İptal Edildi</option>
|
||||||
Teslim Edildi
|
|
||||||
</option>
|
|
||||||
<option value={OrderStatusEnum.Completed}>
|
|
||||||
Tamamlandı
|
|
||||||
</option>
|
|
||||||
<option value={OrderStatusEnum.Cancelled}>
|
|
||||||
İptal Edildi
|
|
||||||
</option>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<h4 className="font-medium text-gray-900">
|
<h4 className="font-medium text-gray-900">Seçili Siparişler:</h4>
|
||||||
Seçili Siparişler:
|
|
||||||
</h4>
|
|
||||||
<div className="max-h-40 overflow-y-auto">
|
<div className="max-h-40 overflow-y-auto">
|
||||||
{orders
|
{orders
|
||||||
.filter((order) => selectedOrders.includes(order.id))
|
.filter((order) => selectedOrders.includes(order.id))
|
||||||
|
|
@ -646,7 +582,7 @@ const OrderManagement: React.FC = () => {
|
||||||
<span className="text-sm">{order.orderNumber}</span>
|
<span className="text-sm">{order.orderNumber}</span>
|
||||||
<span
|
<span
|
||||||
className={`text-xs px-2 py-1 rounded-full ${getStatusColor(
|
className={`text-xs px-2 py-1 rounded-full ${getStatusColor(
|
||||||
order.status
|
order.status,
|
||||||
)}`}
|
)}`}
|
||||||
>
|
>
|
||||||
{getStatusText(order.status)}
|
{getStatusText(order.status)}
|
||||||
|
|
@ -669,8 +605,8 @@ const OrderManagement: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</Container>
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
export default OrderManagement;
|
export default OrderManagement
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from 'react'
|
||||||
import { useParams, useNavigate } from "react-router-dom";
|
import { useParams, useNavigate } from 'react-router-dom'
|
||||||
import {
|
import {
|
||||||
FaArrowLeft,
|
FaArrowLeft,
|
||||||
FaSave,
|
FaSave,
|
||||||
|
|
@ -11,85 +11,76 @@ import {
|
||||||
FaTrash,
|
FaTrash,
|
||||||
FaMapMarkerAlt,
|
FaMapMarkerAlt,
|
||||||
FaUser,
|
FaUser,
|
||||||
} from "react-icons/fa";
|
} from 'react-icons/fa'
|
||||||
import {
|
import { MmPurchaseOrder, OrderStatusEnum, MmPurchaseOrderItem } from '../../../types/mm'
|
||||||
MmPurchaseOrder,
|
import { mockMaterials } from '../../../mocks/mockMaterials'
|
||||||
OrderStatusEnum,
|
import { mockBusinessParties } from '../../../mocks/mockBusinessParties'
|
||||||
MmPurchaseOrderItem,
|
import { mockPurchaseOrders } from '../../../mocks/mockPurchaseOrders'
|
||||||
} from "../../../types/mm";
|
import { Address, PaymentTerms } from '../../../types/common'
|
||||||
import { mockMaterials } from "../../../mocks/mockMaterials";
|
import { Container } from '@/components/shared'
|
||||||
import { mockBusinessParties } from "../../../mocks/mockBusinessParties";
|
|
||||||
import { mockPurchaseOrders } from "../../../mocks/mockPurchaseOrders";
|
|
||||||
import { Address, PaymentTerms } from "../../../types/common";
|
|
||||||
|
|
||||||
const OrderManagementForm: React.FC = () => {
|
const OrderManagementForm: React.FC = () => {
|
||||||
const { id } = useParams<{ id: string }>();
|
const { id } = useParams<{ id: string }>()
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate()
|
||||||
const isEdit = id !== undefined && id !== "new";
|
const isEdit = id !== undefined && id !== 'new'
|
||||||
const isView = window.location.pathname.includes("/view/");
|
const isView = window.location.pathname.includes('/view/')
|
||||||
|
|
||||||
// Yeni sipariş için başlangıç şablonu
|
// Yeni sipariş için başlangıç şablonu
|
||||||
const newOrderDefaults: Partial<MmPurchaseOrder> = {
|
const newOrderDefaults: Partial<MmPurchaseOrder> = {
|
||||||
orderNumber: "",
|
orderNumber: '',
|
||||||
supplierId: "",
|
supplierId: '',
|
||||||
orderDate: new Date(),
|
orderDate: new Date(),
|
||||||
deliveryDate: new Date(),
|
deliveryDate: new Date(),
|
||||||
status: OrderStatusEnum.Draft,
|
status: OrderStatusEnum.Draft,
|
||||||
paymentTerms: PaymentTerms.Net30,
|
paymentTerms: PaymentTerms.Net30,
|
||||||
currency: "TRY",
|
currency: 'TRY',
|
||||||
exchangeRate: 1,
|
exchangeRate: 1,
|
||||||
subtotal: 0,
|
subtotal: 0,
|
||||||
taxAmount: 0,
|
taxAmount: 0,
|
||||||
totalAmount: 0,
|
totalAmount: 0,
|
||||||
items: [],
|
items: [],
|
||||||
deliveryAddress: {
|
deliveryAddress: {
|
||||||
street: "",
|
street: '',
|
||||||
city: "",
|
city: '',
|
||||||
state: "",
|
state: '',
|
||||||
postalCode: "",
|
postalCode: '',
|
||||||
country: "Türkiye",
|
country: 'Türkiye',
|
||||||
},
|
},
|
||||||
terms: "",
|
terms: '',
|
||||||
notes: "",
|
notes: '',
|
||||||
receipts: [],
|
receipts: [],
|
||||||
};
|
}
|
||||||
|
|
||||||
// İlk state (isEdit vs new)
|
// İlk state (isEdit vs new)
|
||||||
const [formData, setFormData] = useState<Partial<MmPurchaseOrder>>(() => {
|
const [formData, setFormData] = useState<Partial<MmPurchaseOrder>>(() => {
|
||||||
if (isEdit) {
|
if (isEdit) {
|
||||||
const po = mockPurchaseOrders.find((o) => o.id === id);
|
const po = mockPurchaseOrders.find((o) => o.id === id)
|
||||||
return { ...po };
|
return { ...po }
|
||||||
}
|
}
|
||||||
return { ...newOrderDefaults };
|
return { ...newOrderDefaults }
|
||||||
});
|
})
|
||||||
|
|
||||||
// id değişirse formu güncelle
|
// id değişirse formu güncelle
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isEdit) {
|
if (isEdit) {
|
||||||
const po = mockPurchaseOrders.find((o) => o.id === id);
|
const po = mockPurchaseOrders.find((o) => o.id === id)
|
||||||
setFormData(po ? { ...po } : { ...newOrderDefaults });
|
setFormData(po ? { ...po } : { ...newOrderDefaults })
|
||||||
} else {
|
} else {
|
||||||
setFormData({ ...newOrderDefaults });
|
setFormData({ ...newOrderDefaults })
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [id, isEdit]);
|
}, [id, isEdit])
|
||||||
|
|
||||||
const handleInputChange = (
|
const handleInputChange = (
|
||||||
e: React.ChangeEvent<
|
e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>,
|
||||||
HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement
|
|
||||||
>
|
|
||||||
) => {
|
) => {
|
||||||
const { name, value, type } = e.target;
|
const { name, value, type } = e.target
|
||||||
setFormData((prev) => ({
|
setFormData((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
[name]:
|
[name]:
|
||||||
type === "number"
|
type === 'number' ? parseFloat(value) || 0 : type === 'date' ? new Date(value) : value,
|
||||||
? parseFloat(value) || 0
|
}))
|
||||||
: type === "date"
|
}
|
||||||
? new Date(value)
|
|
||||||
: value,
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleAddressChange = (field: keyof Address, value: string) => {
|
const handleAddressChange = (field: keyof Address, value: string) => {
|
||||||
setFormData((prev) => ({
|
setFormData((prev) => ({
|
||||||
|
|
@ -98,15 +89,15 @@ const OrderManagementForm: React.FC = () => {
|
||||||
...prev.deliveryAddress!,
|
...prev.deliveryAddress!,
|
||||||
[field]: value,
|
[field]: value,
|
||||||
},
|
},
|
||||||
}));
|
}))
|
||||||
};
|
}
|
||||||
|
|
||||||
const addOrderItem = () => {
|
const addOrderItem = () => {
|
||||||
const newItem: MmPurchaseOrderItem = {
|
const newItem: MmPurchaseOrderItem = {
|
||||||
id: `item-${Date.now()}`,
|
id: `item-${Date.now()}`,
|
||||||
orderId: formData.id || "",
|
orderId: formData.id || '',
|
||||||
materialId: "",
|
materialId: '',
|
||||||
description: "",
|
description: '',
|
||||||
quantity: 0,
|
quantity: 0,
|
||||||
unitPrice: 0,
|
unitPrice: 0,
|
||||||
totalPrice: 0,
|
totalPrice: 0,
|
||||||
|
|
@ -114,91 +105,88 @@ const OrderManagementForm: React.FC = () => {
|
||||||
receivedQuantity: 0,
|
receivedQuantity: 0,
|
||||||
deliveredQuantity: 0,
|
deliveredQuantity: 0,
|
||||||
remainingQuantity: 0,
|
remainingQuantity: 0,
|
||||||
unit: "",
|
unit: '',
|
||||||
};
|
}
|
||||||
|
|
||||||
setFormData((prev) => ({
|
setFormData((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
items: [...(prev.items || []), newItem],
|
items: [...(prev.items || []), newItem],
|
||||||
}));
|
}))
|
||||||
};
|
}
|
||||||
|
|
||||||
const removeOrderItem = (index: number) => {
|
const removeOrderItem = (index: number) => {
|
||||||
setFormData((prev) => ({
|
setFormData((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
items: prev.items?.filter((_, i) => i !== index) || [],
|
items: prev.items?.filter((_, i) => i !== index) || [],
|
||||||
}));
|
}))
|
||||||
calculateTotals();
|
calculateTotals()
|
||||||
};
|
}
|
||||||
|
|
||||||
const updateOrderItem = (
|
const updateOrderItem = (
|
||||||
index: number,
|
index: number,
|
||||||
field: keyof MmPurchaseOrderItem,
|
field: keyof MmPurchaseOrderItem,
|
||||||
value: string | number | Date | undefined
|
value: string | number | Date | undefined,
|
||||||
) => {
|
) => {
|
||||||
setFormData((prev) => {
|
setFormData((prev) => {
|
||||||
const updatedItems =
|
const updatedItems =
|
||||||
prev.items?.map((item, i) => {
|
prev.items?.map((item, i) => {
|
||||||
if (i === index) {
|
if (i === index) {
|
||||||
const updatedItem = { ...item, [field]: value };
|
const updatedItem = { ...item, [field]: value }
|
||||||
// Auto-calculate total amount when quantity or unit price changes
|
// Auto-calculate total amount when quantity or unit price changes
|
||||||
if (field === "quantity" || field === "unitPrice") {
|
if (field === 'quantity' || field === 'unitPrice') {
|
||||||
updatedItem.totalPrice =
|
updatedItem.totalPrice = (updatedItem.quantity || 0) * (updatedItem.unitPrice || 0)
|
||||||
(updatedItem.quantity || 0) * (updatedItem.unitPrice || 0);
|
|
||||||
updatedItem.remainingQuantity =
|
updatedItem.remainingQuantity =
|
||||||
updatedItem.quantity - (updatedItem.receivedQuantity || 0);
|
updatedItem.quantity - (updatedItem.receivedQuantity || 0)
|
||||||
}
|
}
|
||||||
return updatedItem;
|
return updatedItem
|
||||||
}
|
}
|
||||||
return item;
|
return item
|
||||||
}) || [];
|
}) || []
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...prev,
|
...prev,
|
||||||
items: updatedItems,
|
items: updatedItems,
|
||||||
};
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
// Recalculate totals
|
// Recalculate totals
|
||||||
setTimeout(calculateTotals, 0);
|
setTimeout(calculateTotals, 0)
|
||||||
};
|
}
|
||||||
|
|
||||||
const calculateTotals = () => {
|
const calculateTotals = () => {
|
||||||
const subtotal =
|
const subtotal = formData.items?.reduce((sum, item) => sum + (item.totalPrice || 0), 0) || 0
|
||||||
formData.items?.reduce((sum, item) => sum + (item.totalPrice || 0), 0) ||
|
const taxAmount = subtotal * 0.18 // %18 KDV
|
||||||
0;
|
const totalAmount = subtotal + taxAmount
|
||||||
const taxAmount = subtotal * 0.18; // %18 KDV
|
|
||||||
const totalAmount = subtotal + taxAmount;
|
|
||||||
|
|
||||||
setFormData((prev) => ({
|
setFormData((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
subtotal,
|
subtotal,
|
||||||
taxAmount,
|
taxAmount,
|
||||||
totalAmount,
|
totalAmount,
|
||||||
}));
|
}))
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleSubmit = (e: React.FormEvent) => {
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault()
|
||||||
// TODO: Implement save logic
|
// TODO: Implement save logic
|
||||||
console.log("Saving purchase order:", formData);
|
console.log('Saving purchase order:', formData)
|
||||||
navigate("/admin/supplychain/orders");
|
navigate('/admin/supplychain/orders')
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
navigate("/admin/supplychain/orders");
|
navigate('/admin/supplychain/orders')
|
||||||
};
|
}
|
||||||
|
|
||||||
const isReadOnly = isView;
|
const isReadOnly = isView
|
||||||
const pageTitle = isEdit
|
const pageTitle = isEdit
|
||||||
? "Satınalma Siparişini Düzenle"
|
? 'Satınalma Siparişini Düzenle'
|
||||||
: isView
|
: isView
|
||||||
? "Satınalma Siparişi Detayları"
|
? 'Satınalma Siparişi Detayları'
|
||||||
: "Yeni Satınalma Siparişi";
|
: 'Yeni Satınalma Siparişi'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-50 py-4">
|
<Container>
|
||||||
<div className="mx-auto">
|
<div className="space-y-2">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="bg-white rounded-lg shadow-md p-2 mb-2">
|
<div className="bg-white rounded-lg shadow-md p-2 mb-2">
|
||||||
<div className="flex items-center justify-between">
|
<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"
|
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" />
|
<FaTimes className="mr-2" />
|
||||||
{isView ? "Kapat" : "İptal"}
|
{isView ? 'Kapat' : 'İptal'}
|
||||||
</button>
|
</button>
|
||||||
{!isView && (
|
{!isView && (
|
||||||
<button
|
<button
|
||||||
|
|
@ -251,7 +239,7 @@ const OrderManagementForm: React.FC = () => {
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="orderNumber"
|
name="orderNumber"
|
||||||
value={formData.orderNumber || ""}
|
value={formData.orderNumber || ''}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
readOnly={isReadOnly || isEdit}
|
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"
|
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>
|
</label>
|
||||||
<select
|
<select
|
||||||
name="supplierId"
|
name="supplierId"
|
||||||
value={formData.supplierId || ""}
|
value={formData.supplierId || ''}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
disabled={isReadOnly}
|
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"
|
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"
|
name="orderDate"
|
||||||
value={
|
value={
|
||||||
formData.orderDate
|
formData.orderDate
|
||||||
? new Date(formData.orderDate)
|
? new Date(formData.orderDate).toISOString().split('T')[0]
|
||||||
.toISOString()
|
: ''
|
||||||
.split("T")[0]
|
|
||||||
: ""
|
|
||||||
}
|
}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
readOnly={isReadOnly}
|
readOnly={isReadOnly}
|
||||||
|
|
@ -311,10 +297,8 @@ const OrderManagementForm: React.FC = () => {
|
||||||
name="deliveryDate"
|
name="deliveryDate"
|
||||||
value={
|
value={
|
||||||
formData.deliveryDate
|
formData.deliveryDate
|
||||||
? new Date(formData.deliveryDate)
|
? new Date(formData.deliveryDate).toISOString().split('T')[0]
|
||||||
.toISOString()
|
: ''
|
||||||
.split("T")[0]
|
|
||||||
: ""
|
|
||||||
}
|
}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
readOnly={isReadOnly}
|
readOnly={isReadOnly}
|
||||||
|
|
@ -345,12 +329,10 @@ const OrderManagementForm: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700">
|
<label className="block text-sm font-medium text-gray-700">Para Birimi</label>
|
||||||
Para Birimi
|
|
||||||
</label>
|
|
||||||
<select
|
<select
|
||||||
name="currency"
|
name="currency"
|
||||||
value={formData.currency || "TRY"}
|
value={formData.currency || 'TRY'}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
disabled={isReadOnly}
|
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"
|
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>
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
name="terms"
|
name="terms"
|
||||||
value={formData.terms || ""}
|
value={formData.terms || ''}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
readOnly={isReadOnly}
|
readOnly={isReadOnly}
|
||||||
rows={2}
|
rows={2}
|
||||||
|
|
@ -376,12 +358,10 @@ const OrderManagementForm: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="md:col-span-2">
|
<div className="md:col-span-2">
|
||||||
<label className="block text-sm font-medium text-gray-700">
|
<label className="block text-sm font-medium text-gray-700">Notlar</label>
|
||||||
Notlar
|
|
||||||
</label>
|
|
||||||
<textarea
|
<textarea
|
||||||
name="notes"
|
name="notes"
|
||||||
value={formData.notes || ""}
|
value={formData.notes || ''}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
readOnly={isReadOnly}
|
readOnly={isReadOnly}
|
||||||
rows={1}
|
rows={1}
|
||||||
|
|
@ -400,75 +380,55 @@ const OrderManagementForm: React.FC = () => {
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||||
<div className="md:col-span-2">
|
<div className="md:col-span-2">
|
||||||
<label className="block text-sm font-medium text-gray-700">
|
<label className="block text-sm font-medium text-gray-700">Adres</label>
|
||||||
Adres
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={formData.deliveryAddress?.street || ""}
|
value={formData.deliveryAddress?.street || ''}
|
||||||
onChange={(e) =>
|
onChange={(e) => handleAddressChange('street', e.target.value)}
|
||||||
handleAddressChange("street", e.target.value)
|
|
||||||
}
|
|
||||||
readOnly={isReadOnly}
|
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"
|
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>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700">
|
<label className="block text-sm font-medium text-gray-700">Şehir</label>
|
||||||
Şehir
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={formData.deliveryAddress?.city || ""}
|
value={formData.deliveryAddress?.city || ''}
|
||||||
onChange={(e) =>
|
onChange={(e) => handleAddressChange('city', e.target.value)}
|
||||||
handleAddressChange("city", e.target.value)
|
|
||||||
}
|
|
||||||
readOnly={isReadOnly}
|
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"
|
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>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700">
|
<label className="block text-sm font-medium text-gray-700">İl/Bölge</label>
|
||||||
İl/Bölge
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={formData.deliveryAddress?.state || ""}
|
value={formData.deliveryAddress?.state || ''}
|
||||||
onChange={(e) =>
|
onChange={(e) => handleAddressChange('state', e.target.value)}
|
||||||
handleAddressChange("state", e.target.value)
|
|
||||||
}
|
|
||||||
readOnly={isReadOnly}
|
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"
|
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>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700">
|
<label className="block text-sm font-medium text-gray-700">Posta Kodu</label>
|
||||||
Posta Kodu
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={formData.deliveryAddress?.postalCode || ""}
|
value={formData.deliveryAddress?.postalCode || ''}
|
||||||
onChange={(e) =>
|
onChange={(e) => handleAddressChange('postalCode', e.target.value)}
|
||||||
handleAddressChange("postalCode", e.target.value)
|
|
||||||
}
|
|
||||||
readOnly={isReadOnly}
|
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"
|
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>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700">
|
<label className="block text-sm font-medium text-gray-700">Ülke</label>
|
||||||
Ülke
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={formData.deliveryAddress?.country || ""}
|
value={formData.deliveryAddress?.country || ''}
|
||||||
onChange={(e) =>
|
onChange={(e) => handleAddressChange('country', e.target.value)}
|
||||||
handleAddressChange("country", e.target.value)
|
|
||||||
}
|
|
||||||
readOnly={isReadOnly}
|
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"
|
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 */}
|
{/* Sipariş Kalemleri */}
|
||||||
<div className="bg-white rounded-lg shadow-md p-4">
|
<div className="bg-white rounded-lg shadow-md p-4">
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-4">
|
||||||
<h3 className="text-lg font-medium text-gray-900">
|
<h3 className="text-lg font-medium text-gray-900">Sipariş Kalemleri</h3>
|
||||||
Sipariş Kalemleri
|
|
||||||
</h3>
|
|
||||||
{!isReadOnly && (
|
{!isReadOnly && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|
@ -496,14 +454,9 @@ const OrderManagementForm: React.FC = () => {
|
||||||
|
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{formData.items?.map((item, index) => (
|
{formData.items?.map((item, index) => (
|
||||||
<div
|
<div key={item.id} className="border rounded-lg p-3 bg-gray-50">
|
||||||
key={item.id}
|
|
||||||
className="border rounded-lg p-3 bg-gray-50"
|
|
||||||
>
|
|
||||||
<div className="flex items-center justify-between mb-2">
|
<div className="flex items-center justify-between mb-2">
|
||||||
<span className="text-sm font-medium text-gray-700">
|
<span className="text-sm font-medium text-gray-700">Kalem {index + 1}</span>
|
||||||
Kalem {index + 1}
|
|
||||||
</span>
|
|
||||||
{!isReadOnly && (
|
{!isReadOnly && (
|
||||||
<button
|
<button
|
||||||
type="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 className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-2">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-medium text-gray-600">
|
<label className="block text-xs font-medium text-gray-600">Malzeme</label>
|
||||||
Malzeme
|
|
||||||
</label>
|
|
||||||
<select
|
<select
|
||||||
value={item.materialId}
|
value={item.materialId}
|
||||||
onChange={(e) =>
|
onChange={(e) => updateOrderItem(index, 'materialId', e.target.value)}
|
||||||
updateOrderItem(
|
|
||||||
index,
|
|
||||||
"materialId",
|
|
||||||
e.target.value
|
|
||||||
)
|
|
||||||
}
|
|
||||||
disabled={isReadOnly}
|
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"
|
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
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={item.description}
|
value={item.description}
|
||||||
onChange={(e) =>
|
onChange={(e) => updateOrderItem(index, 'description', e.target.value)}
|
||||||
updateOrderItem(
|
|
||||||
index,
|
|
||||||
"description",
|
|
||||||
e.target.value
|
|
||||||
)
|
|
||||||
}
|
|
||||||
readOnly={isReadOnly}
|
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"
|
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>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-medium text-gray-600">
|
<label className="block text-xs font-medium text-gray-600">Miktar</label>
|
||||||
Miktar
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
value={item.quantity}
|
value={item.quantity}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
updateOrderItem(
|
updateOrderItem(index, 'quantity', parseFloat(e.target.value) || 0)
|
||||||
index,
|
|
||||||
"quantity",
|
|
||||||
parseFloat(e.target.value) || 0
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
readOnly={isReadOnly}
|
readOnly={isReadOnly}
|
||||||
min="0"
|
min="0"
|
||||||
|
|
@ -589,11 +522,7 @@ const OrderManagementForm: React.FC = () => {
|
||||||
type="number"
|
type="number"
|
||||||
value={item.unitPrice}
|
value={item.unitPrice}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
updateOrderItem(
|
updateOrderItem(index, 'unitPrice', parseFloat(e.target.value) || 0)
|
||||||
index,
|
|
||||||
"unitPrice",
|
|
||||||
parseFloat(e.target.value) || 0
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
readOnly={isReadOnly}
|
readOnly={isReadOnly}
|
||||||
min="0"
|
min="0"
|
||||||
|
|
@ -622,17 +551,11 @@ const OrderManagementForm: React.FC = () => {
|
||||||
type="date"
|
type="date"
|
||||||
value={
|
value={
|
||||||
item.deliveryDate
|
item.deliveryDate
|
||||||
? new Date(item.deliveryDate)
|
? new Date(item.deliveryDate).toISOString().split('T')[0]
|
||||||
.toISOString()
|
: ''
|
||||||
.split("T")[0]
|
|
||||||
: ""
|
|
||||||
}
|
}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
updateOrderItem(
|
updateOrderItem(index, 'deliveryDate', new Date(e.target.value))
|
||||||
index,
|
|
||||||
"deliveryDate",
|
|
||||||
new Date(e.target.value)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
readOnly={isReadOnly}
|
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"
|
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) =>
|
onChange={(e) =>
|
||||||
updateOrderItem(
|
updateOrderItem(
|
||||||
index,
|
index,
|
||||||
"receivedQuantity",
|
'receivedQuantity',
|
||||||
parseFloat(e.target.value) || 0
|
parseFloat(e.target.value) || 0,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
readOnly={isReadOnly}
|
readOnly={isReadOnly}
|
||||||
|
|
@ -678,13 +601,9 @@ const OrderManagementForm: React.FC = () => {
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={item.specifications || ""}
|
value={item.specifications || ''}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
updateOrderItem(
|
updateOrderItem(index, 'specifications', e.target.value)
|
||||||
index,
|
|
||||||
"specifications",
|
|
||||||
e.target.value
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
readOnly={isReadOnly}
|
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"
|
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>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={item.qualityRequirements || ""}
|
value={item.qualityRequirements || ''}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
updateOrderItem(
|
updateOrderItem(index, 'qualityRequirements', e.target.value)
|
||||||
index,
|
|
||||||
"qualityRequirements",
|
|
||||||
e.target.value
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
readOnly={isReadOnly}
|
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"
|
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">
|
<div className="space-y-4">
|
||||||
{/* Durum ve Tutar */}
|
{/* Durum ve Tutar */}
|
||||||
<div className="bg-white rounded-lg shadow-md p-4">
|
<div className="bg-white rounded-lg shadow-md p-4">
|
||||||
<h3 className="text-lg font-medium text-gray-900 mb-3">
|
<h3 className="text-lg font-medium text-gray-900 mb-3">Durum Bilgileri</h3>
|
||||||
Durum Bilgileri
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700">
|
<label className="block text-sm font-medium text-gray-700">Durum</label>
|
||||||
Durum
|
|
||||||
</label>
|
|
||||||
<select
|
<select
|
||||||
name="status"
|
name="status"
|
||||||
value={formData.status || OrderStatusEnum.Draft}
|
value={formData.status || OrderStatusEnum.Draft}
|
||||||
|
|
@ -744,29 +655,17 @@ const OrderManagementForm: React.FC = () => {
|
||||||
>
|
>
|
||||||
<option value={OrderStatusEnum.Draft}>Taslak</option>
|
<option value={OrderStatusEnum.Draft}>Taslak</option>
|
||||||
<option value={OrderStatusEnum.Sent}>Gönderildi</option>
|
<option value={OrderStatusEnum.Sent}>Gönderildi</option>
|
||||||
<option value={OrderStatusEnum.Confirmed}>
|
<option value={OrderStatusEnum.Confirmed}>Onaylandı</option>
|
||||||
Onaylandı
|
<option value={OrderStatusEnum.PartiallyReceived}>Kısmi Teslim</option>
|
||||||
</option>
|
<option value={OrderStatusEnum.Received}>Teslim Alındı</option>
|
||||||
<option value={OrderStatusEnum.PartiallyReceived}>
|
<option value={OrderStatusEnum.Invoiced}>Faturalandı</option>
|
||||||
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.Closed}>Kapatıldı</option>
|
||||||
<option value={OrderStatusEnum.Cancelled}>
|
<option value={OrderStatusEnum.Cancelled}>İptal Edildi</option>
|
||||||
İptal Edildi
|
|
||||||
</option>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700">
|
<label className="block text-sm font-medium text-gray-700">Döviz Kuru</label>
|
||||||
Döviz Kuru
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
name="exchangeRate"
|
name="exchangeRate"
|
||||||
|
|
@ -805,12 +704,9 @@ const OrderManagementForm: React.FC = () => {
|
||||||
|
|
||||||
<div className="border-t pt-2">
|
<div className="border-t pt-2">
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<span className="text-lg font-medium text-gray-900">
|
<span className="text-lg font-medium text-gray-900">Genel Toplam:</span>
|
||||||
Genel Toplam:
|
|
||||||
</span>
|
|
||||||
<span className="text-lg font-bold text-green-600">
|
<span className="text-lg font-bold text-green-600">
|
||||||
{formData.totalAmount?.toLocaleString()}{" "}
|
{formData.totalAmount?.toLocaleString()} {formData.currency}
|
||||||
{formData.currency}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -820,27 +716,18 @@ const OrderManagementForm: React.FC = () => {
|
||||||
{/* Sipariş Geçmişi (sadece görüntüleme) */}
|
{/* Sipariş Geçmişi (sadece görüntüleme) */}
|
||||||
{isView && formData.receipts && formData.receipts.length > 0 && (
|
{isView && formData.receipts && formData.receipts.length > 0 && (
|
||||||
<div className="bg-white rounded-lg shadow-md p-4">
|
<div className="bg-white rounded-lg shadow-md p-4">
|
||||||
<h3 className="text-base font-medium text-gray-900 mb-3">
|
<h3 className="text-base font-medium text-gray-900 mb-3">Teslimat Geçmişi</h3>
|
||||||
Teslimat Geçmişi
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{formData.receipts.map((receipt) => (
|
{formData.receipts.map((receipt) => (
|
||||||
<div
|
<div key={receipt.id} className="border-l-4 border-green-500 pl-4">
|
||||||
key={receipt.id}
|
|
||||||
className="border-l-4 border-green-500 pl-4"
|
|
||||||
>
|
|
||||||
<div className="text-sm font-medium text-gray-900">
|
<div className="text-sm font-medium text-gray-900">
|
||||||
{receipt.receiptNumber}
|
{receipt.receiptNumber}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-gray-500">
|
<div className="text-xs text-gray-500">
|
||||||
{new Date(receipt.receiptDate).toLocaleDateString(
|
{new Date(receipt.receiptDate).toLocaleDateString('tr-TR')}
|
||||||
"tr-TR"
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="text-xs text-gray-600">
|
|
||||||
Durumu: {receipt.status}
|
|
||||||
</div>
|
</div>
|
||||||
|
<div className="text-xs text-gray-600">Durumu: {receipt.status}</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -850,8 +737,8 @@ const OrderManagementForm: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Container>
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
export default OrderManagementForm;
|
export default OrderManagementForm
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from 'react'
|
||||||
import { useParams, useNavigate } from "react-router-dom";
|
import { useParams, useNavigate } from 'react-router-dom'
|
||||||
import {
|
import {
|
||||||
FaArrowLeft,
|
FaArrowLeft,
|
||||||
FaSave,
|
FaSave,
|
||||||
|
|
@ -9,135 +9,127 @@ import {
|
||||||
FaExclamationTriangle,
|
FaExclamationTriangle,
|
||||||
FaPlus,
|
FaPlus,
|
||||||
FaTrash,
|
FaTrash,
|
||||||
} from "react-icons/fa";
|
} from 'react-icons/fa'
|
||||||
import {
|
import {
|
||||||
MmPurchaseRequest,
|
MmPurchaseRequest,
|
||||||
RequestTypeEnum,
|
RequestTypeEnum,
|
||||||
RequestStatusEnum,
|
RequestStatusEnum,
|
||||||
MmPurchaseRequestItem,
|
MmPurchaseRequestItem,
|
||||||
} from "../../../types/mm";
|
} from '../../../types/mm'
|
||||||
import { mockMaterials } from "../../../mocks/mockMaterials";
|
import { mockMaterials } from '../../../mocks/mockMaterials'
|
||||||
import { mockPurchaseRequests } from "../../../mocks/mockPurchaseRequests";
|
import { mockPurchaseRequests } from '../../../mocks/mockPurchaseRequests'
|
||||||
import { PriorityEnum } from "../../../types/common";
|
import { PriorityEnum } from '../../../types/common'
|
||||||
|
import { Container } from '@/components/shared'
|
||||||
|
|
||||||
const PurchaseRequestForm: React.FC = () => {
|
const PurchaseRequestForm: React.FC = () => {
|
||||||
const { id } = useParams<{ id: string }>();
|
const { id } = useParams<{ id: string }>()
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate()
|
||||||
const isEdit = id !== undefined && id !== "new";
|
const isEdit = id !== undefined && id !== 'new'
|
||||||
const isView = window.location.pathname.includes("/view/");
|
const isView = window.location.pathname.includes('/view/')
|
||||||
|
|
||||||
// Yeni kayıt için başlangıç şablonu
|
// Yeni kayıt için başlangıç şablonu
|
||||||
const newRequestDefaults: Partial<MmPurchaseRequest> = {
|
const newRequestDefaults: Partial<MmPurchaseRequest> = {
|
||||||
requestNumber: "",
|
requestNumber: '',
|
||||||
requestType: RequestTypeEnum.Material,
|
requestType: RequestTypeEnum.Material,
|
||||||
description: "",
|
description: '',
|
||||||
department: "",
|
department: '',
|
||||||
requestedBy: "",
|
requestedBy: '',
|
||||||
requestDate: new Date(),
|
requestDate: new Date(),
|
||||||
requiredDate: new Date(),
|
requiredDate: new Date(),
|
||||||
priority: PriorityEnum.Normal,
|
priority: PriorityEnum.Normal,
|
||||||
status: RequestStatusEnum.Draft,
|
status: RequestStatusEnum.Draft,
|
||||||
currency: "TRY",
|
currency: 'TRY',
|
||||||
items: [],
|
items: [],
|
||||||
approvals: [],
|
approvals: [],
|
||||||
attachments: [],
|
attachments: [],
|
||||||
comments: [],
|
comments: [],
|
||||||
};
|
}
|
||||||
|
|
||||||
// isEdit & id'ye göre ilk state
|
// isEdit & id'ye göre ilk state
|
||||||
const [formData, setFormData] = useState<Partial<MmPurchaseRequest>>(() => {
|
const [formData, setFormData] = useState<Partial<MmPurchaseRequest>>(() => {
|
||||||
if (isEdit) {
|
if (isEdit) {
|
||||||
const pr = mockPurchaseRequests.find((a) => a.id === id); // DİKKAT: === kullan
|
const pr = mockPurchaseRequests.find((a) => a.id === id) // DİKKAT: === kullan
|
||||||
return { ...pr };
|
return { ...pr }
|
||||||
}
|
}
|
||||||
// new
|
// new
|
||||||
return { ...newRequestDefaults };
|
return { ...newRequestDefaults }
|
||||||
});
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isEdit) {
|
if (isEdit) {
|
||||||
const pr = mockPurchaseRequests.find((a) => a.id === id);
|
const pr = mockPurchaseRequests.find((a) => a.id === id)
|
||||||
setFormData({ ...pr });
|
setFormData({ ...pr })
|
||||||
} else {
|
} else {
|
||||||
setFormData({ ...newRequestDefaults });
|
setFormData({ ...newRequestDefaults })
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [id, isEdit]);
|
}, [id, isEdit])
|
||||||
|
|
||||||
const handleInputChange = (
|
const handleInputChange = (
|
||||||
e: React.ChangeEvent<
|
e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>,
|
||||||
HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement
|
|
||||||
>
|
|
||||||
) => {
|
) => {
|
||||||
const { name, value, type } = e.target;
|
const { name, value, type } = e.target
|
||||||
setFormData((prev) => ({
|
setFormData((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
[name]:
|
[name]:
|
||||||
type === "number"
|
type === 'number' ? parseFloat(value) || 0 : type === 'date' ? new Date(value) : value,
|
||||||
? parseFloat(value) || 0
|
}))
|
||||||
: type === "date"
|
}
|
||||||
? new Date(value)
|
|
||||||
: value,
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
const addRequestItem = () => {
|
const addRequestItem = () => {
|
||||||
const newItem: MmPurchaseRequestItem = {
|
const newItem: MmPurchaseRequestItem = {
|
||||||
id: `item-${Date.now()}`,
|
id: `item-${Date.now()}`,
|
||||||
requestId: formData.id || "",
|
requestId: formData.id || '',
|
||||||
quantity: 0,
|
quantity: 0,
|
||||||
unit: "",
|
unit: '',
|
||||||
isUrgent: false,
|
isUrgent: false,
|
||||||
};
|
}
|
||||||
|
|
||||||
setFormData((prev) => ({
|
setFormData((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
items: [...(prev.items || []), newItem],
|
items: [...(prev.items || []), newItem],
|
||||||
}));
|
}))
|
||||||
};
|
}
|
||||||
|
|
||||||
const removeRequestItem = (index: number) => {
|
const removeRequestItem = (index: number) => {
|
||||||
setFormData((prev) => ({
|
setFormData((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
items: prev.items?.filter((_, i) => i !== index) || [],
|
items: prev.items?.filter((_, i) => i !== index) || [],
|
||||||
}));
|
}))
|
||||||
};
|
}
|
||||||
|
|
||||||
const updateRequestItem = (
|
const updateRequestItem = (
|
||||||
index: number,
|
index: number,
|
||||||
field: keyof MmPurchaseRequestItem,
|
field: keyof MmPurchaseRequestItem,
|
||||||
value: string | number | boolean | undefined
|
value: string | number | boolean | undefined,
|
||||||
) => {
|
) => {
|
||||||
setFormData((prev) => ({
|
setFormData((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
items:
|
items: prev.items?.map((item, i) => (i === index ? { ...item, [field]: value } : item)) || [],
|
||||||
prev.items?.map((item, i) =>
|
}))
|
||||||
i === index ? { ...item, [field]: value } : item
|
}
|
||||||
) || [],
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = (e: React.FormEvent) => {
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault()
|
||||||
// TODO: Implement save logic
|
// TODO: Implement save logic
|
||||||
console.log("Saving purchase request:", formData);
|
console.log('Saving purchase request:', formData)
|
||||||
navigate("/admin/supplychain/requests");
|
navigate('/admin/supplychain/requests')
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
navigate("/admin/supplychain/requests");
|
navigate('/admin/supplychain/requests')
|
||||||
};
|
}
|
||||||
|
|
||||||
const isReadOnly = isView;
|
const isReadOnly = isView
|
||||||
const pageTitle = isEdit
|
const pageTitle = isEdit
|
||||||
? "Satınalma Talebini Düzenle"
|
? 'Satınalma Talebini Düzenle'
|
||||||
: isView
|
: isView
|
||||||
? "Satınalma Talebi Detayları"
|
? 'Satınalma Talebi Detayları'
|
||||||
: "Yeni Satınalma Talebi";
|
: 'Yeni Satınalma Talebi'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-50 py-4">
|
<Container>
|
||||||
<div className="mx-auto">
|
<div className="space-y-2">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="bg-white rounded-lg shadow-md p-2 mb-2">
|
<div className="bg-white rounded-lg shadow-md p-2 mb-2">
|
||||||
<div className="flex items-center justify-between">
|
<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"
|
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" />
|
<FaTimes className="mr-2" />
|
||||||
{isView ? "Kapat" : "İptal"}
|
{isView ? 'Kapat' : 'İptal'}
|
||||||
</button>
|
</button>
|
||||||
{!isView && (
|
{!isView && (
|
||||||
<button
|
<button
|
||||||
|
|
@ -189,7 +181,7 @@ const PurchaseRequestForm: React.FC = () => {
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="requestNumber"
|
name="requestNumber"
|
||||||
value={formData.requestNumber || ""}
|
value={formData.requestNumber || ''}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
readOnly={isReadOnly || isEdit}
|
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"
|
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>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700">
|
<label className="block text-sm font-medium text-gray-700">Talep Tipi</label>
|
||||||
Talep Tipi
|
|
||||||
</label>
|
|
||||||
<select
|
<select
|
||||||
name="requestType"
|
name="requestType"
|
||||||
value={formData.requestType || RequestTypeEnum.Material}
|
value={formData.requestType || RequestTypeEnum.Material}
|
||||||
|
|
@ -209,20 +199,16 @@ const PurchaseRequestForm: React.FC = () => {
|
||||||
>
|
>
|
||||||
<option value={RequestTypeEnum.Material}>Malzeme</option>
|
<option value={RequestTypeEnum.Material}>Malzeme</option>
|
||||||
<option value={RequestTypeEnum.Service}>Hizmet</option>
|
<option value={RequestTypeEnum.Service}>Hizmet</option>
|
||||||
<option value={RequestTypeEnum.WorkCenter}>
|
<option value={RequestTypeEnum.WorkCenter}>İş Merkezi</option>
|
||||||
İş Merkezi
|
|
||||||
</option>
|
|
||||||
<option value={RequestTypeEnum.Maintenance}>Bakım</option>
|
<option value={RequestTypeEnum.Maintenance}>Bakım</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="md:col-span-2">
|
<div className="md:col-span-2">
|
||||||
<label className="block text-sm font-medium text-gray-700">
|
<label className="block text-sm font-medium text-gray-700">Açıklama</label>
|
||||||
Açıklama
|
|
||||||
</label>
|
|
||||||
<textarea
|
<textarea
|
||||||
name="description"
|
name="description"
|
||||||
value={formData.description || ""}
|
value={formData.description || ''}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
readOnly={isReadOnly}
|
readOnly={isReadOnly}
|
||||||
rows={2}
|
rows={2}
|
||||||
|
|
@ -232,13 +218,11 @@ const PurchaseRequestForm: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700">
|
<label className="block text-sm font-medium text-gray-700">Departman</label>
|
||||||
Departman
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="department"
|
name="department"
|
||||||
value={formData.department || ""}
|
value={formData.department || ''}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
readOnly={isReadOnly}
|
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"
|
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>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700">
|
<label className="block text-sm font-medium text-gray-700">Talep Eden</label>
|
||||||
Talep Eden
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="requestedBy"
|
name="requestedBy"
|
||||||
value={formData.requestedBy || ""}
|
value={formData.requestedBy || ''}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
readOnly={isReadOnly}
|
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"
|
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"
|
name="requestDate"
|
||||||
value={
|
value={
|
||||||
formData.requestDate
|
formData.requestDate
|
||||||
? new Date(formData.requestDate)
|
? new Date(formData.requestDate).toISOString().split('T')[0]
|
||||||
.toISOString()
|
: ''
|
||||||
.split("T")[0]
|
|
||||||
: ""
|
|
||||||
}
|
}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
readOnly={isReadOnly}
|
readOnly={isReadOnly}
|
||||||
|
|
@ -292,10 +272,8 @@ const PurchaseRequestForm: React.FC = () => {
|
||||||
name="requiredDate"
|
name="requiredDate"
|
||||||
value={
|
value={
|
||||||
formData.requiredDate
|
formData.requiredDate
|
||||||
? new Date(formData.requiredDate)
|
? new Date(formData.requiredDate).toISOString().split('T')[0]
|
||||||
.toISOString()
|
: ''
|
||||||
.split("T")[0]
|
|
||||||
: ""
|
|
||||||
}
|
}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
readOnly={isReadOnly}
|
readOnly={isReadOnly}
|
||||||
|
|
@ -323,12 +301,10 @@ const PurchaseRequestForm: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700">
|
<label className="block text-sm font-medium text-gray-700">Para Birimi</label>
|
||||||
Para Birimi
|
|
||||||
</label>
|
|
||||||
<select
|
<select
|
||||||
name="currency"
|
name="currency"
|
||||||
value={formData.currency || "TRY"}
|
value={formData.currency || 'TRY'}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
disabled={isReadOnly}
|
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"
|
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 */}
|
{/* Talep Kalemleri */}
|
||||||
<div className="bg-white rounded-lg shadow-md p-4">
|
<div className="bg-white rounded-lg shadow-md p-4">
|
||||||
<div className="flex items-center justify-between mb-3">
|
<div className="flex items-center justify-between mb-3">
|
||||||
<h3 className="text-base font-medium text-gray-900">
|
<h3 className="text-base font-medium text-gray-900">Talep Kalemleri</h3>
|
||||||
Talep Kalemleri
|
|
||||||
</h3>
|
|
||||||
{!isReadOnly && (
|
{!isReadOnly && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|
@ -361,14 +335,9 @@ const PurchaseRequestForm: React.FC = () => {
|
||||||
|
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{formData.items?.map((item, index) => (
|
{formData.items?.map((item, index) => (
|
||||||
<div
|
<div key={item.id} className="border rounded-lg p-3 bg-gray-50">
|
||||||
key={item.id}
|
|
||||||
className="border rounded-lg p-3 bg-gray-50"
|
|
||||||
>
|
|
||||||
<div className="flex items-center justify-between mb-3">
|
<div className="flex items-center justify-between mb-3">
|
||||||
<span className="text-sm font-medium text-gray-700">
|
<span className="text-sm font-medium text-gray-700">Kalem {index + 1}</span>
|
||||||
Kalem {index + 1}
|
|
||||||
</span>
|
|
||||||
{!isReadOnly && (
|
{!isReadOnly && (
|
||||||
<button
|
<button
|
||||||
type="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 className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-2">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-medium text-gray-600">
|
<label className="block text-xs font-medium text-gray-600">Malzeme</label>
|
||||||
Malzeme
|
|
||||||
</label>
|
|
||||||
<select
|
<select
|
||||||
value={item.materialId || ""}
|
value={item.materialId || ''}
|
||||||
onChange={(e) =>
|
onChange={(e) => updateRequestItem(index, 'materialId', e.target.value)}
|
||||||
updateRequestItem(
|
|
||||||
index,
|
|
||||||
"materialId",
|
|
||||||
e.target.value
|
|
||||||
)
|
|
||||||
}
|
|
||||||
disabled={isReadOnly}
|
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"
|
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>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={item.serviceDescription || ""}
|
value={item.serviceDescription || ''}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
updateRequestItem(
|
updateRequestItem(index, 'serviceDescription', e.target.value)
|
||||||
index,
|
|
||||||
"serviceDescription",
|
|
||||||
e.target.value
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
readOnly={isReadOnly}
|
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"
|
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>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-medium text-gray-600">
|
<label className="block text-xs font-medium text-gray-600">Miktar</label>
|
||||||
Miktar
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
value={item.quantity}
|
value={item.quantity}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
updateRequestItem(
|
updateRequestItem(index, 'quantity', parseFloat(e.target.value) || 0)
|
||||||
index,
|
|
||||||
"quantity",
|
|
||||||
parseFloat(e.target.value) || 0
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
readOnly={isReadOnly}
|
readOnly={isReadOnly}
|
||||||
min="0"
|
min="0"
|
||||||
|
|
@ -447,15 +398,11 @@ const PurchaseRequestForm: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-medium text-gray-600">
|
<label className="block text-xs font-medium text-gray-600">Birim</label>
|
||||||
Birim
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={item.unit}
|
value={item.unit}
|
||||||
onChange={(e) =>
|
onChange={(e) => updateRequestItem(index, 'unit', e.target.value)}
|
||||||
updateRequestItem(index, "unit", e.target.value)
|
|
||||||
}
|
|
||||||
readOnly={isReadOnly}
|
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"
|
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>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
value={item.estimatedPrice || ""}
|
value={item.estimatedPrice || ''}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
updateRequestItem(
|
updateRequestItem(
|
||||||
index,
|
index,
|
||||||
"estimatedPrice",
|
'estimatedPrice',
|
||||||
parseFloat(e.target.value) || undefined
|
parseFloat(e.target.value) || undefined,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
readOnly={isReadOnly}
|
readOnly={isReadOnly}
|
||||||
|
|
@ -488,11 +435,7 @@ const PurchaseRequestForm: React.FC = () => {
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={item.isUrgent}
|
checked={item.isUrgent}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
updateRequestItem(
|
updateRequestItem(index, 'isUrgent', e.target.checked)
|
||||||
index,
|
|
||||||
"isUrgent",
|
|
||||||
e.target.checked
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
disabled={isReadOnly}
|
disabled={isReadOnly}
|
||||||
className="mr-1"
|
className="mr-1"
|
||||||
|
|
@ -507,13 +450,9 @@ const PurchaseRequestForm: React.FC = () => {
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={item.specification || ""}
|
value={item.specification || ''}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
updateRequestItem(
|
updateRequestItem(index, 'specification', e.target.value)
|
||||||
index,
|
|
||||||
"specification",
|
|
||||||
e.target.value
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
readOnly={isReadOnly}
|
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"
|
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>
|
||||||
|
|
||||||
<div className="md:col-span-2 lg:col-span-3">
|
<div className="md:col-span-2 lg:col-span-3">
|
||||||
<label className="block text-xs font-medium text-gray-600">
|
<label className="block text-xs font-medium text-gray-600">Gerekçe</label>
|
||||||
Gerekçe
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={item.justification || ""}
|
value={item.justification || ''}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
updateRequestItem(
|
updateRequestItem(index, 'justification', e.target.value)
|
||||||
index,
|
|
||||||
"justification",
|
|
||||||
e.target.value
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
readOnly={isReadOnly}
|
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"
|
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 */}
|
{/* Yan Panel - Durum ve Onaylar */}
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="bg-white rounded-lg shadow-md p-4">
|
<div className="bg-white rounded-lg shadow-md p-4">
|
||||||
<h3 className="text-base font-medium text-gray-900 mb-3">
|
<h3 className="text-base font-medium text-gray-900 mb-3">Durum Bilgileri</h3>
|
||||||
Durum Bilgileri
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700">
|
<label className="block text-sm font-medium text-gray-700">Durum</label>
|
||||||
Durum
|
|
||||||
</label>
|
|
||||||
<select
|
<select
|
||||||
name="status"
|
name="status"
|
||||||
value={formData.status || RequestStatusEnum.Draft}
|
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"
|
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.Draft}>Taslak</option>
|
||||||
<option value={RequestStatusEnum.Submitted}>
|
<option value={RequestStatusEnum.Submitted}>Gönderildi</option>
|
||||||
Gönderildi
|
<option value={RequestStatusEnum.InReview}>İncelemede</option>
|
||||||
</option>
|
<option value={RequestStatusEnum.Approved}>Onaylandı</option>
|
||||||
<option value={RequestStatusEnum.InReview}>
|
<option value={RequestStatusEnum.Rejected}>Reddedildi</option>
|
||||||
İncelemede
|
<option value={RequestStatusEnum.Cancelled}>İptal Edildi</option>
|
||||||
</option>
|
|
||||||
<option value={RequestStatusEnum.Approved}>
|
|
||||||
Onaylandı
|
|
||||||
</option>
|
|
||||||
<option value={RequestStatusEnum.Rejected}>
|
|
||||||
Reddedildi
|
|
||||||
</option>
|
|
||||||
<option value={RequestStatusEnum.Cancelled}>
|
|
||||||
İptal Edildi
|
|
||||||
</option>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -595,8 +514,7 @@ const PurchaseRequestForm: React.FC = () => {
|
||||||
Toplam Tutar
|
Toplam Tutar
|
||||||
</label>
|
</label>
|
||||||
<div className="mt-1 text-base font-semibold text-green-600">
|
<div className="mt-1 text-base font-semibold text-green-600">
|
||||||
{formData.totalAmount?.toLocaleString()}{" "}
|
{formData.totalAmount?.toLocaleString()} {formData.currency}
|
||||||
{formData.currency}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -604,46 +522,36 @@ const PurchaseRequestForm: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Onay Geçmişi (sadece görüntüleme) */}
|
{/* Onay Geçmişi (sadece görüntüleme) */}
|
||||||
{isView &&
|
{isView && formData.approvals && formData.approvals.length > 0 && (
|
||||||
formData.approvals &&
|
|
||||||
formData.approvals.length > 0 && (
|
|
||||||
<div className="bg-white rounded-lg shadow-md p-4">
|
<div className="bg-white rounded-lg shadow-md p-4">
|
||||||
<h3 className="text-base font-medium text-gray-900 mb-3">
|
<h3 className="text-base font-medium text-gray-900 mb-3">Onay Geçmişi</h3>
|
||||||
Onay Geçmişi
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{formData.approvals.map((approval) => (
|
{formData.approvals.map((approval) => (
|
||||||
<div
|
<div key={approval.id} className="border-l-4 border-blue-500 pl-4">
|
||||||
key={approval.id}
|
|
||||||
className="border-l-4 border-blue-500 pl-4"
|
|
||||||
>
|
|
||||||
<div className="text-sm font-medium text-gray-900">
|
<div className="text-sm font-medium text-gray-900">
|
||||||
{approval.approverName}
|
{approval.approverName}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-gray-500">
|
<div className="text-xs text-gray-500">
|
||||||
{approval.approvalLevel} - Seviye{" "}
|
{approval.approvalLevel} - Seviye {approval.sequence}
|
||||||
{approval.sequence}
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={`text-xs mt-1 ${
|
className={`text-xs mt-1 ${
|
||||||
approval.status === "APPROVED"
|
approval.status === 'APPROVED'
|
||||||
? "text-green-600"
|
? 'text-green-600'
|
||||||
: approval.status === "REJECTED"
|
: approval.status === 'REJECTED'
|
||||||
? "text-red-600"
|
? 'text-red-600'
|
||||||
: "text-yellow-600"
|
: 'text-yellow-600'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{approval.status === "APPROVED"
|
{approval.status === 'APPROVED'
|
||||||
? "Onaylandı"
|
? 'Onaylandı'
|
||||||
: approval.status === "REJECTED"
|
: approval.status === 'REJECTED'
|
||||||
? "Reddedildi"
|
? 'Reddedildi'
|
||||||
: "Beklemede"}
|
: 'Beklemede'}
|
||||||
</div>
|
</div>
|
||||||
{approval.comments && (
|
{approval.comments && (
|
||||||
<div className="text-xs text-gray-600 mt-1">
|
<div className="text-xs text-gray-600 mt-1">{approval.comments}</div>
|
||||||
{approval.comments}
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
@ -654,8 +562,8 @@ const PurchaseRequestForm: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Container>
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
export default PurchaseRequestForm;
|
export default PurchaseRequestForm
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import {
|
||||||
getRequestStatusColor,
|
getRequestStatusColor,
|
||||||
getRequestStatusText,
|
getRequestStatusText,
|
||||||
} from '../../../utils/erp'
|
} from '../../../utils/erp'
|
||||||
|
import { Container } from '@/components/shared'
|
||||||
|
|
||||||
const PurchaseRequests: React.FC = () => {
|
const PurchaseRequests: React.FC = () => {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|
@ -60,7 +61,8 @@ const PurchaseRequests: React.FC = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-3 pt-2">
|
<Container>
|
||||||
|
<div className="space-y-2">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -127,7 +129,9 @@ const PurchaseRequests: React.FC = () => {
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<div className="flex items-center space-x-2 mb-1.5">
|
<div className="flex items-center space-x-2 mb-1.5">
|
||||||
<FaFileAlt className="w-5 h-5 text-gray-600" />
|
<FaFileAlt className="w-5 h-5 text-gray-600" />
|
||||||
<h4 className="text-lg font-semibold text-gray-900">{request.requestNumber}</h4>
|
<h4 className="text-lg font-semibold text-gray-900">
|
||||||
|
{request.requestNumber}
|
||||||
|
</h4>
|
||||||
<span
|
<span
|
||||||
className={`px-2 py-1 rounded-full text-xs font-medium ${getRequestTypeColor(
|
className={`px-2 py-1 rounded-full text-xs font-medium ${getRequestTypeColor(
|
||||||
request.requestType,
|
request.requestType,
|
||||||
|
|
@ -282,6 +286,7 @@ const PurchaseRequests: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</Container>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { useState } from "react";
|
import React, { useState } from 'react'
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from 'react-router-dom'
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from '@tanstack/react-query'
|
||||||
import {
|
import {
|
||||||
FaFileAlt,
|
FaFileAlt,
|
||||||
FaPlus,
|
FaPlus,
|
||||||
|
|
@ -14,64 +14,54 @@ import {
|
||||||
FaExclamationTriangle,
|
FaExclamationTriangle,
|
||||||
FaUser,
|
FaUser,
|
||||||
FaCalendar,
|
FaCalendar,
|
||||||
} from "react-icons/fa";
|
} from 'react-icons/fa'
|
||||||
import classNames from "classnames";
|
import classNames from 'classnames'
|
||||||
import { RequisitionStatusEnum } from "../../../types/mm";
|
import { RequisitionStatusEnum } from '../../../types/mm'
|
||||||
import dayjs from "dayjs";
|
import dayjs from 'dayjs'
|
||||||
import { mockPurchaseRequisitions } from "../../../mocks/mockPurchaseRequisitions";
|
import { mockPurchaseRequisitions } from '../../../mocks/mockPurchaseRequisitions'
|
||||||
import { PriorityEnum } from "../../../types/common";
|
import { PriorityEnum } from '../../../types/common'
|
||||||
import {
|
import {
|
||||||
getPriorityColor,
|
getPriorityColor,
|
||||||
getPriorityText,
|
getPriorityText,
|
||||||
getRequisitionStatusColor,
|
getRequisitionStatusColor,
|
||||||
getRequisitionStatusIcon,
|
getRequisitionStatusIcon,
|
||||||
getRequisitionStatusText,
|
getRequisitionStatusText,
|
||||||
} from "../../../utils/erp";
|
} from '../../../utils/erp'
|
||||||
|
import { Container } from '@/components/shared'
|
||||||
|
|
||||||
const PurchaseRequisitionList: React.FC = () => {
|
const PurchaseRequisitionList: React.FC = () => {
|
||||||
const [searchTerm, setSearchTerm] = useState("");
|
const [searchTerm, setSearchTerm] = useState('')
|
||||||
const [filterStatus, setFilterStatus] = useState("all");
|
const [filterStatus, setFilterStatus] = useState('all')
|
||||||
const [filterPriority, setFilterPriority] = useState("all");
|
const [filterPriority, setFilterPriority] = useState('all')
|
||||||
const [showFilters, setShowFilters] = useState(false);
|
const [showFilters, setShowFilters] = useState(false)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: requisitions,
|
data: requisitions,
|
||||||
isLoading,
|
isLoading,
|
||||||
error,
|
error,
|
||||||
} = useQuery({
|
} = useQuery({
|
||||||
queryKey: [
|
queryKey: ['purchase-requisitions', searchTerm, filterStatus, filterPriority],
|
||||||
"purchase-requisitions",
|
|
||||||
searchTerm,
|
|
||||||
filterStatus,
|
|
||||||
filterPriority,
|
|
||||||
],
|
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
await new Promise((resolve) => setTimeout(resolve, 500))
|
||||||
return mockPurchaseRequisitions.filter((req) => {
|
return mockPurchaseRequisitions.filter((req) => {
|
||||||
const matchesSearch =
|
const matchesSearch =
|
||||||
req.requisitionNumber
|
req.requisitionNumber.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||||
.toLowerCase()
|
|
||||||
.includes(searchTerm.toLowerCase()) ||
|
|
||||||
req.description?.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
req.description?.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||||
req.requestedBy.toLowerCase().includes(searchTerm.toLowerCase());
|
req.requestedBy.toLowerCase().includes(searchTerm.toLowerCase())
|
||||||
const matchesStatus =
|
const matchesStatus = filterStatus === 'all' || req.status === filterStatus
|
||||||
filterStatus === "all" || req.status === filterStatus;
|
const matchesPriority = filterPriority === 'all' || req.priority === filterPriority
|
||||||
const matchesPriority =
|
return matchesSearch && matchesStatus && matchesPriority
|
||||||
filterPriority === "all" || req.priority === filterPriority;
|
})
|
||||||
return matchesSearch && matchesStatus && matchesPriority;
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center py-12">
|
<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>
|
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
|
||||||
<span className="ml-3 text-gray-600">
|
<span className="ml-3 text-gray-600">Satınalma talepleri yükleniyor...</span>
|
||||||
Satınalma talepleri yükleniyor...
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
|
|
@ -79,16 +69,15 @@ const PurchaseRequisitionList: React.FC = () => {
|
||||||
<div className="bg-red-50 border border-red-200 rounded-lg p-4">
|
<div className="bg-red-50 border border-red-200 rounded-lg p-4">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<FaExclamationTriangle className="h-5 w-5 text-red-600 mr-2" />
|
<FaExclamationTriangle className="h-5 w-5 text-red-600 mr-2" />
|
||||||
<span className="text-red-800">
|
<span className="text-red-800">Satınalma talepleri yüklenirken hata oluştu.</span>
|
||||||
Satınalma talepleri yüklenirken hata oluştu.
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-3 pt-2">
|
<Container>
|
||||||
|
<div className="space-y-2">
|
||||||
{/* Header Actions */}
|
{/* Header Actions */}
|
||||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
<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="flex items-center space-x-4">
|
||||||
|
|
@ -109,10 +98,10 @@ const PurchaseRequisitionList: React.FC = () => {
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowFilters(!showFilters)}
|
onClick={() => setShowFilters(!showFilters)}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"flex items-center px-3 py-1.5 border rounded-lg transition-colors",
|
'flex items-center px-3 py-1.5 border rounded-lg transition-colors',
|
||||||
showFilters
|
showFilters
|
||||||
? "border-blue-500 bg-blue-50 text-blue-700"
|
? 'border-blue-500 bg-blue-50 text-blue-700'
|
||||||
: "border-gray-300 bg-white text-gray-700 hover:bg-gray-50"
|
: 'border-gray-300 bg-white text-gray-700 hover:bg-gray-50',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<FaFilter size={16} className="mr-2" />
|
<FaFilter size={16} className="mr-2" />
|
||||||
|
|
@ -122,7 +111,7 @@ const PurchaseRequisitionList: React.FC = () => {
|
||||||
|
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
<button
|
<button
|
||||||
onClick={() => alert("Dışa aktarma özelliği yakında eklenecek")}
|
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"
|
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" />
|
<FaDownload size={16} className="mr-2" />
|
||||||
|
|
@ -144,9 +133,7 @@ const PurchaseRequisitionList: React.FC = () => {
|
||||||
<div className="bg-white border border-gray-200 rounded-lg p-3">
|
<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 className="grid grid-cols-1 md:grid-cols-4 gap-3">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
<label className="block text-sm font-medium text-gray-700 mb-1">Durum</label>
|
||||||
Durum
|
|
||||||
</label>
|
|
||||||
<select
|
<select
|
||||||
value={filterStatus}
|
value={filterStatus}
|
||||||
onChange={(e) => setFilterStatus(e.target.value)}
|
onChange={(e) => setFilterStatus(e.target.value)}
|
||||||
|
|
@ -154,23 +141,15 @@ const PurchaseRequisitionList: React.FC = () => {
|
||||||
>
|
>
|
||||||
<option value="all">Tümü</option>
|
<option value="all">Tümü</option>
|
||||||
<option value={RequisitionStatusEnum.Draft}>Taslak</option>
|
<option value={RequisitionStatusEnum.Draft}>Taslak</option>
|
||||||
<option value={RequisitionStatusEnum.Submitted}>
|
<option value={RequisitionStatusEnum.Submitted}>Gönderildi</option>
|
||||||
Gönderildi
|
|
||||||
</option>
|
|
||||||
<option value={RequisitionStatusEnum.InApproval}>Onayda</option>
|
<option value={RequisitionStatusEnum.InApproval}>Onayda</option>
|
||||||
<option value={RequisitionStatusEnum.Approved}>
|
<option value={RequisitionStatusEnum.Approved}>Onaylandı</option>
|
||||||
Onaylandı
|
<option value={RequisitionStatusEnum.Rejected}>Reddedildi</option>
|
||||||
</option>
|
|
||||||
<option value={RequisitionStatusEnum.Rejected}>
|
|
||||||
Reddedildi
|
|
||||||
</option>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
<label className="block text-sm font-medium text-gray-700 mb-1">Öncelik</label>
|
||||||
Öncelik
|
|
||||||
</label>
|
|
||||||
<select
|
<select
|
||||||
value={filterPriority}
|
value={filterPriority}
|
||||||
onChange={(e) => setFilterPriority(e.target.value)}
|
onChange={(e) => setFilterPriority(e.target.value)}
|
||||||
|
|
@ -187,9 +166,9 @@ const PurchaseRequisitionList: React.FC = () => {
|
||||||
<div className="flex items-end">
|
<div className="flex items-end">
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setFilterStatus("all");
|
setFilterStatus('all')
|
||||||
setFilterPriority("all");
|
setFilterPriority('all')
|
||||||
setSearchTerm("");
|
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"
|
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"
|
||||||
>
|
>
|
||||||
|
|
@ -206,9 +185,7 @@ const PurchaseRequisitionList: React.FC = () => {
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-medium text-gray-600">Toplam Talep</p>
|
<p className="text-sm font-medium text-gray-600">Toplam Talep</p>
|
||||||
<p className="text-xl font-bold text-gray-900">
|
<p className="text-xl font-bold text-gray-900">{requisitions?.length || 0}</p>
|
||||||
{requisitions?.length || 0}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<FaFileAlt className="h-8 w-8 text-blue-600" />
|
<FaFileAlt className="h-8 w-8 text-blue-600" />
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -219,9 +196,8 @@ const PurchaseRequisitionList: React.FC = () => {
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-medium text-gray-600">Onay Bekleyen</p>
|
<p className="text-sm font-medium text-gray-600">Onay Bekleyen</p>
|
||||||
<p className="text-xl font-bold text-yellow-600">
|
<p className="text-xl font-bold text-yellow-600">
|
||||||
{requisitions?.filter(
|
{requisitions?.filter((r) => r.status === RequisitionStatusEnum.InApproval)
|
||||||
(r) => r.status === RequisitionStatusEnum.InApproval
|
.length || 0}
|
||||||
).length || 0}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<FaClock className="h-8 w-8 text-yellow-600" />
|
<FaClock className="h-8 w-8 text-yellow-600" />
|
||||||
|
|
@ -233,9 +209,8 @@ const PurchaseRequisitionList: React.FC = () => {
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-medium text-gray-600">Onaylanan</p>
|
<p className="text-sm font-medium text-gray-600">Onaylanan</p>
|
||||||
<p className="text-xl font-bold text-green-600">
|
<p className="text-xl font-bold text-green-600">
|
||||||
{requisitions?.filter(
|
{requisitions?.filter((r) => r.status === RequisitionStatusEnum.Approved)
|
||||||
(r) => r.status === RequisitionStatusEnum.Approved
|
.length || 0}
|
||||||
).length || 0}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<FaCheckCircle className="h-8 w-8 text-green-600" />
|
<FaCheckCircle className="h-8 w-8 text-green-600" />
|
||||||
|
|
@ -247,8 +222,7 @@ const PurchaseRequisitionList: React.FC = () => {
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-medium text-gray-600">Acil Talepler</p>
|
<p className="text-sm font-medium text-gray-600">Acil Talepler</p>
|
||||||
<p className="text-xl font-bold text-red-600">
|
<p className="text-xl font-bold text-red-600">
|
||||||
{requisitions?.filter((r) => r.priority === PriorityEnum.Urgent)
|
{requisitions?.filter((r) => r.priority === PriorityEnum.Urgent).length || 0}
|
||||||
.length || 0}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<FaExclamationTriangle className="h-8 w-8 text-red-600" />
|
<FaExclamationTriangle className="h-8 w-8 text-red-600" />
|
||||||
|
|
@ -259,9 +233,7 @@ const PurchaseRequisitionList: React.FC = () => {
|
||||||
{/* Requisitions Table */}
|
{/* Requisitions Table */}
|
||||||
<div className="bg-white rounded-lg shadow-sm border border-gray-200">
|
<div className="bg-white rounded-lg shadow-sm border border-gray-200">
|
||||||
<div className="px-4 py-3 border-b border-gray-200">
|
<div className="px-4 py-3 border-b border-gray-200">
|
||||||
<h2 className="text-xl font-bold text-gray-900">
|
<h2 className="text-xl font-bold text-gray-900">Satınalma Talepleri</h2>
|
||||||
Satınalma Talepleri
|
|
||||||
</h2>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="overflow-x-auto">
|
<div className="overflow-x-auto">
|
||||||
|
|
@ -294,10 +266,7 @@ const PurchaseRequisitionList: React.FC = () => {
|
||||||
|
|
||||||
<tbody className="bg-white divide-y divide-gray-200">
|
<tbody className="bg-white divide-y divide-gray-200">
|
||||||
{requisitions?.map((requisition) => (
|
{requisitions?.map((requisition) => (
|
||||||
<tr
|
<tr key={requisition.id} className="hover:bg-gray-50 transition-colors">
|
||||||
key={requisition.id}
|
|
||||||
className="hover:bg-gray-50 transition-colors"
|
|
||||||
>
|
|
||||||
<td className="px-4 py-3">
|
<td className="px-4 py-3">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<div className="flex-shrink-0 h-10 w-10">
|
<div className="flex-shrink-0 h-10 w-10">
|
||||||
|
|
@ -320,12 +289,8 @@ const PurchaseRequisitionList: React.FC = () => {
|
||||||
<div className="flex items-center text-sm">
|
<div className="flex items-center text-sm">
|
||||||
<FaUser size={16} className="text-gray-400 mr-2" />
|
<FaUser size={16} className="text-gray-400 mr-2" />
|
||||||
<div>
|
<div>
|
||||||
<div className="font-medium text-gray-900">
|
<div className="font-medium text-gray-900">{requisition.requestedBy}</div>
|
||||||
{requisition.requestedBy}
|
<div className="text-gray-500">{requisition.departmentId}</div>
|
||||||
</div>
|
|
||||||
<div className="text-gray-500">
|
|
||||||
{requisition.departmentId}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
@ -334,16 +299,16 @@ const PurchaseRequisitionList: React.FC = () => {
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"text-sm font-medium",
|
'text-sm font-medium',
|
||||||
getPriorityColor(requisition.priority)
|
getPriorityColor(requisition.priority),
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{getPriorityText(requisition.priority)}
|
{getPriorityText(requisition.priority)}
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium",
|
'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium',
|
||||||
getRequisitionStatusColor(requisition.status)
|
getRequisitionStatusColor(requisition.status),
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{getRequisitionStatusIcon(requisition.status)}
|
{getRequisitionStatusIcon(requisition.status)}
|
||||||
|
|
@ -358,11 +323,10 @@ const PurchaseRequisitionList: React.FC = () => {
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<div className="flex items-center text-sm text-gray-900">
|
<div className="flex items-center text-sm text-gray-900">
|
||||||
<FaCalendar size={14} className="mr-1" />
|
<FaCalendar size={14} className="mr-1" />
|
||||||
{dayjs(requisition.requestDate).format("DD.MM.YYYY")}
|
{dayjs(requisition.requestDate).format('DD.MM.YYYY')}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-gray-500">
|
<div className="text-sm text-gray-500">
|
||||||
İhtiyaç:{" "}
|
İhtiyaç: {dayjs(requisition.requiredDate).format('DD.MM.YYYY')}
|
||||||
{dayjs(requisition.requiredDate).format("DD.MM.YYYY")}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
@ -371,9 +335,7 @@ const PurchaseRequisitionList: React.FC = () => {
|
||||||
<div className="text-sm font-medium text-gray-900">
|
<div className="text-sm font-medium text-gray-900">
|
||||||
₺{requisition.totalAmount.toLocaleString()}
|
₺{requisition.totalAmount.toLocaleString()}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-gray-500">
|
<div className="text-sm text-gray-500">{requisition.currency}</div>
|
||||||
{requisition.currency}
|
|
||||||
</div>
|
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td className="px-4 py-3">
|
<td className="px-4 py-3">
|
||||||
|
|
@ -431,7 +393,8 @@ const PurchaseRequisitionList: React.FC = () => {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
</Container>
|
||||||
};
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export default PurchaseRequisitionList;
|
export default PurchaseRequisitionList
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { useState } from "react";
|
import React, { useState } from 'react'
|
||||||
import { useParams, useNavigate } from "react-router-dom";
|
import { useParams, useNavigate } from 'react-router-dom'
|
||||||
import {
|
import {
|
||||||
FaArrowLeft,
|
FaArrowLeft,
|
||||||
FaSave,
|
FaSave,
|
||||||
|
|
@ -10,153 +10,139 @@ import {
|
||||||
FaPlus,
|
FaPlus,
|
||||||
FaTrash,
|
FaTrash,
|
||||||
FaPaperclip,
|
FaPaperclip,
|
||||||
} from "react-icons/fa";
|
} from 'react-icons/fa'
|
||||||
import {
|
import {
|
||||||
MmQuotation,
|
MmQuotation,
|
||||||
QuotationStatusEnum,
|
QuotationStatusEnum,
|
||||||
RequestTypeEnum,
|
RequestTypeEnum,
|
||||||
MmQuotationItem,
|
MmQuotationItem,
|
||||||
MmAttachment,
|
MmAttachment,
|
||||||
} from "../../../types/mm";
|
} from '../../../types/mm'
|
||||||
import { mockMaterials } from "../../../mocks/mockMaterials";
|
import { mockMaterials } from '../../../mocks/mockMaterials'
|
||||||
import { mockBusinessParties } from "../../../mocks/mockBusinessParties";
|
import { mockBusinessParties } from '../../../mocks/mockBusinessParties'
|
||||||
|
import { Container } from '@/components/shared'
|
||||||
|
|
||||||
const QuotationForm: React.FC = () => {
|
const QuotationForm: React.FC = () => {
|
||||||
const { id } = useParams<{ id: string }>();
|
const { id } = useParams<{ id: string }>()
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate()
|
||||||
const isEdit = id !== undefined && id !== "new";
|
const isEdit = id !== undefined && id !== 'new'
|
||||||
const isView = window.location.pathname.includes("/view/");
|
const isView = window.location.pathname.includes('/view/')
|
||||||
|
|
||||||
const [formData, setFormData] = useState<Partial<MmQuotation>>({
|
const [formData, setFormData] = useState<Partial<MmQuotation>>({
|
||||||
quotationNumber: isEdit ? `TEK-2024-${id}` : "",
|
quotationNumber: isEdit ? `TEK-2024-${id}` : '',
|
||||||
requestId: "",
|
requestId: '',
|
||||||
requestTitle: "",
|
requestTitle: '',
|
||||||
requestType: RequestTypeEnum.Material,
|
requestType: RequestTypeEnum.Material,
|
||||||
supplierId: "",
|
supplierId: '',
|
||||||
quotationDate: new Date(),
|
quotationDate: new Date(),
|
||||||
validUntil: new Date(),
|
validUntil: new Date(),
|
||||||
status: QuotationStatusEnum.Draft,
|
status: QuotationStatusEnum.Draft,
|
||||||
totalAmount: 0,
|
totalAmount: 0,
|
||||||
currency: "TRY",
|
currency: 'TRY',
|
||||||
paymentTerms: "",
|
paymentTerms: '',
|
||||||
deliveryTerms: "",
|
deliveryTerms: '',
|
||||||
items: [],
|
items: [],
|
||||||
attachments: [],
|
attachments: [],
|
||||||
notes: "",
|
notes: '',
|
||||||
});
|
})
|
||||||
|
|
||||||
const handleInputChange = (
|
const handleInputChange = (
|
||||||
e: React.ChangeEvent<
|
e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>,
|
||||||
HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement
|
|
||||||
>
|
|
||||||
) => {
|
) => {
|
||||||
const { name, value, type } = e.target;
|
const { name, value, type } = e.target
|
||||||
setFormData((prev) => ({
|
setFormData((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
[name]:
|
[name]:
|
||||||
type === "number"
|
type === 'number' ? parseFloat(value) || 0 : type === 'date' ? new Date(value) : value,
|
||||||
? parseFloat(value) || 0
|
}))
|
||||||
: type === "date"
|
}
|
||||||
? new Date(value)
|
|
||||||
: value,
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
const addQuotationItem = () => {
|
const addQuotationItem = () => {
|
||||||
const newItem: MmQuotationItem = {
|
const newItem: MmQuotationItem = {
|
||||||
id: `item-${Date.now()}`,
|
id: `item-${Date.now()}`,
|
||||||
materialCode: "",
|
materialCode: '',
|
||||||
materialName: "",
|
materialName: '',
|
||||||
description: "",
|
description: '',
|
||||||
quantity: 0,
|
quantity: 0,
|
||||||
unit: "",
|
unit: '',
|
||||||
unitPrice: 0,
|
unitPrice: 0,
|
||||||
totalPrice: 0,
|
totalPrice: 0,
|
||||||
specifications: [],
|
specifications: [],
|
||||||
};
|
}
|
||||||
|
|
||||||
setFormData((prev) => ({
|
setFormData((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
items: [...(prev.items || []), newItem],
|
items: [...(prev.items || []), newItem],
|
||||||
}));
|
}))
|
||||||
};
|
}
|
||||||
|
|
||||||
const removeQuotationItem = (index: number) => {
|
const removeQuotationItem = (index: number) => {
|
||||||
setFormData((prev) => ({
|
setFormData((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
items: prev.items?.filter((_, i) => i !== index) || [],
|
items: prev.items?.filter((_, i) => i !== index) || [],
|
||||||
}));
|
}))
|
||||||
calculateTotal();
|
calculateTotal()
|
||||||
};
|
}
|
||||||
|
|
||||||
const updateQuotationItem = (
|
const updateQuotationItem = (
|
||||||
index: number,
|
index: number,
|
||||||
field: keyof MmQuotationItem,
|
field: keyof MmQuotationItem,
|
||||||
value: string | number | string[] | undefined
|
value: string | number | string[] | undefined,
|
||||||
) => {
|
) => {
|
||||||
setFormData((prev) => {
|
setFormData((prev) => {
|
||||||
const updatedItems =
|
const updatedItems =
|
||||||
prev.items?.map((item, i) => {
|
prev.items?.map((item, i) => {
|
||||||
if (i === index) {
|
if (i === index) {
|
||||||
const updatedItem = { ...item, [field]: value };
|
const updatedItem = { ...item, [field]: value }
|
||||||
// Auto-calculate total price when quantity or unit price changes
|
// Auto-calculate total price when quantity or unit price changes
|
||||||
if (field === "quantity" || field === "unitPrice") {
|
if (field === 'quantity' || field === 'unitPrice') {
|
||||||
updatedItem.totalPrice =
|
updatedItem.totalPrice = (updatedItem.quantity || 0) * (updatedItem.unitPrice || 0)
|
||||||
(updatedItem.quantity || 0) * (updatedItem.unitPrice || 0);
|
|
||||||
}
|
}
|
||||||
return updatedItem;
|
return updatedItem
|
||||||
}
|
}
|
||||||
return item;
|
return item
|
||||||
}) || [];
|
}) || []
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...prev,
|
...prev,
|
||||||
items: updatedItems,
|
items: updatedItems,
|
||||||
};
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
// Recalculate total amount
|
// Recalculate total amount
|
||||||
setTimeout(calculateTotal, 0);
|
setTimeout(calculateTotal, 0)
|
||||||
};
|
}
|
||||||
|
|
||||||
const calculateTotal = () => {
|
const calculateTotal = () => {
|
||||||
const total =
|
const total = formData.items?.reduce((sum, item) => sum + (item.totalPrice || 0), 0) || 0
|
||||||
formData.items?.reduce((sum, item) => sum + (item.totalPrice || 0), 0) ||
|
|
||||||
0;
|
|
||||||
setFormData((prev) => ({
|
setFormData((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
totalAmount: total,
|
totalAmount: total,
|
||||||
}));
|
}))
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleSpecificationsChange = (index: number, value: string) => {
|
const handleSpecificationsChange = (index: number, value: string) => {
|
||||||
const specifications = value
|
const specifications = value.split('\n').filter((spec) => spec.trim() !== '')
|
||||||
.split("\n")
|
updateQuotationItem(index, 'specifications', specifications)
|
||||||
.filter((spec) => spec.trim() !== "");
|
}
|
||||||
updateQuotationItem(index, "specifications", specifications);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = (e: React.FormEvent) => {
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault()
|
||||||
// TODO: Implement save logic
|
// TODO: Implement save logic
|
||||||
console.log("Saving quotation:", formData);
|
console.log('Saving quotation:', formData)
|
||||||
navigate("/admin/supplychain/quotations");
|
navigate('/admin/supplychain/quotations')
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
navigate("/admin/supplychain/quotations");
|
navigate('/admin/supplychain/quotations')
|
||||||
};
|
}
|
||||||
|
|
||||||
const isReadOnly = isView;
|
const isReadOnly = isView
|
||||||
const pageTitle = isEdit
|
const pageTitle = isEdit ? 'Teklifi Düzenle' : isView ? 'Teklif Detayları' : 'Yeni Teklif'
|
||||||
? "Teklifi Düzenle"
|
|
||||||
: isView
|
|
||||||
? "Teklif Detayları"
|
|
||||||
: "Yeni Teklif";
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-50 py-4">
|
<Container>
|
||||||
<div className="mx-auto">
|
<div className="space-y-2">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="bg-white rounded-lg shadow-md p-2 mb-2">
|
<div className="bg-white rounded-lg shadow-md p-2 mb-2">
|
||||||
<div className="flex items-center justify-between">
|
<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"
|
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" />
|
<FaTimes className="mr-2" />
|
||||||
{isView ? "Kapat" : "İptal"}
|
{isView ? 'Kapat' : 'İptal'}
|
||||||
</button>
|
</button>
|
||||||
{!isView && (
|
{!isView && (
|
||||||
<button
|
<button
|
||||||
|
|
@ -209,7 +195,7 @@ const QuotationForm: React.FC = () => {
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="quotationNumber"
|
name="quotationNumber"
|
||||||
value={formData.quotationNumber || ""}
|
value={formData.quotationNumber || ''}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
readOnly={isReadOnly || isEdit}
|
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"
|
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>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700">
|
<label className="block text-sm font-medium text-gray-700">Talep ID</label>
|
||||||
Talep ID
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="requestId"
|
name="requestId"
|
||||||
value={formData.requestId || ""}
|
value={formData.requestId || ''}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
readOnly={isReadOnly}
|
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"
|
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>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700">
|
<label className="block text-sm font-medium text-gray-700">Talep Başlığı</label>
|
||||||
Talep Başlığı
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="requestTitle"
|
name="requestTitle"
|
||||||
value={formData.requestTitle || ""}
|
value={formData.requestTitle || ''}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
readOnly={isReadOnly}
|
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"
|
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>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700">
|
<label className="block text-sm font-medium text-gray-700">Talep Tipi</label>
|
||||||
Talep Tipi
|
|
||||||
</label>
|
|
||||||
<select
|
<select
|
||||||
name="requestType"
|
name="requestType"
|
||||||
value={formData.requestType || RequestTypeEnum.Material}
|
value={formData.requestType || RequestTypeEnum.Material}
|
||||||
|
|
@ -259,20 +239,16 @@ const QuotationForm: React.FC = () => {
|
||||||
>
|
>
|
||||||
<option value={RequestTypeEnum.Material}>Malzeme</option>
|
<option value={RequestTypeEnum.Material}>Malzeme</option>
|
||||||
<option value={RequestTypeEnum.Service}>Hizmet</option>
|
<option value={RequestTypeEnum.Service}>Hizmet</option>
|
||||||
<option value={RequestTypeEnum.WorkCenter}>
|
<option value={RequestTypeEnum.WorkCenter}>İş Merkezi</option>
|
||||||
İş Merkezi
|
|
||||||
</option>
|
|
||||||
<option value={RequestTypeEnum.Maintenance}>Bakım</option>
|
<option value={RequestTypeEnum.Maintenance}>Bakım</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700">
|
<label className="block text-sm font-medium text-gray-700">Tedarikçi</label>
|
||||||
Tedarikçi
|
|
||||||
</label>
|
|
||||||
<select
|
<select
|
||||||
name="supplierId"
|
name="supplierId"
|
||||||
value={formData.supplierId || ""}
|
value={formData.supplierId || ''}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
disabled={isReadOnly}
|
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"
|
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>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700">
|
<label className="block text-sm font-medium text-gray-700">Tedarikçi Adı</label>
|
||||||
Tedarikçi Adı
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="supplierName"
|
name="supplierName"
|
||||||
value={formData.supplier?.name || ""}
|
value={formData.supplier?.name || ''}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
readOnly={isReadOnly}
|
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"
|
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"
|
name="quotationDate"
|
||||||
value={
|
value={
|
||||||
formData.quotationDate
|
formData.quotationDate
|
||||||
? new Date(formData.quotationDate)
|
? new Date(formData.quotationDate).toISOString().split('T')[0]
|
||||||
.toISOString()
|
: ''
|
||||||
.split("T")[0]
|
|
||||||
: ""
|
|
||||||
}
|
}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
readOnly={isReadOnly}
|
readOnly={isReadOnly}
|
||||||
|
|
@ -333,10 +305,8 @@ const QuotationForm: React.FC = () => {
|
||||||
name="validUntil"
|
name="validUntil"
|
||||||
value={
|
value={
|
||||||
formData.validUntil
|
formData.validUntil
|
||||||
? new Date(formData.validUntil)
|
? new Date(formData.validUntil).toISOString().split('T')[0]
|
||||||
.toISOString()
|
: ''
|
||||||
.split("T")[0]
|
|
||||||
: ""
|
|
||||||
}
|
}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
readOnly={isReadOnly}
|
readOnly={isReadOnly}
|
||||||
|
|
@ -345,12 +315,10 @@ const QuotationForm: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700">
|
<label className="block text-sm font-medium text-gray-700">Para Birimi</label>
|
||||||
Para Birimi
|
|
||||||
</label>
|
|
||||||
<select
|
<select
|
||||||
name="currency"
|
name="currency"
|
||||||
value={formData.currency || "TRY"}
|
value={formData.currency || 'TRY'}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
disabled={isReadOnly}
|
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"
|
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
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="paymentTerms"
|
name="paymentTerms"
|
||||||
value={formData.paymentTerms || ""}
|
value={formData.paymentTerms || ''}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
readOnly={isReadOnly}
|
readOnly={isReadOnly}
|
||||||
placeholder="ör: 30 gün vadeli"
|
placeholder="ör: 30 gün vadeli"
|
||||||
|
|
@ -383,7 +351,7 @@ const QuotationForm: React.FC = () => {
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="deliveryTerms"
|
name="deliveryTerms"
|
||||||
value={formData.deliveryTerms || ""}
|
value={formData.deliveryTerms || ''}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
readOnly={isReadOnly}
|
readOnly={isReadOnly}
|
||||||
placeholder="ör: 15 gün içinde teslim"
|
placeholder="ör: 15 gün içinde teslim"
|
||||||
|
|
@ -398,7 +366,7 @@ const QuotationForm: React.FC = () => {
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
name="deliveryTime"
|
name="deliveryTime"
|
||||||
value={formData.deliveryTime || ""}
|
value={formData.deliveryTime || ''}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
readOnly={isReadOnly}
|
readOnly={isReadOnly}
|
||||||
min="0"
|
min="0"
|
||||||
|
|
@ -408,12 +376,10 @@ const QuotationForm: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<label className="block text-sm font-medium text-gray-700">
|
<label className="block text-sm font-medium text-gray-700">Notlar</label>
|
||||||
Notlar
|
|
||||||
</label>
|
|
||||||
<textarea
|
<textarea
|
||||||
name="notes"
|
name="notes"
|
||||||
value={formData.notes || ""}
|
value={formData.notes || ''}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
readOnly={isReadOnly}
|
readOnly={isReadOnly}
|
||||||
rows={2}
|
rows={2}
|
||||||
|
|
@ -425,9 +391,7 @@ const QuotationForm: React.FC = () => {
|
||||||
{/* Teklif Kalemleri */}
|
{/* Teklif Kalemleri */}
|
||||||
<div className="bg-white rounded-lg shadow-md p-4">
|
<div className="bg-white rounded-lg shadow-md p-4">
|
||||||
<div className="flex items-center justify-between mb-3">
|
<div className="flex items-center justify-between mb-3">
|
||||||
<h3 className="text-base font-medium text-gray-900">
|
<h3 className="text-base font-medium text-gray-900">Teklif Kalemleri</h3>
|
||||||
Teklif Kalemleri
|
|
||||||
</h3>
|
|
||||||
{!isReadOnly && (
|
{!isReadOnly && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|
@ -442,14 +406,9 @@ const QuotationForm: React.FC = () => {
|
||||||
|
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{formData.items?.map((item, index) => (
|
{formData.items?.map((item, index) => (
|
||||||
<div
|
<div key={item.id} className="border rounded-lg p-3 bg-gray-50">
|
||||||
key={item.id}
|
|
||||||
className="border rounded-lg p-3 bg-gray-50"
|
|
||||||
>
|
|
||||||
<div className="flex items-center justify-between mb-2">
|
<div className="flex items-center justify-between mb-2">
|
||||||
<span className="text-sm font-medium text-gray-700">
|
<span className="text-sm font-medium text-gray-700">Kalem {index + 1}</span>
|
||||||
Kalem {index + 1}
|
|
||||||
</span>
|
|
||||||
{!isReadOnly && (
|
{!isReadOnly && (
|
||||||
<button
|
<button
|
||||||
type="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 className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-2">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-medium text-gray-600">
|
<label className="block text-xs font-medium text-gray-600">Malzeme</label>
|
||||||
Malzeme
|
|
||||||
</label>
|
|
||||||
<select
|
<select
|
||||||
value={item.materialCode}
|
value={item.materialCode}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
updateQuotationItem(
|
updateQuotationItem(index, 'materialCode', e.target.value)
|
||||||
index,
|
|
||||||
"materialCode",
|
|
||||||
e.target.value
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
disabled={isReadOnly}
|
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"
|
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"
|
type="text"
|
||||||
value={item.materialName}
|
value={item.materialName}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
updateQuotationItem(
|
updateQuotationItem(index, 'materialName', e.target.value)
|
||||||
index,
|
|
||||||
"materialName",
|
|
||||||
e.target.value
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
readOnly={isReadOnly}
|
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"
|
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"
|
type="text"
|
||||||
value={item.description}
|
value={item.description}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
updateQuotationItem(
|
updateQuotationItem(index, 'description', e.target.value)
|
||||||
index,
|
|
||||||
"description",
|
|
||||||
e.target.value
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
readOnly={isReadOnly}
|
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"
|
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>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-medium text-gray-600">
|
<label className="block text-xs font-medium text-gray-600">Miktar</label>
|
||||||
Miktar
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
value={item.quantity}
|
value={item.quantity}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
updateQuotationItem(
|
updateQuotationItem(
|
||||||
index,
|
index,
|
||||||
"quantity",
|
'quantity',
|
||||||
parseFloat(e.target.value) || 0
|
parseFloat(e.target.value) || 0,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
readOnly={isReadOnly}
|
readOnly={isReadOnly}
|
||||||
|
|
@ -547,15 +490,11 @@ const QuotationForm: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-medium text-gray-600">
|
<label className="block text-xs font-medium text-gray-600">Birim</label>
|
||||||
Birim
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={item.unit}
|
value={item.unit}
|
||||||
onChange={(e) =>
|
onChange={(e) => updateQuotationItem(index, 'unit', e.target.value)}
|
||||||
updateQuotationItem(index, "unit", e.target.value)
|
|
||||||
}
|
|
||||||
readOnly={isReadOnly}
|
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"
|
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) =>
|
onChange={(e) =>
|
||||||
updateQuotationItem(
|
updateQuotationItem(
|
||||||
index,
|
index,
|
||||||
"unitPrice",
|
'unitPrice',
|
||||||
parseFloat(e.target.value) || 0
|
parseFloat(e.target.value) || 0,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
readOnly={isReadOnly}
|
readOnly={isReadOnly}
|
||||||
|
|
@ -600,12 +539,12 @@ const QuotationForm: React.FC = () => {
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
value={item.leadTime || ""}
|
value={item.leadTime || ''}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
updateQuotationItem(
|
updateQuotationItem(
|
||||||
index,
|
index,
|
||||||
"leadTime",
|
'leadTime',
|
||||||
parseInt(e.target.value) || undefined
|
parseInt(e.target.value) || undefined,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
readOnly={isReadOnly}
|
readOnly={isReadOnly}
|
||||||
|
|
@ -619,10 +558,8 @@ const QuotationForm: React.FC = () => {
|
||||||
Spesifikasyonlar (her satıra bir spesifikasyon)
|
Spesifikasyonlar (her satıra bir spesifikasyon)
|
||||||
</label>
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
value={item.specifications?.join("\n") || ""}
|
value={item.specifications?.join('\n') || ''}
|
||||||
onChange={(e) =>
|
onChange={(e) => handleSpecificationsChange(index, e.target.value)}
|
||||||
handleSpecificationsChange(index, e.target.value)
|
|
||||||
}
|
|
||||||
readOnly={isReadOnly}
|
readOnly={isReadOnly}
|
||||||
rows={1}
|
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"
|
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">
|
<div className="space-y-4">
|
||||||
{/* Durum ve Tutar */}
|
{/* Durum ve Tutar */}
|
||||||
<div className="bg-white rounded-lg shadow-md p-4">
|
<div className="bg-white rounded-lg shadow-md p-4">
|
||||||
<h3 className="text-base font-medium text-gray-900 mb-3">
|
<h3 className="text-base font-medium text-gray-900 mb-3">Durum Bilgileri</h3>
|
||||||
Durum Bilgileri
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700">
|
<label className="block text-sm font-medium text-gray-700">Durum</label>
|
||||||
Durum
|
|
||||||
</label>
|
|
||||||
<select
|
<select
|
||||||
name="status"
|
name="status"
|
||||||
value={formData.status || QuotationStatusEnum.Draft}
|
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"
|
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.Draft}>Taslak</option>
|
||||||
<option value={QuotationStatusEnum.Pending}>
|
<option value={QuotationStatusEnum.Pending}>Beklemede</option>
|
||||||
Beklemede
|
<option value={QuotationStatusEnum.UnderReview}>İncelemede</option>
|
||||||
</option>
|
<option value={QuotationStatusEnum.Submitted}>Gönderildi</option>
|
||||||
<option value={QuotationStatusEnum.UnderReview}>
|
<option value={QuotationStatusEnum.Approved}>Onaylandı</option>
|
||||||
İncelemede
|
<option value={QuotationStatusEnum.Rejected}>Reddedildi</option>
|
||||||
</option>
|
<option value={QuotationStatusEnum.Expired}>Süresi Doldu</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>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -689,8 +610,7 @@ const QuotationForm: React.FC = () => {
|
||||||
Toplam Tutar
|
Toplam Tutar
|
||||||
</label>
|
</label>
|
||||||
<div className="mt-1 text-base font-semibold text-green-600">
|
<div className="mt-1 text-base font-semibold text-green-600">
|
||||||
{formData.totalAmount?.toLocaleString()}{" "}
|
{formData.totalAmount?.toLocaleString()} {formData.currency}
|
||||||
{formData.currency}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -699,15 +619,11 @@ const QuotationForm: React.FC = () => {
|
||||||
{/* Değerlendirme (sadece görüntüleme) */}
|
{/* Değerlendirme (sadece görüntüleme) */}
|
||||||
{isView && formData.evaluationScore && (
|
{isView && formData.evaluationScore && (
|
||||||
<div className="bg-white rounded-lg shadow-md p-4">
|
<div className="bg-white rounded-lg shadow-md p-4">
|
||||||
<h3 className="text-base font-medium text-gray-900 mb-3">
|
<h3 className="text-base font-medium text-gray-900 mb-3">Değerlendirme</h3>
|
||||||
Değerlendirme
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-sm font-medium text-gray-700">
|
<div className="text-sm font-medium text-gray-700">Değerlendirme Puanı</div>
|
||||||
Değerlendirme Puanı
|
|
||||||
</div>
|
|
||||||
<div className="text-base font-semibold text-blue-600">
|
<div className="text-base font-semibold text-blue-600">
|
||||||
{formData.evaluationScore}/100
|
{formData.evaluationScore}/100
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -715,12 +631,8 @@ const QuotationForm: React.FC = () => {
|
||||||
|
|
||||||
{formData.evaluatedBy && (
|
{formData.evaluatedBy && (
|
||||||
<div>
|
<div>
|
||||||
<div className="text-sm font-medium text-gray-700">
|
<div className="text-sm font-medium text-gray-700">Değerlendiren</div>
|
||||||
Değerlendiren
|
<div className="text-sm text-gray-600">{formData.evaluatedBy}</div>
|
||||||
</div>
|
|
||||||
<div className="text-sm text-gray-600">
|
|
||||||
{formData.evaluatedBy}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
@ -773,8 +685,8 @@ const QuotationForm: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Container>
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
export default QuotationForm;
|
export default QuotationForm
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { useState } from "react";
|
import React, { useState } from 'react'
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from 'react-router-dom'
|
||||||
import {
|
import {
|
||||||
FaPlus,
|
FaPlus,
|
||||||
FaSearch,
|
FaSearch,
|
||||||
|
|
@ -16,114 +16,102 @@ import {
|
||||||
FaClock,
|
FaClock,
|
||||||
FaCheckCircle,
|
FaCheckCircle,
|
||||||
FaTimes,
|
FaTimes,
|
||||||
} from "react-icons/fa";
|
} from 'react-icons/fa'
|
||||||
import { MmQuotation, QuotationStatusEnum } from "../../../types/mm";
|
import { MmQuotation, QuotationStatusEnum } from '../../../types/mm'
|
||||||
import { mockQuotations } from "../../../mocks/mockQuotations";
|
import { mockQuotations } from '../../../mocks/mockQuotations'
|
||||||
import {
|
import {
|
||||||
getQuotationStatusColor,
|
getQuotationStatusColor,
|
||||||
getQuotationStatusIcon,
|
getQuotationStatusIcon,
|
||||||
getQuotationStatusText,
|
getQuotationStatusText,
|
||||||
getRequestTypeText,
|
getRequestTypeText,
|
||||||
} from "../../../utils/erp";
|
} from '../../../utils/erp'
|
||||||
|
import { Container } from '@/components/shared'
|
||||||
|
|
||||||
const QuotationManagement: React.FC = () => {
|
const QuotationManagement: React.FC = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate()
|
||||||
const [searchTerm, setSearchTerm] = useState("");
|
const [searchTerm, setSearchTerm] = useState('')
|
||||||
const [statusFilter, setStatusFilter] = useState<QuotationStatusEnum | "all">(
|
const [statusFilter, setStatusFilter] = useState<QuotationStatusEnum | 'all'>('all')
|
||||||
"all"
|
const [showModal, setShowModal] = useState(false)
|
||||||
);
|
const [modalType, setModalType] = useState<'bulk' | 'comparison'>('bulk')
|
||||||
const [showModal, setShowModal] = useState(false);
|
const [selectedQuotations, setSelectedQuotations] = useState<string[]>([])
|
||||||
const [modalType, setModalType] = useState<"bulk" | "comparison">("bulk");
|
|
||||||
const [selectedQuotations, setSelectedQuotations] = useState<string[]>([]);
|
|
||||||
|
|
||||||
// Mock data - replace with actual API calls
|
// Mock data - replace with actual API calls
|
||||||
const [quotations] = useState<MmQuotation[]>(mockQuotations);
|
const [quotations] = useState<MmQuotation[]>(mockQuotations)
|
||||||
|
|
||||||
const filteredQuotations = quotations.filter((quotation) => {
|
const filteredQuotations = quotations.filter((quotation) => {
|
||||||
const matchesSearch =
|
const matchesSearch =
|
||||||
quotation.quotationNumber
|
quotation.quotationNumber.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||||
.toLowerCase()
|
quotation.supplier?.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||||
.includes(searchTerm.toLowerCase()) ||
|
quotation.requestTitle.toLowerCase().includes(searchTerm.toLowerCase())
|
||||||
quotation.supplier?.name
|
const matchesStatus = statusFilter === 'all' || quotation.status === statusFilter
|
||||||
.toLowerCase()
|
return matchesSearch && matchesStatus
|
||||||
.includes(searchTerm.toLowerCase()) ||
|
})
|
||||||
quotation.requestTitle.toLowerCase().includes(searchTerm.toLowerCase());
|
|
||||||
const matchesStatus =
|
|
||||||
statusFilter === "all" || quotation.status === statusFilter;
|
|
||||||
return matchesSearch && matchesStatus;
|
|
||||||
});
|
|
||||||
|
|
||||||
const isExpiringSoon = (date: Date) => {
|
const isExpiringSoon = (date: Date) => {
|
||||||
const today = new Date();
|
const today = new Date()
|
||||||
const diffTime = date.getTime() - today.getTime();
|
const diffTime = date.getTime() - today.getTime()
|
||||||
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
|
||||||
return diffDays <= 7 && diffDays > 0;
|
return diffDays <= 7 && diffDays > 0
|
||||||
};
|
}
|
||||||
|
|
||||||
const isExpired = (date: Date) => {
|
const isExpired = (date: Date) => {
|
||||||
const today = new Date();
|
const today = new Date()
|
||||||
return date < today;
|
return date < today
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleAddQuotation = () => {
|
const handleAddQuotation = () => {
|
||||||
navigate("/admin/supplychain/quotations/new");
|
navigate('/admin/supplychain/quotations/new')
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleBulkQuotation = () => {
|
const handleBulkQuotation = () => {
|
||||||
if (selectedQuotations.length === 0) {
|
if (selectedQuotations.length === 0) {
|
||||||
alert("Toplu işlem için en az 1 teklif seçmelisiniz.");
|
alert('Toplu işlem için en az 1 teklif seçmelisiniz.')
|
||||||
return;
|
return
|
||||||
|
}
|
||||||
|
setModalType('bulk')
|
||||||
|
setShowModal(true)
|
||||||
}
|
}
|
||||||
setModalType("bulk");
|
|
||||||
setShowModal(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCompareQuotations = () => {
|
const handleCompareQuotations = () => {
|
||||||
if (selectedQuotations.length < 2) {
|
if (selectedQuotations.length < 2) {
|
||||||
alert("Karşılaştırma için en az 2 teklif seçmelisiniz.");
|
alert('Karşılaştırma için en az 2 teklif seçmelisiniz.')
|
||||||
return;
|
return
|
||||||
|
}
|
||||||
|
setModalType('comparison')
|
||||||
|
setShowModal(true)
|
||||||
}
|
}
|
||||||
setModalType("comparison");
|
|
||||||
setShowModal(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleEdit = (quotation: MmQuotation) => {
|
const handleEdit = (quotation: MmQuotation) => {
|
||||||
navigate(`/admin/supplychain/quotations/edit/${quotation.id}`);
|
navigate(`/admin/supplychain/quotations/edit/${quotation.id}`)
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleView = (quotation: MmQuotation) => {
|
const handleView = (quotation: MmQuotation) => {
|
||||||
navigate(`/admin/supplychain/quotations/view/${quotation.id}`);
|
navigate(`/admin/supplychain/quotations/view/${quotation.id}`)
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleSelectQuotation = (quotationId: string) => {
|
const handleSelectQuotation = (quotationId: string) => {
|
||||||
setSelectedQuotations((prev) =>
|
setSelectedQuotations((prev) =>
|
||||||
prev.includes(quotationId)
|
prev.includes(quotationId) ? prev.filter((id) => id !== quotationId) : [...prev, quotationId],
|
||||||
? prev.filter((id) => id !== quotationId)
|
)
|
||||||
: [...prev, quotationId]
|
}
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getTotalItems = (quotation: MmQuotation) => {
|
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 getAverageLeadTime = (quotation: MmQuotation) => {
|
||||||
const totalLeadTime = quotation.items.reduce(
|
const totalLeadTime = quotation.items.reduce((sum, item) => sum + (item.leadTime || 0), 0)
|
||||||
(sum, item) => sum + (item.leadTime || 0),
|
return Math.round(totalLeadTime / quotation.items.length)
|
||||||
0
|
}
|
||||||
);
|
|
||||||
return Math.round(totalLeadTime / quotation.items.length);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-3 pt-2">
|
<Container>
|
||||||
|
<div className="space-y-2">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-2xl font-bold text-gray-900">Teklif Yönetimi</h2>
|
<h2 className="text-2xl font-bold text-gray-900">Teklif Yönetimi</h2>
|
||||||
<p className="text-gray-600">
|
<p className="text-gray-600">Tedarikçi tekliflerini yönetin ve karşılaştırın</p>
|
||||||
Tedarikçi tekliflerini yönetin ve karşılaştırın
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex space-x-2">
|
<div className="flex space-x-2">
|
||||||
<button
|
<button
|
||||||
|
|
@ -166,9 +154,7 @@ const QuotationManagement: React.FC = () => {
|
||||||
<FaFilter className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-5 h-5" />
|
<FaFilter className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-5 h-5" />
|
||||||
<select
|
<select
|
||||||
value={statusFilter}
|
value={statusFilter}
|
||||||
onChange={(e) =>
|
onChange={(e) => setStatusFilter(e.target.value as QuotationStatusEnum | 'all')}
|
||||||
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"
|
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="all">Tüm Durumlar</option>
|
||||||
|
|
@ -204,7 +190,7 @@ const QuotationManagement: React.FC = () => {
|
||||||
</h3>
|
</h3>
|
||||||
<span
|
<span
|
||||||
className={`px-2 py-1 rounded-full text-xs font-medium flex items-center space-x-1 ${getQuotationStatusColor(
|
className={`px-2 py-1 rounded-full text-xs font-medium flex items-center space-x-1 ${getQuotationStatusColor(
|
||||||
quotation.status
|
quotation.status,
|
||||||
)}`}
|
)}`}
|
||||||
>
|
>
|
||||||
{getQuotationStatusIcon(quotation.status)}
|
{getQuotationStatusIcon(quotation.status)}
|
||||||
|
|
@ -213,8 +199,7 @@ const QuotationManagement: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
<p className="text-gray-600">{quotation.requestTitle}</p>
|
<p className="text-gray-600">{quotation.requestTitle}</p>
|
||||||
<p className="text-sm text-gray-500">
|
<p className="text-sm text-gray-500">
|
||||||
{quotation.supplier?.name} •{" "}
|
{quotation.supplier?.name} • {getRequestTypeText(quotation.requestType)}
|
||||||
{getRequestTypeText(quotation.requestType)}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex space-x-1">
|
<div className="flex space-x-1">
|
||||||
|
|
@ -253,18 +238,15 @@ const QuotationManagement: React.FC = () => {
|
||||||
<p
|
<p
|
||||||
className={`font-medium ${
|
className={`font-medium ${
|
||||||
isExpired(quotation.validUntil)
|
isExpired(quotation.validUntil)
|
||||||
? "text-red-600"
|
? 'text-red-600'
|
||||||
: isExpiringSoon(quotation.validUntil)
|
: isExpiringSoon(quotation.validUntil)
|
||||||
? "text-orange-600"
|
? 'text-orange-600'
|
||||||
: "text-gray-900"
|
: 'text-gray-900'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{quotation.validUntil.toLocaleDateString("tr-TR")}
|
{quotation.validUntil.toLocaleDateString('tr-TR')}
|
||||||
{isExpiringSoon(quotation.validUntil) &&
|
{isExpiringSoon(quotation.validUntil) && !isExpired(quotation.validUntil) && (
|
||||||
!isExpired(quotation.validUntil) && (
|
<span className="text-xs text-orange-600 ml-1">(Yakında)</span>
|
||||||
<span className="text-xs text-orange-600 ml-1">
|
|
||||||
(Yakında)
|
|
||||||
</span>
|
|
||||||
)}
|
)}
|
||||||
{isExpired(quotation.validUntil) && (
|
{isExpired(quotation.validUntil) && (
|
||||||
<span className="text-xs text-red-600 ml-1">(Doldu)</span>
|
<span className="text-xs text-red-600 ml-1">(Doldu)</span>
|
||||||
|
|
@ -284,18 +266,14 @@ const QuotationManagement: React.FC = () => {
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div className="flex items-center justify-center mb-1">
|
<div className="flex items-center justify-center mb-1">
|
||||||
<FaArrowUp className="w-4 h-4 text-gray-400 mr-1" />
|
<FaArrowUp className="w-4 h-4 text-gray-400 mr-1" />
|
||||||
<span className="font-medium">
|
<span className="font-medium">{getTotalItems(quotation)}</span>
|
||||||
{getTotalItems(quotation)}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<span className="text-gray-500">Adet</span>
|
<span className="text-gray-500">Adet</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div className="flex items-center justify-center mb-1">
|
<div className="flex items-center justify-center mb-1">
|
||||||
<FaClock className="w-4 h-4 text-gray-400 mr-1" />
|
<FaClock className="w-4 h-4 text-gray-400 mr-1" />
|
||||||
<span className="font-medium">
|
<span className="font-medium">{getAverageLeadTime(quotation)}</span>
|
||||||
{getAverageLeadTime(quotation)}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<span className="text-gray-500">Gün</span>
|
<span className="text-gray-500">Gün</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -303,40 +281,31 @@ const QuotationManagement: React.FC = () => {
|
||||||
|
|
||||||
{/* Items Details */}
|
{/* Items Details */}
|
||||||
<div className="mt-3 mb-3">
|
<div className="mt-3 mb-3">
|
||||||
<h4 className="text-sm font-medium text-gray-900 mb-1.5">
|
<h4 className="text-sm font-medium text-gray-900 mb-1.5">Teklif Kalemleri</h4>
|
||||||
Teklif Kalemleri
|
|
||||||
</h4>
|
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
{quotation.items.map((item) => (
|
{quotation.items.map((item) => (
|
||||||
<div key={item.id} className="bg-gray-50 p-2 rounded-lg">
|
<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 items-center justify-between mb-1.5">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<p className="font-medium text-gray-900">
|
<p className="font-medium text-gray-900">{item.materialName}</p>
|
||||||
{item.materialName}
|
|
||||||
</p>
|
|
||||||
<p className="text-sm text-gray-600">
|
<p className="text-sm text-gray-600">
|
||||||
Malzeme Kodu: {item.materialCode} | Miktar:{" "}
|
Malzeme Kodu: {item.materialCode} | Miktar: {item.quantity} {item.unit}{' '}
|
||||||
{item.quantity} {item.unit} | Birim Fiyat: ₺
|
| Birim Fiyat: ₺{item.unitPrice.toLocaleString()}
|
||||||
{item.unitPrice.toLocaleString()}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-right">
|
<div className="text-right">
|
||||||
<p className="font-semibold text-gray-900">
|
<p className="font-semibold text-gray-900">
|
||||||
₺{item.totalPrice.toLocaleString()}
|
₺{item.totalPrice.toLocaleString()}
|
||||||
</p>
|
</p>
|
||||||
<div className="text-xs text-gray-500">
|
<div className="text-xs text-gray-500">Teslimat: {item.leadTime} gün</div>
|
||||||
Teslimat: {item.leadTime} gün
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{item.description && (
|
{item.description && (
|
||||||
<div className="text-xs text-gray-500 mb-1">
|
<div className="text-xs text-gray-500 mb-1">{item.description}</div>
|
||||||
{item.description}
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
{item.specifications && item.specifications.length > 0 && (
|
{item.specifications && item.specifications.length > 0 && (
|
||||||
<div className="text-xs text-gray-500">
|
<div className="text-xs text-gray-500">
|
||||||
Özellikler: {item.specifications.join(", ")}
|
Özellikler: {item.specifications.join(', ')}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -349,7 +318,7 @@ const QuotationManagement: React.FC = () => {
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<FaCalendar className="w-4 h-4 text-gray-400" />
|
<FaCalendar className="w-4 h-4 text-gray-400" />
|
||||||
<span className="text-gray-600">
|
<span className="text-gray-600">
|
||||||
{quotation.quotationDate.toLocaleDateString("tr-TR")}
|
{quotation.quotationDate.toLocaleDateString('tr-TR')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
|
|
@ -377,9 +346,7 @@ const QuotationManagement: React.FC = () => {
|
||||||
{filteredQuotations.length === 0 && (
|
{filteredQuotations.length === 0 && (
|
||||||
<div className="text-center py-8">
|
<div className="text-center py-8">
|
||||||
<FaFileAlt className="w-12 h-12 text-gray-400 mx-auto mb-4" />
|
<FaFileAlt className="w-12 h-12 text-gray-400 mx-auto mb-4" />
|
||||||
<h3 className="text-lg font-medium text-gray-900 mb-2">
|
<h3 className="text-lg font-medium text-gray-900 mb-2">Teklif bulunamadı</h3>
|
||||||
Teklif bulunamadı
|
|
||||||
</h3>
|
|
||||||
<p className="text-gray-500 mb-3">
|
<p className="text-gray-500 mb-3">
|
||||||
Arama kriterlerinizi değiştirin veya yeni bir teklif ekleyin.
|
Arama kriterlerinizi değiştirin veya yeni bir teklif ekleyin.
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -419,6 +386,7 @@ const QuotationManagement: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Modal for Bulk/Comparison operations */}
|
{/* Modal for Bulk/Comparison operations */}
|
||||||
{showModal && (
|
{showModal && (
|
||||||
|
|
@ -426,8 +394,8 @@ const QuotationManagement: React.FC = () => {
|
||||||
<div className="bg-white rounded-lg p-4 w-full max-w-6xl mx-4 max-h-[90vh] overflow-y-auto">
|
<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">
|
<div className="flex items-center justify-between mb-4">
|
||||||
<h3 className="text-lg font-semibold">
|
<h3 className="text-lg font-semibold">
|
||||||
{modalType === "bulk" && "Toplu Teklif Talebi"}
|
{modalType === 'bulk' && 'Toplu Teklif Talebi'}
|
||||||
{modalType === "comparison" && "Teklif Karşılaştırması"}
|
{modalType === 'comparison' && 'Teklif Karşılaştırması'}
|
||||||
</h3>
|
</h3>
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowModal(false)}
|
onClick={() => setShowModal(false)}
|
||||||
|
|
@ -437,13 +405,11 @@ const QuotationManagement: React.FC = () => {
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{modalType === "comparison" && (
|
{modalType === 'comparison' && (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{/* Comparison Summary */}
|
{/* Comparison Summary */}
|
||||||
<div className="bg-gray-50 p-3 rounded-lg">
|
<div className="bg-gray-50 p-3 rounded-lg">
|
||||||
<h4 className="font-medium text-gray-900 mb-2">
|
<h4 className="font-medium text-gray-900 mb-2">Karşılaştırma Özeti</h4>
|
||||||
Karşılaştırma Özeti
|
|
||||||
</h4>
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-3 text-sm">
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-3 text-sm">
|
||||||
<div>
|
<div>
|
||||||
<span className="text-gray-500">Toplam Teklif:</span>
|
<span className="text-gray-500">Toplam Teklif:</span>
|
||||||
|
|
@ -456,7 +422,7 @@ const QuotationManagement: React.FC = () => {
|
||||||
{Math.min(
|
{Math.min(
|
||||||
...quotations
|
...quotations
|
||||||
.filter((q) => selectedQuotations.includes(q.id))
|
.filter((q) => selectedQuotations.includes(q.id))
|
||||||
.map((q) => q.totalAmount)
|
.map((q) => q.totalAmount),
|
||||||
).toLocaleString()}
|
).toLocaleString()}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -467,7 +433,7 @@ const QuotationManagement: React.FC = () => {
|
||||||
{Math.max(
|
{Math.max(
|
||||||
...quotations
|
...quotations
|
||||||
.filter((q) => selectedQuotations.includes(q.id))
|
.filter((q) => selectedQuotations.includes(q.id))
|
||||||
.map((q) => q.totalAmount)
|
.map((q) => q.totalAmount),
|
||||||
).toLocaleString()}
|
).toLocaleString()}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -478,21 +444,17 @@ const QuotationManagement: React.FC = () => {
|
||||||
((Math.max(
|
((Math.max(
|
||||||
...quotations
|
...quotations
|
||||||
.filter((q) => selectedQuotations.includes(q.id))
|
.filter((q) => selectedQuotations.includes(q.id))
|
||||||
.map((q) => q.totalAmount)
|
.map((q) => q.totalAmount),
|
||||||
) -
|
) -
|
||||||
Math.min(
|
Math.min(
|
||||||
...quotations
|
...quotations
|
||||||
.filter((q) =>
|
.filter((q) => selectedQuotations.includes(q.id))
|
||||||
selectedQuotations.includes(q.id)
|
.map((q) => q.totalAmount),
|
||||||
)
|
|
||||||
.map((q) => q.totalAmount)
|
|
||||||
)) /
|
)) /
|
||||||
Math.min(
|
Math.min(
|
||||||
...quotations
|
...quotations
|
||||||
.filter((q) =>
|
.filter((q) => selectedQuotations.includes(q.id))
|
||||||
selectedQuotations.includes(q.id)
|
.map((q) => q.totalAmount),
|
||||||
)
|
|
||||||
.map((q) => q.totalAmount)
|
|
||||||
)) *
|
)) *
|
||||||
100
|
100
|
||||||
).toFixed(1)}
|
).toFixed(1)}
|
||||||
|
|
@ -524,52 +486,38 @@ const QuotationManagement: React.FC = () => {
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="bg-white divide-y divide-gray-200">
|
<tbody className="bg-white divide-y divide-gray-200">
|
||||||
<tr>
|
<tr>
|
||||||
<td className="px-3 py-2 font-medium text-gray-900">
|
<td className="px-3 py-2 font-medium text-gray-900">Teklif No</td>
|
||||||
Teklif No
|
|
||||||
</td>
|
|
||||||
{quotations
|
{quotations
|
||||||
.filter((q) => selectedQuotations.includes(q.id))
|
.filter((q) => selectedQuotations.includes(q.id))
|
||||||
.map((quotation) => (
|
.map((quotation) => (
|
||||||
<td
|
<td key={quotation.id} className="px-3 py-2 text-center text-gray-600">
|
||||||
key={quotation.id}
|
|
||||||
className="px-3 py-2 text-center text-gray-600"
|
|
||||||
>
|
|
||||||
{quotation.quotationNumber}
|
{quotation.quotationNumber}
|
||||||
</td>
|
</td>
|
||||||
))}
|
))}
|
||||||
</tr>
|
</tr>
|
||||||
<tr className="bg-gray-50">
|
<tr className="bg-gray-50">
|
||||||
<td className="px-3 py-2 font-medium text-gray-900">
|
<td className="px-3 py-2 font-medium text-gray-900">Toplam Tutar</td>
|
||||||
Toplam Tutar
|
|
||||||
</td>
|
|
||||||
{quotations
|
{quotations
|
||||||
.filter((q) => selectedQuotations.includes(q.id))
|
.filter((q) => selectedQuotations.includes(q.id))
|
||||||
.map((quotation) => (
|
.map((quotation) => (
|
||||||
<td
|
<td key={quotation.id} className="px-3 py-2 text-center">
|
||||||
key={quotation.id}
|
|
||||||
className="px-3 py-2 text-center"
|
|
||||||
>
|
|
||||||
<span
|
<span
|
||||||
className={`font-semibold ${
|
className={`font-semibold ${
|
||||||
quotation.totalAmount ===
|
quotation.totalAmount ===
|
||||||
Math.min(
|
Math.min(
|
||||||
...quotations
|
...quotations
|
||||||
.filter((q) =>
|
.filter((q) => selectedQuotations.includes(q.id))
|
||||||
selectedQuotations.includes(q.id)
|
.map((q) => q.totalAmount),
|
||||||
)
|
)
|
||||||
.map((q) => q.totalAmount)
|
? 'text-green-600'
|
||||||
)
|
|
||||||
? "text-green-600"
|
|
||||||
: quotation.totalAmount ===
|
: quotation.totalAmount ===
|
||||||
Math.max(
|
Math.max(
|
||||||
...quotations
|
...quotations
|
||||||
.filter((q) =>
|
.filter((q) => selectedQuotations.includes(q.id))
|
||||||
selectedQuotations.includes(q.id)
|
.map((q) => q.totalAmount),
|
||||||
)
|
)
|
||||||
.map((q) => q.totalAmount)
|
? 'text-red-600'
|
||||||
)
|
: 'text-gray-900'
|
||||||
? "text-red-600"
|
|
||||||
: "text-gray-900"
|
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
₺{quotation.totalAmount.toLocaleString()}
|
₺{quotation.totalAmount.toLocaleString()}
|
||||||
|
|
@ -578,34 +526,24 @@ const QuotationManagement: React.FC = () => {
|
||||||
))}
|
))}
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td className="px-3 py-2 font-medium text-gray-900">
|
<td className="px-3 py-2 font-medium text-gray-900">Geçerlilik Tarihi</td>
|
||||||
Geçerlilik Tarihi
|
|
||||||
</td>
|
|
||||||
{quotations
|
{quotations
|
||||||
.filter((q) => selectedQuotations.includes(q.id))
|
.filter((q) => selectedQuotations.includes(q.id))
|
||||||
.map((quotation) => (
|
.map((quotation) => (
|
||||||
<td
|
<td key={quotation.id} className="px-3 py-2 text-center text-gray-600">
|
||||||
key={quotation.id}
|
{quotation.validUntil.toLocaleDateString('tr-TR')}
|
||||||
className="px-3 py-2 text-center text-gray-600"
|
|
||||||
>
|
|
||||||
{quotation.validUntil.toLocaleDateString("tr-TR")}
|
|
||||||
</td>
|
</td>
|
||||||
))}
|
))}
|
||||||
</tr>
|
</tr>
|
||||||
<tr className="bg-gray-50">
|
<tr className="bg-gray-50">
|
||||||
<td className="px-3 py-2 font-medium text-gray-900">
|
<td className="px-3 py-2 font-medium text-gray-900">Durum</td>
|
||||||
Durum
|
|
||||||
</td>
|
|
||||||
{quotations
|
{quotations
|
||||||
.filter((q) => selectedQuotations.includes(q.id))
|
.filter((q) => selectedQuotations.includes(q.id))
|
||||||
.map((quotation) => (
|
.map((quotation) => (
|
||||||
<td
|
<td key={quotation.id} className="px-3 py-2 text-center">
|
||||||
key={quotation.id}
|
|
||||||
className="px-3 py-2 text-center"
|
|
||||||
>
|
|
||||||
<span
|
<span
|
||||||
className={`px-2 py-1 rounded-full text-xs font-medium ${getQuotationStatusColor(
|
className={`px-2 py-1 rounded-full text-xs font-medium ${getQuotationStatusColor(
|
||||||
quotation.status
|
quotation.status,
|
||||||
)}`}
|
)}`}
|
||||||
>
|
>
|
||||||
{getQuotationStatusText(quotation.status)}
|
{getQuotationStatusText(quotation.status)}
|
||||||
|
|
@ -614,16 +552,11 @@ const QuotationManagement: React.FC = () => {
|
||||||
))}
|
))}
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td className="px-3 py-2 font-medium text-gray-900">
|
<td className="px-3 py-2 font-medium text-gray-900">Kalem Sayısı</td>
|
||||||
Kalem Sayısı
|
|
||||||
</td>
|
|
||||||
{quotations
|
{quotations
|
||||||
.filter((q) => selectedQuotations.includes(q.id))
|
.filter((q) => selectedQuotations.includes(q.id))
|
||||||
.map((quotation) => (
|
.map((quotation) => (
|
||||||
<td
|
<td key={quotation.id} className="px-3 py-2 text-center text-gray-600">
|
||||||
key={quotation.id}
|
|
||||||
className="px-3 py-2 text-center text-gray-600"
|
|
||||||
>
|
|
||||||
{quotation.items.length}
|
{quotation.items.length}
|
||||||
</td>
|
</td>
|
||||||
))}
|
))}
|
||||||
|
|
@ -635,10 +568,7 @@ const QuotationManagement: React.FC = () => {
|
||||||
{quotations
|
{quotations
|
||||||
.filter((q) => selectedQuotations.includes(q.id))
|
.filter((q) => selectedQuotations.includes(q.id))
|
||||||
.map((quotation) => (
|
.map((quotation) => (
|
||||||
<td
|
<td key={quotation.id} className="px-3 py-2 text-center text-gray-600">
|
||||||
key={quotation.id}
|
|
||||||
className="px-3 py-2 text-center text-gray-600"
|
|
||||||
>
|
|
||||||
{getAverageLeadTime(quotation)} gün
|
{getAverageLeadTime(quotation)} gün
|
||||||
</td>
|
</td>
|
||||||
))}
|
))}
|
||||||
|
|
@ -671,7 +601,7 @@ const QuotationManagement: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{modalType === "bulk" && (
|
{modalType === 'bulk' && (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Bulk Request Form */}
|
{/* Bulk Request Form */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
|
@ -711,9 +641,7 @@ const QuotationManagement: React.FC = () => {
|
||||||
>
|
>
|
||||||
{quotation.supplier?.name}
|
{quotation.supplier?.name}
|
||||||
<button
|
<button
|
||||||
onClick={() =>
|
onClick={() => handleSelectQuotation(quotation.id)}
|
||||||
handleSelectQuotation(quotation.id)
|
|
||||||
}
|
|
||||||
className="ml-2 text-blue-600 hover:text-blue-800"
|
className="ml-2 text-blue-600 hover:text-blue-800"
|
||||||
>
|
>
|
||||||
<FaTimes className="w-3 h-3" />
|
<FaTimes className="w-3 h-3" />
|
||||||
|
|
@ -785,8 +713,8 @@ const QuotationManagement: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</Container>
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
export default QuotationManagement;
|
export default QuotationManagement
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from 'react'
|
||||||
import { FaTimes, FaSave, FaCreditCard, FaStar } from "react-icons/fa";
|
import { FaTimes, FaSave, FaCreditCard, FaStar } from 'react-icons/fa'
|
||||||
import { SupplierCardTypeEnum, SupplierTypeEnum } from "../../../types/mm";
|
import { SupplierCardTypeEnum, SupplierTypeEnum } from '../../../types/mm'
|
||||||
import { mockBusinessPartyNew } from "../../../mocks/mockBusinessParties";
|
import { mockBusinessPartyNew } from '../../../mocks/mockBusinessParties'
|
||||||
import { BusinessParty, PartyType, PaymentTerms } from "../../../types/common";
|
import { BusinessParty, PartyType, PaymentTerms } from '../../../types/common'
|
||||||
|
|
||||||
interface SupplierCardModalProps {
|
interface SupplierCardModalProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean
|
||||||
onClose: () => void;
|
onClose: () => void
|
||||||
onSave: (supplierCard: BusinessParty) => void;
|
onSave: (supplierCard: BusinessParty) => void
|
||||||
supplierCard?: BusinessParty | null;
|
supplierCard?: BusinessParty | null
|
||||||
mode: "create" | "view" | "edit";
|
mode: 'create' | 'view' | 'edit'
|
||||||
}
|
}
|
||||||
|
|
||||||
const SupplierCardModal: React.FC<SupplierCardModalProps> = ({
|
const SupplierCardModal: React.FC<SupplierCardModalProps> = ({
|
||||||
|
|
@ -19,50 +19,45 @@ const SupplierCardModal: React.FC<SupplierCardModalProps> = ({
|
||||||
supplierCard,
|
supplierCard,
|
||||||
mode,
|
mode,
|
||||||
}) => {
|
}) => {
|
||||||
const [formData, setFormData] =
|
const [formData, setFormData] = useState<Partial<BusinessParty>>(mockBusinessPartyNew)
|
||||||
useState<Partial<BusinessParty>>(mockBusinessPartyNew);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (mode === "create") {
|
if (mode === 'create') {
|
||||||
setFormData(mockBusinessPartyNew);
|
setFormData(mockBusinessPartyNew)
|
||||||
} else if (supplierCard) {
|
} else if (supplierCard) {
|
||||||
setFormData(supplierCard);
|
setFormData(supplierCard)
|
||||||
}
|
}
|
||||||
}, [supplierCard, mode]);
|
}, [supplierCard, mode])
|
||||||
|
|
||||||
const handleInputChange = (
|
const handleInputChange = (
|
||||||
e: React.ChangeEvent<
|
e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>,
|
||||||
HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement
|
|
||||||
>
|
|
||||||
) => {
|
) => {
|
||||||
const { name, value, type } = e.target;
|
const { name, value, type } = e.target
|
||||||
setFormData((prev) => ({
|
setFormData((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
[name]:
|
[name]:
|
||||||
type === "number"
|
type === 'number'
|
||||||
? parseFloat(value) || 0
|
? parseFloat(value) || 0
|
||||||
: type === "checkbox"
|
: type === 'checkbox'
|
||||||
? (e.target as HTMLInputElement).checked
|
? (e.target as HTMLInputElement).checked
|
||||||
: value,
|
: value,
|
||||||
}));
|
}))
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleSpecialConditionsChange = (value: string) => {
|
const handleSpecialConditionsChange = (value: string) => {
|
||||||
const conditions = value
|
const conditions = value.split('\n').filter((condition) => condition.trim() !== '')
|
||||||
.split("\n")
|
|
||||||
.filter((condition) => condition.trim() !== "");
|
|
||||||
setFormData((prev) => ({
|
setFormData((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
specialConditions: conditions,
|
specialConditions: conditions,
|
||||||
}));
|
}))
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleSubmit = (e: React.FormEvent) => {
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault()
|
||||||
if (mode !== "view") {
|
if (mode !== 'view') {
|
||||||
const newSupplierCard: BusinessParty = {
|
const newSupplierCard: BusinessParty = {
|
||||||
id: supplierCard?.id || `SC-${Date.now()}`,
|
id: supplierCard?.id || `SC-${Date.now()}`,
|
||||||
code: formData.code || "",
|
code: formData.code || '',
|
||||||
cardNumber:
|
cardNumber:
|
||||||
formData.cardNumber ||
|
formData.cardNumber ||
|
||||||
`SC-${new Date().getFullYear()}-${Date.now().toString().slice(-3)}`,
|
`SC-${new Date().getFullYear()}-${Date.now().toString().slice(-3)}`,
|
||||||
|
|
@ -88,27 +83,27 @@ const SupplierCardModal: React.FC<SupplierCardModalProps> = ({
|
||||||
creationTime: supplierCard?.creationTime || new Date(),
|
creationTime: supplierCard?.creationTime || new Date(),
|
||||||
lastModificationTime: new Date(),
|
lastModificationTime: new Date(),
|
||||||
supplierType: SupplierTypeEnum.Material,
|
supplierType: SupplierTypeEnum.Material,
|
||||||
name: "",
|
name: '',
|
||||||
currency: "",
|
currency: '',
|
||||||
certifications: [],
|
certifications: [],
|
||||||
bankAccounts: [],
|
bankAccounts: [],
|
||||||
contacts: [],
|
contacts: [],
|
||||||
partyType: PartyType.Supplier,
|
partyType: PartyType.Supplier,
|
||||||
};
|
|
||||||
onSave(newSupplierCard);
|
|
||||||
}
|
}
|
||||||
onClose();
|
onSave(newSupplierCard)
|
||||||
};
|
}
|
||||||
|
onClose()
|
||||||
|
}
|
||||||
|
|
||||||
if (!isOpen) return null;
|
if (!isOpen) return null
|
||||||
|
|
||||||
const isReadOnly = mode === "view";
|
const isReadOnly = mode === 'view'
|
||||||
const modalTitle =
|
const modalTitle =
|
||||||
mode === "create"
|
mode === 'create'
|
||||||
? "Yeni Tedarikçi Kartı"
|
? 'Yeni Tedarikçi Kartı'
|
||||||
: mode === "edit"
|
: mode === 'edit'
|
||||||
? "Tedarikçi Kartını Düzenle"
|
? 'Tedarikçi Kartını Düzenle'
|
||||||
: "Tedarikçi Kartı Detayları";
|
: 'Tedarikçi Kartı Detayları'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
<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" />
|
<FaCreditCard className="mr-2 text-blue-600" />
|
||||||
{modalTitle}
|
{modalTitle}
|
||||||
</h3>
|
</h3>
|
||||||
<button
|
<button onClick={onClose} className="p-1 text-gray-400 hover:text-gray-600">
|
||||||
onClick={onClose}
|
|
||||||
className="p-1 text-gray-400 hover:text-gray-600"
|
|
||||||
>
|
|
||||||
<FaTimes />
|
<FaTimes />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -130,18 +122,14 @@ const SupplierCardModal: React.FC<SupplierCardModalProps> = ({
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
{/* Temel Bilgiler */}
|
{/* Temel Bilgiler */}
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<h4 className="text-sm font-medium text-gray-900 border-b pb-1.5">
|
<h4 className="text-sm font-medium text-gray-900 border-b pb-1.5">Temel Bilgiler</h4>
|
||||||
Temel Bilgiler
|
|
||||||
</h4>
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700">
|
<label className="block text-sm font-medium text-gray-700">Tedarikçi</label>
|
||||||
Tedarikçi
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="customerCode"
|
name="customerCode"
|
||||||
value={formData.code || ""}
|
value={formData.code || ''}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
readOnly={isReadOnly}
|
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"
|
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>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700">
|
<label className="block text-sm font-medium text-gray-700">Kart Numarası</label>
|
||||||
Kart Numarası
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="cardNumber"
|
name="cardNumber"
|
||||||
value={formData.cardNumber || ""}
|
value={formData.cardNumber || ''}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
readOnly={isReadOnly}
|
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"
|
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>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700">
|
<label className="block text-sm font-medium text-gray-700">Kart Tipi</label>
|
||||||
Kart Tipi
|
|
||||||
</label>
|
|
||||||
<select
|
<select
|
||||||
name="cardType"
|
name="cardType"
|
||||||
value={formData.cardType || SupplierCardTypeEnum.Standard}
|
value={formData.cardType || SupplierCardTypeEnum.Standard}
|
||||||
|
|
@ -174,16 +158,10 @@ const SupplierCardModal: React.FC<SupplierCardModalProps> = ({
|
||||||
disabled={isReadOnly}
|
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"
|
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}>
|
<option value={SupplierCardTypeEnum.Standard}>Standart</option>
|
||||||
Standart
|
|
||||||
</option>
|
|
||||||
<option value={SupplierCardTypeEnum.Premium}>Premium</option>
|
<option value={SupplierCardTypeEnum.Premium}>Premium</option>
|
||||||
<option value={SupplierCardTypeEnum.Strategic}>
|
<option value={SupplierCardTypeEnum.Strategic}>Stratejik</option>
|
||||||
Stratejik
|
<option value={SupplierCardTypeEnum.Preferred}>Tercihli</option>
|
||||||
</option>
|
|
||||||
<option value={SupplierCardTypeEnum.Preferred}>
|
|
||||||
Tercihli
|
|
||||||
</option>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -196,8 +174,8 @@ const SupplierCardModal: React.FC<SupplierCardModalProps> = ({
|
||||||
name="validFrom"
|
name="validFrom"
|
||||||
value={
|
value={
|
||||||
formData.validFrom
|
formData.validFrom
|
||||||
? new Date(formData.validFrom).toISOString().split("T")[0]
|
? new Date(formData.validFrom).toISOString().split('T')[0]
|
||||||
: ""
|
: ''
|
||||||
}
|
}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
readOnly={isReadOnly}
|
readOnly={isReadOnly}
|
||||||
|
|
@ -206,16 +184,12 @@ const SupplierCardModal: React.FC<SupplierCardModalProps> = ({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700">
|
<label className="block text-sm font-medium text-gray-700">Geçerlilik Bitişi</label>
|
||||||
Geçerlilik Bitişi
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
type="date"
|
type="date"
|
||||||
name="validTo"
|
name="validTo"
|
||||||
value={
|
value={
|
||||||
formData.validTo
|
formData.validTo ? new Date(formData.validTo).toISOString().split('T')[0] : ''
|
||||||
? new Date(formData.validTo).toISOString().split("T")[0]
|
|
||||||
: ""
|
|
||||||
}
|
}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
readOnly={isReadOnly}
|
readOnly={isReadOnly}
|
||||||
|
|
@ -226,14 +200,10 @@ const SupplierCardModal: React.FC<SupplierCardModalProps> = ({
|
||||||
|
|
||||||
{/* Mali Bilgiler */}
|
{/* Mali Bilgiler */}
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<h4 className="text-sm font-medium text-gray-900 border-b pb-1.5">
|
<h4 className="text-sm font-medium text-gray-900 border-b pb-1.5">Mali Bilgiler</h4>
|
||||||
Mali Bilgiler
|
|
||||||
</h4>
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700">
|
<label className="block text-sm font-medium text-gray-700">Kredi Limiti (TL)</label>
|
||||||
Kredi Limiti (TL)
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
name="creditLimit"
|
name="creditLimit"
|
||||||
|
|
@ -259,9 +229,7 @@ const SupplierCardModal: React.FC<SupplierCardModalProps> = ({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700">
|
<label className="block text-sm font-medium text-gray-700">Ödeme Koşulları</label>
|
||||||
Ödeme Koşulları
|
|
||||||
</label>
|
|
||||||
<select
|
<select
|
||||||
name="paymentTerms"
|
name="paymentTerms"
|
||||||
value={formData.paymentTerms || PaymentTerms.Net30}
|
value={formData.paymentTerms || PaymentTerms.Net30}
|
||||||
|
|
@ -281,9 +249,7 @@ const SupplierCardModal: React.FC<SupplierCardModalProps> = ({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700">
|
<label className="block text-sm font-medium text-gray-700">İndirim Oranı (%)</label>
|
||||||
İndirim Oranı (%)
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
name="discountRate"
|
name="discountRate"
|
||||||
|
|
@ -307,9 +273,7 @@ const SupplierCardModal: React.FC<SupplierCardModalProps> = ({
|
||||||
disabled={isReadOnly}
|
disabled={isReadOnly}
|
||||||
className="mr-2"
|
className="mr-2"
|
||||||
/>
|
/>
|
||||||
<span className="text-sm font-medium text-gray-700">
|
<span className="text-sm font-medium text-gray-700">Aktif</span>
|
||||||
Aktif
|
|
||||||
</span>
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -322,7 +286,7 @@ const SupplierCardModal: React.FC<SupplierCardModalProps> = ({
|
||||||
</h4>
|
</h4>
|
||||||
<textarea
|
<textarea
|
||||||
name="specialConditions"
|
name="specialConditions"
|
||||||
value={formData.specialConditions?.join("\n") || ""}
|
value={formData.specialConditions?.join('\n') || ''}
|
||||||
onChange={(e) => handleSpecialConditionsChange(e.target.value)}
|
onChange={(e) => handleSpecialConditionsChange(e.target.value)}
|
||||||
readOnly={isReadOnly}
|
readOnly={isReadOnly}
|
||||||
rows={3}
|
rows={3}
|
||||||
|
|
@ -332,7 +296,7 @@ const SupplierCardModal: React.FC<SupplierCardModalProps> = ({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Performans Metrikleri - Sadece görüntüleme modu */}
|
{/* Performans Metrikleri - Sadece görüntüleme modu */}
|
||||||
{mode === "view" && formData.performanceMetrics && (
|
{mode === 'view' && formData.performanceMetrics && (
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<h4 className="text-sm font-medium text-gray-900 border-b pb-1.5 mb-3 flex items-center">
|
<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" />
|
<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">
|
<div className="text-2xl font-bold text-blue-600">
|
||||||
{formData.performanceMetrics.deliveryPerformance}%
|
{formData.performanceMetrics.deliveryPerformance}%
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-gray-600">
|
<div className="text-sm text-gray-600">Teslimat Performansı</div>
|
||||||
Teslimat Performansı
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div className="text-2xl font-bold text-green-600">
|
<div className="text-2xl font-bold text-green-600">
|
||||||
{formData.performanceMetrics.qualityRating}%
|
{formData.performanceMetrics.qualityRating}%
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-gray-600">
|
<div className="text-sm text-gray-600">Kalite Değerlendirmesi</div>
|
||||||
Kalite Değerlendirmesi
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div className="text-2xl font-bold text-purple-600">
|
<div className="text-2xl font-bold text-purple-600">
|
||||||
|
|
@ -390,9 +350,9 @@ const SupplierCardModal: React.FC<SupplierCardModalProps> = ({
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="px-3 py-1.5 border border-gray-300 rounded-md text-sm text-gray-700 hover:bg-gray-50"
|
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>
|
</button>
|
||||||
{mode !== "view" && (
|
{mode !== 'view' && (
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="px-3 py-1.5 bg-blue-600 text-white rounded-md text-sm hover:bg-blue-700 flex items-center"
|
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>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
export default SupplierCardModal;
|
export default SupplierCardModal
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useState } from "react";
|
import React, { useState } from 'react'
|
||||||
import {
|
import {
|
||||||
FaPlus,
|
FaPlus,
|
||||||
FaSearch,
|
FaSearch,
|
||||||
|
|
@ -13,113 +13,112 @@ import {
|
||||||
FaTh,
|
FaTh,
|
||||||
FaList,
|
FaList,
|
||||||
FaBuilding,
|
FaBuilding,
|
||||||
} from "react-icons/fa";
|
} from 'react-icons/fa'
|
||||||
import SupplierCardModal from "./SupplierCardModal";
|
import SupplierCardModal from './SupplierCardModal'
|
||||||
import { mockBusinessParties } from "../../../mocks/mockBusinessParties";
|
import { mockBusinessParties } from '../../../mocks/mockBusinessParties'
|
||||||
import { BusinessParty, PartyType } from "../../../types/common";
|
import { BusinessParty, PartyType } from '../../../types/common'
|
||||||
import {
|
import {
|
||||||
getSupplierCardTypeColor,
|
getSupplierCardTypeColor,
|
||||||
getSupplierCardTypeText,
|
getSupplierCardTypeText,
|
||||||
getPaymentTermsText,
|
getPaymentTermsText,
|
||||||
} from "../../../utils/erp";
|
} from '../../../utils/erp'
|
||||||
|
import { Container } from '@/components/shared'
|
||||||
|
|
||||||
const SupplierCards: React.FC = () => {
|
const SupplierCards: React.FC = () => {
|
||||||
const [searchTerm, setSearchTerm] = useState("");
|
const [searchTerm, setSearchTerm] = useState('')
|
||||||
const [showModal, setShowModal] = useState(false);
|
const [showModal, setShowModal] = useState(false)
|
||||||
const [editingCard, setEditingCard] = useState<BusinessParty | null>(null);
|
const [editingCard, setEditingCard] = useState<BusinessParty | null>(null)
|
||||||
const [modalMode, setModalMode] = useState<"create" | "view" | "edit">(
|
const [modalMode, setModalMode] = useState<'create' | 'view' | 'edit'>('create')
|
||||||
"create"
|
const [viewMode, setViewMode] = useState<'cards' | 'list'>('cards')
|
||||||
);
|
|
||||||
const [viewMode, setViewMode] = useState<"cards" | "list">("cards");
|
|
||||||
|
|
||||||
// Mock data - replace with actual API calls
|
// Mock data - replace with actual API calls
|
||||||
const [supplierCards] = useState<BusinessParty[]>(mockBusinessParties);
|
const [supplierCards] = useState<BusinessParty[]>(mockBusinessParties)
|
||||||
|
|
||||||
const filteredCards = supplierCards
|
const filteredCards = supplierCards
|
||||||
.filter((card) => card.partyType === PartyType.Supplier)
|
.filter((card) => card.partyType === PartyType.Supplier)
|
||||||
.filter(
|
.filter(
|
||||||
(card) =>
|
(card) =>
|
||||||
card.cardNumber!.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
card.cardNumber!.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||||
card.id.toLowerCase().includes(searchTerm.toLowerCase())
|
card.id.toLowerCase().includes(searchTerm.toLowerCase()),
|
||||||
);
|
)
|
||||||
|
|
||||||
const getCreditUtilization = (card: BusinessParty) => {
|
const getCreditUtilization = (card: BusinessParty) => {
|
||||||
return (card.currentBalance! / card.creditLimit) * 100;
|
return (card.currentBalance! / card.creditLimit) * 100
|
||||||
};
|
}
|
||||||
|
|
||||||
const getPerformanceColor = (score: number) => {
|
const getPerformanceColor = (score: number) => {
|
||||||
if (score >= 90) return "text-green-600";
|
if (score >= 90) return 'text-green-600'
|
||||||
if (score >= 80) return "text-yellow-600";
|
if (score >= 80) return 'text-yellow-600'
|
||||||
return "text-red-600";
|
return 'text-red-600'
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleEdit = (card: BusinessParty) => {
|
const handleEdit = (card: BusinessParty) => {
|
||||||
setEditingCard(card);
|
setEditingCard(card)
|
||||||
setModalMode("edit");
|
setModalMode('edit')
|
||||||
setShowModal(true);
|
setShowModal(true)
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleView = (card: BusinessParty) => {
|
const handleView = (card: BusinessParty) => {
|
||||||
setEditingCard(card);
|
setEditingCard(card)
|
||||||
setModalMode("view");
|
setModalMode('view')
|
||||||
setShowModal(true);
|
setShowModal(true)
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleAddNew = () => {
|
const handleAddNew = () => {
|
||||||
setEditingCard(null);
|
setEditingCard(null)
|
||||||
setModalMode("create");
|
setModalMode('create')
|
||||||
setShowModal(true);
|
setShowModal(true)
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleSaveCard = (card: BusinessParty) => {
|
const handleSaveCard = (card: BusinessParty) => {
|
||||||
// TODO: Implement save logic
|
// TODO: Implement save logic
|
||||||
console.log("Saving supplier card:", card);
|
console.log('Saving supplier card:', card)
|
||||||
setShowModal(false);
|
setShowModal(false)
|
||||||
setEditingCard(null);
|
setEditingCard(null)
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleCloseModal = () => {
|
const handleCloseModal = () => {
|
||||||
setShowModal(false);
|
setShowModal(false)
|
||||||
setEditingCard(null);
|
setEditingCard(null)
|
||||||
};
|
}
|
||||||
|
|
||||||
const isCardExpiring = (card: BusinessParty) => {
|
const isCardExpiring = (card: BusinessParty) => {
|
||||||
if (!card.validTo) return false;
|
if (!card.validTo) return false
|
||||||
const daysUntilExpiry = Math.ceil(
|
const daysUntilExpiry = Math.ceil(
|
||||||
(card.validTo.getTime() - new Date().getTime()) / (1000 * 60 * 60 * 24)
|
(card.validTo.getTime() - new Date().getTime()) / (1000 * 60 * 60 * 24),
|
||||||
);
|
)
|
||||||
return daysUntilExpiry <= 30;
|
return daysUntilExpiry <= 30
|
||||||
};
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-3 pt-2">
|
<Container>
|
||||||
|
<div className="space-y-2">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-2xl font-bold text-gray-900">Tedarikçiler</h2>
|
<h2 className="text-2xl font-bold text-gray-900">Tedarikçiler</h2>
|
||||||
<p className="text-gray-600">
|
<p className="text-gray-600">
|
||||||
Tedarikçi kredi limitleri, özel şartlar ve performans metriklerini
|
Tedarikçi kredi limitleri, özel şartlar ve performans metriklerini yönetin
|
||||||
yönetin
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<div className="flex bg-gray-100 rounded-lg">
|
<div className="flex bg-gray-100 rounded-lg">
|
||||||
<button
|
<button
|
||||||
onClick={() => setViewMode("cards")}
|
onClick={() => setViewMode('cards')}
|
||||||
className={`px-3 py-2 rounded-md text-sm font-medium transition-colors ${
|
className={`px-3 py-2 rounded-md text-sm font-medium transition-colors ${
|
||||||
viewMode === "cards"
|
viewMode === 'cards'
|
||||||
? "bg-white text-gray-900 shadow-sm"
|
? 'bg-white text-gray-900 shadow-sm'
|
||||||
: "text-gray-500 hover:text-gray-700"
|
: 'text-gray-500 hover:text-gray-700'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<FaTh className="w-4 h-4 inline" />
|
<FaTh className="w-4 h-4 inline" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setViewMode("list")}
|
onClick={() => setViewMode('list')}
|
||||||
className={`px-3 py-2 rounded-md text-sm font-medium transition-colors ${
|
className={`px-3 py-2 rounded-md text-sm font-medium transition-colors ${
|
||||||
viewMode === "list"
|
viewMode === 'list'
|
||||||
? "bg-white text-gray-900 shadow-sm"
|
? 'bg-white text-gray-900 shadow-sm'
|
||||||
: "text-gray-500 hover:text-gray-700"
|
: 'text-gray-500 hover:text-gray-700'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<FaList className="w-4 h-4 inline" />
|
<FaList className="w-4 h-4 inline" />
|
||||||
|
|
@ -150,40 +149,34 @@ const SupplierCards: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Content Area */}
|
{/* Content Area */}
|
||||||
{viewMode === "cards" ? (
|
{viewMode === 'cards' ? (
|
||||||
/* Cards Grid */
|
/* Cards Grid */
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||||
{filteredCards.map((card) => (
|
{filteredCards.map((card) => (
|
||||||
<div
|
<div
|
||||||
key={card.id}
|
key={card.id}
|
||||||
className={`bg-white rounded-lg shadow-md border-l-4 p-4 hover:shadow-lg transition-shadow ${
|
className={`bg-white rounded-lg shadow-md border-l-4 p-4 hover:shadow-lg transition-shadow ${
|
||||||
getSupplierCardTypeColor(card.cardType!).split(" ").slice(-1)[0]
|
getSupplierCardTypeColor(card.cardType!).split(' ').slice(-1)[0]
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="flex items-start justify-between mb-3">
|
<div className="flex items-start justify-between mb-3">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<div className="flex items-center space-x-2 mb-1.5">
|
<div className="flex items-center space-x-2 mb-1.5">
|
||||||
<FaCreditCard className="w-6 h-6 text-gray-600" />
|
<FaCreditCard className="w-6 h-6 text-gray-600" />
|
||||||
<h3 className="text-lg font-semibold text-gray-900">
|
<h3 className="text-lg font-semibold text-gray-900">{card.cardNumber}</h3>
|
||||||
{card.cardNumber}
|
|
||||||
</h3>
|
|
||||||
<span
|
<span
|
||||||
className={`px-3 py-1 rounded-full text-sm font-medium border ${getSupplierCardTypeColor(
|
className={`px-3 py-1 rounded-full text-sm font-medium border ${getSupplierCardTypeColor(
|
||||||
card.cardType!
|
card.cardType!,
|
||||||
)}`}
|
)}`}
|
||||||
>
|
>
|
||||||
{getSupplierCardTypeText(card.cardType!)}
|
{getSupplierCardTypeText(card.cardType!)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-gray-600 mb-1">
|
<p className="text-sm text-gray-600 mb-1">Tedarikçi: {card.code}</p>
|
||||||
Tedarikçi: {card.code}
|
|
||||||
</p>
|
|
||||||
<div className="flex items-center space-x-4 text-sm text-gray-500">
|
<div className="flex items-center space-x-4 text-sm text-gray-500">
|
||||||
<span>Ödeme: {getPaymentTermsText(card.paymentTerms)}</span>
|
<span>Ödeme: {getPaymentTermsText(card.paymentTerms)}</span>
|
||||||
{card.discountRate && (
|
{card.discountRate && (
|
||||||
<span className="text-green-600">
|
<span className="text-green-600">%{card.discountRate} indirim</span>
|
||||||
%{card.discountRate} indirim
|
|
||||||
</span>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -223,9 +216,7 @@ const SupplierCards: React.FC = () => {
|
||||||
{isCardExpiring(card) && (
|
{isCardExpiring(card) && (
|
||||||
<div className="flex items-center space-x-2 text-orange-600 bg-orange-50 px-2.5 py-1.5 rounded-lg">
|
<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" />
|
<FaCalendar className="w-4 h-4" />
|
||||||
<span className="text-sm">
|
<span className="text-sm">Kartın süresi 30 gün içinde dolacak</span>
|
||||||
Kartın süresi 30 gün içinde dolacak
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -238,36 +229,28 @@ const SupplierCards: React.FC = () => {
|
||||||
<FaDollarSign className="w-4 h-4 mr-1" />
|
<FaDollarSign className="w-4 h-4 mr-1" />
|
||||||
Kredi Limiti
|
Kredi Limiti
|
||||||
</span>
|
</span>
|
||||||
<span className="font-medium">
|
<span className="font-medium">₺{card.creditLimit.toLocaleString()}</span>
|
||||||
₺{card.creditLimit.toLocaleString()}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between mb-1.5">
|
<div className="flex items-center justify-between mb-1.5">
|
||||||
<span className="text-sm text-gray-600">Kullanılan</span>
|
<span className="text-sm text-gray-600">Kullanılan</span>
|
||||||
<span className="font-medium">
|
<span className="font-medium">₺{card.currentBalance!.toLocaleString()}</span>
|
||||||
₺{card.currentBalance!.toLocaleString()}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full bg-gray-200 rounded-full h-2">
|
<div className="w-full bg-gray-200 rounded-full h-2">
|
||||||
<div
|
<div
|
||||||
className={`h-2 rounded-full ${
|
className={`h-2 rounded-full ${
|
||||||
getCreditUtilization(card) > 80
|
getCreditUtilization(card) > 80
|
||||||
? "bg-red-500"
|
? 'bg-red-500'
|
||||||
: getCreditUtilization(card) > 60
|
: getCreditUtilization(card) > 60
|
||||||
? "bg-yellow-500"
|
? 'bg-yellow-500'
|
||||||
: "bg-green-500"
|
: 'bg-green-500'
|
||||||
}`}
|
}`}
|
||||||
style={{ width: `${getCreditUtilization(card)}%` }}
|
style={{ width: `${getCreditUtilization(card)}%` }}
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between text-xs text-gray-500 mt-1">
|
<div className="flex justify-between text-xs text-gray-500 mt-1">
|
||||||
|
<span>%{getCreditUtilization(card).toFixed(1)} kullanıldı</span>
|
||||||
<span>
|
<span>
|
||||||
%{getCreditUtilization(card).toFixed(1)} kullanıldı
|
₺{(card.creditLimit - card.currentBalance!).toLocaleString()} müsait
|
||||||
</span>
|
|
||||||
<span>
|
|
||||||
₺
|
|
||||||
{(card.creditLimit - card.currentBalance!).toLocaleString()}{" "}
|
|
||||||
müsait
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -281,7 +264,7 @@ const SupplierCards: React.FC = () => {
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
className={`font-bold text-lg ${getPerformanceColor(
|
className={`font-bold text-lg ${getPerformanceColor(
|
||||||
card.performanceMetrics!.overallScore
|
card.performanceMetrics!.overallScore,
|
||||||
)}`}
|
)}`}
|
||||||
>
|
>
|
||||||
{card.performanceMetrics!.overallScore}/100
|
{card.performanceMetrics!.overallScore}/100
|
||||||
|
|
@ -292,7 +275,7 @@ const SupplierCards: React.FC = () => {
|
||||||
<span className="text-gray-600">Teslimat:</span>
|
<span className="text-gray-600">Teslimat:</span>
|
||||||
<span
|
<span
|
||||||
className={getPerformanceColor(
|
className={getPerformanceColor(
|
||||||
card.performanceMetrics!.deliveryPerformance
|
card.performanceMetrics!.deliveryPerformance,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{card.performanceMetrics!.deliveryPerformance}%
|
{card.performanceMetrics!.deliveryPerformance}%
|
||||||
|
|
@ -300,11 +283,7 @@ const SupplierCards: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<span className="text-gray-600">Kalite:</span>
|
<span className="text-gray-600">Kalite:</span>
|
||||||
<span
|
<span className={getPerformanceColor(card.performanceMetrics!.qualityRating)}>
|
||||||
className={getPerformanceColor(
|
|
||||||
card.performanceMetrics!.qualityRating
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{card.performanceMetrics!.qualityRating}%
|
{card.performanceMetrics!.qualityRating}%
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -312,7 +291,7 @@ const SupplierCards: React.FC = () => {
|
||||||
<span className="text-gray-600">Fiyat:</span>
|
<span className="text-gray-600">Fiyat:</span>
|
||||||
<span
|
<span
|
||||||
className={getPerformanceColor(
|
className={getPerformanceColor(
|
||||||
card.performanceMetrics!.priceCompetitiveness
|
card.performanceMetrics!.priceCompetitiveness,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{card.performanceMetrics!.priceCompetitiveness}%
|
{card.performanceMetrics!.priceCompetitiveness}%
|
||||||
|
|
@ -321,9 +300,7 @@ const SupplierCards: React.FC = () => {
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<span className="text-gray-600">Uyum:</span>
|
<span className="text-gray-600">Uyum:</span>
|
||||||
<span
|
<span
|
||||||
className={getPerformanceColor(
|
className={getPerformanceColor(card.performanceMetrics!.complianceRating)}
|
||||||
card.performanceMetrics!.complianceRating
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
{card.performanceMetrics!.complianceRating}%
|
{card.performanceMetrics!.complianceRating}%
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -334,9 +311,7 @@ const SupplierCards: React.FC = () => {
|
||||||
{/* Special Conditions */}
|
{/* Special Conditions */}
|
||||||
{card.specialConditions && card.specialConditions.length > 0 && (
|
{card.specialConditions && card.specialConditions.length > 0 && (
|
||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
<h4 className="text-sm font-medium text-gray-900 mb-2">
|
<h4 className="text-sm font-medium text-gray-900 mb-2">Özel Şartlar:</h4>
|
||||||
Özel Şartlar:
|
|
||||||
</h4>
|
|
||||||
<div className="flex flex-wrap gap-1">
|
<div className="flex flex-wrap gap-1">
|
||||||
{card.specialConditions.map((condition, index) => (
|
{card.specialConditions.map((condition, index) => (
|
||||||
<span
|
<span
|
||||||
|
|
@ -355,21 +330,19 @@ const SupplierCards: React.FC = () => {
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<span
|
<span
|
||||||
className={`px-2 py-1 rounded-full text-xs font-medium ${
|
className={`px-2 py-1 rounded-full text-xs font-medium ${
|
||||||
card.isActive
|
card.isActive ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
|
||||||
? "bg-green-100 text-green-800"
|
|
||||||
: "bg-red-100 text-red-800"
|
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{card.isActive ? "Aktif" : "Pasif"}
|
{card.isActive ? 'Aktif' : 'Pasif'}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-xs text-gray-500">
|
<span className="text-xs text-gray-500">
|
||||||
Geçerli: {card.validFrom!.toLocaleDateString("tr-TR")} -{" "}
|
Geçerli: {card.validFrom!.toLocaleDateString('tr-TR')} -{' '}
|
||||||
{card.validTo?.toLocaleDateString("tr-TR") || "Süresiz"}
|
{card.validTo?.toLocaleDateString('tr-TR') || 'Süresiz'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{card.lastOrderDate && (
|
{card.lastOrderDate && (
|
||||||
<span className="text-xs text-gray-400">
|
<span className="text-xs text-gray-400">
|
||||||
Son işlem: {card.lastOrderDate.toLocaleDateString("tr-TR")}
|
Son işlem: {card.lastOrderDate.toLocaleDateString('tr-TR')}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -423,16 +396,14 @@ const SupplierCards: React.FC = () => {
|
||||||
<div className="text-sm font-medium text-gray-900">
|
<div className="text-sm font-medium text-gray-900">
|
||||||
{card.cardNumber}
|
{card.cardNumber}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-gray-500">
|
<div className="text-sm text-gray-500">{card.code}</div>
|
||||||
{card.code}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-2 py-2 whitespace-nowrap">
|
<td className="px-2 py-2 whitespace-nowrap">
|
||||||
<span
|
<span
|
||||||
className={`inline-flex px-2 py-1 text-xs font-semibold rounded-full ${getSupplierCardTypeColor(
|
className={`inline-flex px-2 py-1 text-xs font-semibold rounded-full ${getSupplierCardTypeColor(
|
||||||
card.cardType!
|
card.cardType!,
|
||||||
)}`}
|
)}`}
|
||||||
>
|
>
|
||||||
{getSupplierCardTypeText(card.cardType!)}
|
{getSupplierCardTypeText(card.cardType!)}
|
||||||
|
|
@ -447,10 +418,10 @@ const SupplierCards: React.FC = () => {
|
||||||
<div
|
<div
|
||||||
className={`h-2 rounded-full ${
|
className={`h-2 rounded-full ${
|
||||||
getCreditUtilization(card) > 80
|
getCreditUtilization(card) > 80
|
||||||
? "bg-red-500"
|
? 'bg-red-500'
|
||||||
: getCreditUtilization(card) > 60
|
: getCreditUtilization(card) > 60
|
||||||
? "bg-yellow-500"
|
? 'bg-yellow-500'
|
||||||
: "bg-green-500"
|
: 'bg-green-500'
|
||||||
}`}
|
}`}
|
||||||
style={{ width: `${getCreditUtilization(card)}%` }}
|
style={{ width: `${getCreditUtilization(card)}%` }}
|
||||||
></div>
|
></div>
|
||||||
|
|
@ -465,7 +436,7 @@ const SupplierCards: React.FC = () => {
|
||||||
<FaStar className="w-4 h-4 text-yellow-400 mr-1" />
|
<FaStar className="w-4 h-4 text-yellow-400 mr-1" />
|
||||||
<span
|
<span
|
||||||
className={`text-sm font-medium ${getPerformanceColor(
|
className={`text-sm font-medium ${getPerformanceColor(
|
||||||
card.performanceMetrics!.overallScore
|
card.performanceMetrics!.overallScore,
|
||||||
)}`}
|
)}`}
|
||||||
>
|
>
|
||||||
{card.performanceMetrics!.overallScore}/100
|
{card.performanceMetrics!.overallScore}/100
|
||||||
|
|
@ -480,17 +451,17 @@ const SupplierCards: React.FC = () => {
|
||||||
<span
|
<span
|
||||||
className={`inline-flex px-2 py-1 text-xs font-semibold rounded-full ${
|
className={`inline-flex px-2 py-1 text-xs font-semibold rounded-full ${
|
||||||
card.isActive
|
card.isActive
|
||||||
? "bg-green-100 text-green-800"
|
? 'bg-green-100 text-green-800'
|
||||||
: "bg-red-100 text-red-800"
|
: 'bg-red-100 text-red-800'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{card.isActive ? "Aktif" : "Pasif"}
|
{card.isActive ? 'Aktif' : 'Pasif'}
|
||||||
</span>
|
</span>
|
||||||
{(isCardExpiring(card) || !card.isActive) && (
|
{(isCardExpiring(card) || !card.isActive) && (
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<FaExclamationCircle className="w-3 h-3 text-orange-500 mr-1" />
|
<FaExclamationCircle className="w-3 h-3 text-orange-500 mr-1" />
|
||||||
<span className="text-xs text-orange-600">
|
<span className="text-xs text-orange-600">
|
||||||
{!card.isActive ? "Pasif" : "Süre dolacak"}
|
{!card.isActive ? 'Pasif' : 'Süre dolacak'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -531,15 +502,13 @@ const SupplierCards: React.FC = () => {
|
||||||
{filteredCards.length === 0 && (
|
{filteredCards.length === 0 && (
|
||||||
<div className="text-center py-8">
|
<div className="text-center py-8">
|
||||||
<FaCreditCard className="w-12 h-12 text-gray-400 mx-auto mb-4" />
|
<FaCreditCard className="w-12 h-12 text-gray-400 mx-auto mb-4" />
|
||||||
<h3 className="text-lg font-medium text-gray-900 mb-2">
|
<h3 className="text-lg font-medium text-gray-900 mb-2">Tedarikçi kartı bulunamadı</h3>
|
||||||
Tedarikçi kartı bulunamadı
|
|
||||||
</h3>
|
|
||||||
<p className="text-gray-500">
|
<p className="text-gray-500">
|
||||||
Arama kriterlerinizi değiştirin veya yeni bir tedarikçi kartı
|
Arama kriterlerinizi değiştirin veya yeni bir tedarikçi kartı ekleyin.
|
||||||
ekleyin.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Supplier Card Modal */}
|
{/* Supplier Card Modal */}
|
||||||
<SupplierCardModal
|
<SupplierCardModal
|
||||||
|
|
@ -549,8 +518,8 @@ const SupplierCards: React.FC = () => {
|
||||||
supplierCard={editingCard}
|
supplierCard={editingCard}
|
||||||
mode={modalMode}
|
mode={modalMode}
|
||||||
/>
|
/>
|
||||||
</div>
|
</Container>
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
export default SupplierCards;
|
export default SupplierCards
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { useState, useEffect, useCallback } from "react";
|
import React, { useState, useEffect, useCallback } from 'react'
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from 'react-router-dom'
|
||||||
import {
|
import {
|
||||||
FaSave,
|
FaSave,
|
||||||
FaTimes,
|
FaTimes,
|
||||||
|
|
@ -10,163 +10,156 @@ import {
|
||||||
FaPhone,
|
FaPhone,
|
||||||
FaEnvelope,
|
FaEnvelope,
|
||||||
FaStar,
|
FaStar,
|
||||||
} from "react-icons/fa";
|
} from 'react-icons/fa'
|
||||||
import LoadingSpinner from "../../../components/common/LoadingSpinner";
|
import LoadingSpinner from '../../../components/common/LoadingSpinner'
|
||||||
import {
|
import { mockBusinessParties, mockBusinessPartyNew } from '../../../mocks/mockBusinessParties'
|
||||||
mockBusinessParties,
|
import { BusinessParty } from '../../../types/common'
|
||||||
mockBusinessPartyNew,
|
import { Container } from '@/components/shared'
|
||||||
} from "../../../mocks/mockBusinessParties";
|
|
||||||
import { BusinessParty } from "../../../types/common";
|
|
||||||
|
|
||||||
interface ValidationErrors {
|
interface ValidationErrors {
|
||||||
[key: string]: string;
|
[key: string]: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const SupplierForm: React.FC = () => {
|
const SupplierForm: React.FC = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate()
|
||||||
const { id } = useParams<{ id: string }>();
|
const { id } = useParams<{ id: string }>()
|
||||||
const isEdit = Boolean(id);
|
const isEdit = Boolean(id)
|
||||||
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false)
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false)
|
||||||
const [errors, setErrors] = useState<ValidationErrors>({});
|
const [errors, setErrors] = useState<ValidationErrors>({})
|
||||||
const [formData, setFormData] = useState<BusinessParty>(mockBusinessPartyNew);
|
const [formData, setFormData] = useState<BusinessParty>(mockBusinessPartyNew)
|
||||||
|
|
||||||
const loadFormData = useCallback(async () => {
|
const loadFormData = useCallback(async () => {
|
||||||
setLoading(true);
|
setLoading(true)
|
||||||
try {
|
try {
|
||||||
if (isEdit && id) {
|
if (isEdit && id) {
|
||||||
// Simulate API call
|
// Simulate API call
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
await new Promise((resolve) => setTimeout(resolve, 1000))
|
||||||
|
|
||||||
setFormData(
|
setFormData(mockBusinessParties.find((s) => s.id === id) || mockBusinessPartyNew)
|
||||||
mockBusinessParties.find((s) => s.id === id) || mockBusinessPartyNew
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error loading form data:", error);
|
console.error('Error loading form data:', error)
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false)
|
||||||
}
|
}
|
||||||
}, [isEdit, id]);
|
}, [isEdit, id])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadFormData();
|
loadFormData()
|
||||||
}, [loadFormData]);
|
}, [loadFormData])
|
||||||
|
|
||||||
const validateForm = (): boolean => {
|
const validateForm = (): boolean => {
|
||||||
const newErrors: ValidationErrors = {};
|
const newErrors: ValidationErrors = {}
|
||||||
|
|
||||||
if (!formData.code?.trim()) {
|
if (!formData.code?.trim()) {
|
||||||
newErrors.customerCode = "Tedarikçi kodu zorunludur";
|
newErrors.customerCode = 'Tedarikçi kodu zorunludur'
|
||||||
}
|
}
|
||||||
if (!formData.name.trim()) {
|
if (!formData.name.trim()) {
|
||||||
newErrors.companyName = "Şirket adı zorunludur";
|
newErrors.companyName = 'Şirket adı zorunludur'
|
||||||
}
|
}
|
||||||
if (!formData.primaryContact?.fullName?.trim()) {
|
if (!formData.primaryContact?.fullName?.trim()) {
|
||||||
newErrors.contactPerson = "İletişim kişisi zorunludur";
|
newErrors.contactPerson = 'İletişim kişisi zorunludur'
|
||||||
}
|
}
|
||||||
if (!formData.email?.trim()) {
|
if (!formData.email?.trim()) {
|
||||||
newErrors.email = "Email zorunludur";
|
newErrors.email = 'Email zorunludur'
|
||||||
} else if (!/\S+@\S+\.\S+/.test(formData.email)) {
|
} else if (!/\S+@\S+\.\S+/.test(formData.email)) {
|
||||||
newErrors.email = "Geçerli bir email adresi girin";
|
newErrors.email = 'Geçerli bir email adresi girin'
|
||||||
}
|
}
|
||||||
if (!formData.phone?.trim()) {
|
if (!formData.phone?.trim()) {
|
||||||
newErrors.phone = "Telefon numarası zorunludur";
|
newErrors.phone = 'Telefon numarası zorunludur'
|
||||||
}
|
}
|
||||||
if (!formData.address?.street?.trim()) {
|
if (!formData.address?.street?.trim()) {
|
||||||
newErrors.street = "Adres zorunludur";
|
newErrors.street = 'Adres zorunludur'
|
||||||
}
|
}
|
||||||
if (!formData.address?.city.trim()) {
|
if (!formData.address?.city.trim()) {
|
||||||
newErrors.city = "Şehir zorunludur";
|
newErrors.city = 'Şehir zorunludur'
|
||||||
}
|
}
|
||||||
if (!formData.address?.country.trim()) {
|
if (!formData.address?.country.trim()) {
|
||||||
newErrors.country = "Ülke zorunludur";
|
newErrors.country = 'Ülke zorunludur'
|
||||||
}
|
}
|
||||||
if (!formData.paymentTerms) {
|
if (!formData.paymentTerms) {
|
||||||
newErrors.paymentTerms = "Ödeme koşulları seçilmelidir";
|
newErrors.paymentTerms = 'Ödeme koşulları seçilmelidir'
|
||||||
}
|
}
|
||||||
if (!formData.supplierType) {
|
if (!formData.supplierType) {
|
||||||
newErrors.supplierType = "Tedarikçi tipi seçilmelidir";
|
newErrors.supplierType = 'Tedarikçi tipi seçilmelidir'
|
||||||
}
|
}
|
||||||
|
|
||||||
setErrors(newErrors);
|
setErrors(newErrors)
|
||||||
return Object.keys(newErrors).length === 0;
|
return Object.keys(newErrors).length === 0
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleInputChange = (field: string, value: any) => {
|
const handleInputChange = (field: string, value: any) => {
|
||||||
setFormData((prev) => {
|
setFormData((prev) => {
|
||||||
const keys = field.split(".");
|
const keys = field.split('.')
|
||||||
const updated: any = { ...prev };
|
const updated: any = { ...prev }
|
||||||
let temp = updated;
|
let temp = updated
|
||||||
|
|
||||||
keys.forEach((key, index) => {
|
keys.forEach((key, index) => {
|
||||||
if (index === keys.length - 1) {
|
if (index === keys.length - 1) {
|
||||||
temp[key] = value;
|
temp[key] = value
|
||||||
} else {
|
} else {
|
||||||
temp[key] = { ...temp[key] };
|
temp[key] = { ...temp[key] }
|
||||||
temp = temp[key];
|
temp = temp[key]
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
return updated;
|
return updated
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault()
|
||||||
|
|
||||||
if (!validateForm()) {
|
if (!validateForm()) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
setSaving(true);
|
setSaving(true)
|
||||||
try {
|
try {
|
||||||
// Simulate API call
|
// Simulate API call
|
||||||
await new Promise((resolve) => setTimeout(resolve, 2000));
|
await new Promise((resolve) => setTimeout(resolve, 2000))
|
||||||
|
|
||||||
console.log("Supplier data:", {
|
console.log('Supplier data:', {
|
||||||
...formData,
|
...formData,
|
||||||
id: isEdit ? id : undefined,
|
id: isEdit ? id : undefined,
|
||||||
});
|
})
|
||||||
|
|
||||||
// Show success message
|
// Show success message
|
||||||
alert(
|
alert(isEdit ? 'Tedarikçi başarıyla güncellendi!' : 'Tedarikçi başarıyla oluşturuldu!')
|
||||||
isEdit
|
|
||||||
? "Tedarikçi başarıyla güncellendi!"
|
|
||||||
: "Tedarikçi başarıyla oluşturuldu!"
|
|
||||||
);
|
|
||||||
|
|
||||||
// Navigate back to list
|
// Navigate back to list
|
||||||
navigate("/admin/supplychain");
|
navigate('/admin/supplychain')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error saving supplier:", error);
|
console.error('Error saving supplier:', error)
|
||||||
alert("Bir hata oluştu. Lütfen tekrar deneyin.");
|
alert('Bir hata oluştu. Lütfen tekrar deneyin.')
|
||||||
} finally {
|
} finally {
|
||||||
setSaving(false);
|
setSaving(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
navigate("/admin/supplychain");
|
navigate('/admin/supplychain')
|
||||||
};
|
}
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <LoadingSpinner />;
|
return <LoadingSpinner />
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-3 pt-2">
|
<Container>
|
||||||
|
<div className="space-y-2">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-lg font-semibold text-gray-900">
|
<h2 className="text-lg font-semibold text-gray-900">
|
||||||
{isEdit ? "Tedarikçi Düzenle" : "Yeni Tedarikçi"}
|
{isEdit ? 'Tedarikçi Düzenle' : 'Yeni Tedarikçi'}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-sm text-gray-600 mt-1">
|
<p className="text-sm text-gray-600 mt-1">
|
||||||
{isEdit
|
{isEdit
|
||||||
? "Mevcut tedarikçi bilgilerini güncelleyin"
|
? 'Mevcut tedarikçi bilgilerini güncelleyin'
|
||||||
: "Yeni tedarikçi bilgilerini girin"}
|
: 'Yeni tedarikçi bilgilerini girin'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -191,20 +184,16 @@ const SupplierForm: React.FC = () => {
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={formData.code}
|
value={formData.code}
|
||||||
onChange={(e) =>
|
onChange={(e) => handleInputChange('customerCode', e.target.value)}
|
||||||
handleInputChange("customerCode", e.target.value)
|
|
||||||
}
|
|
||||||
className={`block w-full px-2.5 py-1.5 border rounded-md shadow-sm text-sm focus:outline-none focus:ring-1 ${
|
className={`block w-full px-2.5 py-1.5 border rounded-md shadow-sm text-sm focus:outline-none focus:ring-1 ${
|
||||||
errors.customerCode
|
errors.customerCode
|
||||||
? "border-red-300 focus:border-red-500 focus:ring-red-500"
|
? 'border-red-300 focus:border-red-500 focus:ring-red-500'
|
||||||
: "border-gray-300 focus:border-blue-500 focus:ring-blue-500"
|
: 'border-gray-300 focus:border-blue-500 focus:ring-blue-500'
|
||||||
}`}
|
}`}
|
||||||
placeholder="Örn: SUP001"
|
placeholder="Örn: SUP001"
|
||||||
/>
|
/>
|
||||||
{errors.supplierCode && (
|
{errors.supplierCode && (
|
||||||
<p className="mt-1 text-sm text-red-600">
|
<p className="mt-1 text-sm text-red-600">{errors.supplierCode}</p>
|
||||||
{errors.supplierCode}
|
|
||||||
</p>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -214,13 +203,11 @@ const SupplierForm: React.FC = () => {
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
value={formData.supplierType}
|
value={formData.supplierType}
|
||||||
onChange={(e) =>
|
onChange={(e) => handleInputChange('supplierType', e.target.value)}
|
||||||
handleInputChange("supplierType", e.target.value)
|
|
||||||
}
|
|
||||||
className={`block w-full px-2.5 py-1.5 border rounded-md shadow-sm text-sm focus:outline-none focus:ring-1 ${
|
className={`block w-full px-2.5 py-1.5 border rounded-md shadow-sm text-sm focus:outline-none focus:ring-1 ${
|
||||||
errors.supplierType
|
errors.supplierType
|
||||||
? "border-red-300 focus:border-red-500 focus:ring-red-500"
|
? 'border-red-300 focus:border-red-500 focus:ring-red-500'
|
||||||
: "border-gray-300 focus:border-blue-500 focus:ring-blue-500"
|
: 'border-gray-300 focus:border-blue-500 focus:ring-blue-500'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<option value="">Tip seçin</option>
|
<option value="">Tip seçin</option>
|
||||||
|
|
@ -231,34 +218,26 @@ const SupplierForm: React.FC = () => {
|
||||||
<option value="OTHER">Diğer</option>
|
<option value="OTHER">Diğer</option>
|
||||||
</select>
|
</select>
|
||||||
{errors.supplierType && (
|
{errors.supplierType && (
|
||||||
<p className="mt-1 text-sm text-red-600">
|
<p className="mt-1 text-sm text-red-600">{errors.supplierType}</p>
|
||||||
{errors.supplierType}
|
|
||||||
</p>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
<label className="block text-sm font-medium text-gray-700 mb-1">Şirket Adı *</label>
|
||||||
Şirket Adı *
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={formData.name}
|
value={formData.name}
|
||||||
onChange={(e) =>
|
onChange={(e) => handleInputChange('companyName', e.target.value)}
|
||||||
handleInputChange("companyName", e.target.value)
|
|
||||||
}
|
|
||||||
className={`block w-full px-2.5 py-1.5 border rounded-md shadow-sm text-sm focus:outline-none focus:ring-1 ${
|
className={`block w-full px-2.5 py-1.5 border rounded-md shadow-sm text-sm focus:outline-none focus:ring-1 ${
|
||||||
errors.companyName
|
errors.companyName
|
||||||
? "border-red-300 focus:border-red-500 focus:ring-red-500"
|
? 'border-red-300 focus:border-red-500 focus:ring-red-500'
|
||||||
: "border-gray-300 focus:border-blue-500 focus:ring-blue-500"
|
: 'border-gray-300 focus:border-blue-500 focus:ring-blue-500'
|
||||||
}`}
|
}`}
|
||||||
placeholder="Şirket adı"
|
placeholder="Şirket adı"
|
||||||
/>
|
/>
|
||||||
{errors.companyName && (
|
{errors.companyName && (
|
||||||
<p className="mt-1 text-sm text-red-600">
|
<p className="mt-1 text-sm text-red-600">{errors.companyName}</p>
|
||||||
{errors.companyName}
|
|
||||||
</p>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -270,9 +249,7 @@ const SupplierForm: React.FC = () => {
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={formData.taxNumber}
|
value={formData.taxNumber}
|
||||||
onChange={(e) =>
|
onChange={(e) => handleInputChange('taxNumber', e.target.value)}
|
||||||
handleInputChange("taxNumber", e.target.value)
|
|
||||||
}
|
|
||||||
className="block w-full px-2.5 py-1.5 border border-gray-300 rounded-md shadow-sm text-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
|
className="block w-full px-2.5 py-1.5 border border-gray-300 rounded-md shadow-sm text-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
|
||||||
placeholder="1234567890"
|
placeholder="1234567890"
|
||||||
/>
|
/>
|
||||||
|
|
@ -285,9 +262,7 @@ const SupplierForm: React.FC = () => {
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={formData.certifications}
|
value={formData.certifications}
|
||||||
onChange={(e) =>
|
onChange={(e) => handleInputChange('certifications', e.target.value)}
|
||||||
handleInputChange("certifications", e.target.value)
|
|
||||||
}
|
|
||||||
className="block w-full px-2.5 py-1.5 border border-gray-300 rounded-md shadow-sm text-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
|
className="block w-full px-2.5 py-1.5 border border-gray-300 rounded-md shadow-sm text-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
|
||||||
placeholder="ISO 9001, ISO 14001, CE"
|
placeholder="ISO 9001, ISO 14001, CE"
|
||||||
/>
|
/>
|
||||||
|
|
@ -315,17 +290,12 @@ const SupplierForm: React.FC = () => {
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-medium text-gray-700 mb-1">
|
<label className="block text-xs font-medium text-gray-700 mb-1">Ad *</label>
|
||||||
Ad *
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={formData.primaryContact?.firstName}
|
value={formData.primaryContact?.firstName}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
handleInputChange(
|
handleInputChange('primaryContact.firstName', e.target.value)
|
||||||
"primaryContact.firstName",
|
|
||||||
e.target.value
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
required
|
required
|
||||||
|
|
@ -333,33 +303,22 @@ const SupplierForm: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-medium text-gray-700 mb-1">
|
<label className="block text-xs font-medium text-gray-700 mb-1">Soyad *</label>
|
||||||
Soyad *
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={formData.primaryContact?.lastName}
|
value={formData.primaryContact?.lastName}
|
||||||
onChange={(e) =>
|
onChange={(e) => handleInputChange('primaryContact.lastName', e.target.value)}
|
||||||
handleInputChange(
|
|
||||||
"primaryContact.lastName",
|
|
||||||
e.target.value
|
|
||||||
)
|
|
||||||
}
|
|
||||||
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-medium text-gray-700 mb-1">
|
<label className="block text-xs font-medium text-gray-700 mb-1">Ünvan</label>
|
||||||
Ünvan
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={formData.primaryContact?.title}
|
value={formData.primaryContact?.title}
|
||||||
onChange={(e) =>
|
onChange={(e) => handleInputChange('primaryContact.title', e.target.value)}
|
||||||
handleInputChange("primaryContact.title", e.target.value)
|
|
||||||
}
|
|
||||||
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
placeholder="Genel Müdür, Satış Direktörü vb."
|
placeholder="Genel Müdür, Satış Direktörü vb."
|
||||||
/>
|
/>
|
||||||
|
|
@ -373,10 +332,7 @@ const SupplierForm: React.FC = () => {
|
||||||
type="text"
|
type="text"
|
||||||
value={formData.primaryContact?.department}
|
value={formData.primaryContact?.department}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
handleInputChange(
|
handleInputChange('primaryContact.department', e.target.value)
|
||||||
"primaryContact.department",
|
|
||||||
e.target.value
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
placeholder="Satış, Pazarlama, İnsan Kaynakları vb."
|
placeholder="Satış, Pazarlama, İnsan Kaynakları vb."
|
||||||
|
|
@ -390,24 +346,18 @@ const SupplierForm: React.FC = () => {
|
||||||
<input
|
<input
|
||||||
type="email"
|
type="email"
|
||||||
value={formData.primaryContact?.email}
|
value={formData.primaryContact?.email}
|
||||||
onChange={(e) =>
|
onChange={(e) => handleInputChange('primaryContact.email', e.target.value)}
|
||||||
handleInputChange("primaryContact.email", e.target.value)
|
|
||||||
}
|
|
||||||
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-medium text-gray-700 mb-1">
|
<label className="block text-xs font-medium text-gray-700 mb-1">Telefon</label>
|
||||||
Telefon
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
type="tel"
|
type="tel"
|
||||||
value={formData.primaryContact?.phone}
|
value={formData.primaryContact?.phone}
|
||||||
onChange={(e) =>
|
onChange={(e) => handleInputChange('primaryContact.phone', e.target.value)}
|
||||||
handleInputChange("primaryContact.phone", e.target.value)
|
|
||||||
}
|
|
||||||
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
placeholder="+90 (212) 555 0123"
|
placeholder="+90 (212) 555 0123"
|
||||||
/>
|
/>
|
||||||
|
|
@ -420,9 +370,7 @@ const SupplierForm: React.FC = () => {
|
||||||
<input
|
<input
|
||||||
type="tel"
|
type="tel"
|
||||||
value={formData.primaryContact?.mobile}
|
value={formData.primaryContact?.mobile}
|
||||||
onChange={(e) =>
|
onChange={(e) => handleInputChange('primaryContact.mobile', e.target.value)}
|
||||||
handleInputChange("primaryContact.mobile", e.target.value)
|
|
||||||
}
|
|
||||||
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
placeholder="+90 (555) 123 4567"
|
placeholder="+90 (555) 123 4567"
|
||||||
/>
|
/>
|
||||||
|
|
@ -432,49 +380,41 @@ const SupplierForm: React.FC = () => {
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
<label className="block text-sm font-medium text-gray-700 mb-1">Email *</label>
|
||||||
Email *
|
|
||||||
</label>
|
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<FaEnvelope className="absolute left-3 top-2.5 h-4 w-4 text-gray-400" />
|
<FaEnvelope className="absolute left-3 top-2.5 h-4 w-4 text-gray-400" />
|
||||||
<input
|
<input
|
||||||
type="email"
|
type="email"
|
||||||
value={formData.email}
|
value={formData.email}
|
||||||
onChange={(e) => handleInputChange("email", e.target.value)}
|
onChange={(e) => handleInputChange('email', e.target.value)}
|
||||||
className={`block w-full pl-10 pr-3 py-1.5 border rounded-md shadow-sm text-sm focus:outline-none focus:ring-1 ${
|
className={`block w-full pl-10 pr-3 py-1.5 border rounded-md shadow-sm text-sm focus:outline-none focus:ring-1 ${
|
||||||
errors.email
|
errors.email
|
||||||
? "border-red-300 focus:border-red-500 focus:ring-red-500"
|
? 'border-red-300 focus:border-red-500 focus:ring-red-500'
|
||||||
: "border-gray-300 focus:border-blue-500 focus:ring-blue-500"
|
: 'border-gray-300 focus:border-blue-500 focus:ring-blue-500'
|
||||||
}`}
|
}`}
|
||||||
placeholder="email@example.com"
|
placeholder="email@example.com"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{errors.email && (
|
{errors.email && <p className="mt-1 text-sm text-red-600">{errors.email}</p>}
|
||||||
<p className="mt-1 text-sm text-red-600">{errors.email}</p>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
<label className="block text-sm font-medium text-gray-700 mb-1">Telefon *</label>
|
||||||
Telefon *
|
|
||||||
</label>
|
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<FaPhone className="absolute left-3 top-2.5 h-4 w-4 text-gray-400" />
|
<FaPhone className="absolute left-3 top-2.5 h-4 w-4 text-gray-400" />
|
||||||
<input
|
<input
|
||||||
type="tel"
|
type="tel"
|
||||||
value={formData.phone}
|
value={formData.phone}
|
||||||
onChange={(e) => handleInputChange("phone", e.target.value)}
|
onChange={(e) => handleInputChange('phone', e.target.value)}
|
||||||
className={`block w-full pl-10 pr-3 py-1.5 border rounded-md shadow-sm text-sm focus:outline-none focus:ring-1 ${
|
className={`block w-full pl-10 pr-3 py-1.5 border rounded-md shadow-sm text-sm focus:outline-none focus:ring-1 ${
|
||||||
errors.phone
|
errors.phone
|
||||||
? "border-red-300 focus:border-red-500 focus:ring-red-500"
|
? 'border-red-300 focus:border-red-500 focus:ring-red-500'
|
||||||
: "border-gray-300 focus:border-blue-500 focus:ring-blue-500"
|
: 'border-gray-300 focus:border-blue-500 focus:ring-blue-500'
|
||||||
}`}
|
}`}
|
||||||
placeholder="+90 212 555 0123"
|
placeholder="+90 212 555 0123"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{errors.phone && (
|
{errors.phone && <p className="mt-1 text-sm text-red-600">{errors.phone}</p>}
|
||||||
<p className="mt-1 text-sm text-red-600">{errors.phone}</p>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -491,100 +431,74 @@ const SupplierForm: React.FC = () => {
|
||||||
|
|
||||||
<div className="px-4 py-3 space-y-3">
|
<div className="px-4 py-3 space-y-3">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
<label className="block text-sm font-medium text-gray-700 mb-1">Adres *</label>
|
||||||
Adres *
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={formData.address?.street}
|
value={formData.address?.street}
|
||||||
onChange={(e) =>
|
onChange={(e) => handleInputChange('address.street', e.target.value)}
|
||||||
handleInputChange("address.street", e.target.value)
|
|
||||||
}
|
|
||||||
className={`block w-full px-2.5 py-1.5 border rounded-md shadow-sm text-sm focus:outline-none focus:ring-1 ${
|
className={`block w-full px-2.5 py-1.5 border rounded-md shadow-sm text-sm focus:outline-none focus:ring-1 ${
|
||||||
errors.street
|
errors.street
|
||||||
? "border-red-300 focus:border-red-500 focus:ring-red-500"
|
? 'border-red-300 focus:border-red-500 focus:ring-red-500'
|
||||||
: "border-gray-300 focus:border-blue-500 focus:ring-blue-500"
|
: 'border-gray-300 focus:border-blue-500 focus:ring-blue-500'
|
||||||
}`}
|
}`}
|
||||||
placeholder="Sokak, cadde, mahalle"
|
placeholder="Sokak, cadde, mahalle"
|
||||||
/>
|
/>
|
||||||
{errors.street && (
|
{errors.street && <p className="mt-1 text-sm text-red-600">{errors.street}</p>}
|
||||||
<p className="mt-1 text-sm text-red-600">{errors.street}</p>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-3">
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-3">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
<label className="block text-sm font-medium text-gray-700 mb-1">Şehir *</label>
|
||||||
Şehir *
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={formData.address?.city}
|
value={formData.address?.city}
|
||||||
onChange={(e) =>
|
onChange={(e) => handleInputChange('address.city', e.target.value)}
|
||||||
handleInputChange("address.city", e.target.value)
|
|
||||||
}
|
|
||||||
className={`block w-full px-2.5 py-1.5 border rounded-md shadow-sm text-sm focus:outline-none focus:ring-1 ${
|
className={`block w-full px-2.5 py-1.5 border rounded-md shadow-sm text-sm focus:outline-none focus:ring-1 ${
|
||||||
errors.city
|
errors.city
|
||||||
? "border-red-300 focus:border-red-500 focus:ring-red-500"
|
? 'border-red-300 focus:border-red-500 focus:ring-red-500'
|
||||||
: "border-gray-300 focus:border-blue-500 focus:ring-blue-500"
|
: 'border-gray-300 focus:border-blue-500 focus:ring-blue-500'
|
||||||
}`}
|
}`}
|
||||||
placeholder="Şehir"
|
placeholder="Şehir"
|
||||||
/>
|
/>
|
||||||
{errors.city && (
|
{errors.city && <p className="mt-1 text-sm text-red-600">{errors.city}</p>}
|
||||||
<p className="mt-1 text-sm text-red-600">{errors.city}</p>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
<label className="block text-sm font-medium text-gray-700 mb-1">İl/Bölge</label>
|
||||||
İl/Bölge
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={formData.address?.state}
|
value={formData.address?.state}
|
||||||
onChange={(e) =>
|
onChange={(e) => handleInputChange('address.state', e.target.value)}
|
||||||
handleInputChange("address.state", e.target.value)
|
|
||||||
}
|
|
||||||
className="block w-full px-2.5 py-1.5 border border-gray-300 rounded-md shadow-sm text-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
|
className="block w-full px-2.5 py-1.5 border border-gray-300 rounded-md shadow-sm text-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
|
||||||
placeholder="İl/Bölge"
|
placeholder="İl/Bölge"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
<label className="block text-sm font-medium text-gray-700 mb-1">Posta Kodu</label>
|
||||||
Posta Kodu
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={formData.address?.postalCode}
|
value={formData.address?.postalCode}
|
||||||
onChange={(e) =>
|
onChange={(e) => handleInputChange('address.postalCode', e.target.value)}
|
||||||
handleInputChange("address.postalCode", e.target.value)
|
|
||||||
}
|
|
||||||
className="block w-full px-2.5 py-1.5 border border-gray-300 rounded-md shadow-sm text-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
|
className="block w-full px-2.5 py-1.5 border border-gray-300 rounded-md shadow-sm text-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
|
||||||
placeholder="34000"
|
placeholder="34000"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
<label className="block text-sm font-medium text-gray-700 mb-1">Ülke *</label>
|
||||||
Ülke *
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={formData.address?.country}
|
value={formData.address?.country}
|
||||||
onChange={(e) =>
|
onChange={(e) => handleInputChange('address.country', e.target.value)}
|
||||||
handleInputChange("address.country", e.target.value)
|
|
||||||
}
|
|
||||||
className={`block w-full px-2.5 py-1.5 border rounded-md shadow-sm text-sm focus:outline-none focus:ring-1 ${
|
className={`block w-full px-2.5 py-1.5 border rounded-md shadow-sm text-sm focus:outline-none focus:ring-1 ${
|
||||||
errors.country
|
errors.country
|
||||||
? "border-red-300 focus:border-red-500 focus:ring-red-500"
|
? 'border-red-300 focus:border-red-500 focus:ring-red-500'
|
||||||
: "border-gray-300 focus:border-blue-500 focus:ring-blue-500"
|
: 'border-gray-300 focus:border-blue-500 focus:ring-blue-500'
|
||||||
}`}
|
}`}
|
||||||
placeholder="Ülke"
|
placeholder="Ülke"
|
||||||
/>
|
/>
|
||||||
{errors.country && (
|
{errors.country && <p className="mt-1 text-sm text-red-600">{errors.country}</p>}
|
||||||
<p className="mt-1 text-sm text-red-600">{errors.country}</p>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -611,10 +525,7 @@ const SupplierForm: React.FC = () => {
|
||||||
min="0"
|
min="0"
|
||||||
value={formData.creditLimit}
|
value={formData.creditLimit}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
handleInputChange(
|
handleInputChange('creditLimit', parseFloat(e.target.value) || 0)
|
||||||
"creditLimit",
|
|
||||||
parseFloat(e.target.value) || 0
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
className="block w-full px-2.5 py-1.5 border border-gray-300 rounded-md shadow-sm text-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
|
className="block w-full px-2.5 py-1.5 border border-gray-300 rounded-md shadow-sm text-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
|
||||||
placeholder="0.00"
|
placeholder="0.00"
|
||||||
|
|
@ -627,13 +538,11 @@ const SupplierForm: React.FC = () => {
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
value={formData.paymentTerms}
|
value={formData.paymentTerms}
|
||||||
onChange={(e) =>
|
onChange={(e) => handleInputChange('paymentTerms', e.target.value)}
|
||||||
handleInputChange("paymentTerms", e.target.value)
|
|
||||||
}
|
|
||||||
className={`block w-full px-2.5 py-1.5 border rounded-md shadow-sm text-sm focus:outline-none focus:ring-1 ${
|
className={`block w-full px-2.5 py-1.5 border rounded-md shadow-sm text-sm focus:outline-none focus:ring-1 ${
|
||||||
errors.paymentTerms
|
errors.paymentTerms
|
||||||
? "border-red-300 focus:border-red-500 focus:ring-red-500"
|
? 'border-red-300 focus:border-red-500 focus:ring-red-500'
|
||||||
: "border-gray-300 focus:border-blue-500 focus:ring-blue-500"
|
: 'border-gray-300 focus:border-blue-500 focus:ring-blue-500'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<option value="">Ödeme koşulu seçin</option>
|
<option value="">Ödeme koşulu seçin</option>
|
||||||
|
|
@ -644,9 +553,7 @@ const SupplierForm: React.FC = () => {
|
||||||
<option value="NET_90">Net 90 Gün</option>
|
<option value="NET_90">Net 90 Gün</option>
|
||||||
</select>
|
</select>
|
||||||
{errors.paymentTerms && (
|
{errors.paymentTerms && (
|
||||||
<p className="mt-1 text-sm text-red-600">
|
<p className="mt-1 text-sm text-red-600">{errors.paymentTerms}</p>
|
||||||
{errors.paymentTerms}
|
|
||||||
</p>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -656,9 +563,7 @@ const SupplierForm: React.FC = () => {
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
value={formData.currency}
|
value={formData.currency}
|
||||||
onChange={(e) =>
|
onChange={(e) => handleInputChange('currency', e.target.value)}
|
||||||
handleInputChange("currency", e.target.value)
|
|
||||||
}
|
|
||||||
className="block w-full px-2.5 py-1.5 border border-gray-300 rounded-md shadow-sm text-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
|
className="block w-full px-2.5 py-1.5 border border-gray-300 rounded-md shadow-sm text-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
|
||||||
>
|
>
|
||||||
<option value="TRY">Türk Lirası (TRY)</option>
|
<option value="TRY">Türk Lirası (TRY)</option>
|
||||||
|
|
@ -670,9 +575,7 @@ const SupplierForm: React.FC = () => {
|
||||||
|
|
||||||
{formData.bankAccounts?.map((account, index) => (
|
{formData.bankAccounts?.map((account, index) => (
|
||||||
<div key={account.id || index} className="space-y-3 mb-4">
|
<div key={account.id || index} className="space-y-3 mb-4">
|
||||||
<h4 className="text-md font-semibold text-gray-800">
|
<h4 className="text-md font-semibold text-gray-800">Banka Hesabı {index + 1}</h4>
|
||||||
Banka Hesabı {index + 1}
|
|
||||||
</h4>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -683,10 +586,7 @@ const SupplierForm: React.FC = () => {
|
||||||
type="text"
|
type="text"
|
||||||
value={account.bankName}
|
value={account.bankName}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
handleInputChange(
|
handleInputChange(`bankAccounts.${index}.bankName`, e.target.value)
|
||||||
`bankAccounts.${index}.bankName`,
|
|
||||||
e.target.value
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
className="block w-full px-2.5 py-1.5 border border-gray-300 rounded-md shadow-sm text-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
|
className="block w-full px-2.5 py-1.5 border border-gray-300 rounded-md shadow-sm text-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
|
||||||
placeholder="Banka adı"
|
placeholder="Banka adı"
|
||||||
|
|
@ -701,10 +601,7 @@ const SupplierForm: React.FC = () => {
|
||||||
type="text"
|
type="text"
|
||||||
value={account.accountNumber}
|
value={account.accountNumber}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
handleInputChange(
|
handleInputChange(`bankAccounts.${index}.accountNumber`, e.target.value)
|
||||||
`bankAccounts.${index}.accountNumber`,
|
|
||||||
e.target.value
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
className="block w-full px-2.5 py-1.5 border border-gray-300 rounded-md shadow-sm text-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
|
className="block w-full px-2.5 py-1.5 border border-gray-300 rounded-md shadow-sm text-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
|
||||||
placeholder="Hesap numarası"
|
placeholder="Hesap numarası"
|
||||||
|
|
@ -714,17 +611,12 @@ const SupplierForm: React.FC = () => {
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
<label className="block text-sm font-medium text-gray-700 mb-1">IBAN</label>
|
||||||
IBAN
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={account.iban}
|
value={account.iban}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
handleInputChange(
|
handleInputChange(`bankAccounts.${index}.iban`, e.target.value)
|
||||||
`bankAccounts.${index}.iban`,
|
|
||||||
e.target.value
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
className="block w-full px-2.5 py-1.5 border border-gray-300 rounded-md shadow-sm text-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
|
className="block w-full px-2.5 py-1.5 border border-gray-300 rounded-md shadow-sm text-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
|
||||||
placeholder="TR00 0000 0000 0000 0000 0000 00"
|
placeholder="TR00 0000 0000 0000 0000 0000 00"
|
||||||
|
|
@ -739,10 +631,7 @@ const SupplierForm: React.FC = () => {
|
||||||
type="text"
|
type="text"
|
||||||
value={account.swiftCode}
|
value={account.swiftCode}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
handleInputChange(
|
handleInputChange(`bankAccounts.${index}.swiftCode`, e.target.value)
|
||||||
`bankAccounts.${index}.swiftCode`,
|
|
||||||
e.target.value
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
className="block w-full px-2.5 py-1.5 border border-gray-300 rounded-md shadow-sm text-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
|
className="block w-full px-2.5 py-1.5 border border-gray-300 rounded-md shadow-sm text-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
|
||||||
placeholder="SWIFT kodu"
|
placeholder="SWIFT kodu"
|
||||||
|
|
@ -776,10 +665,7 @@ const SupplierForm: React.FC = () => {
|
||||||
max="5"
|
max="5"
|
||||||
value={formData.performanceMetrics?.qualityRating}
|
value={formData.performanceMetrics?.qualityRating}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
handleInputChange(
|
handleInputChange('qualityRating', parseFloat(e.target.value) || 1)
|
||||||
"qualityRating",
|
|
||||||
parseFloat(e.target.value) || 1
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
className="block w-full px-2.5 py-1.5 border border-gray-300 rounded-md shadow-sm text-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
|
className="block w-full px-2.5 py-1.5 border border-gray-300 rounded-md shadow-sm text-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
|
||||||
/>
|
/>
|
||||||
|
|
@ -796,10 +682,7 @@ const SupplierForm: React.FC = () => {
|
||||||
max="5"
|
max="5"
|
||||||
value={formData.performanceMetrics?.deliveryPerformance}
|
value={formData.performanceMetrics?.deliveryPerformance}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
handleInputChange(
|
handleInputChange('deliveryRating', parseFloat(e.target.value) || 1)
|
||||||
"deliveryRating",
|
|
||||||
parseFloat(e.target.value) || 1
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
className="block w-full px-2.5 py-1.5 border border-gray-300 rounded-md shadow-sm text-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
|
className="block w-full px-2.5 py-1.5 border border-gray-300 rounded-md shadow-sm text-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
|
||||||
/>
|
/>
|
||||||
|
|
@ -816,10 +699,7 @@ const SupplierForm: React.FC = () => {
|
||||||
max="5"
|
max="5"
|
||||||
value={formData.performanceMetrics?.priceCompetitiveness}
|
value={formData.performanceMetrics?.priceCompetitiveness}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
handleInputChange(
|
handleInputChange('priceRating', parseFloat(e.target.value) || 1)
|
||||||
"priceRating",
|
|
||||||
parseFloat(e.target.value) || 1
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
className="block w-full px-2.5 py-1.5 border border-gray-300 rounded-md shadow-sm text-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
|
className="block w-full px-2.5 py-1.5 border border-gray-300 rounded-md shadow-sm text-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
|
||||||
/>
|
/>
|
||||||
|
|
@ -836,10 +716,7 @@ const SupplierForm: React.FC = () => {
|
||||||
max="5"
|
max="5"
|
||||||
value={formData.performanceMetrics?.overallScore}
|
value={formData.performanceMetrics?.overallScore}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
handleInputChange(
|
handleInputChange('overallRating', parseFloat(e.target.value) || 1)
|
||||||
"overallRating",
|
|
||||||
parseFloat(e.target.value) || 1
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
className="block w-full px-2.5 py-1.5 border border-gray-300 rounded-md shadow-sm text-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
|
className="block w-full px-2.5 py-1.5 border border-gray-300 rounded-md shadow-sm text-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
|
||||||
/>
|
/>
|
||||||
|
|
@ -851,15 +728,10 @@ const SupplierForm: React.FC = () => {
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
id="isActive"
|
id="isActive"
|
||||||
checked={formData.isActive}
|
checked={formData.isActive}
|
||||||
onChange={(e) =>
|
onChange={(e) => handleInputChange('isActive', e.target.checked)}
|
||||||
handleInputChange("isActive", e.target.checked)
|
|
||||||
}
|
|
||||||
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
|
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
|
||||||
/>
|
/>
|
||||||
<label
|
<label htmlFor="isActive" className="ml-2 block text-sm text-gray-900">
|
||||||
htmlFor="isActive"
|
|
||||||
className="ml-2 block text-sm text-gray-900"
|
|
||||||
>
|
|
||||||
Aktif
|
Aktif
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -889,14 +761,15 @@ const SupplierForm: React.FC = () => {
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<FaSave className="w-4 h-4 mr-2" />
|
<FaSave className="w-4 h-4 mr-2" />
|
||||||
{isEdit ? "Güncelle" : "Kaydet"}
|
{isEdit ? 'Güncelle' : 'Kaydet'}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
);
|
</Container>
|
||||||
};
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export default SupplierForm;
|
export default SupplierForm
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { useState } from "react";
|
import React, { useState } from 'react'
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from 'react-router-dom'
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from '@tanstack/react-query'
|
||||||
import {
|
import {
|
||||||
FaTruck,
|
FaTruck,
|
||||||
FaPlus,
|
FaPlus,
|
||||||
|
|
@ -17,45 +17,39 @@ import {
|
||||||
FaArrowUp,
|
FaArrowUp,
|
||||||
FaUsers,
|
FaUsers,
|
||||||
FaBuilding,
|
FaBuilding,
|
||||||
} from "react-icons/fa";
|
} from 'react-icons/fa'
|
||||||
import classNames from "classnames";
|
import classNames from 'classnames'
|
||||||
import { SupplierTypeEnum } from "../../../types/mm";
|
import { SupplierTypeEnum } from '../../../types/mm'
|
||||||
import { mockBusinessParties } from "../../../mocks/mockBusinessParties";
|
import { mockBusinessParties } from '../../../mocks/mockBusinessParties'
|
||||||
import { PartyType } from "../../../types/common";
|
import { PartyType } from '../../../types/common'
|
||||||
import {
|
import { getSupplierTypeColor, getSupplierTypeText, getRatingColor } from '../../../utils/erp'
|
||||||
getSupplierTypeColor,
|
import { Container } from '@/components/shared'
|
||||||
getSupplierTypeText,
|
|
||||||
getRatingColor,
|
|
||||||
} from "../../../utils/erp";
|
|
||||||
|
|
||||||
const SupplierList: React.FC = () => {
|
const SupplierList: React.FC = () => {
|
||||||
const [searchTerm, setSearchTerm] = useState("");
|
const [searchTerm, setSearchTerm] = useState('')
|
||||||
const [filterType, setFilterType] = useState("all");
|
const [filterType, setFilterType] = useState('all')
|
||||||
const [showFilters, setShowFilters] = useState(false);
|
const [showFilters, setShowFilters] = useState(false)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: suppliers,
|
data: suppliers,
|
||||||
isLoading,
|
isLoading,
|
||||||
error,
|
error,
|
||||||
} = useQuery({
|
} = useQuery({
|
||||||
queryKey: ["suppliers", searchTerm, filterType],
|
queryKey: ['suppliers', searchTerm, filterType],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
await new Promise((resolve) => setTimeout(resolve, 500))
|
||||||
|
|
||||||
const filtredSuppliers = mockBusinessParties.filter(
|
const filtredSuppliers = mockBusinessParties.filter((a) => a.partyType === PartyType.Supplier)
|
||||||
(a) => a.partyType === PartyType.Supplier
|
|
||||||
);
|
|
||||||
|
|
||||||
return filtredSuppliers.filter((supplier) => {
|
return filtredSuppliers.filter((supplier) => {
|
||||||
const matchesSearch =
|
const matchesSearch =
|
||||||
supplier.code.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
supplier.code.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||||
supplier.name.toLowerCase().includes(searchTerm.toLowerCase());
|
supplier.name.toLowerCase().includes(searchTerm.toLowerCase())
|
||||||
const matchesType =
|
const matchesType = filterType === 'all' || supplier.supplierType === filterType
|
||||||
filterType === "all" || supplier.supplierType === filterType;
|
return matchesSearch && matchesType
|
||||||
return matchesSearch && matchesType;
|
})
|
||||||
});
|
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
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>
|
<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>
|
<span className="ml-3 text-gray-600">Tedarikçiler yükleniyor...</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
|
|
@ -71,16 +65,15 @@ const SupplierList: React.FC = () => {
|
||||||
<div className="bg-red-50 border border-red-200 rounded-lg p-4">
|
<div className="bg-red-50 border border-red-200 rounded-lg p-4">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<FaExclamationTriangle className="h-5 w-5 text-red-600 mr-2" />
|
<FaExclamationTriangle className="h-5 w-5 text-red-600 mr-2" />
|
||||||
<span className="text-red-800">
|
<span className="text-red-800">Tedarikçiler yüklenirken hata oluştu.</span>
|
||||||
Tedarikçiler yüklenirken hata oluştu.
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-3 pt-2">
|
<Container>
|
||||||
|
<div className="space-y-2">
|
||||||
{/* Header Actions */}
|
{/* Header Actions */}
|
||||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
<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="flex items-center space-x-4">
|
||||||
|
|
@ -101,10 +94,10 @@ const SupplierList: React.FC = () => {
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowFilters(!showFilters)}
|
onClick={() => setShowFilters(!showFilters)}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"flex items-center px-3 py-1.5 border rounded-lg transition-colors",
|
'flex items-center px-3 py-1.5 border rounded-lg transition-colors',
|
||||||
showFilters
|
showFilters
|
||||||
? "border-blue-500 bg-blue-50 text-blue-700"
|
? 'border-blue-500 bg-blue-50 text-blue-700'
|
||||||
: "border-gray-300 bg-white text-gray-700 hover:bg-gray-50"
|
: 'border-gray-300 bg-white text-gray-700 hover:bg-gray-50',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<FaFilter size={16} className="mr-2" />
|
<FaFilter size={16} className="mr-2" />
|
||||||
|
|
@ -114,7 +107,7 @@ const SupplierList: React.FC = () => {
|
||||||
|
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
<button
|
<button
|
||||||
onClick={() => alert("Dışa aktarma özelliği yakında eklenecek")}
|
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"
|
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" />
|
<FaDownload size={16} className="mr-2" />
|
||||||
|
|
@ -154,8 +147,8 @@ const SupplierList: React.FC = () => {
|
||||||
<div className="flex items-end">
|
<div className="flex items-end">
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setFilterType("all");
|
setFilterType('all')
|
||||||
setSearchTerm("");
|
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"
|
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"
|
||||||
>
|
>
|
||||||
|
|
@ -171,12 +164,8 @@ const SupplierList: React.FC = () => {
|
||||||
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-3">
|
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-3">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-medium text-gray-600">
|
<p className="text-sm font-medium text-gray-600">Toplam Tedarikçi</p>
|
||||||
Toplam Tedarikçi
|
<p className="text-xl font-bold text-gray-900">{suppliers?.length || 0}</p>
|
||||||
</p>
|
|
||||||
<p className="text-xl font-bold text-gray-900">
|
|
||||||
{suppliers?.length || 0}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<FaBuilding className="h-8 w-8 text-blue-600" />
|
<FaBuilding className="h-8 w-8 text-blue-600" />
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -185,9 +174,7 @@ const SupplierList: React.FC = () => {
|
||||||
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-3">
|
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-3">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-medium text-gray-600">
|
<p className="text-sm font-medium text-gray-600">Aktif Tedarikçi</p>
|
||||||
Aktif Tedarikçi
|
|
||||||
</p>
|
|
||||||
<p className="text-xl font-bold text-green-600">
|
<p className="text-xl font-bold text-green-600">
|
||||||
{suppliers?.filter((s) => s.isActive).length || 0}
|
{suppliers?.filter((s) => s.isActive).length || 0}
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -204,12 +191,11 @@ const SupplierList: React.FC = () => {
|
||||||
{suppliers?.length
|
{suppliers?.length
|
||||||
? (
|
? (
|
||||||
suppliers.reduce(
|
suppliers.reduce(
|
||||||
(acc, s) =>
|
(acc, s) => acc + (s.performanceMetrics?.overallScore ?? 0),
|
||||||
acc + (s.performanceMetrics?.overallScore ?? 0),
|
0,
|
||||||
0
|
|
||||||
) / suppliers.length
|
) / suppliers.length
|
||||||
).toFixed(1)
|
).toFixed(1)
|
||||||
: "0.0"}
|
: '0.0'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<FaStar className="h-8 w-8 text-yellow-600" />
|
<FaStar className="h-8 w-8 text-yellow-600" />
|
||||||
|
|
@ -219,13 +205,10 @@ const SupplierList: React.FC = () => {
|
||||||
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-3">
|
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-3">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-medium text-gray-600">
|
<p className="text-sm font-medium text-gray-600">Yüksek Performans</p>
|
||||||
Yüksek Performans
|
|
||||||
</p>
|
|
||||||
<p className="text-xl font-bold text-purple-600">
|
<p className="text-xl font-bold text-purple-600">
|
||||||
{suppliers?.filter(
|
{suppliers?.filter((s) => (s.performanceMetrics?.overallScore ?? 0) >= 4.5)
|
||||||
(s) => (s.performanceMetrics?.overallScore ?? 0) >= 4.5
|
.length || 0}
|
||||||
).length || 0}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<FaArrowUp className="h-8 w-8 text-purple-600" />
|
<FaArrowUp className="h-8 w-8 text-purple-600" />
|
||||||
|
|
@ -269,10 +252,7 @@ const SupplierList: React.FC = () => {
|
||||||
|
|
||||||
<tbody className="bg-white divide-y divide-gray-200">
|
<tbody className="bg-white divide-y divide-gray-200">
|
||||||
{suppliers?.map((supplier) => (
|
{suppliers?.map((supplier) => (
|
||||||
<tr
|
<tr key={supplier.id} className="hover:bg-gray-50 transition-colors">
|
||||||
key={supplier.id}
|
|
||||||
className="hover:bg-gray-50 transition-colors"
|
|
||||||
>
|
|
||||||
<td className="px-4 py-3">
|
<td className="px-4 py-3">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<div className="flex-shrink-0 h-10 w-10">
|
<div className="flex-shrink-0 h-10 w-10">
|
||||||
|
|
@ -281,12 +261,8 @@ const SupplierList: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-4">
|
<div className="ml-4">
|
||||||
<div className="text-sm font-medium text-gray-900">
|
<div className="text-sm font-medium text-gray-900">{supplier.code}</div>
|
||||||
{supplier.code}
|
<div className="text-sm text-gray-500">{supplier.name}</div>
|
||||||
</div>
|
|
||||||
<div className="text-sm text-gray-500">
|
|
||||||
{supplier.name}
|
|
||||||
</div>
|
|
||||||
{supplier.taxNumber && (
|
{supplier.taxNumber && (
|
||||||
<div className="text-xs text-gray-400 mt-1">
|
<div className="text-xs text-gray-400 mt-1">
|
||||||
VKN: {supplier.taxNumber}
|
VKN: {supplier.taxNumber}
|
||||||
|
|
@ -322,8 +298,8 @@ const SupplierList: React.FC = () => {
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<span
|
<span
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium",
|
'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium',
|
||||||
getSupplierTypeColor(supplier.supplierType!)
|
getSupplierTypeColor(supplier.supplierType!),
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{getSupplierTypeText(supplier.supplierType!)}
|
{getSupplierTypeText(supplier.supplierType!)}
|
||||||
|
|
@ -341,43 +317,28 @@ const SupplierList: React.FC = () => {
|
||||||
<FaStar
|
<FaStar
|
||||||
size={14}
|
size={14}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"mr-1",
|
'mr-1',
|
||||||
getRatingColor(
|
getRatingColor(supplier.performanceMetrics?.overallScore ?? 0),
|
||||||
supplier.performanceMetrics?.overallScore ?? 0
|
|
||||||
)
|
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"text-sm font-medium",
|
'text-sm font-medium',
|
||||||
getRatingColor(
|
getRatingColor(supplier.performanceMetrics?.overallScore ?? 0),
|
||||||
supplier.performanceMetrics?.overallScore ?? 0
|
|
||||||
)
|
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{(
|
{(supplier.performanceMetrics?.overallScore ?? 0).toFixed(1)}
|
||||||
supplier.performanceMetrics?.overallScore ?? 0
|
|
||||||
).toFixed(1)}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-gray-500">
|
<div className="text-xs text-gray-500">
|
||||||
K:
|
K:
|
||||||
{(
|
{(supplier.performanceMetrics?.qualityRating ?? 0).toFixed(1)} T:
|
||||||
supplier.performanceMetrics?.qualityRating ?? 0
|
{(supplier.performanceMetrics?.deliveryPerformance ?? 0).toFixed(1)} F:
|
||||||
).toFixed(1)}{" "}
|
{(supplier.performanceMetrics?.priceCompetitiveness ?? 0).toFixed(1)}
|
||||||
T:
|
|
||||||
{(
|
|
||||||
supplier.performanceMetrics?.deliveryPerformance ?? 0
|
|
||||||
).toFixed(1)}{" "}
|
|
||||||
F:
|
|
||||||
{(
|
|
||||||
supplier.performanceMetrics?.priceCompetitiveness ?? 0
|
|
||||||
).toFixed(1)}
|
|
||||||
</div>
|
</div>
|
||||||
{supplier.certifications &&
|
{supplier.certifications && supplier.certifications.length > 0 && (
|
||||||
supplier.certifications.length > 0 && (
|
|
||||||
<div className="text-xs text-blue-600">
|
<div className="text-xs text-blue-600">
|
||||||
{supplier.certifications.join(", ")}
|
{supplier.certifications.join(', ')}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -387,21 +348,19 @@ const SupplierList: React.FC = () => {
|
||||||
<div className="text-sm font-medium text-gray-900">
|
<div className="text-sm font-medium text-gray-900">
|
||||||
₺{supplier.creditLimit.toLocaleString()}
|
₺{supplier.creditLimit.toLocaleString()}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-gray-500">
|
<div className="text-sm text-gray-500">{supplier.paymentTerms}</div>
|
||||||
{supplier.paymentTerms}
|
|
||||||
</div>
|
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td className="px-4 py-3">
|
<td className="px-4 py-3">
|
||||||
<span
|
<span
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium",
|
'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium',
|
||||||
supplier.isActive
|
supplier.isActive
|
||||||
? "bg-green-100 text-green-800"
|
? 'bg-green-100 text-green-800'
|
||||||
: "bg-red-100 text-red-800"
|
: 'bg-red-100 text-red-800',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{supplier.isActive ? "Aktif" : "Pasif"}
|
{supplier.isActive ? 'Aktif' : 'Pasif'}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
|
|
@ -433,12 +392,8 @@ const SupplierList: React.FC = () => {
|
||||||
{(!suppliers || suppliers.length === 0) && (
|
{(!suppliers || suppliers.length === 0) && (
|
||||||
<div className="text-center py-10">
|
<div className="text-center py-10">
|
||||||
<FaTruck className="mx-auto h-12 w-12 text-gray-400" />
|
<FaTruck className="mx-auto h-12 w-12 text-gray-400" />
|
||||||
<h3 className="mt-2 text-sm font-medium text-gray-900">
|
<h3 className="mt-2 text-sm font-medium text-gray-900">Tedarikçi bulunamadı</h3>
|
||||||
Tedarikçi bulunamadı
|
<p className="mt-1 text-sm text-gray-500">Yeni tedarikçi ekleyerek başlayın.</p>
|
||||||
</h3>
|
|
||||||
<p className="mt-1 text-sm text-gray-500">
|
|
||||||
Yeni tedarikçi ekleyerek başlayın.
|
|
||||||
</p>
|
|
||||||
<div className="mt-6">
|
<div className="mt-6">
|
||||||
<Link
|
<Link
|
||||||
to="/admin/supplychain/suppliers/new"
|
to="/admin/supplychain/suppliers/new"
|
||||||
|
|
@ -452,7 +407,8 @@ const SupplierList: React.FC = () => {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
</Container>
|
||||||
};
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export default SupplierList;
|
export default SupplierList
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue