Supply Management

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

View file

@ -3270,16 +3270,6 @@
"RequiredPermissionName": null, "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",

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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>
) )
} }

View file

@ -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

View file

@ -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">ıklama</label>
ı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

View file

@ -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

View file

@ -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

View file

@ -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">ıklama</label>
ı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

View file

@ -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>
) )
} }

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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