Warehouse Management
This commit is contained in:
parent
8b88970fe2
commit
bb2e39c2c3
13 changed files with 4751 additions and 5982 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState } from "react";
|
||||
import React, { useState } from 'react'
|
||||
import {
|
||||
FaBox,
|
||||
FaMapMarkerAlt,
|
||||
|
|
@ -8,71 +8,61 @@ import {
|
|||
FaEye,
|
||||
FaEdit,
|
||||
FaPlus,
|
||||
} from "react-icons/fa";
|
||||
} from 'react-icons/fa'
|
||||
// using modals for create actions; no navigation needed here
|
||||
import {
|
||||
MmLotNumber,
|
||||
MmSerialNumber,
|
||||
QualityStatusEnum,
|
||||
SerialStatusEnum,
|
||||
} from "../../../types/mm";
|
||||
import LotForm from "./LotForm";
|
||||
import SerialForm from "./SerialForm";
|
||||
import { mockLotNumbers } from "../../../mocks/mockLotNumbers";
|
||||
import { mockSerialNumbers } from "../../../mocks/mockSerialNumbers";
|
||||
import { mockMaterials } from "../../../mocks/mockMaterials";
|
||||
import { mockUnits } from "../../../mocks/mockUnits";
|
||||
import { mockBusinessParties } from "../../../mocks/mockBusinessParties";
|
||||
import Widget from "../../../components/common/Widget";
|
||||
import { PartyType } from "../../../types/common";
|
||||
import { getQualityStatusInfo, getSerialStatusInfo } from "../../../utils/erp";
|
||||
import { MmLotNumber, MmSerialNumber, QualityStatusEnum, SerialStatusEnum } from '../../../types/mm'
|
||||
import LotForm from './LotForm'
|
||||
import SerialForm from './SerialForm'
|
||||
import { mockLotNumbers } from '../../../mocks/mockLotNumbers'
|
||||
import { mockSerialNumbers } from '../../../mocks/mockSerialNumbers'
|
||||
import { mockMaterials } from '../../../mocks/mockMaterials'
|
||||
import { mockUnits } from '../../../mocks/mockUnits'
|
||||
import { mockBusinessParties } from '../../../mocks/mockBusinessParties'
|
||||
import Widget from '../../../components/common/Widget'
|
||||
import { PartyType } from '../../../types/common'
|
||||
import { getQualityStatusInfo, getSerialStatusInfo } from '../../../utils/erp'
|
||||
import { Container } from '@/components/shared'
|
||||
|
||||
const InventoryTracking: React.FC = () => {
|
||||
const [activeTab, setActiveTab] = useState<"lots" | "serials">("lots");
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [showCreateMenu, setShowCreateMenu] = useState(false);
|
||||
const [activeTab, setActiveTab] = useState<'lots' | 'serials'>('lots')
|
||||
const [searchTerm, setSearchTerm] = useState('')
|
||||
const [showCreateMenu, setShowCreateMenu] = useState(false)
|
||||
|
||||
// Mock data için
|
||||
const [lotNumbers, setLotNumbers] = useState<MmLotNumber[]>(mockLotNumbers);
|
||||
const [lotNumbers, setLotNumbers] = useState<MmLotNumber[]>(mockLotNumbers)
|
||||
|
||||
const [serialNumbers, setSerialNumbers] =
|
||||
useState<MmSerialNumber[]>(mockSerialNumbers);
|
||||
const [serialNumbers, setSerialNumbers] = useState<MmSerialNumber[]>(mockSerialNumbers)
|
||||
|
||||
// Modal control state
|
||||
const [openLotModal, setOpenLotModal] = useState(false);
|
||||
const [openSerialModal, setOpenSerialModal] = useState(false);
|
||||
const [currentLot, setCurrentLot] = useState<MmLotNumber | null>(null);
|
||||
const [currentSerial, setCurrentSerial] = useState<MmSerialNumber | null>(
|
||||
null
|
||||
);
|
||||
const [openLotModal, setOpenLotModal] = useState(false)
|
||||
const [openSerialModal, setOpenSerialModal] = useState(false)
|
||||
const [currentLot, setCurrentLot] = useState<MmLotNumber | null>(null)
|
||||
const [currentSerial, setCurrentSerial] = useState<MmSerialNumber | null>(null)
|
||||
|
||||
const filteredLots = lotNumbers.filter(
|
||||
(lot) =>
|
||||
lot.lotNumber.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
lot.material?.name.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
);
|
||||
lot.material?.name.toLowerCase().includes(searchTerm.toLowerCase()),
|
||||
)
|
||||
|
||||
const filteredSerials = serialNumbers.filter(
|
||||
(serial) =>
|
||||
serial.serialNumber.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
serial.material?.name.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
);
|
||||
serial.material?.name.toLowerCase().includes(searchTerm.toLowerCase()),
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="space-y-3 py-2">
|
||||
<Container>
|
||||
<div className="space-y-2">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold text-gray-900">Envanter Takibi</h2>
|
||||
<p className="text-gray-600">
|
||||
Lot ve seri numarası takiplerini yönetin
|
||||
</p>
|
||||
<p className="text-gray-600">Lot ve seri numarası takiplerini yönetin</p>
|
||||
</div>
|
||||
<div className="relative">
|
||||
<button
|
||||
onClick={() =>
|
||||
setShowCreateMenu((s) => !s)
|
||||
} /* Kontrol küçültüldü */
|
||||
onClick={() => setShowCreateMenu((s) => !s)} /* Kontrol küçültüldü */
|
||||
className="bg-blue-600 text-white px-2.5 py-1 rounded-md hover:bg-blue-700 flex items-center space-x-1.5 text-sm"
|
||||
>
|
||||
<FaPlus className="h-4 w-4" />
|
||||
|
|
@ -84,8 +74,8 @@ const InventoryTracking: React.FC = () => {
|
|||
<div className="absolute right-0 mt-2 w-44 bg-white border rounded shadow-lg z-10">
|
||||
<button
|
||||
onClick={() => {
|
||||
setOpenLotModal(true);
|
||||
setShowCreateMenu(false);
|
||||
setOpenLotModal(true)
|
||||
setShowCreateMenu(false)
|
||||
}}
|
||||
className="w-full text-left px-2.5 py-1 text-sm hover:bg-gray-50"
|
||||
>
|
||||
|
|
@ -93,8 +83,8 @@ const InventoryTracking: React.FC = () => {
|
|||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
setOpenSerialModal(true);
|
||||
setShowCreateMenu(false);
|
||||
setOpenSerialModal(true)
|
||||
setShowCreateMenu(false)
|
||||
}}
|
||||
className="w-full text-left px-2.5 py-1 text-sm hover:bg-gray-50"
|
||||
>
|
||||
|
|
@ -107,19 +97,12 @@ const InventoryTracking: React.FC = () => {
|
|||
|
||||
{/* Summary Statistics */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-2">
|
||||
<Widget
|
||||
title="Toplam Lot"
|
||||
value={lotNumbers.length}
|
||||
color="blue"
|
||||
icon="FaBox"
|
||||
/>
|
||||
<Widget title="Toplam Lot" value={lotNumbers.length} color="blue" icon="FaBox" />
|
||||
|
||||
<Widget
|
||||
title="Onaylı Lot"
|
||||
value={
|
||||
lotNumbers.filter(
|
||||
(lot) => lot.qualityStatus === QualityStatusEnum.Approved
|
||||
).length
|
||||
lotNumbers.filter((lot) => lot.qualityStatus === QualityStatusEnum.Approved).length
|
||||
}
|
||||
color="green"
|
||||
icon="FaBox"
|
||||
|
|
@ -135,9 +118,7 @@ const InventoryTracking: React.FC = () => {
|
|||
<Widget
|
||||
title="Müsait Seri"
|
||||
value={
|
||||
serialNumbers.filter(
|
||||
(serial) => serial.status === SerialStatusEnum.Available
|
||||
).length
|
||||
serialNumbers.filter((serial) => serial.status === SerialStatusEnum.Available).length
|
||||
}
|
||||
color="orange"
|
||||
icon="FaHashtag"
|
||||
|
|
@ -160,22 +141,22 @@ const InventoryTracking: React.FC = () => {
|
|||
<div className="border-b border-gray-200">
|
||||
<nav className="flex -mb-px">
|
||||
<button
|
||||
onClick={() => setActiveTab("lots")}
|
||||
onClick={() => setActiveTab('lots')}
|
||||
className={`flex items-center px-2.5 py-1 text-sm font-medium border-b-2 ${
|
||||
activeTab === "lots"
|
||||
? "border-blue-500 text-blue-600"
|
||||
: "border-transparent text-gray-500 hover:text-gray-700"
|
||||
activeTab === 'lots'
|
||||
? 'border-blue-500 text-blue-600'
|
||||
: 'border-transparent text-gray-500 hover:text-gray-700'
|
||||
}`}
|
||||
>
|
||||
<FaBox className="h-4 w-4 mr-2" />
|
||||
Lot Numaraları ({filteredLots.length})
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab("serials")}
|
||||
onClick={() => setActiveTab('serials')}
|
||||
className={`flex items-center px-2.5 py-1 text-sm font-medium border-b-2 ${
|
||||
activeTab === "serials"
|
||||
? "border-blue-500 text-blue-600"
|
||||
: "border-transparent text-gray-500 hover:text-gray-700"
|
||||
activeTab === 'serials'
|
||||
? 'border-blue-500 text-blue-600'
|
||||
: 'border-transparent text-gray-500 hover:text-gray-700'
|
||||
}`}
|
||||
>
|
||||
<FaHashtag className="h-4 w-4 mr-2" />
|
||||
|
|
@ -185,7 +166,7 @@ const InventoryTracking: React.FC = () => {
|
|||
</div>
|
||||
|
||||
{/* Lot Numbers Tab */}
|
||||
{activeTab === "lots" && (
|
||||
{activeTab === 'lots' && (
|
||||
<div className="bg-white shadow-sm rounded-lg overflow-hidden">
|
||||
<table className="min-w-full divide-y divide-gray-200">
|
||||
<thead className="bg-gray-50">
|
||||
|
|
@ -218,20 +199,18 @@ const InventoryTracking: React.FC = () => {
|
|||
</thead>
|
||||
<tbody className="bg-white divide-y divide-gray-200">
|
||||
{filteredLots.map((lot) => {
|
||||
const qualityInfo = getQualityStatusInfo(lot.qualityStatus);
|
||||
const qualityInfo = getQualityStatusInfo(lot.qualityStatus)
|
||||
const isExpiringSoon =
|
||||
lot.expiryDate &&
|
||||
new Date(lot.expiryDate).getTime() - new Date().getTime() <
|
||||
30 * 24 * 60 * 60 * 1000;
|
||||
30 * 24 * 60 * 60 * 1000
|
||||
|
||||
return (
|
||||
<tr key={lot.id} className="hover:bg-gray-50 text-xs">
|
||||
<td className="px-2 py-1.5 whitespace-nowrap">
|
||||
<div className="flex items-center">
|
||||
<FaBox className="h-4 w-4 text-gray-400 mr-2" />
|
||||
<div className="text-xs font-medium text-gray-900">
|
||||
{lot.lotNumber}
|
||||
</div>
|
||||
<div className="text-xs font-medium text-gray-900">{lot.lotNumber}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-2 py-1.5 whitespace-nowrap">
|
||||
|
|
@ -248,9 +227,7 @@ const InventoryTracking: React.FC = () => {
|
|||
<div className="flex items-center">
|
||||
<FaCalendar className="h-4 w-4 text-gray-400 mr-2" />
|
||||
<div className="text-xs text-gray-900">
|
||||
{new Date(lot.productionDate).toLocaleDateString(
|
||||
"tr-TR"
|
||||
)}
|
||||
{new Date(lot.productionDate).toLocaleDateString('tr-TR')}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
|
|
@ -258,16 +235,12 @@ const InventoryTracking: React.FC = () => {
|
|||
{lot.expiryDate ? (
|
||||
<div
|
||||
className={`text-xs ${
|
||||
isExpiringSoon
|
||||
? "text-red-600 font-medium"
|
||||
: "text-gray-900"
|
||||
isExpiringSoon ? 'text-red-600 font-medium' : 'text-gray-900'
|
||||
}`}
|
||||
>
|
||||
{new Date(lot.expiryDate).toLocaleDateString("tr-TR")}
|
||||
{new Date(lot.expiryDate).toLocaleDateString('tr-TR')}
|
||||
{isExpiringSoon && (
|
||||
<div className="text-xs text-red-500">
|
||||
Yakında Dolacak
|
||||
</div>
|
||||
<div className="text-xs text-red-500">Yakında Dolacak</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
|
|
@ -275,9 +248,7 @@ const InventoryTracking: React.FC = () => {
|
|||
)}
|
||||
</td>
|
||||
<td className="px-2 py-1.5 whitespace-nowrap">
|
||||
<div className="text-xs text-gray-900">
|
||||
{lot.supplierId || "-"}
|
||||
</div>
|
||||
<div className="text-xs text-gray-900">{lot.supplierId || '-'}</div>
|
||||
</td>
|
||||
<td className="px-2 py-1.5 whitespace-nowrap">
|
||||
<span
|
||||
|
|
@ -290,8 +261,8 @@ const InventoryTracking: React.FC = () => {
|
|||
<div className="flex items-center justify-end space-x-2">
|
||||
<button
|
||||
onClick={() => {
|
||||
setCurrentLot(lot);
|
||||
setOpenLotModal(true);
|
||||
setCurrentLot(lot)
|
||||
setOpenLotModal(true)
|
||||
}}
|
||||
className="text-blue-600 hover:text-blue-900"
|
||||
>
|
||||
|
|
@ -299,8 +270,8 @@ const InventoryTracking: React.FC = () => {
|
|||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
setCurrentLot(lot);
|
||||
setOpenLotModal(true);
|
||||
setCurrentLot(lot)
|
||||
setOpenLotModal(true)
|
||||
}}
|
||||
className="text-green-600 hover:text-green-900"
|
||||
>
|
||||
|
|
@ -309,37 +280,31 @@ const InventoryTracking: React.FC = () => {
|
|||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
<LotForm
|
||||
isOpen={openLotModal}
|
||||
onClose={() => {
|
||||
setOpenLotModal(false);
|
||||
setCurrentLot(null);
|
||||
setOpenLotModal(false)
|
||||
setCurrentLot(null)
|
||||
}}
|
||||
onSave={(lot) => setLotNumbers((prev) => [lot, ...prev])}
|
||||
onUpdate={(updated) =>
|
||||
setLotNumbers((prev) =>
|
||||
prev.map((l) => (l.id === updated.id ? updated : l))
|
||||
)
|
||||
setLotNumbers((prev) => prev.map((l) => (l.id === updated.id ? updated : l)))
|
||||
}
|
||||
materials={mockMaterials}
|
||||
units={mockUnits}
|
||||
suppliers={mockBusinessParties.filter(
|
||||
(bp) => bp.partyType === PartyType.Supplier
|
||||
)}
|
||||
suppliers={mockBusinessParties.filter((bp) => bp.partyType === PartyType.Supplier)}
|
||||
initial={currentLot}
|
||||
mode={currentLot ? "edit" : "create"}
|
||||
mode={currentLot ? 'edit' : 'create'}
|
||||
/>
|
||||
|
||||
{filteredLots.length === 0 && (
|
||||
<div className="text-center py-12">
|
||||
<FaBox className="mx-auto h-12 w-12 text-gray-400" />
|
||||
<h3 className="mt-2 text-sm font-medium text-gray-900">
|
||||
Lot bulunamadı
|
||||
</h3>
|
||||
<h3 className="mt-2 text-sm font-medium text-gray-900">Lot bulunamadı</h3>
|
||||
<p className="mt-1 text-sm text-gray-500">
|
||||
Arama kriterlerinize uygun lot kaydı bulunmuyor.
|
||||
</p>
|
||||
|
|
@ -349,7 +314,7 @@ const InventoryTracking: React.FC = () => {
|
|||
)}
|
||||
|
||||
{/* Serial Numbers Tab */}
|
||||
{activeTab === "serials" && (
|
||||
{activeTab === 'serials' && (
|
||||
<div className="bg-white shadow-sm rounded-lg overflow-hidden">
|
||||
<table className="min-w-full divide-y divide-gray-200">
|
||||
<thead className="bg-gray-50">
|
||||
|
|
@ -382,12 +347,11 @@ const InventoryTracking: React.FC = () => {
|
|||
</thead>
|
||||
<tbody className="bg-white divide-y divide-gray-200">
|
||||
{filteredSerials.map((serial) => {
|
||||
const statusInfo = getSerialStatusInfo(serial.status);
|
||||
const statusInfo = getSerialStatusInfo(serial.status)
|
||||
const isWarrantyExpiring =
|
||||
serial.warrantyExpiryDate &&
|
||||
new Date(serial.warrantyExpiryDate).getTime() -
|
||||
new Date().getTime() <
|
||||
30 * 24 * 60 * 60 * 1000;
|
||||
new Date(serial.warrantyExpiryDate).getTime() - new Date().getTime() <
|
||||
30 * 24 * 60 * 60 * 1000
|
||||
|
||||
return (
|
||||
<tr key={serial.id} className="hover:bg-gray-50 text-xs">
|
||||
|
|
@ -411,7 +375,7 @@ const InventoryTracking: React.FC = () => {
|
|||
LOT-{serial.lotId}
|
||||
</span>
|
||||
) : (
|
||||
"-"
|
||||
'-'
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
|
|
@ -419,9 +383,7 @@ const InventoryTracking: React.FC = () => {
|
|||
<div className="flex items-center">
|
||||
<FaCalendar className="h-4 w-4 text-gray-400 mr-2" />
|
||||
<div className="text-xs text-gray-900">
|
||||
{new Date(serial.productionDate).toLocaleDateString(
|
||||
"tr-TR"
|
||||
)}
|
||||
{new Date(serial.productionDate).toLocaleDateString('tr-TR')}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
|
|
@ -429,18 +391,12 @@ const InventoryTracking: React.FC = () => {
|
|||
{serial.warrantyExpiryDate ? (
|
||||
<div
|
||||
className={`text-xs ${
|
||||
isWarrantyExpiring
|
||||
? "text-red-600 font-medium"
|
||||
: "text-gray-900"
|
||||
isWarrantyExpiring ? 'text-red-600 font-medium' : 'text-gray-900'
|
||||
}`}
|
||||
>
|
||||
{new Date(
|
||||
serial.warrantyExpiryDate
|
||||
).toLocaleDateString("tr-TR")}
|
||||
{new Date(serial.warrantyExpiryDate).toLocaleDateString('tr-TR')}
|
||||
{isWarrantyExpiring && (
|
||||
<div className="text-xs text-red-500">
|
||||
Yakında Dolacak
|
||||
</div>
|
||||
<div className="text-xs text-red-500">Yakında Dolacak</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
|
|
@ -451,7 +407,7 @@ const InventoryTracking: React.FC = () => {
|
|||
<div className="flex items-center">
|
||||
<FaMapMarkerAlt className="h-4 w-4 text-gray-400 mr-2" />
|
||||
<div className="text-xs text-gray-900">
|
||||
{serial.currentLocationId || "-"}
|
||||
{serial.currentLocationId || '-'}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
|
|
@ -466,8 +422,8 @@ const InventoryTracking: React.FC = () => {
|
|||
<div className="flex items-center justify-end space-x-2">
|
||||
<button
|
||||
onClick={() => {
|
||||
setCurrentSerial(serial);
|
||||
setOpenSerialModal(true);
|
||||
setCurrentSerial(serial)
|
||||
setOpenSerialModal(true)
|
||||
}}
|
||||
className="text-blue-600 hover:text-blue-900"
|
||||
>
|
||||
|
|
@ -475,8 +431,8 @@ const InventoryTracking: React.FC = () => {
|
|||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
setCurrentSerial(serial);
|
||||
setOpenSerialModal(true);
|
||||
setCurrentSerial(serial)
|
||||
setOpenSerialModal(true)
|
||||
}}
|
||||
className="text-green-600 hover:text-green-900"
|
||||
>
|
||||
|
|
@ -485,7 +441,7 @@ const InventoryTracking: React.FC = () => {
|
|||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
@ -493,27 +449,23 @@ const InventoryTracking: React.FC = () => {
|
|||
<SerialForm
|
||||
isOpen={openSerialModal}
|
||||
onClose={() => {
|
||||
setOpenSerialModal(false);
|
||||
setCurrentSerial(null);
|
||||
setOpenSerialModal(false)
|
||||
setCurrentSerial(null)
|
||||
}}
|
||||
onSave={(serial) => setSerialNumbers((prev) => [serial, ...prev])}
|
||||
onUpdate={(updated) =>
|
||||
setSerialNumbers((prev) =>
|
||||
prev.map((s) => (s.id === updated.id ? updated : s))
|
||||
)
|
||||
setSerialNumbers((prev) => prev.map((s) => (s.id === updated.id ? updated : s)))
|
||||
}
|
||||
materials={mockMaterials}
|
||||
lots={lotNumbers.map((l) => ({ id: l.id, label: l.lotNumber }))}
|
||||
initial={currentSerial}
|
||||
mode={currentSerial ? "edit" : "create"}
|
||||
mode={currentSerial ? 'edit' : 'create'}
|
||||
/>
|
||||
|
||||
{filteredSerials.length === 0 && (
|
||||
<div className="text-center py-12">
|
||||
<FaHashtag className="mx-auto h-12 w-12 text-gray-400" />
|
||||
<h3 className="mt-2 text-sm font-medium text-gray-900">
|
||||
Seri numarası bulunamadı
|
||||
</h3>
|
||||
<h3 className="mt-2 text-sm font-medium text-gray-900">Seri numarası bulunamadı</h3>
|
||||
<p className="mt-1 text-sm text-gray-500">
|
||||
Arama kriterlerinize uygun seri numarası bulunmuyor.
|
||||
</p>
|
||||
|
|
@ -522,7 +474,8 @@ const InventoryTracking: React.FC = () => {
|
|||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
export default InventoryTracking;
|
||||
export default InventoryTracking
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState } from "react";
|
||||
import React, { useState } from 'react'
|
||||
import {
|
||||
FaSearch,
|
||||
FaMapMarkerAlt,
|
||||
|
|
@ -7,45 +7,43 @@ import {
|
|||
FaEye,
|
||||
FaTh,
|
||||
FaList,
|
||||
} from "react-icons/fa";
|
||||
import { mockWarehouses } from "../../../mocks/mockWarehouses";
|
||||
import { mockLocations } from "../../../mocks/mockLocations";
|
||||
import { mockStockItems } from "../../../mocks/mockStockItems";
|
||||
import { getStockStatusColor, getStockStatusText } from "../../../utils/erp";
|
||||
} from 'react-icons/fa'
|
||||
import { mockWarehouses } from '../../../mocks/mockWarehouses'
|
||||
import { mockLocations } from '../../../mocks/mockLocations'
|
||||
import { mockStockItems } from '../../../mocks/mockStockItems'
|
||||
import { getStockStatusColor, getStockStatusText } from '../../../utils/erp'
|
||||
import { Container } from '@/components/shared'
|
||||
|
||||
const LocationTracking: React.FC = () => {
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [selectedWarehouse, setSelectedWarehouse] = useState<string>("");
|
||||
const [selectedLocation, setSelectedLocation] = useState<string>("");
|
||||
const [viewMode, setViewMode] = useState<"grid" | "list">("grid");
|
||||
const [searchTerm, setSearchTerm] = useState('')
|
||||
const [selectedWarehouse, setSelectedWarehouse] = useState<string>('')
|
||||
const [selectedLocation, setSelectedLocation] = useState<string>('')
|
||||
const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid')
|
||||
|
||||
const getLocationUtilization = (locationId: string) => {
|
||||
const location = mockLocations.find((l) => l.id === locationId);
|
||||
if (!location) return 0;
|
||||
return (location.currentStock / location.capacity) * 100;
|
||||
};
|
||||
const location = mockLocations.find((l) => l.id === locationId)
|
||||
if (!location) return 0
|
||||
return (location.currentStock / location.capacity) * 100
|
||||
}
|
||||
|
||||
const getLocationStockItems = (locationId: string) => {
|
||||
return mockStockItems.filter((item) => item.locationId === locationId);
|
||||
};
|
||||
return mockStockItems.filter((item) => item.locationId === locationId)
|
||||
}
|
||||
|
||||
const filteredLocations = mockLocations.filter((location) => {
|
||||
const matchesSearch =
|
||||
location.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
location.locationCode.toLowerCase().includes(searchTerm.toLowerCase());
|
||||
const matchesWarehouse =
|
||||
selectedWarehouse === "" || location.warehouseId === selectedWarehouse;
|
||||
return matchesSearch && matchesWarehouse;
|
||||
});
|
||||
location.locationCode.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
const matchesWarehouse = selectedWarehouse === '' || location.warehouseId === selectedWarehouse
|
||||
return matchesSearch && matchesWarehouse
|
||||
})
|
||||
|
||||
const GridView = () => (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{filteredLocations.map((location) => {
|
||||
const warehouse = mockWarehouses.find(
|
||||
(w) => w.id === location.warehouseId
|
||||
);
|
||||
const locationStockItems = getLocationStockItems(location.id);
|
||||
const utilization = getLocationUtilization(location.id);
|
||||
const warehouse = mockWarehouses.find((w) => w.id === location.warehouseId)
|
||||
const locationStockItems = getLocationStockItems(location.id)
|
||||
const utilization = getLocationUtilization(location.id)
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
@ -59,9 +57,7 @@ const LocationTracking: React.FC = () => {
|
|||
</div>
|
||||
<div>
|
||||
<h3 className="font-medium text-gray-900">{location.name}</h3>
|
||||
<p className="text-sm text-gray-500">
|
||||
{location.locationCode}
|
||||
</p>
|
||||
<p className="text-sm text-gray-500">{location.locationCode}</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
|
|
@ -84,18 +80,16 @@ const LocationTracking: React.FC = () => {
|
|||
<div className="space-y-1">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-gray-600">Doluluk Oranı</span>
|
||||
<span className="font-medium">
|
||||
%{Math.round(utilization)}
|
||||
</span>
|
||||
<span className="font-medium">%{Math.round(utilization)}</span>
|
||||
</div>
|
||||
<div className="w-full bg-gray-200 rounded-full h-2">
|
||||
<div
|
||||
className={`h-2 rounded-full ${
|
||||
utilization > 90
|
||||
? "bg-red-500"
|
||||
? 'bg-red-500'
|
||||
: utilization > 70
|
||||
? "bg-yellow-500"
|
||||
: "bg-green-500"
|
||||
? 'bg-yellow-500'
|
||||
: 'bg-green-500'
|
||||
}`}
|
||||
style={{ width: `${utilization}%` }}
|
||||
/>
|
||||
|
|
@ -108,29 +102,22 @@ const LocationTracking: React.FC = () => {
|
|||
{/* Stock Items Summary */}
|
||||
<div className="bg-gray-50 rounded-lg p-2">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<h4 className="text-sm font-medium text-gray-900">
|
||||
Malzemeler
|
||||
</h4>
|
||||
<span className="text-xs text-gray-500">
|
||||
{locationStockItems.length} çeşit
|
||||
</span>
|
||||
<h4 className="text-sm font-medium text-gray-900">Malzemeler</h4>
|
||||
<span className="text-xs text-gray-500">{locationStockItems.length} çeşit</span>
|
||||
</div>
|
||||
|
||||
{locationStockItems.length > 0 ? (
|
||||
<div className="space-y-1">
|
||||
{locationStockItems.slice(0, 3).map((item) => (
|
||||
<div
|
||||
key={item.id}
|
||||
className="flex justify-between items-center text-xs"
|
||||
>
|
||||
<div key={item.id} className="flex justify-between items-center text-xs">
|
||||
<span className="text-gray-700 truncate">
|
||||
{item.material?.code || "N/A"}
|
||||
{item.material?.code || 'N/A'}
|
||||
</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-medium">{item.quantity}</span>
|
||||
<span
|
||||
className={`px-1.5 py-0.5 rounded-full text-xs ${getStockStatusColor(
|
||||
item.status
|
||||
item.status,
|
||||
)}`}
|
||||
>
|
||||
{getStockStatusText(item.status)}
|
||||
|
|
@ -145,22 +132,16 @@ const LocationTracking: React.FC = () => {
|
|||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-xs text-gray-500 text-center py-2">
|
||||
Malzeme bulunmuyor
|
||||
</div>
|
||||
<div className="text-xs text-gray-500 text-center py-2">Malzeme bulunmuyor</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Restrictions */}
|
||||
{location.restrictions && location.restrictions.length > 0 && (
|
||||
<div>
|
||||
<h4 className="text-sm font-medium text-gray-900 mb-1">
|
||||
Kısıtlamalar
|
||||
</h4>
|
||||
<h4 className="text-sm font-medium text-gray-900 mb-1">Kısıtlamalar</h4>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{location.restrictions
|
||||
.slice(0, 2)
|
||||
.map((restriction, index) => (
|
||||
{location.restrictions.slice(0, 2).map((restriction, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className="inline-flex px-2 py-1 text-xs bg-yellow-100 text-yellow-800 rounded"
|
||||
|
|
@ -193,23 +174,21 @@ const LocationTracking: React.FC = () => {
|
|||
)}
|
||||
</div>
|
||||
<div className="text-xs text-gray-500">
|
||||
Son hareket:{" "}
|
||||
Son hareket:{' '}
|
||||
{locationStockItems.length > 0
|
||||
? new Date(
|
||||
Math.max(
|
||||
...locationStockItems.map((item) =>
|
||||
item.lastMovementDate.getTime()
|
||||
...locationStockItems.map((item) => item.lastMovementDate.getTime()),
|
||||
),
|
||||
).toLocaleDateString('tr-TR')
|
||||
: 'N/A'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
)
|
||||
).toLocaleDateString("tr-TR")
|
||||
: "N/A"}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
)
|
||||
|
||||
const ListView = () => (
|
||||
<div className="bg-white rounded-lg shadow-sm border border-gray-200">
|
||||
|
|
@ -242,11 +221,9 @@ const LocationTracking: React.FC = () => {
|
|||
</thead>
|
||||
<tbody className="bg-white divide-y divide-gray-200">
|
||||
{filteredLocations.map((location) => {
|
||||
const warehouse = mockWarehouses.find(
|
||||
(w) => w.id === location.warehouseId
|
||||
);
|
||||
const locationStockItems = getLocationStockItems(location.id);
|
||||
const utilization = getLocationUtilization(location.id);
|
||||
const warehouse = mockWarehouses.find((w) => w.id === location.warehouseId)
|
||||
const locationStockItems = getLocationStockItems(location.id)
|
||||
const utilization = getLocationUtilization(location.id)
|
||||
|
||||
return (
|
||||
<tr key={location.id} className="hover:bg-gray-50">
|
||||
|
|
@ -256,22 +233,14 @@ const LocationTracking: React.FC = () => {
|
|||
<FaMapMarkerAlt className="w-4 h-4 text-blue-600" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-sm font-medium text-gray-900">
|
||||
{location.name}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500">
|
||||
{location.locationCode}
|
||||
</div>
|
||||
<div className="text-sm font-medium text-gray-900">{location.name}</div>
|
||||
<div className="text-sm text-gray-500">{location.locationCode}</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-3 py-2 whitespace-nowrap">
|
||||
<div className="text-sm text-gray-900">
|
||||
{warehouse?.name}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500">
|
||||
{warehouse?.code}
|
||||
</div>
|
||||
<div className="text-sm text-gray-900">{warehouse?.name}</div>
|
||||
<div className="text-sm text-gray-500">{warehouse?.code}</div>
|
||||
</td>
|
||||
<td className="px-3 py-2 whitespace-nowrap">
|
||||
<div className="flex items-center">
|
||||
|
|
@ -279,17 +248,15 @@ const LocationTracking: React.FC = () => {
|
|||
<div
|
||||
className={`h-2 rounded-full ${
|
||||
utilization > 90
|
||||
? "bg-red-500"
|
||||
? 'bg-red-500'
|
||||
: utilization > 70
|
||||
? "bg-yellow-500"
|
||||
: "bg-green-500"
|
||||
? 'bg-yellow-500'
|
||||
: 'bg-green-500'
|
||||
}`}
|
||||
style={{ width: `${utilization}%` }}
|
||||
/>
|
||||
</div>
|
||||
<span className="text-sm text-gray-900">
|
||||
%{Math.round(utilization)}
|
||||
</span>
|
||||
<span className="text-sm text-gray-900">%{Math.round(utilization)}</span>
|
||||
</div>
|
||||
<div className="text-xs text-gray-500">
|
||||
{location.currentStock} / {location.capacity}
|
||||
|
|
@ -302,12 +269,10 @@ const LocationTracking: React.FC = () => {
|
|||
{locationStockItems.length > 0
|
||||
? new Date(
|
||||
Math.max(
|
||||
...locationStockItems.map((item) =>
|
||||
item.lastMovementDate.getTime()
|
||||
)
|
||||
)
|
||||
).toLocaleDateString("tr-TR")
|
||||
: "N/A"}
|
||||
...locationStockItems.map((item) => item.lastMovementDate.getTime()),
|
||||
),
|
||||
).toLocaleDateString('tr-TR')
|
||||
: 'N/A'}
|
||||
</td>
|
||||
<td className="px-3 py-2 whitespace-nowrap">
|
||||
{location.isActive ? (
|
||||
|
|
@ -331,44 +296,37 @@ const LocationTracking: React.FC = () => {
|
|||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
|
||||
const LocationDetailModal = () => {
|
||||
const location = mockLocations.find((l) => l.id === selectedLocation);
|
||||
const locationStockItems = getLocationStockItems(selectedLocation);
|
||||
const location = mockLocations.find((l) => l.id === selectedLocation)
|
||||
const locationStockItems = getLocationStockItems(selectedLocation)
|
||||
|
||||
if (!selectedLocation || !location) return null;
|
||||
if (!selectedLocation || !location) return null
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 overflow-y-auto">
|
||||
<div className="flex items-center justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
|
||||
<div
|
||||
className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"
|
||||
onClick={() => setSelectedLocation("")}
|
||||
onClick={() => setSelectedLocation('')}
|
||||
/>
|
||||
|
||||
<div className="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-4xl sm:w-full">
|
||||
<div className="bg-white px-4 pt-4 pb-4 sm:p-4">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-lg font-medium text-gray-900">
|
||||
{location.name} - Detaylar
|
||||
</h3>
|
||||
<h3 className="text-lg font-medium text-gray-900">{location.name} - Detaylar</h3>
|
||||
<button
|
||||
onClick={() => setSelectedLocation("")}
|
||||
onClick={() => setSelectedLocation('')}
|
||||
className="text-gray-400 hover:text-gray-600"
|
||||
>
|
||||
<svg
|
||||
className="w-6 h-6"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<svg className="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
|
|
@ -382,9 +340,7 @@ const LocationTracking: React.FC = () => {
|
|||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
{/* Location Info */}
|
||||
<div className="bg-gray-50 rounded-lg p-3">
|
||||
<h4 className="font-medium text-gray-900 mb-3">
|
||||
Lokasyon Bilgileri
|
||||
</h4>
|
||||
<h4 className="font-medium text-gray-900 mb-3">Lokasyon Bilgileri</h4>
|
||||
<div className="space-y-2 text-sm">
|
||||
<div>
|
||||
<strong>Kod:</strong> {location.locationCode}
|
||||
|
|
@ -399,14 +355,12 @@ const LocationTracking: React.FC = () => {
|
|||
<strong>Kapasite:</strong> {location.capacity} birim
|
||||
</div>
|
||||
<div>
|
||||
<strong>Mevcut Stok:</strong> {location.currentStock}{" "}
|
||||
birim
|
||||
<strong>Mevcut Stok:</strong> {location.currentStock} birim
|
||||
</div>
|
||||
{location.dimensions && (
|
||||
<div>
|
||||
<strong>Boyutlar:</strong> {location.dimensions.length}x
|
||||
{location.dimensions.width}x{location.dimensions.height}
|
||||
m
|
||||
{location.dimensions.width}x{location.dimensions.height}m
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -419,22 +373,15 @@ const LocationTracking: React.FC = () => {
|
|||
</h4>
|
||||
<div className="space-y-2 max-h-64 overflow-y-auto">
|
||||
{locationStockItems.map((item) => (
|
||||
<div
|
||||
key={item.id}
|
||||
className="bg-white rounded p-3 border"
|
||||
>
|
||||
<div key={item.id} className="bg-white rounded p-3 border">
|
||||
<div className="flex justify-between items-start mb-2">
|
||||
<div>
|
||||
<div className="font-medium text-sm">
|
||||
{item.material?.code}
|
||||
</div>
|
||||
<div className="text-xs text-gray-500">
|
||||
{item.material?.code}
|
||||
</div>
|
||||
<div className="font-medium text-sm">{item.material?.code}</div>
|
||||
<div className="text-xs text-gray-500">{item.material?.code}</div>
|
||||
</div>
|
||||
<span
|
||||
className={`px-2 py-1 rounded-full text-xs ${getStockStatusColor(
|
||||
item.status
|
||||
item.status,
|
||||
)}`}
|
||||
>
|
||||
{getStockStatusText(item.status)}
|
||||
|
|
@ -450,11 +397,10 @@ const LocationTracking: React.FC = () => {
|
|||
<div>
|
||||
Rezerve: {item.reservedQuantity} {item.unitId}
|
||||
</div>
|
||||
<div>Lot: {item.lotNumber || "N/A"}</div>
|
||||
<div>Lot: {item.lotNumber || 'N/A'}</div>
|
||||
</div>
|
||||
<div className="mt-2 text-xs text-gray-500">
|
||||
Son hareket:{" "}
|
||||
{item.lastMovementDate.toLocaleDateString("tr-TR")}
|
||||
Son hareket: {item.lastMovementDate.toLocaleDateString('tr-TR')}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
|
@ -470,38 +416,35 @@ const LocationTracking: React.FC = () => {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4 pt-2">
|
||||
<Container>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold text-gray-900">
|
||||
Raf/Lokasyon Bazlı Takip
|
||||
</h2>
|
||||
<p className="text-gray-600">
|
||||
Lokasyonlardaki stok durumunu takip edin
|
||||
</p>
|
||||
<h2 className="text-2xl font-bold text-gray-900">Raf/Lokasyon Bazlı Takip</h2>
|
||||
<p className="text-gray-600">Lokasyonlardaki stok durumunu takip edin</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={() => setViewMode("grid")}
|
||||
onClick={() => setViewMode('grid')}
|
||||
className={`p-1.5 rounded-lg ${
|
||||
viewMode === "grid"
|
||||
? "bg-blue-100 text-blue-600"
|
||||
: "text-gray-400 hover:text-gray-600"
|
||||
viewMode === 'grid'
|
||||
? 'bg-blue-100 text-blue-600'
|
||||
: 'text-gray-400 hover:text-gray-600'
|
||||
}`}
|
||||
>
|
||||
<FaTh className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setViewMode("list")}
|
||||
onClick={() => setViewMode('list')}
|
||||
className={`p-1.5 rounded-lg ${
|
||||
viewMode === "list"
|
||||
? "bg-blue-100 text-blue-600"
|
||||
: "text-gray-400 hover:text-gray-600"
|
||||
viewMode === 'list'
|
||||
? 'bg-blue-100 text-blue-600'
|
||||
: 'text-gray-400 hover:text-gray-600'
|
||||
}`}
|
||||
>
|
||||
<FaList className="w-4 h-4" />
|
||||
|
|
@ -536,12 +479,13 @@ const LocationTracking: React.FC = () => {
|
|||
</div>
|
||||
|
||||
{/* Content */}
|
||||
{viewMode === "grid" ? <GridView /> : <ListView />}
|
||||
{viewMode === 'grid' ? <GridView /> : <ListView />}
|
||||
</div>
|
||||
|
||||
{/* Location Detail Modal */}
|
||||
<LocationDetailModal />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
export default LocationTracking;
|
||||
export default LocationTracking
|
||||
|
|
|
|||
|
|
@ -1,31 +1,26 @@
|
|||
import React from "react";
|
||||
import { useFormik } from "formik";
|
||||
import * as Yup from "yup";
|
||||
import { FaSave, FaTimes } from "react-icons/fa";
|
||||
import {
|
||||
QualityStatusEnum,
|
||||
MmLotNumber,
|
||||
MmUnit,
|
||||
MmMaterial,
|
||||
} from "../../../types/mm";
|
||||
import { BusinessParty } from "../../../types/common";
|
||||
import React from 'react'
|
||||
import { useFormik } from 'formik'
|
||||
import * as Yup from 'yup'
|
||||
import { FaSave, FaTimes } from 'react-icons/fa'
|
||||
import { QualityStatusEnum, MmLotNumber, MmUnit, MmMaterial } from '../../../types/mm'
|
||||
import { BusinessParty } from '../../../types/common'
|
||||
|
||||
const validationSchema = Yup.object({
|
||||
materialId: Yup.string().required("Malzeme seçimi zorunlu"),
|
||||
lotNumber: Yup.string().required("Lot numarası zorunlu"),
|
||||
materialId: Yup.string().required('Malzeme seçimi zorunlu'),
|
||||
lotNumber: Yup.string().required('Lot numarası zorunlu'),
|
||||
quantity: Yup.number().min(0, "Miktar 0'dan büyük olmalıdır"),
|
||||
});
|
||||
})
|
||||
|
||||
interface LotFormProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onSave: (lot: MmLotNumber) => void;
|
||||
onUpdate?: (lot: MmLotNumber) => void;
|
||||
materials: MmMaterial[];
|
||||
suppliers?: BusinessParty[];
|
||||
units?: MmUnit[];
|
||||
initial?: MmLotNumber | null;
|
||||
mode?: "create" | "edit" | "view";
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
onSave: (lot: MmLotNumber) => void
|
||||
onUpdate?: (lot: MmLotNumber) => void
|
||||
materials: MmMaterial[]
|
||||
suppliers?: BusinessParty[]
|
||||
units?: MmUnit[]
|
||||
initial?: MmLotNumber | null
|
||||
mode?: 'create' | 'edit' | 'view'
|
||||
}
|
||||
|
||||
const LotForm: React.FC<LotFormProps> = ({
|
||||
|
|
@ -37,17 +32,17 @@ const LotForm: React.FC<LotFormProps> = ({
|
|||
suppliers = [],
|
||||
units = [],
|
||||
initial = null,
|
||||
mode = "create",
|
||||
mode = 'create',
|
||||
}) => {
|
||||
const formik = useFormik({
|
||||
initialValues: {
|
||||
materialId: "",
|
||||
lotNumber: "",
|
||||
productionDate: "",
|
||||
expiryDate: "",
|
||||
materialId: '',
|
||||
lotNumber: '',
|
||||
productionDate: '',
|
||||
expiryDate: '',
|
||||
quantity: 0,
|
||||
unitId: "KG",
|
||||
supplierId: suppliers.length ? suppliers[0].id : "",
|
||||
unitId: 'KG',
|
||||
supplierId: suppliers.length ? suppliers[0].id : '',
|
||||
qualityStatus: QualityStatusEnum.Pending,
|
||||
isActive: true,
|
||||
},
|
||||
|
|
@ -57,52 +52,48 @@ const LotForm: React.FC<LotFormProps> = ({
|
|||
id: (initial && initial.id) || Date.now().toString(),
|
||||
materialId: values.materialId,
|
||||
lotNumber: values.lotNumber,
|
||||
productionDate: values.productionDate
|
||||
? new Date(values.productionDate)
|
||||
: new Date(),
|
||||
productionDate: values.productionDate ? new Date(values.productionDate) : new Date(),
|
||||
expiryDate: values.expiryDate ? new Date(values.expiryDate) : undefined,
|
||||
quantity: Number(values.quantity),
|
||||
unitId: values.unitId,
|
||||
supplierId: values.supplierId || undefined,
|
||||
qualityStatus: values.qualityStatus,
|
||||
isActive: !!values.isActive,
|
||||
};
|
||||
}
|
||||
|
||||
// simulate API
|
||||
await new Promise((r) => setTimeout(r, 300));
|
||||
if (mode === "edit" && onUpdate) {
|
||||
onUpdate(newLot);
|
||||
await new Promise((r) => setTimeout(r, 300))
|
||||
if (mode === 'edit' && onUpdate) {
|
||||
onUpdate(newLot)
|
||||
} else {
|
||||
onSave(newLot);
|
||||
onSave(newLot)
|
||||
}
|
||||
onClose();
|
||||
onClose()
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
// sync initial values when editing/viewing
|
||||
React.useEffect(() => {
|
||||
if (initial) {
|
||||
const src = initial;
|
||||
const src = initial
|
||||
formik.setValues({
|
||||
materialId: src.materialId || "",
|
||||
lotNumber: src.lotNumber || "",
|
||||
materialId: src.materialId || '',
|
||||
lotNumber: src.lotNumber || '',
|
||||
productionDate: src.productionDate
|
||||
? new Date(src.productionDate).toISOString().slice(0, 10)
|
||||
: "",
|
||||
expiryDate: src.expiryDate
|
||||
? new Date(src.expiryDate).toISOString().slice(0, 10)
|
||||
: "",
|
||||
: '',
|
||||
expiryDate: src.expiryDate ? new Date(src.expiryDate).toISOString().slice(0, 10) : '',
|
||||
quantity: src.quantity || 0,
|
||||
unitId: src.unitId || (units.length ? units[0].id : ""),
|
||||
supplierId: src.supplierId || (suppliers.length ? suppliers[0].id : ""),
|
||||
unitId: src.unitId || (units.length ? units[0].id : ''),
|
||||
supplierId: src.supplierId || (suppliers.length ? suppliers[0].id : ''),
|
||||
qualityStatus: src.qualityStatus || QualityStatusEnum.Pending,
|
||||
isActive: !!src.isActive,
|
||||
});
|
||||
})
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [initial]);
|
||||
}, [initial])
|
||||
|
||||
if (!isOpen) return null;
|
||||
if (!isOpen) return null
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
||||
|
|
@ -112,18 +103,18 @@ const LotForm: React.FC<LotFormProps> = ({
|
|||
<div className="flex items-center justify-between p-2 border-b">
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold text-gray-900">
|
||||
{mode === "create"
|
||||
? "Yeni Lot Kaydı"
|
||||
: mode === "edit"
|
||||
? "Lot Düzenle"
|
||||
: "Lot Detayı"}
|
||||
{mode === 'create'
|
||||
? 'Yeni Lot Kaydı'
|
||||
: mode === 'edit'
|
||||
? 'Lot Düzenle'
|
||||
: 'Lot Detayı'}
|
||||
</h2>
|
||||
<p className="text-gray-600">
|
||||
{mode === "create"
|
||||
? "Lot bilgilerini girin"
|
||||
: mode === "edit"
|
||||
? "Mevcut lot bilgilerini güncelleyin"
|
||||
: "Lot bilgileri (sadece gösterim)"}
|
||||
{mode === 'create'
|
||||
? 'Lot bilgilerini girin'
|
||||
: mode === 'edit'
|
||||
? 'Mevcut lot bilgilerini güncelleyin'
|
||||
: 'Lot bilgileri (sadece gösterim)'}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
|
|
@ -134,7 +125,7 @@ const LotForm: React.FC<LotFormProps> = ({
|
|||
>
|
||||
<FaTimes className="inline mr-1 h-3 w-3" /> Kapat
|
||||
</button>
|
||||
{mode !== "view" && (
|
||||
{mode !== 'view' && (
|
||||
<button
|
||||
type="submit"
|
||||
className="px-2.5 py-1 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700 flex items-center"
|
||||
|
|
@ -147,12 +138,10 @@ const LotForm: React.FC<LotFormProps> = ({
|
|||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 p-2 gap-x-3 gap-y-2">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">
|
||||
Malzeme *
|
||||
</label>
|
||||
<label className="block text-sm font-medium text-gray-700">Malzeme *</label>
|
||||
<select
|
||||
{...formik.getFieldProps("materialId")}
|
||||
disabled={mode === "view"}
|
||||
{...formik.getFieldProps('materialId')}
|
||||
disabled={mode === 'view'}
|
||||
className="w-full border rounded-md px-2 py-1 text-sm"
|
||||
>
|
||||
<option value="">Seçiniz...</option>
|
||||
|
|
@ -165,61 +154,51 @@ const LotForm: React.FC<LotFormProps> = ({
|
|||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">
|
||||
Lot Numarası *
|
||||
</label>
|
||||
<label className="block text-sm font-medium text-gray-700">Lot Numarası *</label>
|
||||
<input
|
||||
type="text"
|
||||
{...formik.getFieldProps("lotNumber")}
|
||||
disabled={mode === "view"}
|
||||
{...formik.getFieldProps('lotNumber')}
|
||||
disabled={mode === 'view'}
|
||||
className="w-full border rounded-md px-2 py-1 text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">
|
||||
Üretim Tarihi
|
||||
</label>
|
||||
<label className="block text-sm font-medium text-gray-700">Üretim Tarihi</label>
|
||||
<input
|
||||
type="date"
|
||||
{...formik.getFieldProps("productionDate")}
|
||||
disabled={mode === "view"}
|
||||
{...formik.getFieldProps('productionDate')}
|
||||
disabled={mode === 'view'}
|
||||
className="w-full border rounded-md px-2 py-1 text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">
|
||||
Son Kullanma Tarihi
|
||||
</label>
|
||||
<label className="block text-sm font-medium text-gray-700">Son Kullanma Tarihi</label>
|
||||
<input
|
||||
type="date"
|
||||
{...formik.getFieldProps("expiryDate")}
|
||||
disabled={mode === "view"}
|
||||
{...formik.getFieldProps('expiryDate')}
|
||||
disabled={mode === 'view'}
|
||||
className="w-full border rounded-md px-2 py-1 text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">
|
||||
Miktar
|
||||
</label>
|
||||
<label className="block text-sm font-medium text-gray-700">Miktar</label>
|
||||
<input
|
||||
type="number"
|
||||
step="0.01"
|
||||
{...formik.getFieldProps("quantity")}
|
||||
disabled={mode === "view"}
|
||||
{...formik.getFieldProps('quantity')}
|
||||
disabled={mode === 'view'}
|
||||
className="w-full border rounded-md px-2 py-1 text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">
|
||||
Birim
|
||||
</label>
|
||||
<label className="block text-sm font-medium text-gray-700">Birim</label>
|
||||
<select
|
||||
{...formik.getFieldProps("unitId")}
|
||||
disabled={mode === "view"}
|
||||
{...formik.getFieldProps('unitId')}
|
||||
disabled={mode === 'view'}
|
||||
className="w-full border rounded-md px-2 py-1 text-sm"
|
||||
>
|
||||
{units.map((u) => (
|
||||
|
|
@ -231,12 +210,10 @@ const LotForm: React.FC<LotFormProps> = ({
|
|||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">
|
||||
Tedarikçi
|
||||
</label>
|
||||
<label className="block text-sm font-medium text-gray-700">Tedarikçi</label>
|
||||
<select
|
||||
{...formik.getFieldProps("supplierId")}
|
||||
disabled={mode === "view"}
|
||||
{...formik.getFieldProps('supplierId')}
|
||||
disabled={mode === 'view'}
|
||||
className="w-full border rounded-md px-2 py-1 text-sm"
|
||||
>
|
||||
<option value="">Seçiniz...</option>
|
||||
|
|
@ -249,11 +226,9 @@ const LotForm: React.FC<LotFormProps> = ({
|
|||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">
|
||||
Kalite Durumu
|
||||
</label>
|
||||
<label className="block text-sm font-medium text-gray-700">Kalite Durumu</label>
|
||||
<select
|
||||
{...formik.getFieldProps("qualityStatus")}
|
||||
{...formik.getFieldProps('qualityStatus')}
|
||||
className="w-full border rounded-md px-2 py-1 text-sm"
|
||||
>
|
||||
<option value={QualityStatusEnum.Pending}>Beklemede</option>
|
||||
|
|
@ -266,7 +241,7 @@ const LotForm: React.FC<LotFormProps> = ({
|
|||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default LotForm;
|
||||
export default LotForm
|
||||
|
|
|
|||
|
|
@ -1,92 +1,81 @@
|
|||
import React, { useState, useEffect, useCallback } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { FaDownload, FaCalendar, FaBox } from "react-icons/fa";
|
||||
import { MmStockMovement, MovementTypeEnum } from "../../../types/mm";
|
||||
import { FaRepeat } from "react-icons/fa6";
|
||||
import { getMovementTypeInfo } from "../../../utils/erp";
|
||||
import { mockStockMovements } from "../../../mocks/mockStockMovements";
|
||||
import Widget, { colorType } from "../../../components/common/Widget";
|
||||
import React, { useState, useEffect, useCallback } from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { FaDownload, FaCalendar, FaBox } from 'react-icons/fa'
|
||||
import { MmStockMovement, MovementTypeEnum } from '../../../types/mm'
|
||||
import { FaRepeat } from 'react-icons/fa6'
|
||||
import { getMovementTypeInfo } from '../../../utils/erp'
|
||||
import { mockStockMovements } from '../../../mocks/mockStockMovements'
|
||||
import Widget, { colorType } from '../../../components/common/Widget'
|
||||
import { Container } from '@/components/shared'
|
||||
|
||||
const MaterialMovements: React.FC<{ materialId?: string }> = ({
|
||||
materialId,
|
||||
}) => {
|
||||
const params = useParams<Record<string, string | undefined>>();
|
||||
const routeMaterialId =
|
||||
materialId || params.materialId || params.id || undefined;
|
||||
const [movements, setMovements] = useState<MmStockMovement[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const MaterialMovements: React.FC<{ materialId?: string }> = ({ materialId }) => {
|
||||
const params = useParams<Record<string, string | undefined>>()
|
||||
const routeMaterialId = materialId || params.materialId || params.id || undefined
|
||||
const [movements, setMovements] = useState<MmStockMovement[]>([])
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [filters, setFilters] = useState({
|
||||
startDate: "",
|
||||
endDate: "",
|
||||
movementType: "",
|
||||
warehouseId: "",
|
||||
});
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
movementType: '',
|
||||
warehouseId: '',
|
||||
})
|
||||
|
||||
const loadMovements = useCallback(() => {
|
||||
setLoading(true);
|
||||
setLoading(true)
|
||||
// Simulate API call
|
||||
setTimeout(() => {
|
||||
let filteredMovements = mockStockMovements;
|
||||
let filteredMovements = mockStockMovements
|
||||
|
||||
const activeMaterialId = routeMaterialId;
|
||||
const activeMaterialId = routeMaterialId
|
||||
if (activeMaterialId) {
|
||||
filteredMovements = filteredMovements.filter(
|
||||
(m) => m.materialId === activeMaterialId
|
||||
);
|
||||
filteredMovements = filteredMovements.filter((m) => m.materialId === activeMaterialId)
|
||||
}
|
||||
|
||||
if (filters.movementType) {
|
||||
filteredMovements = filteredMovements.filter(
|
||||
(m) =>
|
||||
m.movementType ===
|
||||
(filters.movementType as unknown as MovementTypeEnum)
|
||||
);
|
||||
(m) => m.movementType === (filters.movementType as unknown as MovementTypeEnum),
|
||||
)
|
||||
}
|
||||
|
||||
setMovements(filteredMovements);
|
||||
setLoading(false);
|
||||
}, 1000);
|
||||
}, [routeMaterialId, filters]);
|
||||
setMovements(filteredMovements)
|
||||
setLoading(false)
|
||||
}, 1000)
|
||||
}, [routeMaterialId, filters])
|
||||
|
||||
useEffect(() => {
|
||||
loadMovements();
|
||||
}, [loadMovements]);
|
||||
loadMovements()
|
||||
}, [loadMovements])
|
||||
|
||||
const calculateRunningBalance = () => {
|
||||
let balance = 0;
|
||||
let balance = 0
|
||||
return movements
|
||||
.sort(
|
||||
(a, b) =>
|
||||
new Date(a.movementDate).getTime() -
|
||||
new Date(b.movementDate).getTime()
|
||||
)
|
||||
.sort((a, b) => new Date(a.movementDate).getTime() - new Date(b.movementDate).getTime())
|
||||
.map((movement) => {
|
||||
balance += movement.quantity;
|
||||
return { ...movement, runningBalance: balance };
|
||||
});
|
||||
};
|
||||
balance += movement.quantity
|
||||
return { ...movement, runningBalance: balance }
|
||||
})
|
||||
}
|
||||
|
||||
const movementsWithBalance = calculateRunningBalance();
|
||||
const movementsWithBalance = calculateRunningBalance()
|
||||
|
||||
return (
|
||||
<div className="space-y-3 py-2">
|
||||
<Container>
|
||||
<div className="space-y-2">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold text-gray-900">
|
||||
{materialId ? "Stok Hareketleri" : "Tüm Stok Hareketleri"}
|
||||
{materialId ? 'Stok Hareketleri' : 'Tüm Stok Hareketleri'}
|
||||
</h2>
|
||||
<p className="text-gray-600">
|
||||
{materialId
|
||||
? `${materialId} kodlu malzeme için`
|
||||
: "Tüm malzemeler için"}{" "}
|
||||
stok hareketlerini görüntüleyin
|
||||
{materialId ? `${materialId} kodlu malzeme için` : 'Tüm malzemeler için'} stok
|
||||
hareketlerini görüntüleyin
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-1.5">
|
||||
<button
|
||||
onClick={() => alert("Dışa aktarma özelliği yakında eklenecek")}
|
||||
onClick={() => alert('Dışa aktarma özelliği yakında eklenecek')}
|
||||
className="bg-gray-600 text-white px-2.5 py-1 rounded-md hover:bg-gray-700 flex items-center space-x-1.5 text-sm"
|
||||
>
|
||||
<FaDownload className="h-4 w-4" />
|
||||
|
|
@ -106,28 +95,28 @@ const MaterialMovements: React.FC<{ materialId?: string }> = ({
|
|||
<div className="grid grid-cols-1 md:grid-cols-4 gap-2">
|
||||
{[
|
||||
{
|
||||
title: "Toplam Giriş",
|
||||
value: "125 KG",
|
||||
color: "green",
|
||||
icon: "FaArrowDown",
|
||||
title: 'Toplam Giriş',
|
||||
value: '125 KG',
|
||||
color: 'green',
|
||||
icon: 'FaArrowDown',
|
||||
},
|
||||
{
|
||||
title: "Toplam Çıkış",
|
||||
value: "50 KG",
|
||||
color: "red",
|
||||
icon: "FaArrowUp",
|
||||
title: 'Toplam Çıkış',
|
||||
value: '50 KG',
|
||||
color: 'red',
|
||||
icon: 'FaArrowUp',
|
||||
},
|
||||
{
|
||||
title: "Net Bakiye",
|
||||
value: "75 KG",
|
||||
color: "blue",
|
||||
icon: "FaBalanceScale",
|
||||
title: 'Net Bakiye',
|
||||
value: '75 KG',
|
||||
color: 'blue',
|
||||
icon: 'FaBalanceScale',
|
||||
},
|
||||
{
|
||||
title: "Hareket Sayısı",
|
||||
title: 'Hareket Sayısı',
|
||||
value: movements.length,
|
||||
color: "gray",
|
||||
icon: "FaExchangeAlt",
|
||||
color: 'gray',
|
||||
icon: 'FaExchangeAlt',
|
||||
},
|
||||
].map((stat) => (
|
||||
<Widget
|
||||
|
|
@ -150,36 +139,26 @@ const MaterialMovements: React.FC<{ materialId?: string }> = ({
|
|||
<input
|
||||
type="date"
|
||||
value={filters.startDate}
|
||||
onChange={(e) =>
|
||||
setFilters({ ...filters, startDate: e.target.value })
|
||||
}
|
||||
onChange={(e) => setFilters({ ...filters, startDate: 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"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex-1">
|
||||
<label className="block text-xs font-medium text-gray-700 mb-1">
|
||||
Bitiş Tarihi
|
||||
</label>
|
||||
<label className="block text-xs font-medium text-gray-700 mb-1">Bitiş Tarihi</label>
|
||||
<input
|
||||
type="date"
|
||||
value={filters.endDate}
|
||||
onChange={(e) =>
|
||||
setFilters({ ...filters, endDate: e.target.value })
|
||||
}
|
||||
onChange={(e) => setFilters({ ...filters, endDate: 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"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex-1">
|
||||
<label className="block text-xs font-medium text-gray-700 mb-1">
|
||||
Hareket Tipi
|
||||
</label>
|
||||
<label className="block text-xs font-medium text-gray-700 mb-1">Hareket Tipi</label>
|
||||
<select
|
||||
value={filters.movementType}
|
||||
onChange={(e) =>
|
||||
setFilters({ ...filters, movementType: e.target.value })
|
||||
}
|
||||
onChange={(e) => setFilters({ ...filters, movementType: 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"
|
||||
>
|
||||
<option value="">Tümü</option>
|
||||
|
|
@ -190,14 +169,10 @@ const MaterialMovements: React.FC<{ materialId?: string }> = ({
|
|||
</div>
|
||||
|
||||
<div className="flex-1">
|
||||
<label className="block text-xs font-medium text-gray-700 mb-1">
|
||||
Depo
|
||||
</label>
|
||||
<label className="block text-xs font-medium text-gray-700 mb-1">Depo</label>
|
||||
<select
|
||||
value={filters.warehouseId}
|
||||
onChange={(e) =>
|
||||
setFilters({ ...filters, warehouseId: e.target.value })
|
||||
}
|
||||
onChange={(e) => setFilters({ ...filters, warehouseId: 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"
|
||||
>
|
||||
<option value="">Tümü</option>
|
||||
|
|
@ -257,8 +232,8 @@ const MaterialMovements: React.FC<{ materialId?: string }> = ({
|
|||
</thead>
|
||||
<tbody className="bg-white divide-y divide-gray-200">
|
||||
{movementsWithBalance.map((movement) => {
|
||||
const typeInfo = getMovementTypeInfo(movement.movementType);
|
||||
const Icon = typeInfo.icon;
|
||||
const typeInfo = getMovementTypeInfo(movement.movementType)
|
||||
const Icon = typeInfo.icon
|
||||
|
||||
return (
|
||||
<tr key={movement.id} className="hover:bg-gray-50 text-xs">
|
||||
|
|
@ -267,14 +242,10 @@ const MaterialMovements: React.FC<{ materialId?: string }> = ({
|
|||
<FaCalendar className="h-4 w-4 text-gray-400" />
|
||||
<div>
|
||||
<div className="text-xs font-medium text-gray-900">
|
||||
{new Date(
|
||||
movement.movementDate
|
||||
).toLocaleDateString("tr-TR")}
|
||||
{new Date(movement.movementDate).toLocaleDateString('tr-TR')}
|
||||
</div>
|
||||
<div className="text-xs text-gray-500">
|
||||
{new Date(
|
||||
movement.movementDate
|
||||
).toLocaleTimeString("tr-TR")}
|
||||
{new Date(movement.movementDate).toLocaleTimeString('tr-TR')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -298,17 +269,13 @@ const MaterialMovements: React.FC<{ materialId?: string }> = ({
|
|||
<div className="flex items-center space-x-1">
|
||||
<span
|
||||
className={`text-sm font-medium ${
|
||||
movement.quantity > 0
|
||||
? "text-green-600"
|
||||
: "text-red-600"
|
||||
movement.quantity > 0 ? 'text-green-600' : 'text-red-600'
|
||||
}`}
|
||||
>
|
||||
{movement.quantity > 0 ? "+" : ""}
|
||||
{movement.quantity > 0 ? '+' : ''}
|
||||
{movement.quantity}
|
||||
</span>
|
||||
<span className="text-xs text-gray-500">
|
||||
{movement.unit?.code}
|
||||
</span>
|
||||
<span className="text-xs text-gray-500">{movement.unit?.code}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-2 py-1.5 whitespace-nowrap">
|
||||
|
|
@ -351,12 +318,10 @@ const MaterialMovements: React.FC<{ materialId?: string }> = ({
|
|||
)}
|
||||
</td>
|
||||
<td className="px-2 py-1.5">
|
||||
<div className="text-xs text-gray-900">
|
||||
{movement.description}
|
||||
</div>
|
||||
<div className="text-xs text-gray-900">{movement.description}</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
@ -366,9 +331,7 @@ const MaterialMovements: React.FC<{ materialId?: string }> = ({
|
|||
{!loading && movements.length === 0 && (
|
||||
<div className="text-center py-10">
|
||||
<FaBox className="mx-auto h-10 w-10 text-gray-400" />
|
||||
<h3 className="mt-2 text-base font-medium text-gray-900">
|
||||
Hareket bulunamadı
|
||||
</h3>
|
||||
<h3 className="mt-2 text-base font-medium text-gray-900">Hareket bulunamadı</h3>
|
||||
<p className="mt-1 text-xs text-gray-500">
|
||||
Seçilen kriterlere uygun hareket kaydı bulunmuyor.
|
||||
</p>
|
||||
|
|
@ -376,7 +339,8 @@ const MaterialMovements: React.FC<{ materialId?: string }> = ({
|
|||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
export default MaterialMovements;
|
||||
export default MaterialMovements
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useState } from "react";
|
||||
import { PutawayStrategyEnum } from "../../../types/wm";
|
||||
import React, { useState } from 'react'
|
||||
import { PutawayStrategyEnum } from '../../../types/wm'
|
||||
import {
|
||||
FaSearch,
|
||||
FaPlus,
|
||||
|
|
@ -13,174 +13,161 @@ import {
|
|||
FaArrowDown,
|
||||
FaTh,
|
||||
FaList,
|
||||
} from "react-icons/fa";
|
||||
import { mockPutawayRules } from "../../../mocks/mockPutawayRules";
|
||||
import { mockWarehouses } from "../../../mocks/mockWarehouses";
|
||||
import { mockZones } from "../../../mocks/mockZones";
|
||||
import { mockLocations } from "../../../mocks/mockLocations";
|
||||
import { mockMaterialTypes } from "../../../mocks/mockMaterialTypes";
|
||||
import { mockMaterialGroups } from "../../../mocks/mockMaterialGroups";
|
||||
} from 'react-icons/fa'
|
||||
import { mockPutawayRules } from '../../../mocks/mockPutawayRules'
|
||||
import { mockWarehouses } from '../../../mocks/mockWarehouses'
|
||||
import { mockZones } from '../../../mocks/mockZones'
|
||||
import { mockLocations } from '../../../mocks/mockLocations'
|
||||
import { mockMaterialTypes } from '../../../mocks/mockMaterialTypes'
|
||||
import { mockMaterialGroups } from '../../../mocks/mockMaterialGroups'
|
||||
import {
|
||||
getPutawayStrategyColor,
|
||||
getPutawayStrategyText,
|
||||
getConditionTypeText,
|
||||
getConditionOperatorText,
|
||||
} from "../../../utils/erp";
|
||||
} from '../../../utils/erp'
|
||||
import { Container } from '@/components/shared'
|
||||
|
||||
const PutawayRules: React.FC = () => {
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [selectedStrategy, setSelectedStrategy] = useState<
|
||||
PutawayStrategyEnum | ""
|
||||
>("");
|
||||
const [viewMode, setViewMode] = useState<"grid" | "list">("grid");
|
||||
const [showRuleForm, setShowRuleForm] = useState(false);
|
||||
const [editingRuleId, setEditingRuleId] = useState<string | null>(null);
|
||||
const [selectedRule, setSelectedRule] = useState<string>("");
|
||||
const [searchTerm, setSearchTerm] = useState('')
|
||||
const [selectedStrategy, setSelectedStrategy] = useState<PutawayStrategyEnum | ''>('')
|
||||
const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid')
|
||||
const [showRuleForm, setShowRuleForm] = useState(false)
|
||||
const [editingRuleId, setEditingRuleId] = useState<string | null>(null)
|
||||
const [selectedRule, setSelectedRule] = useState<string>('')
|
||||
|
||||
// Form States
|
||||
const [formData, setFormData] = useState({
|
||||
name: "",
|
||||
ruleCode: "",
|
||||
description: "",
|
||||
warehouseId: "",
|
||||
materialTypeId: "",
|
||||
materialGroupId: "",
|
||||
name: '',
|
||||
ruleCode: '',
|
||||
description: '',
|
||||
warehouseId: '',
|
||||
materialTypeId: '',
|
||||
materialGroupId: '',
|
||||
priority: 1,
|
||||
targetZoneId: "",
|
||||
targetLocationId: "",
|
||||
targetZoneId: '',
|
||||
targetLocationId: '',
|
||||
strategy: PutawayStrategyEnum.FIFO,
|
||||
isActive: true,
|
||||
});
|
||||
})
|
||||
|
||||
// Helper functions for names
|
||||
const getWarehouseName = (warehouseId: string | undefined) => {
|
||||
if (!warehouseId) return "Belirtilmemiş";
|
||||
const warehouse = mockWarehouses.find((w) => w.id === warehouseId);
|
||||
return warehouse ? warehouse.name : `Depo ${warehouseId}`;
|
||||
};
|
||||
if (!warehouseId) return 'Belirtilmemiş'
|
||||
const warehouse = mockWarehouses.find((w) => w.id === warehouseId)
|
||||
return warehouse ? warehouse.name : `Depo ${warehouseId}`
|
||||
}
|
||||
|
||||
const getZoneName = (zoneId: string | undefined) => {
|
||||
if (!zoneId) return "Belirtilmemiş";
|
||||
const zone = mockZones.find((z) => z.id === zoneId);
|
||||
return zone ? zone.name : `Bölge ${zoneId}`;
|
||||
};
|
||||
if (!zoneId) return 'Belirtilmemiş'
|
||||
const zone = mockZones.find((z) => z.id === zoneId)
|
||||
return zone ? zone.name : `Bölge ${zoneId}`
|
||||
}
|
||||
|
||||
const getLocationName = (locationId: string | undefined) => {
|
||||
if (!locationId) return "Belirtilmemiş";
|
||||
const location = mockLocations.find((l) => l.id === locationId);
|
||||
return location ? location.name : `Lokasyon ${locationId}`;
|
||||
};
|
||||
if (!locationId) return 'Belirtilmemiş'
|
||||
const location = mockLocations.find((l) => l.id === locationId)
|
||||
return location ? location.name : `Lokasyon ${locationId}`
|
||||
}
|
||||
|
||||
const getMaterialTypeName = (materialTypeId: string | undefined) => {
|
||||
if (!materialTypeId) return "Belirtilmemiş";
|
||||
const materialType = mockMaterialTypes.find(
|
||||
(mt) => mt.id === materialTypeId
|
||||
);
|
||||
return materialType ? materialType.name : `Tip ${materialTypeId}`;
|
||||
};
|
||||
if (!materialTypeId) return 'Belirtilmemiş'
|
||||
const materialType = mockMaterialTypes.find((mt) => mt.id === materialTypeId)
|
||||
return materialType ? materialType.name : `Tip ${materialTypeId}`
|
||||
}
|
||||
|
||||
const getMaterialGroupName = (materialGroupId: string | undefined) => {
|
||||
if (!materialGroupId) return "Belirtilmemiş";
|
||||
const materialGroup = mockMaterialGroups.find(
|
||||
(mg) => mg.id === materialGroupId
|
||||
);
|
||||
return materialGroup ? materialGroup.name : `Grup ${materialGroupId}`;
|
||||
};
|
||||
if (!materialGroupId) return 'Belirtilmemiş'
|
||||
const materialGroup = mockMaterialGroups.find((mg) => mg.id === materialGroupId)
|
||||
return materialGroup ? materialGroup.name : `Grup ${materialGroupId}`
|
||||
}
|
||||
|
||||
// Form Functions
|
||||
const resetForm = () => {
|
||||
setFormData({
|
||||
name: "",
|
||||
ruleCode: "",
|
||||
description: "",
|
||||
warehouseId: "",
|
||||
materialTypeId: "",
|
||||
materialGroupId: "",
|
||||
name: '',
|
||||
ruleCode: '',
|
||||
description: '',
|
||||
warehouseId: '',
|
||||
materialTypeId: '',
|
||||
materialGroupId: '',
|
||||
priority: 1,
|
||||
targetZoneId: "",
|
||||
targetLocationId: "",
|
||||
targetZoneId: '',
|
||||
targetLocationId: '',
|
||||
strategy: PutawayStrategyEnum.FIFO,
|
||||
isActive: true,
|
||||
});
|
||||
};
|
||||
})
|
||||
}
|
||||
|
||||
const handleEditRule = (ruleId: string) => {
|
||||
const rule = mockPutawayRules.find((r) => r.id === ruleId);
|
||||
const rule = mockPutawayRules.find((r) => r.id === ruleId)
|
||||
if (rule) {
|
||||
setFormData({
|
||||
name: rule.name,
|
||||
ruleCode: rule.code,
|
||||
description: rule.description || "",
|
||||
description: rule.description || '',
|
||||
warehouseId: rule.warehouseId,
|
||||
materialTypeId: rule.materialTypeId || "",
|
||||
materialGroupId: rule.materialGroupId || "",
|
||||
materialTypeId: rule.materialTypeId || '',
|
||||
materialGroupId: rule.materialGroupId || '',
|
||||
priority: rule.priority,
|
||||
targetZoneId: rule.targetZoneId || "",
|
||||
targetLocationId: rule.targetLocationId || "",
|
||||
targetZoneId: rule.targetZoneId || '',
|
||||
targetLocationId: rule.targetLocationId || '',
|
||||
strategy: rule.strategy,
|
||||
isActive: rule.isActive,
|
||||
});
|
||||
})
|
||||
}
|
||||
setEditingRuleId(ruleId)
|
||||
setShowRuleForm(true)
|
||||
}
|
||||
setEditingRuleId(ruleId);
|
||||
setShowRuleForm(true);
|
||||
};
|
||||
|
||||
const handleNewRule = () => {
|
||||
resetForm();
|
||||
setEditingRuleId(null);
|
||||
setShowRuleForm(true);
|
||||
};
|
||||
resetForm()
|
||||
setEditingRuleId(null)
|
||||
setShowRuleForm(true)
|
||||
}
|
||||
|
||||
const handleCloseForm = () => {
|
||||
setShowRuleForm(false);
|
||||
setEditingRuleId(null);
|
||||
resetForm();
|
||||
};
|
||||
setShowRuleForm(false)
|
||||
setEditingRuleId(null)
|
||||
resetForm()
|
||||
}
|
||||
|
||||
const handleSubmitForm = () => {
|
||||
// Form validation and submission logic here
|
||||
console.log("Form Data:", formData);
|
||||
handleCloseForm();
|
||||
};
|
||||
console.log('Form Data:', formData)
|
||||
handleCloseForm()
|
||||
}
|
||||
|
||||
const filteredRules = mockPutawayRules.filter((rule) => {
|
||||
const matchesSearch =
|
||||
rule.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
rule.code.toLowerCase().includes(searchTerm.toLowerCase());
|
||||
const matchesStrategy =
|
||||
selectedStrategy === "" || rule.strategy === selectedStrategy;
|
||||
return matchesSearch && matchesStrategy;
|
||||
});
|
||||
rule.code.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
const matchesStrategy = selectedStrategy === '' || rule.strategy === selectedStrategy
|
||||
return matchesSearch && matchesStrategy
|
||||
})
|
||||
|
||||
const RuleDetailModal = () => {
|
||||
const rule = mockPutawayRules.find((r) => r.id === selectedRule);
|
||||
const rule = mockPutawayRules.find((r) => r.id === selectedRule)
|
||||
|
||||
if (!selectedRule || !rule) return null;
|
||||
if (!selectedRule || !rule) return null
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 overflow-y-auto">
|
||||
<div className="flex items-center justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
|
||||
<div
|
||||
className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"
|
||||
onClick={() => setSelectedRule("")}
|
||||
onClick={() => setSelectedRule('')}
|
||||
/>
|
||||
|
||||
<div className="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-4xl sm:w-full">
|
||||
<div className="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-lg font-medium text-gray-900">
|
||||
{rule.name} - Kural Detayları
|
||||
</h3>
|
||||
<h3 className="text-lg font-medium text-gray-900">{rule.name} - Kural Detayları</h3>
|
||||
<button
|
||||
onClick={() => setSelectedRule("")}
|
||||
onClick={() => setSelectedRule('')}
|
||||
className="text-gray-400 hover:text-gray-600"
|
||||
>
|
||||
<svg
|
||||
className="w-5 h-5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
|
|
@ -194,9 +181,7 @@ const PutawayRules: React.FC = () => {
|
|||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||
{/* Rule Info */}
|
||||
<div className="bg-gray-50 rounded-lg p-3">
|
||||
<h4 className="font-medium text-sm text-gray-900 mb-2">
|
||||
Kural Bilgileri
|
||||
</h4>
|
||||
<h4 className="font-medium text-sm text-gray-900 mb-2">Kural Bilgileri</h4>
|
||||
<div className="space-y-2 text-sm">
|
||||
<div>
|
||||
<strong>Kod:</strong> {rule.code}
|
||||
|
|
@ -211,7 +196,7 @@ const PutawayRules: React.FC = () => {
|
|||
<strong>Strateji:</strong>
|
||||
<span
|
||||
className={`ml-2 inline-flex px-2 py-1 text-xs font-medium rounded-full ${getPutawayStrategyColor(
|
||||
rule.strategy
|
||||
rule.strategy,
|
||||
)}`}
|
||||
>
|
||||
{getPutawayStrategyText(rule.strategy)}
|
||||
|
|
@ -241,14 +226,9 @@ const PutawayRules: React.FC = () => {
|
|||
</h4>
|
||||
<div className="space-y-2">
|
||||
{rule.conditions.map((condition, index) => (
|
||||
<div
|
||||
key={condition.id}
|
||||
className="bg-white rounded p-2 border"
|
||||
>
|
||||
<div key={condition.id} className="bg-white rounded p-2 border">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm font-medium">
|
||||
Koşul {index + 1}
|
||||
</span>
|
||||
<span className="text-sm font-medium">Koşul {index + 1}</span>
|
||||
</div>
|
||||
<div className="mt-2 text-sm text-gray-600">
|
||||
<div className="flex items-center gap-2">
|
||||
|
|
@ -278,31 +258,25 @@ const PutawayRules: React.FC = () => {
|
|||
|
||||
{/* Target Info */}
|
||||
<div className="mt-4 bg-gray-50 rounded-lg p-3">
|
||||
<h4 className="font-medium text-sm text-gray-900 mb-2">
|
||||
Hedef Lokasyon
|
||||
</h4>
|
||||
<h4 className="font-medium text-sm text-gray-900 mb-2">Hedef Lokasyon</h4>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-3 text-sm">
|
||||
<div>
|
||||
<strong>Depo:</strong> {getWarehouseName(rule.warehouseId)}
|
||||
</div>
|
||||
<div>
|
||||
<strong>Hedef Bölge:</strong>{" "}
|
||||
{getZoneName(rule.targetZoneId)}
|
||||
<strong>Hedef Bölge:</strong> {getZoneName(rule.targetZoneId)}
|
||||
</div>
|
||||
<div>
|
||||
<strong>Hedef Lokasyon:</strong>{" "}
|
||||
{getLocationName(rule.targetLocationId)}
|
||||
<strong>Hedef Lokasyon:</strong> {getLocationName(rule.targetLocationId)}
|
||||
</div>
|
||||
{rule.materialTypeId && (
|
||||
<div>
|
||||
<strong>Malzeme Tipi:</strong>{" "}
|
||||
{getMaterialTypeName(rule.materialTypeId)}
|
||||
<strong>Malzeme Tipi:</strong> {getMaterialTypeName(rule.materialTypeId)}
|
||||
</div>
|
||||
)}
|
||||
{rule.materialGroupId && (
|
||||
<div>
|
||||
<strong>Malzeme Grubu:</strong>{" "}
|
||||
{getMaterialGroupName(rule.materialGroupId)}
|
||||
<strong>Malzeme Grubu:</strong> {getMaterialGroupName(rule.materialGroupId)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -311,40 +285,37 @@ const PutawayRules: React.FC = () => {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4 pt-2">
|
||||
<Container>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold text-gray-900">
|
||||
Yerleştirme Kuralları
|
||||
</h2>
|
||||
<p className="text-gray-600">
|
||||
Malzeme yerleştirme kurallarını tanımlayın ve yönetin
|
||||
</p>
|
||||
<h2 className="text-2xl font-bold text-gray-900">Yerleştirme Kuralları</h2>
|
||||
<p className="text-gray-600">Malzeme yerleştirme kurallarını tanımlayın ve yönetin</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{/* View Mode Toggle */}
|
||||
<div className="flex items-center bg-gray-100 rounded-lg p-1">
|
||||
<button
|
||||
onClick={() => setViewMode("grid")}
|
||||
onClick={() => setViewMode('grid')}
|
||||
className={`p-1.5 rounded-md ${
|
||||
viewMode === "grid"
|
||||
? "bg-white text-blue-600 shadow-sm"
|
||||
: "text-gray-500 hover:text-gray-700"
|
||||
viewMode === 'grid'
|
||||
? 'bg-white text-blue-600 shadow-sm'
|
||||
: 'text-gray-500 hover:text-gray-700'
|
||||
}`}
|
||||
title="Kart Görünümü"
|
||||
>
|
||||
<FaTh className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setViewMode("list")}
|
||||
onClick={() => setViewMode('list')}
|
||||
className={`p-1.5 rounded-md ${
|
||||
viewMode === "list"
|
||||
? "bg-white text-blue-600 shadow-sm"
|
||||
: "text-gray-500 hover:text-gray-700"
|
||||
viewMode === 'list'
|
||||
? 'bg-white text-blue-600 shadow-sm'
|
||||
: 'text-gray-500 hover:text-gray-700'
|
||||
}`}
|
||||
title="Liste Görünümü"
|
||||
>
|
||||
|
|
@ -375,9 +346,7 @@ const PutawayRules: React.FC = () => {
|
|||
</div>
|
||||
<select
|
||||
value={selectedStrategy}
|
||||
onChange={(e) =>
|
||||
setSelectedStrategy(e.target.value as PutawayStrategyEnum | "")
|
||||
}
|
||||
onChange={(e) => setSelectedStrategy(e.target.value as PutawayStrategyEnum | '')}
|
||||
className="px-4 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
>
|
||||
<option value="">Tüm Stratejiler</option>
|
||||
|
|
@ -394,7 +363,7 @@ const PutawayRules: React.FC = () => {
|
|||
</div>
|
||||
|
||||
{/* Rules Display */}
|
||||
{viewMode === "grid" ? (
|
||||
{viewMode === 'grid' ? (
|
||||
// Grid View (Kart Görünümü)
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
{filteredRules.map((rule) => (
|
||||
|
|
@ -431,7 +400,7 @@ const PutawayRules: React.FC = () => {
|
|||
|
||||
<span
|
||||
className={`inline-flex px-2 py-1 text-xs font-medium rounded-full ${getPutawayStrategyColor(
|
||||
rule.strategy
|
||||
rule.strategy,
|
||||
)}`}
|
||||
>
|
||||
{getPutawayStrategyText(rule.strategy)}
|
||||
|
|
@ -452,9 +421,7 @@ const PutawayRules: React.FC = () => {
|
|||
<span className="font-medium">
|
||||
{getConditionTypeText(condition.conditionType)}
|
||||
</span>
|
||||
<span>
|
||||
{getConditionOperatorText(condition.operator)}
|
||||
</span>
|
||||
<span>{getConditionOperatorText(condition.operator)}</span>
|
||||
<span className="font-medium">{condition.value}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -466,9 +433,7 @@ const PutawayRules: React.FC = () => {
|
|||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-xs text-gray-500">
|
||||
Koşul tanımlanmamış
|
||||
</div>
|
||||
<div className="text-xs text-gray-500">Koşul tanımlanmamış</div>
|
||||
)}
|
||||
|
||||
{/* Target Info */}
|
||||
|
|
@ -477,25 +442,15 @@ const PutawayRules: React.FC = () => {
|
|||
<div className="bg-gray-50 rounded p-2">
|
||||
<div className="text-xs text-gray-600 space-y-0.5">
|
||||
<div>Depo: {getWarehouseName(rule.warehouseId)}</div>
|
||||
{rule.targetZoneId && (
|
||||
<div>Bölge: {getZoneName(rule.targetZoneId)}</div>
|
||||
)}
|
||||
{rule.targetZoneId && <div>Bölge: {getZoneName(rule.targetZoneId)}</div>}
|
||||
{rule.targetLocationId && (
|
||||
<div>
|
||||
Lokasyon: {getLocationName(rule.targetLocationId)}
|
||||
</div>
|
||||
<div>Lokasyon: {getLocationName(rule.targetLocationId)}</div>
|
||||
)}
|
||||
{rule.materialTypeId && (
|
||||
<div>
|
||||
Malzeme Tipi:{" "}
|
||||
{getMaterialTypeName(rule.materialTypeId)}
|
||||
</div>
|
||||
<div>Malzeme Tipi: {getMaterialTypeName(rule.materialTypeId)}</div>
|
||||
)}
|
||||
{rule.materialGroupId && (
|
||||
<div>
|
||||
Malzeme Grubu:{" "}
|
||||
{getMaterialGroupName(rule.materialGroupId)}
|
||||
</div>
|
||||
<div>Malzeme Grubu: {getMaterialGroupName(rule.materialGroupId)}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -585,16 +540,14 @@ const PutawayRules: React.FC = () => {
|
|||
>
|
||||
{rule.name}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500">
|
||||
{rule.code}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500">{rule.code}</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-3 py-2 whitespace-nowrap">
|
||||
<span
|
||||
className={`inline-flex px-2 py-1 text-xs font-medium rounded-full ${getPutawayStrategyColor(
|
||||
rule.strategy
|
||||
rule.strategy,
|
||||
)}`}
|
||||
>
|
||||
{getPutawayStrategyText(rule.strategy)}
|
||||
|
|
@ -605,13 +558,9 @@ const PutawayRules: React.FC = () => {
|
|||
</td>
|
||||
<td className="px-3 py-2 whitespace-nowrap text-sm text-gray-900">
|
||||
<div className="space-y-1">
|
||||
{rule.targetZoneId && (
|
||||
<div>Bölge: {getZoneName(rule.targetZoneId)}</div>
|
||||
)}
|
||||
{rule.targetZoneId && <div>Bölge: {getZoneName(rule.targetZoneId)}</div>}
|
||||
{rule.targetLocationId && (
|
||||
<div>
|
||||
Lokasyon: {getLocationName(rule.targetLocationId)}
|
||||
</div>
|
||||
<div>Lokasyon: {getLocationName(rule.targetLocationId)}</div>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
|
|
@ -620,9 +569,8 @@ const PutawayRules: React.FC = () => {
|
|||
<div className="space-y-1">
|
||||
{rule.conditions.slice(0, 1).map((condition) => (
|
||||
<div key={condition.id} className="text-xs">
|
||||
{getConditionTypeText(condition.conditionType)}{" "}
|
||||
{getConditionOperatorText(condition.operator)}{" "}
|
||||
{condition.value}
|
||||
{getConditionTypeText(condition.conditionType)}{' '}
|
||||
{getConditionOperatorText(condition.operator)} {condition.value}
|
||||
</div>
|
||||
))}
|
||||
{rule.conditions.length > 1 && (
|
||||
|
|
@ -645,9 +593,7 @@ const PutawayRules: React.FC = () => {
|
|||
{rule.isActive ? (
|
||||
<>
|
||||
<FaCheckCircle className="w-4 h-4 text-green-500 mr-2" />
|
||||
<span className="text-sm text-green-600">
|
||||
Aktif
|
||||
</span>
|
||||
<span className="text-sm text-green-600">Aktif</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
|
|
@ -692,9 +638,7 @@ const PutawayRules: React.FC = () => {
|
|||
{filteredRules.length === 0 && (
|
||||
<div className="text-center py-12">
|
||||
<FaCog className="w-12 h-12 text-gray-400 mx-auto mb-4" />
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-2">
|
||||
Kural bulunamadı
|
||||
</h3>
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-2">Kural bulunamadı</h3>
|
||||
<p className="text-gray-500 mb-4">
|
||||
Arama kriterlerinize uygun yerleştirme kuralı bulunamadı.
|
||||
</p>
|
||||
|
|
@ -706,6 +650,7 @@ const PutawayRules: React.FC = () => {
|
|||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Rule Detail Modal */}
|
||||
<RuleDetailModal />
|
||||
|
|
@ -717,8 +662,8 @@ const PutawayRules: React.FC = () => {
|
|||
<div
|
||||
className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"
|
||||
onClick={() => {
|
||||
setShowRuleForm(false);
|
||||
setEditingRuleId(null);
|
||||
setShowRuleForm(false)
|
||||
setEditingRuleId(null)
|
||||
}}
|
||||
/>
|
||||
|
||||
|
|
@ -726,20 +671,13 @@ const PutawayRules: React.FC = () => {
|
|||
<div className="bg-white rounded-lg w-full max-w-4xl mx-4 max-h-[90vh] overflow-y-auto">
|
||||
<div className="flex items-center justify-between p-4 border-b">
|
||||
<h3 className="text-base font-medium text-gray-900">
|
||||
{editingRuleId
|
||||
? "Yerleştirme Kuralını Güncelle"
|
||||
: "Yeni Yerleştirme Kuralı"}
|
||||
{editingRuleId ? 'Yerleştirme Kuralını Güncelle' : 'Yeni Yerleştirme Kuralı'}
|
||||
</h3>
|
||||
<button
|
||||
onClick={handleCloseForm}
|
||||
className="text-gray-400 hover:text-gray-600 p-1 rounded-full"
|
||||
>
|
||||
<svg
|
||||
className="w-6 h-6"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<svg className="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
|
|
@ -761,9 +699,7 @@ const PutawayRules: React.FC = () => {
|
|||
<input
|
||||
type="text"
|
||||
value={formData.name}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, name: e.target.value })
|
||||
}
|
||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
placeholder="Kural adını girin"
|
||||
required
|
||||
|
|
@ -776,9 +712,7 @@ const PutawayRules: React.FC = () => {
|
|||
<input
|
||||
type="text"
|
||||
value={formData.ruleCode}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, ruleCode: e.target.value })
|
||||
}
|
||||
onChange={(e) => setFormData({ ...formData, ruleCode: e.target.value })}
|
||||
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
placeholder="PR001"
|
||||
required
|
||||
|
|
@ -787,9 +721,7 @@ const PutawayRules: React.FC = () => {
|
|||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Açıklama
|
||||
</label>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Açıklama</label>
|
||||
<textarea
|
||||
value={formData.description}
|
||||
onChange={(e) =>
|
||||
|
|
@ -806,9 +738,7 @@ const PutawayRules: React.FC = () => {
|
|||
|
||||
{/* Warehouse and Location Settings */}
|
||||
<div className="border-t pt-4">
|
||||
<h4 className="text-base font-medium text-gray-900 mb-2">
|
||||
Lokasyon Ayarları
|
||||
</h4>
|
||||
<h4 className="text-base font-medium text-gray-900 mb-2">Lokasyon Ayarları</h4>
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
|
|
@ -820,9 +750,9 @@ const PutawayRules: React.FC = () => {
|
|||
setFormData({
|
||||
...formData,
|
||||
warehouseId: e.target.value,
|
||||
targetZoneId: "",
|
||||
targetLocationId: "",
|
||||
});
|
||||
targetZoneId: '',
|
||||
targetLocationId: '',
|
||||
})
|
||||
}}
|
||||
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
required
|
||||
|
|
@ -847,17 +777,14 @@ const PutawayRules: React.FC = () => {
|
|||
setFormData({
|
||||
...formData,
|
||||
targetZoneId: e.target.value,
|
||||
targetLocationId: "",
|
||||
});
|
||||
targetLocationId: '',
|
||||
})
|
||||
}}
|
||||
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
>
|
||||
<option value="">Bölge seçin</option>
|
||||
{mockZones
|
||||
.filter(
|
||||
(zone) =>
|
||||
zone.warehouseId === formData.warehouseId
|
||||
)
|
||||
.filter((zone) => zone.warehouseId === formData.warehouseId)
|
||||
.map((zone) => (
|
||||
<option key={zone.id} value={zone.id}>
|
||||
{zone.name}
|
||||
|
|
@ -884,10 +811,7 @@ const PutawayRules: React.FC = () => {
|
|||
>
|
||||
<option value="">Lokasyon seçin</option>
|
||||
{mockLocations
|
||||
.filter(
|
||||
(location) =>
|
||||
location.zoneId === formData.targetZoneId
|
||||
)
|
||||
.filter((location) => location.zoneId === formData.targetZoneId)
|
||||
.map((location) => (
|
||||
<option key={location.id} value={location.id}>
|
||||
{location.name}
|
||||
|
|
@ -901,9 +825,7 @@ const PutawayRules: React.FC = () => {
|
|||
|
||||
{/* Material Settings */}
|
||||
<div className="border-t pt-4">
|
||||
<h4 className="text-base font-medium text-gray-900 mb-2">
|
||||
Malzeme Ayarları
|
||||
</h4>
|
||||
<h4 className="text-base font-medium text-gray-900 mb-2">Malzeme Ayarları</h4>
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
|
|
@ -982,9 +904,7 @@ const PutawayRules: React.FC = () => {
|
|||
<option value={PutawayStrategyEnum.NearestLocation}>
|
||||
En Yakın Lokasyon
|
||||
</option>
|
||||
<option value={PutawayStrategyEnum.EmptyLocation}>
|
||||
Boş Lokasyon
|
||||
</option>
|
||||
<option value={PutawayStrategyEnum.EmptyLocation}>Boş Lokasyon</option>
|
||||
<option value={PutawayStrategyEnum.SameProduct}>
|
||||
Aynı Ürün Gruplaması
|
||||
</option>
|
||||
|
|
@ -1027,10 +947,7 @@ const PutawayRules: React.FC = () => {
|
|||
}
|
||||
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
|
||||
/>
|
||||
<label
|
||||
htmlFor="isActive"
|
||||
className="ml-2 block text-sm text-gray-900"
|
||||
>
|
||||
<label htmlFor="isActive" className="ml-2 block text-sm text-gray-900">
|
||||
Bu kural aktif
|
||||
</label>
|
||||
</div>
|
||||
|
|
@ -1051,7 +968,7 @@ const PutawayRules: React.FC = () => {
|
|||
onClick={handleSubmitForm}
|
||||
className="px-4 py-1.5 text-sm font-medium text-white bg-blue-600 border border-transparent rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
>
|
||||
{editingRuleId ? "Güncelle" : "Kaydet"}
|
||||
{editingRuleId ? 'Güncelle' : 'Kaydet'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1059,8 +976,8 @@ const PutawayRules: React.FC = () => {
|
|||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
export default PutawayRules;
|
||||
export default PutawayRules
|
||||
|
|
|
|||
|
|
@ -1,24 +1,24 @@
|
|||
import React from "react";
|
||||
import { useFormik } from "formik";
|
||||
import * as Yup from "yup";
|
||||
import { FaSave, FaTimes } from "react-icons/fa";
|
||||
import { SerialStatusEnum, MmSerialNumber, MmMaterial } from "../../../types/mm";
|
||||
import React from 'react'
|
||||
import { useFormik } from 'formik'
|
||||
import * as Yup from 'yup'
|
||||
import { FaSave, FaTimes } from 'react-icons/fa'
|
||||
import { SerialStatusEnum, MmSerialNumber, MmMaterial } from '../../../types/mm'
|
||||
|
||||
export interface SerialFormProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onSave: (serial: MmSerialNumber) => void;
|
||||
onUpdate?: (serial: MmSerialNumber) => void;
|
||||
materials: MmMaterial[];
|
||||
lots?: { id: string; label: string }[];
|
||||
initial?: MmSerialNumber | null;
|
||||
mode?: "create" | "edit" | "view";
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
onSave: (serial: MmSerialNumber) => void
|
||||
onUpdate?: (serial: MmSerialNumber) => void
|
||||
materials: MmMaterial[]
|
||||
lots?: { id: string; label: string }[]
|
||||
initial?: MmSerialNumber | null
|
||||
mode?: 'create' | 'edit' | 'view'
|
||||
}
|
||||
|
||||
const validationSchema = Yup.object({
|
||||
materialId: Yup.string().required("Malzeme seçimi zorunlu"),
|
||||
serialNumber: Yup.string().required("Seri numarası zorunlu"),
|
||||
});
|
||||
materialId: Yup.string().required('Malzeme seçimi zorunlu'),
|
||||
serialNumber: Yup.string().required('Seri numarası zorunlu'),
|
||||
})
|
||||
|
||||
const SerialForm: React.FC<SerialFormProps> = ({
|
||||
isOpen,
|
||||
|
|
@ -28,16 +28,16 @@ const SerialForm: React.FC<SerialFormProps> = ({
|
|||
materials,
|
||||
lots = [],
|
||||
initial = null,
|
||||
mode = "create",
|
||||
mode = 'create',
|
||||
}) => {
|
||||
const formik = useFormik({
|
||||
initialValues: {
|
||||
materialId: "",
|
||||
serialNumber: "",
|
||||
lotId: lots.length ? lots[0].id : "",
|
||||
productionDate: "",
|
||||
warrantyExpiryDate: "",
|
||||
currentLocationId: "",
|
||||
materialId: '',
|
||||
serialNumber: '',
|
||||
lotId: lots.length ? lots[0].id : '',
|
||||
productionDate: '',
|
||||
warrantyExpiryDate: '',
|
||||
currentLocationId: '',
|
||||
status: SerialStatusEnum.Available,
|
||||
isActive: true,
|
||||
},
|
||||
|
|
@ -48,51 +48,49 @@ const SerialForm: React.FC<SerialFormProps> = ({
|
|||
materialId: values.materialId,
|
||||
serialNumber: values.serialNumber,
|
||||
lotId: values.lotId || undefined,
|
||||
productionDate: values.productionDate
|
||||
? new Date(values.productionDate)
|
||||
: new Date(),
|
||||
productionDate: values.productionDate ? new Date(values.productionDate) : new Date(),
|
||||
warrantyExpiryDate: values.warrantyExpiryDate
|
||||
? new Date(values.warrantyExpiryDate)
|
||||
: undefined,
|
||||
currentLocationId: values.currentLocationId || undefined,
|
||||
status: values.status,
|
||||
isActive: !!values.isActive,
|
||||
};
|
||||
}
|
||||
|
||||
// simulate API
|
||||
await new Promise((r) => setTimeout(r, 300));
|
||||
if (mode === "edit" && onUpdate) {
|
||||
onUpdate(newSerial);
|
||||
await new Promise((r) => setTimeout(r, 300))
|
||||
if (mode === 'edit' && onUpdate) {
|
||||
onUpdate(newSerial)
|
||||
} else {
|
||||
onSave(newSerial);
|
||||
onSave(newSerial)
|
||||
}
|
||||
onClose();
|
||||
onClose()
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
// sync initial values when editing/viewing
|
||||
React.useEffect(() => {
|
||||
if (initial) {
|
||||
const src = initial;
|
||||
const src = initial
|
||||
formik.setValues({
|
||||
materialId: src.materialId || "",
|
||||
serialNumber: src.serialNumber || "",
|
||||
lotId: src.lotId || (lots.length ? lots[0].id : ""),
|
||||
materialId: src.materialId || '',
|
||||
serialNumber: src.serialNumber || '',
|
||||
lotId: src.lotId || (lots.length ? lots[0].id : ''),
|
||||
productionDate: src.productionDate
|
||||
? new Date(src.productionDate).toISOString().slice(0, 10)
|
||||
: "",
|
||||
: '',
|
||||
warrantyExpiryDate: src.warrantyExpiryDate
|
||||
? new Date(src.warrantyExpiryDate).toISOString().slice(0, 10)
|
||||
: "",
|
||||
currentLocationId: src.currentLocationId || "",
|
||||
: '',
|
||||
currentLocationId: src.currentLocationId || '',
|
||||
status: src.status || SerialStatusEnum.Available,
|
||||
isActive: !!src.isActive,
|
||||
});
|
||||
})
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [initial]);
|
||||
}, [initial])
|
||||
|
||||
if (!isOpen) return null;
|
||||
if (!isOpen) return null
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
||||
|
|
@ -102,18 +100,18 @@ const SerialForm: React.FC<SerialFormProps> = ({
|
|||
<div className="flex items-center justify-between p-2 border-b">
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-gray-900">
|
||||
{mode === "create"
|
||||
? "Yeni Seri Kaydı"
|
||||
: mode === "edit"
|
||||
? "Seri Düzenle"
|
||||
: "Seri Detayı"}
|
||||
{mode === 'create'
|
||||
? 'Yeni Seri Kaydı'
|
||||
: mode === 'edit'
|
||||
? 'Seri Düzenle'
|
||||
: 'Seri Detayı'}
|
||||
</h2>
|
||||
<p className="text-sm text-gray-600">
|
||||
{mode === "create"
|
||||
? "Seri numarası girin"
|
||||
: mode === "edit"
|
||||
? "Mevcut seri bilgisini güncelleyin"
|
||||
: "Seri bilgileri (sadece gösterim)"}
|
||||
{mode === 'create'
|
||||
? 'Seri numarası girin'
|
||||
: mode === 'edit'
|
||||
? 'Mevcut seri bilgisini güncelleyin'
|
||||
: 'Seri bilgileri (sadece gösterim)'}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
|
|
@ -124,7 +122,7 @@ const SerialForm: React.FC<SerialFormProps> = ({
|
|||
>
|
||||
<FaTimes className="inline mr-1 h-3 w-3" /> Kapat
|
||||
</button>
|
||||
{mode !== "view" && (
|
||||
{mode !== 'view' && (
|
||||
<button
|
||||
type="submit"
|
||||
className="px-2.5 py-1 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700 flex items-center"
|
||||
|
|
@ -137,12 +135,10 @@ const SerialForm: React.FC<SerialFormProps> = ({
|
|||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 p-2 gap-x-3 gap-y-2">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">
|
||||
Malzeme *
|
||||
</label>
|
||||
<label className="block text-sm font-medium text-gray-700">Malzeme *</label>
|
||||
<select
|
||||
{...formik.getFieldProps("materialId")}
|
||||
disabled={mode === "view"}
|
||||
{...formik.getFieldProps('materialId')}
|
||||
disabled={mode === 'view'}
|
||||
className="w-full border rounded-md px-2 py-1 text-sm"
|
||||
>
|
||||
<option value="">Seçiniz...</option>
|
||||
|
|
@ -155,24 +151,20 @@ const SerialForm: React.FC<SerialFormProps> = ({
|
|||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">
|
||||
Seri Numarası *
|
||||
</label>
|
||||
<label className="block text-sm font-medium text-gray-700">Seri Numarası *</label>
|
||||
<input
|
||||
type="text"
|
||||
{...formik.getFieldProps("serialNumber")}
|
||||
disabled={mode === "view"}
|
||||
{...formik.getFieldProps('serialNumber')}
|
||||
disabled={mode === 'view'}
|
||||
className="w-full border rounded-md px-2 py-1 text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">
|
||||
Lot
|
||||
</label>
|
||||
<label className="block text-sm font-medium text-gray-700">Lot</label>
|
||||
<select
|
||||
{...formik.getFieldProps("lotId")}
|
||||
disabled={mode === "view"}
|
||||
{...formik.getFieldProps('lotId')}
|
||||
disabled={mode === 'view'}
|
||||
className="w-full border rounded-md px-2 py-1 text-sm"
|
||||
>
|
||||
<option value="">Seçiniz...</option>
|
||||
|
|
@ -185,36 +177,30 @@ const SerialForm: React.FC<SerialFormProps> = ({
|
|||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">
|
||||
Üretim Tarihi
|
||||
</label>
|
||||
<label className="block text-sm font-medium text-gray-700">Üretim Tarihi</label>
|
||||
<input
|
||||
type="date"
|
||||
{...formik.getFieldProps("productionDate")}
|
||||
disabled={mode === "view"}
|
||||
{...formik.getFieldProps('productionDate')}
|
||||
disabled={mode === 'view'}
|
||||
className="w-full border rounded-md px-2 py-1 text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">
|
||||
Garanti Bitiş
|
||||
</label>
|
||||
<label className="block text-sm font-medium text-gray-700">Garanti Bitiş</label>
|
||||
<input
|
||||
type="date"
|
||||
{...formik.getFieldProps("warrantyExpiryDate")}
|
||||
disabled={mode === "view"}
|
||||
{...formik.getFieldProps('warrantyExpiryDate')}
|
||||
disabled={mode === 'view'}
|
||||
className="w-full border rounded-md px-2 py-1 text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">
|
||||
Durum
|
||||
</label>
|
||||
<label className="block text-sm font-medium text-gray-700">Durum</label>
|
||||
<select
|
||||
{...formik.getFieldProps("status")}
|
||||
disabled={mode === "view"}
|
||||
{...formik.getFieldProps('status')}
|
||||
disabled={mode === 'view'}
|
||||
className="w-full border rounded-md px-2 py-1 text-sm"
|
||||
>
|
||||
<option value={SerialStatusEnum.Available}>Müsait</option>
|
||||
|
|
@ -227,7 +213,7 @@ const SerialForm: React.FC<SerialFormProps> = ({
|
|||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default SerialForm;
|
||||
export default SerialForm
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,10 +1,5 @@
|
|||
import React, { useState } from "react";
|
||||
import {
|
||||
WmWarehouse,
|
||||
WarehouseTypeEnum,
|
||||
WmZone,
|
||||
WmLocation,
|
||||
} from "../../../types/wm";
|
||||
import React, { useState } from 'react'
|
||||
import { WmWarehouse, WarehouseTypeEnum, WmZone, WmLocation } from '../../../types/wm'
|
||||
import {
|
||||
FaPlus,
|
||||
FaSearch,
|
||||
|
|
@ -18,101 +13,91 @@ import {
|
|||
FaEye,
|
||||
FaTh,
|
||||
FaList,
|
||||
} from "react-icons/fa";
|
||||
import { WarehouseModal, ZoneModal, LocationModal } from "./modals";
|
||||
import { mockWarehouses } from "../../../mocks/mockWarehouses";
|
||||
import { mockZones } from "../../../mocks/mockZones";
|
||||
import { mockLocations } from "../../../mocks/mockLocations";
|
||||
} from 'react-icons/fa'
|
||||
import { WarehouseModal, ZoneModal, LocationModal } from './modals'
|
||||
import { mockWarehouses } from '../../../mocks/mockWarehouses'
|
||||
import { mockZones } from '../../../mocks/mockZones'
|
||||
import { mockLocations } from '../../../mocks/mockLocations'
|
||||
import {
|
||||
getWarehouseTypeColor,
|
||||
getWarehouseTypeText,
|
||||
getZoneTypeText,
|
||||
getLocationTypeText,
|
||||
} from "../../../utils/erp";
|
||||
} from '../../../utils/erp'
|
||||
import { Container } from '@/components/shared'
|
||||
|
||||
const WarehouseDefinitions: React.FC = () => {
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [selectedType, setSelectedType] = useState<WarehouseTypeEnum | "">("");
|
||||
const [activeTab, setActiveTab] = useState<
|
||||
"warehouses" | "zones" | "locations"
|
||||
>("warehouses");
|
||||
const [selectedWarehouse, setSelectedWarehouse] = useState<string>("");
|
||||
const [viewMode, setViewMode] = useState<"grid" | "list">("grid");
|
||||
const [searchTerm, setSearchTerm] = useState('')
|
||||
const [selectedType, setSelectedType] = useState<WarehouseTypeEnum | ''>('')
|
||||
const [activeTab, setActiveTab] = useState<'warehouses' | 'zones' | 'locations'>('warehouses')
|
||||
const [selectedWarehouse, setSelectedWarehouse] = useState<string>('')
|
||||
const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid')
|
||||
|
||||
// Modal states
|
||||
const [warehouseModal, setWarehouseModal] = useState<{
|
||||
isOpen: boolean;
|
||||
mode: "create" | "edit" | "view";
|
||||
warehouse: WmWarehouse | null;
|
||||
isOpen: boolean
|
||||
mode: 'create' | 'edit' | 'view'
|
||||
warehouse: WmWarehouse | null
|
||||
}>({
|
||||
isOpen: false,
|
||||
mode: "create",
|
||||
mode: 'create',
|
||||
warehouse: null,
|
||||
});
|
||||
})
|
||||
|
||||
const [zoneModal, setZoneModal] = useState<{
|
||||
isOpen: boolean;
|
||||
mode: "create" | "edit" | "view";
|
||||
zone: WmZone | null;
|
||||
isOpen: boolean
|
||||
mode: 'create' | 'edit' | 'view'
|
||||
zone: WmZone | null
|
||||
}>({
|
||||
isOpen: false,
|
||||
mode: "create",
|
||||
mode: 'create',
|
||||
zone: null,
|
||||
});
|
||||
})
|
||||
|
||||
const [locationModal, setLocationModal] = useState<{
|
||||
isOpen: boolean;
|
||||
mode: "create" | "edit" | "view";
|
||||
location: WmLocation | null;
|
||||
isOpen: boolean
|
||||
mode: 'create' | 'edit' | 'view'
|
||||
location: WmLocation | null
|
||||
}>({
|
||||
isOpen: false,
|
||||
mode: "create",
|
||||
mode: 'create',
|
||||
location: null,
|
||||
});
|
||||
})
|
||||
|
||||
// Data states (in a real app, these would come from an API)
|
||||
const [warehouses, setWarehouses] = useState<WmWarehouse[]>(mockWarehouses);
|
||||
const [warehouses, setWarehouses] = useState<WmWarehouse[]>(mockWarehouses)
|
||||
|
||||
const [zones, setZones] = useState<WmZone[]>(mockZones);
|
||||
const [zones, setZones] = useState<WmZone[]>(mockZones)
|
||||
|
||||
const [locations, setLocations] = useState<WmLocation[]>(mockLocations);
|
||||
const [locations, setLocations] = useState<WmLocation[]>(mockLocations)
|
||||
|
||||
// Modal handlers
|
||||
const handleWarehouseAction = (
|
||||
mode: "create" | "edit" | "view",
|
||||
warehouse?: WmWarehouse
|
||||
) => {
|
||||
const handleWarehouseAction = (mode: 'create' | 'edit' | 'view', warehouse?: WmWarehouse) => {
|
||||
setWarehouseModal({
|
||||
isOpen: true,
|
||||
mode,
|
||||
warehouse: warehouse || null,
|
||||
});
|
||||
};
|
||||
})
|
||||
}
|
||||
|
||||
const handleZoneAction = (
|
||||
mode: "create" | "edit" | "view",
|
||||
zone?: WmZone
|
||||
) => {
|
||||
const handleZoneAction = (mode: 'create' | 'edit' | 'view', zone?: WmZone) => {
|
||||
setZoneModal({
|
||||
isOpen: true,
|
||||
mode,
|
||||
zone: zone || null,
|
||||
});
|
||||
};
|
||||
})
|
||||
}
|
||||
|
||||
const handleLocationAction = (
|
||||
mode: "create" | "edit" | "view",
|
||||
location?: WmLocation
|
||||
) => {
|
||||
const handleLocationAction = (mode: 'create' | 'edit' | 'view', location?: WmLocation) => {
|
||||
setLocationModal({
|
||||
isOpen: true,
|
||||
mode,
|
||||
location: location || null,
|
||||
});
|
||||
};
|
||||
})
|
||||
}
|
||||
|
||||
const handleWarehouseSave = (warehouseData: Partial<WmWarehouse>) => {
|
||||
if (warehouseModal.mode === "create") {
|
||||
if (warehouseModal.mode === 'create') {
|
||||
const newWarehouse: WmWarehouse = {
|
||||
...warehouseData,
|
||||
id: Date.now().toString(),
|
||||
|
|
@ -120,9 +105,9 @@ const WarehouseDefinitions: React.FC = () => {
|
|||
zones: [],
|
||||
creationTime: new Date(),
|
||||
lastModificationTime: new Date(),
|
||||
} as WmWarehouse;
|
||||
setWarehouses([...warehouses, newWarehouse]);
|
||||
} else if (warehouseModal.mode === "edit" && warehouseModal.warehouse) {
|
||||
} as WmWarehouse
|
||||
setWarehouses([...warehouses, newWarehouse])
|
||||
} else if (warehouseModal.mode === 'edit' && warehouseModal.warehouse) {
|
||||
setWarehouses(
|
||||
warehouses.map((w) =>
|
||||
w.id === warehouseModal.warehouse!.id
|
||||
|
|
@ -131,91 +116,82 @@ const WarehouseDefinitions: React.FC = () => {
|
|||
...warehouseData,
|
||||
lastModificationTime: new Date(),
|
||||
} as WmWarehouse)
|
||||
: w
|
||||
: w,
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const handleZoneSave = (zoneData: Partial<WmZone>) => {
|
||||
if (zoneModal.mode === "create") {
|
||||
if (zoneModal.mode === 'create') {
|
||||
const newZone: WmZone = {
|
||||
...zoneData,
|
||||
id: Date.now().toString(),
|
||||
locations: [],
|
||||
} as WmZone;
|
||||
setZones([...zones, newZone]);
|
||||
} else if (zoneModal.mode === "edit" && zoneModal.zone) {
|
||||
} as WmZone
|
||||
setZones([...zones, newZone])
|
||||
} else if (zoneModal.mode === 'edit' && zoneModal.zone) {
|
||||
setZones(
|
||||
zones.map((z) =>
|
||||
z.id === zoneModal.zone!.id ? ({ ...z, ...zoneData } as WmZone) : z
|
||||
zones.map((z) => (z.id === zoneModal.zone!.id ? ({ ...z, ...zoneData } as WmZone) : z)),
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const handleLocationSave = (locationData: Partial<WmLocation>) => {
|
||||
if (locationModal.mode === "create") {
|
||||
if (locationModal.mode === 'create') {
|
||||
const newLocation: WmLocation = {
|
||||
...locationData,
|
||||
id: Date.now().toString(),
|
||||
stockItems: [],
|
||||
} as WmLocation;
|
||||
setLocations([...locations, newLocation]);
|
||||
} else if (locationModal.mode === "edit" && locationModal.location) {
|
||||
} as WmLocation
|
||||
setLocations([...locations, newLocation])
|
||||
} else if (locationModal.mode === 'edit' && locationModal.location) {
|
||||
setLocations(
|
||||
locations.map((l) =>
|
||||
l.id === locationModal.location!.id
|
||||
? ({ ...l, ...locationData } as WmLocation)
|
||||
: l
|
||||
l.id === locationModal.location!.id ? ({ ...l, ...locationData } as WmLocation) : l,
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const handleDelete = (
|
||||
type: "warehouse" | "zone" | "location",
|
||||
id: string
|
||||
) => {
|
||||
const handleDelete = (type: 'warehouse' | 'zone' | 'location', id: string) => {
|
||||
if (
|
||||
window.confirm(
|
||||
`Bu ${
|
||||
type === "warehouse" ? "depo" : type === "zone" ? "bölge" : "lokasyon"
|
||||
}u silmek istediğinizden emin misiniz?`
|
||||
type === 'warehouse' ? 'depo' : type === 'zone' ? 'bölge' : 'lokasyon'
|
||||
}u silmek istediğinizden emin misiniz?`,
|
||||
)
|
||||
) {
|
||||
switch (type) {
|
||||
case "warehouse":
|
||||
setWarehouses(warehouses.filter((w) => w.id !== id));
|
||||
break;
|
||||
case "zone":
|
||||
setZones(zones.filter((z) => z.id !== id));
|
||||
break;
|
||||
case "location":
|
||||
setLocations(locations.filter((l) => l.id !== id));
|
||||
break;
|
||||
case 'warehouse':
|
||||
setWarehouses(warehouses.filter((w) => w.id !== id))
|
||||
break
|
||||
case 'zone':
|
||||
setZones(zones.filter((z) => z.id !== id))
|
||||
break
|
||||
case 'location':
|
||||
setLocations(locations.filter((l) => l.id !== id))
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Filtered data
|
||||
const filteredWarehouses = warehouses.filter((warehouse) => {
|
||||
const matchesSearch =
|
||||
warehouse.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
warehouse.code.toLowerCase().includes(searchTerm.toLowerCase());
|
||||
const matchesType =
|
||||
selectedType === "" || warehouse.warehouseType === selectedType;
|
||||
return matchesSearch && matchesType;
|
||||
});
|
||||
warehouse.code.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
const matchesType = selectedType === '' || warehouse.warehouseType === selectedType
|
||||
return matchesSearch && matchesType
|
||||
})
|
||||
|
||||
const filteredZones = zones.filter(
|
||||
(zone) => selectedWarehouse === "" || zone.warehouseId === selectedWarehouse
|
||||
);
|
||||
(zone) => selectedWarehouse === '' || zone.warehouseId === selectedWarehouse,
|
||||
)
|
||||
|
||||
const filteredLocations = locations.filter(
|
||||
(location) =>
|
||||
selectedWarehouse === "" || location.warehouseId === selectedWarehouse
|
||||
);
|
||||
(location) => selectedWarehouse === '' || location.warehouseId === selectedWarehouse,
|
||||
)
|
||||
|
||||
// Warehouse Components
|
||||
const WarehousesGridView = () => (
|
||||
|
|
@ -231,9 +207,7 @@ const WarehouseDefinitions: React.FC = () => {
|
|||
<FaBuilding className="w-4 h-4 text-blue-600" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-medium text-sm text-gray-900">
|
||||
{warehouse.name}
|
||||
</h3>
|
||||
<h3 className="font-medium text-sm text-gray-900">{warehouse.name}</h3>
|
||||
<p className="text-gray-600">{warehouse.code}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -248,7 +222,7 @@ const WarehouseDefinitions: React.FC = () => {
|
|||
<div>
|
||||
<span
|
||||
className={`inline-flex px-2 py-1 text-xs font-medium rounded-full ${getWarehouseTypeColor(
|
||||
warehouse.warehouseType
|
||||
warehouse.warehouseType,
|
||||
)}`}
|
||||
>
|
||||
{getWarehouseTypeText(warehouse.warehouseType)}
|
||||
|
|
@ -278,18 +252,12 @@ const WarehouseDefinitions: React.FC = () => {
|
|||
<div
|
||||
className="bg-blue-600 h-2 rounded-full"
|
||||
style={{
|
||||
width: `${
|
||||
(warehouse.currentUtilization / warehouse.capacity) * 100
|
||||
}%`,
|
||||
width: `${(warehouse.currentUtilization / warehouse.capacity) * 100}%`,
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
<div className="text-xs text-gray-500">
|
||||
%
|
||||
{Math.round(
|
||||
(warehouse.currentUtilization / warehouse.capacity) * 100
|
||||
)}{" "}
|
||||
dolu
|
||||
%{Math.round((warehouse.currentUtilization / warehouse.capacity) * 100)} dolu
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -310,21 +278,21 @@ const WarehouseDefinitions: React.FC = () => {
|
|||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<button
|
||||
onClick={() => handleWarehouseAction("view", warehouse)}
|
||||
onClick={() => handleWarehouseAction('view', warehouse)}
|
||||
className="p-1.5 text-gray-400 hover:text-green-600 hover:bg-green-50 rounded-md"
|
||||
title="Görüntüle"
|
||||
>
|
||||
<FaEye className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleWarehouseAction("edit", warehouse)}
|
||||
onClick={() => handleWarehouseAction('edit', warehouse)}
|
||||
className="p-1.5 text-gray-400 hover:text-blue-600 hover:bg-blue-50 rounded-md"
|
||||
title="Düzenle"
|
||||
>
|
||||
<FaEdit className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleDelete("warehouse", warehouse.id)}
|
||||
onClick={() => handleDelete('warehouse', warehouse.id)}
|
||||
className="p-1.5 text-gray-400 hover:text-red-600 hover:bg-red-50 rounded-md"
|
||||
title="Sil"
|
||||
>
|
||||
|
|
@ -335,7 +303,7 @@ const WarehouseDefinitions: React.FC = () => {
|
|||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
)
|
||||
|
||||
const WarehousesListView = () => (
|
||||
<div className="bg-white rounded-lg shadow-sm border border-gray-200">
|
||||
|
|
@ -369,7 +337,7 @@ const WarehouseDefinitions: React.FC = () => {
|
|||
<tbody className="bg-white divide-y divide-gray-200">
|
||||
{filteredWarehouses.map((warehouse) => {
|
||||
const utilizationPercentage =
|
||||
(warehouse.currentUtilization / warehouse.capacity) * 100;
|
||||
(warehouse.currentUtilization / warehouse.capacity) * 100
|
||||
return (
|
||||
<tr key={warehouse.id} className="hover:bg-gray-50">
|
||||
<td className="px-3 py-2 whitespace-nowrap">
|
||||
|
|
@ -386,16 +354,14 @@ const WarehouseDefinitions: React.FC = () => {
|
|||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-xs text-gray-500">
|
||||
{warehouse.code}
|
||||
</div>
|
||||
<div className="text-xs text-gray-500">{warehouse.code}</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-3 py-2 whitespace-nowrap">
|
||||
<span
|
||||
className={`inline-flex px-2 py-1 text-xs font-medium rounded-full ${getWarehouseTypeColor(
|
||||
warehouse.warehouseType
|
||||
warehouse.warehouseType,
|
||||
)}`}
|
||||
>
|
||||
{getWarehouseTypeText(warehouse.warehouseType)}
|
||||
|
|
@ -405,9 +371,7 @@ const WarehouseDefinitions: React.FC = () => {
|
|||
<div className="text-xs text-gray-900">
|
||||
{warehouse.address?.city}, {warehouse.address?.state}
|
||||
</div>
|
||||
<div className="text-xs text-gray-500">
|
||||
{warehouse.address?.street}
|
||||
</div>
|
||||
<div className="text-xs text-gray-500">{warehouse.address?.street}</div>
|
||||
</td>
|
||||
<td className="px-3 py-2 whitespace-nowrap text-sm text-gray-900">
|
||||
{warehouse.capacity.toLocaleString()} birim
|
||||
|
|
@ -418,10 +382,10 @@ const WarehouseDefinitions: React.FC = () => {
|
|||
<div
|
||||
className={`h-2 rounded-full ${
|
||||
utilizationPercentage > 90
|
||||
? "bg-red-500"
|
||||
? 'bg-red-500'
|
||||
: utilizationPercentage > 70
|
||||
? "bg-yellow-500"
|
||||
: "bg-blue-500"
|
||||
? 'bg-yellow-500'
|
||||
: 'bg-blue-500'
|
||||
}`}
|
||||
style={{ width: `${utilizationPercentage}%` }}
|
||||
></div>
|
||||
|
|
@ -431,7 +395,7 @@ const WarehouseDefinitions: React.FC = () => {
|
|||
</span>
|
||||
</div>
|
||||
<div className="text-xs text-gray-500">
|
||||
{warehouse.currentUtilization.toLocaleString()} /{" "}
|
||||
{warehouse.currentUtilization.toLocaleString()} /{' '}
|
||||
{warehouse.capacity.toLocaleString()}
|
||||
</div>
|
||||
</td>
|
||||
|
|
@ -451,21 +415,21 @@ const WarehouseDefinitions: React.FC = () => {
|
|||
<td className="px-3 py-2 whitespace-nowrap text-right text-sm font-medium">
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
<button
|
||||
onClick={() => handleWarehouseAction("view", warehouse)}
|
||||
onClick={() => handleWarehouseAction('view', warehouse)}
|
||||
className="text-green-600 hover:text-green-900"
|
||||
title="Görüntüle"
|
||||
>
|
||||
<FaEye className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleWarehouseAction("edit", warehouse)}
|
||||
onClick={() => handleWarehouseAction('edit', warehouse)}
|
||||
className="text-blue-600 hover:text-blue-900"
|
||||
title="Düzenle"
|
||||
>
|
||||
<FaEdit className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleDelete("warehouse", warehouse.id)}
|
||||
onClick={() => handleDelete('warehouse', warehouse.id)}
|
||||
className="text-red-600 hover:text-red-900"
|
||||
title="Sil"
|
||||
>
|
||||
|
|
@ -474,13 +438,13 @@ const WarehouseDefinitions: React.FC = () => {
|
|||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
|
||||
const WarehousesTab = () => (
|
||||
<div className="space-y-4 pt-2">
|
||||
|
|
@ -498,9 +462,7 @@ const WarehouseDefinitions: React.FC = () => {
|
|||
</div>
|
||||
<select
|
||||
value={selectedType}
|
||||
onChange={(e) =>
|
||||
setSelectedType(e.target.value as WarehouseTypeEnum | "")
|
||||
}
|
||||
onChange={(e) => setSelectedType(e.target.value as WarehouseTypeEnum | '')}
|
||||
className="px-4 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
>
|
||||
<option value="">Tüm Tipler</option>
|
||||
|
|
@ -511,7 +473,7 @@ const WarehouseDefinitions: React.FC = () => {
|
|||
))}
|
||||
</select>
|
||||
<button
|
||||
onClick={() => handleWarehouseAction("create")}
|
||||
onClick={() => handleWarehouseAction('create')}
|
||||
className="bg-blue-600 text-white px-4 py-1.5 text-sm rounded-lg hover:bg-blue-700 flex items-center gap-2"
|
||||
>
|
||||
<FaPlus className="w-4 h-4" />
|
||||
|
|
@ -520,15 +482,15 @@ const WarehouseDefinitions: React.FC = () => {
|
|||
</div>
|
||||
|
||||
{/* Content based on view mode */}
|
||||
{viewMode === "grid" ? <WarehousesGridView /> : <WarehousesListView />}
|
||||
{viewMode === 'grid' ? <WarehousesGridView /> : <WarehousesListView />}
|
||||
</div>
|
||||
);
|
||||
)
|
||||
|
||||
// Zone Components
|
||||
const ZonesGridView = () => (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
|
||||
{filteredZones.map((zone) => {
|
||||
const warehouse = warehouses.find((w) => w.id === zone.warehouseId);
|
||||
const warehouse = warehouses.find((w) => w.id === zone.warehouseId)
|
||||
return (
|
||||
<div
|
||||
key={zone.id}
|
||||
|
|
@ -540,9 +502,7 @@ const WarehouseDefinitions: React.FC = () => {
|
|||
<FaLayerGroup className="w-4 h-4 text-purple-600" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-medium text-sm text-gray-900">
|
||||
{zone.name}
|
||||
</h3>
|
||||
<h3 className="font-medium text-sm text-gray-900">{zone.name}</h3>
|
||||
<p className="text-gray-600">{zone.zoneCode}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -564,13 +524,9 @@ const WarehouseDefinitions: React.FC = () => {
|
|||
|
||||
{(zone.temperature || zone.humidity) && (
|
||||
<div className="bg-gray-50 rounded-lg p-2">
|
||||
<h4 className="text-xs font-medium text-gray-900 mb-1">
|
||||
Çevresel Koşullar
|
||||
</h4>
|
||||
<h4 className="text-xs font-medium text-gray-900 mb-1">Çevresel Koşullar</h4>
|
||||
<div className="grid grid-cols-2 gap-2 text-xs text-gray-600">
|
||||
{zone.temperature && (
|
||||
<div>Sıcaklık: {zone.temperature}°C</div>
|
||||
)}
|
||||
{zone.temperature && <div>Sıcaklık: {zone.temperature}°C</div>}
|
||||
{zone.humidity && <div>Nem: %{zone.humidity}</div>}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -593,21 +549,21 @@ const WarehouseDefinitions: React.FC = () => {
|
|||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={() => handleZoneAction("view", zone)}
|
||||
onClick={() => handleZoneAction('view', zone)}
|
||||
className="p-1 text-gray-400 hover:text-green-600"
|
||||
title="Görüntüle"
|
||||
>
|
||||
<FaEye className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleZoneAction("edit", zone)}
|
||||
onClick={() => handleZoneAction('edit', zone)}
|
||||
className="p-1 text-gray-400 hover:text-blue-600"
|
||||
title="Düzenle"
|
||||
>
|
||||
<FaEdit className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleDelete("zone", zone.id)}
|
||||
onClick={() => handleDelete('zone', zone.id)}
|
||||
className="p-1 text-gray-400 hover:text-red-600"
|
||||
title="Sil"
|
||||
>
|
||||
|
|
@ -616,10 +572,10 @@ const WarehouseDefinitions: React.FC = () => {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
)
|
||||
|
||||
const ZonesListView = () => (
|
||||
<div className="bg-white rounded-lg shadow-sm border border-gray-200">
|
||||
|
|
@ -652,9 +608,7 @@ const WarehouseDefinitions: React.FC = () => {
|
|||
</thead>
|
||||
<tbody className="bg-white divide-y divide-gray-200">
|
||||
{filteredZones.map((zone) => {
|
||||
const warehouse = warehouses.find(
|
||||
(w) => w.id === zone.warehouseId
|
||||
);
|
||||
const warehouse = warehouses.find((w) => w.id === zone.warehouseId)
|
||||
return (
|
||||
<tr key={zone.id} className="hover:bg-gray-50">
|
||||
<td className="px-3 py-2 whitespace-nowrap">
|
||||
|
|
@ -663,22 +617,14 @@ const WarehouseDefinitions: React.FC = () => {
|
|||
<FaLayerGroup className="w-4 h-4 text-purple-600" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-sm font-medium text-gray-900">
|
||||
{zone.name}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500">
|
||||
{zone.zoneCode}
|
||||
</div>
|
||||
<div className="text-sm font-medium text-gray-900">{zone.name}</div>
|
||||
<div className="text-sm text-gray-500">{zone.zoneCode}</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-3 py-2 whitespace-nowrap">
|
||||
<div className="text-sm text-gray-900">
|
||||
{warehouse?.name}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500">
|
||||
{warehouse?.code}
|
||||
</div>
|
||||
<div className="text-sm text-gray-900">{warehouse?.name}</div>
|
||||
<div className="text-sm text-gray-500">{warehouse?.code}</div>
|
||||
</td>
|
||||
<td className="px-3 py-2 whitespace-nowrap">
|
||||
<span className="inline-flex px-2 py-1 text-xs font-medium rounded-full bg-blue-100 text-blue-800">
|
||||
|
|
@ -686,10 +632,10 @@ const WarehouseDefinitions: React.FC = () => {
|
|||
</span>
|
||||
</td>
|
||||
<td className="px-3 py-2 whitespace-nowrap text-sm text-gray-900">
|
||||
{zone.temperature ? `${zone.temperature}°C` : "-"}
|
||||
{zone.temperature ? `${zone.temperature}°C` : '-'}
|
||||
</td>
|
||||
<td className="px-3 py-2 whitespace-nowrap text-sm text-gray-900">
|
||||
{zone.humidity ? `%${zone.humidity}` : "-"}
|
||||
{zone.humidity ? `%${zone.humidity}` : '-'}
|
||||
</td>
|
||||
<td className="px-3 py-2 whitespace-nowrap">
|
||||
{zone.isActive ? (
|
||||
|
|
@ -707,21 +653,21 @@ const WarehouseDefinitions: React.FC = () => {
|
|||
<td className="px-3 py-2 whitespace-nowrap text-right text-sm font-medium">
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
<button
|
||||
onClick={() => handleZoneAction("view", zone)}
|
||||
onClick={() => handleZoneAction('view', zone)}
|
||||
className="text-green-600 hover:text-green-900"
|
||||
title="Görüntüle"
|
||||
>
|
||||
<FaEye className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleZoneAction("edit", zone)}
|
||||
onClick={() => handleZoneAction('edit', zone)}
|
||||
className="text-blue-600 hover:text-blue-900"
|
||||
title="Düzenle"
|
||||
>
|
||||
<FaEdit className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleDelete("zone", zone.id)}
|
||||
onClick={() => handleDelete('zone', zone.id)}
|
||||
className="text-red-600 hover:text-red-900"
|
||||
title="Sil"
|
||||
>
|
||||
|
|
@ -730,13 +676,13 @@ const WarehouseDefinitions: React.FC = () => {
|
|||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
|
||||
const ZonesTab = () => (
|
||||
<div className="space-y-4 pt-2">
|
||||
|
|
@ -755,7 +701,7 @@ const WarehouseDefinitions: React.FC = () => {
|
|||
))}
|
||||
</select>
|
||||
<button
|
||||
onClick={() => handleZoneAction("create")}
|
||||
onClick={() => handleZoneAction('create')}
|
||||
className="bg-blue-600 text-white px-4 py-1.5 text-sm rounded-lg hover:bg-blue-700 flex items-center gap-2"
|
||||
>
|
||||
<FaPlus className="w-4 h-4" />
|
||||
|
|
@ -764,18 +710,17 @@ const WarehouseDefinitions: React.FC = () => {
|
|||
</div>
|
||||
|
||||
{/* Content based on view mode */}
|
||||
{viewMode === "grid" ? <ZonesGridView /> : <ZonesListView />}
|
||||
{viewMode === 'grid' ? <ZonesGridView /> : <ZonesListView />}
|
||||
</div>
|
||||
);
|
||||
)
|
||||
|
||||
// Location Components
|
||||
const LocationsGridView = () => (
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
{filteredLocations.map((location) => {
|
||||
const warehouse = warehouses.find((w) => w.id === location.warehouseId);
|
||||
const zone = zones.find((z) => z.id === location.zoneId);
|
||||
const utilizationPercentage =
|
||||
(location.currentStock / location.capacity) * 100;
|
||||
const warehouse = warehouses.find((w) => w.id === location.warehouseId)
|
||||
const zone = zones.find((z) => z.id === location.zoneId)
|
||||
const utilizationPercentage = (location.currentStock / location.capacity) * 100
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
@ -789,9 +734,7 @@ const WarehouseDefinitions: React.FC = () => {
|
|||
</div>
|
||||
<div>
|
||||
<h3 className="font-medium text-gray-900">{location.name}</h3>
|
||||
<p className="text-sm text-gray-500">
|
||||
{location.locationCode}
|
||||
</p>
|
||||
<p className="text-sm text-gray-500">{location.locationCode}</p>
|
||||
</div>
|
||||
</div>
|
||||
<span className="inline-flex px-2 py-1 text-xs font-medium rounded-full bg-gray-100 text-gray-800">
|
||||
|
|
@ -815,9 +758,7 @@ const WarehouseDefinitions: React.FC = () => {
|
|||
{/* Dimensions */}
|
||||
{location.dimensions && (
|
||||
<div className="bg-gray-50 rounded-lg p-2 mt-2">
|
||||
<h4 className="text-sm font-medium text-gray-900 mb-2">
|
||||
Boyutlar
|
||||
</h4>
|
||||
<h4 className="text-sm font-medium text-gray-900 mb-2">Boyutlar</h4>
|
||||
<div className="grid grid-cols-2 gap-2 text-xs text-gray-600">
|
||||
<div>Uzunluk: {location.dimensions.length}m</div>
|
||||
<div>Genişlik: {location.dimensions.width}m</div>
|
||||
|
|
@ -842,10 +783,10 @@ const WarehouseDefinitions: React.FC = () => {
|
|||
<div
|
||||
className={`h-2 rounded-full ${
|
||||
utilizationPercentage > 90
|
||||
? "bg-red-500"
|
||||
? 'bg-red-500'
|
||||
: utilizationPercentage > 70
|
||||
? "bg-yellow-500"
|
||||
: "bg-green-500"
|
||||
? 'bg-yellow-500'
|
||||
: 'bg-green-500'
|
||||
}`}
|
||||
style={{ width: `${utilizationPercentage}%` }}
|
||||
></div>
|
||||
|
|
@ -858,9 +799,7 @@ const WarehouseDefinitions: React.FC = () => {
|
|||
{/* Restrictions */}
|
||||
{location.restrictions && location.restrictions.length > 0 && (
|
||||
<div className="pt-2">
|
||||
<h4 className="text-sm font-medium text-gray-900 mb-1">
|
||||
Kısıtlamalar
|
||||
</h4>
|
||||
<h4 className="text-sm font-medium text-gray-900 mb-1">Kısıtlamalar</h4>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{location.restrictions.map((restriction, index) => (
|
||||
<span
|
||||
|
|
@ -891,21 +830,21 @@ const WarehouseDefinitions: React.FC = () => {
|
|||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={() => handleLocationAction("view", location)}
|
||||
onClick={() => handleLocationAction('view', location)}
|
||||
className="p-1 text-gray-400 hover:text-green-600"
|
||||
title="Görüntüle"
|
||||
>
|
||||
<FaEye className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleLocationAction("edit", location)}
|
||||
onClick={() => handleLocationAction('edit', location)}
|
||||
className="p-1 text-gray-400 hover:text-blue-600"
|
||||
title="Düzenle"
|
||||
>
|
||||
<FaEdit className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleDelete("location", location.id)}
|
||||
onClick={() => handleDelete('location', location.id)}
|
||||
className="p-1 text-gray-400 hover:text-red-600"
|
||||
title="Sil"
|
||||
>
|
||||
|
|
@ -914,10 +853,10 @@ const WarehouseDefinitions: React.FC = () => {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
)
|
||||
|
||||
const LocationsListView = () => (
|
||||
<div className="bg-white rounded-lg shadow-sm border border-gray-200">
|
||||
|
|
@ -953,12 +892,9 @@ const WarehouseDefinitions: React.FC = () => {
|
|||
</thead>
|
||||
<tbody className="bg-white divide-y divide-gray-200">
|
||||
{filteredLocations.map((location) => {
|
||||
const warehouse = warehouses.find(
|
||||
(w) => w.id === location.warehouseId
|
||||
);
|
||||
const zone = zones.find((z) => z.id === location.zoneId);
|
||||
const utilizationPercentage =
|
||||
(location.currentStock / location.capacity) * 100;
|
||||
const warehouse = warehouses.find((w) => w.id === location.warehouseId)
|
||||
const zone = zones.find((z) => z.id === location.zoneId)
|
||||
const utilizationPercentage = (location.currentStock / location.capacity) * 100
|
||||
|
||||
return (
|
||||
<tr key={location.id} className="hover:bg-gray-50">
|
||||
|
|
@ -968,30 +904,18 @@ const WarehouseDefinitions: React.FC = () => {
|
|||
<FaMapMarkerAlt className="w-4 h-4 text-orange-600" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-sm font-medium text-gray-900">
|
||||
{location.name}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500">
|
||||
{location.locationCode}
|
||||
</div>
|
||||
<div className="text-sm font-medium text-gray-900">{location.name}</div>
|
||||
<div className="text-sm text-gray-500">{location.locationCode}</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-3 py-2 whitespace-nowrap">
|
||||
<div className="text-sm text-gray-900">
|
||||
{warehouse?.name}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500">
|
||||
{warehouse?.code}
|
||||
</div>
|
||||
<div className="text-sm text-gray-900">{warehouse?.name}</div>
|
||||
<div className="text-sm text-gray-500">{warehouse?.code}</div>
|
||||
</td>
|
||||
<td className="px-3 py-2 whitespace-nowrap">
|
||||
<div className="text-sm text-gray-900">
|
||||
{zone?.name || "-"}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500">
|
||||
{zone?.zoneCode || "-"}
|
||||
</div>
|
||||
<div className="text-sm text-gray-900">{zone?.name || '-'}</div>
|
||||
<div className="text-sm text-gray-500">{zone?.zoneCode || '-'}</div>
|
||||
</td>
|
||||
<td className="px-3 py-2 whitespace-nowrap">
|
||||
<span className="inline-flex px-2 py-1 text-xs font-medium rounded-full bg-gray-100 text-gray-800">
|
||||
|
|
@ -1007,10 +931,10 @@ const WarehouseDefinitions: React.FC = () => {
|
|||
<div
|
||||
className={`h-2 rounded-full ${
|
||||
utilizationPercentage > 90
|
||||
? "bg-red-500"
|
||||
? 'bg-red-500'
|
||||
: utilizationPercentage > 70
|
||||
? "bg-yellow-500"
|
||||
: "bg-green-500"
|
||||
? 'bg-yellow-500'
|
||||
: 'bg-green-500'
|
||||
}`}
|
||||
style={{ width: `${utilizationPercentage}%` }}
|
||||
></div>
|
||||
|
|
@ -1039,21 +963,21 @@ const WarehouseDefinitions: React.FC = () => {
|
|||
<td className="px-3 py-2 whitespace-nowrap text-right text-sm font-medium">
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
<button
|
||||
onClick={() => handleLocationAction("view", location)}
|
||||
onClick={() => handleLocationAction('view', location)}
|
||||
className="text-green-600 hover:text-green-900"
|
||||
title="Görüntüle"
|
||||
>
|
||||
<FaEye className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleLocationAction("edit", location)}
|
||||
onClick={() => handleLocationAction('edit', location)}
|
||||
className="text-blue-600 hover:text-blue-900"
|
||||
title="Düzenle"
|
||||
>
|
||||
<FaEdit className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleDelete("location", location.id)}
|
||||
onClick={() => handleDelete('location', location.id)}
|
||||
className="text-red-600 hover:text-red-900"
|
||||
title="Sil"
|
||||
>
|
||||
|
|
@ -1062,13 +986,13 @@ const WarehouseDefinitions: React.FC = () => {
|
|||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
|
||||
const LocationsTab = () => (
|
||||
<div className="space-y-4 pt-2">
|
||||
|
|
@ -1087,7 +1011,7 @@ const WarehouseDefinitions: React.FC = () => {
|
|||
))}
|
||||
</select>
|
||||
<button
|
||||
onClick={() => handleLocationAction("create")}
|
||||
onClick={() => handleLocationAction('create')}
|
||||
className="bg-blue-600 text-white px-4 py-1.5 text-sm rounded-lg hover:bg-blue-700 flex items-center gap-2"
|
||||
>
|
||||
<FaPlus className="w-4 h-4" />
|
||||
|
|
@ -1096,42 +1020,39 @@ const WarehouseDefinitions: React.FC = () => {
|
|||
</div>
|
||||
|
||||
{/* Content based on view mode */}
|
||||
{viewMode === "grid" ? <LocationsGridView /> : <LocationsListView />}
|
||||
{viewMode === 'grid' ? <LocationsGridView /> : <LocationsListView />}
|
||||
</div>
|
||||
);
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="space-y-4 pt-2">
|
||||
<Container>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold text-gray-900">Depo Tanımları</h2>
|
||||
<p className="text-gray-600">
|
||||
Depolar, bölgeler ve lokasyonları yönetin
|
||||
</p>
|
||||
<p className="text-gray-600">Depolar, bölgeler ve lokasyonları yönetin</p>
|
||||
</div>
|
||||
|
||||
{/* View Toggle Buttons */}
|
||||
{(activeTab === "warehouses" ||
|
||||
activeTab === "zones" ||
|
||||
activeTab === "locations") && (
|
||||
{(activeTab === 'warehouses' || activeTab === 'zones' || activeTab === 'locations') && (
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={() => setViewMode("grid")}
|
||||
onClick={() => setViewMode('grid')}
|
||||
className={`p-1.5 rounded-lg ${
|
||||
viewMode === "grid"
|
||||
? "bg-blue-100 text-blue-600"
|
||||
: "text-gray-400 hover:text-gray-600"
|
||||
viewMode === 'grid'
|
||||
? 'bg-blue-100 text-blue-600'
|
||||
: 'text-gray-400 hover:text-gray-600'
|
||||
}`}
|
||||
title="Kart Görünümü"
|
||||
>
|
||||
<FaTh className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setViewMode("list")}
|
||||
onClick={() => setViewMode('list')}
|
||||
className={`p-1.5 rounded-lg ${
|
||||
viewMode === "list"
|
||||
? "bg-blue-100 text-blue-600"
|
||||
: "text-gray-400 hover:text-gray-600"
|
||||
viewMode === 'list'
|
||||
? 'bg-blue-100 text-blue-600'
|
||||
: 'text-gray-400 hover:text-gray-600'
|
||||
}`}
|
||||
title="Liste Görünümü"
|
||||
>
|
||||
|
|
@ -1145,11 +1066,11 @@ const WarehouseDefinitions: React.FC = () => {
|
|||
<div className="border-b border-gray-200">
|
||||
<nav className="-mb-px flex space-x-8" aria-label="Tabs">
|
||||
<button
|
||||
onClick={() => setActiveTab("warehouses")}
|
||||
onClick={() => setActiveTab('warehouses')}
|
||||
className={`whitespace-nowrap py-2 px-1 border-b-2 font-medium text-sm ${
|
||||
activeTab === "warehouses"
|
||||
? "border-blue-500 text-blue-600"
|
||||
: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300"
|
||||
activeTab === 'warehouses'
|
||||
? 'border-blue-500 text-blue-600'
|
||||
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
|
|
@ -1158,11 +1079,11 @@ const WarehouseDefinitions: React.FC = () => {
|
|||
</div>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab("zones")}
|
||||
onClick={() => setActiveTab('zones')}
|
||||
className={`whitespace-nowrap py-2 px-1 border-b-2 font-medium text-sm ${
|
||||
activeTab === "zones"
|
||||
? "border-blue-500 text-blue-600"
|
||||
: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300"
|
||||
activeTab === 'zones'
|
||||
? 'border-blue-500 text-blue-600'
|
||||
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
|
|
@ -1171,11 +1092,11 @@ const WarehouseDefinitions: React.FC = () => {
|
|||
</div>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab("locations")}
|
||||
onClick={() => setActiveTab('locations')}
|
||||
className={`whitespace-nowrap py-2 px-1 border-b-2 font-medium text-sm ${
|
||||
activeTab === "locations"
|
||||
? "border-blue-500 text-blue-600"
|
||||
: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300"
|
||||
activeTab === 'locations'
|
||||
? 'border-blue-500 text-blue-600'
|
||||
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
|
|
@ -1187,9 +1108,10 @@ const WarehouseDefinitions: React.FC = () => {
|
|||
</div>
|
||||
|
||||
{/* Tab Content */}
|
||||
{activeTab === "warehouses" && <WarehousesTab />}
|
||||
{activeTab === "zones" && <ZonesTab />}
|
||||
{activeTab === "locations" && <LocationsTab />}
|
||||
{activeTab === 'warehouses' && <WarehousesTab />}
|
||||
{activeTab === 'zones' && <ZonesTab />}
|
||||
{activeTab === 'locations' && <LocationsTab />}
|
||||
</div>
|
||||
|
||||
{/* Modals */}
|
||||
<WarehouseModal
|
||||
|
|
@ -1218,8 +1140,8 @@ const WarehouseDefinitions: React.FC = () => {
|
|||
warehouses={warehouses}
|
||||
zones={zones}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
export default WarehouseDefinitions;
|
||||
export default WarehouseDefinitions
|
||||
|
|
|
|||
|
|
@ -1,42 +1,36 @@
|
|||
import React, { useState, useEffect, useCallback } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import {
|
||||
FaSave,
|
||||
FaTimes,
|
||||
FaBox,
|
||||
FaMapMarkerAlt,
|
||||
FaLayerGroup,
|
||||
FaBuilding,
|
||||
} from "react-icons/fa";
|
||||
import LoadingSpinner from "../../../components/common/LoadingSpinner";
|
||||
import { WmWarehouse, WarehouseTypeEnum } from "../../../types/wm";
|
||||
import { SecurityLevelEnum } from "../../../types/mrp";
|
||||
import { mockWarehouses } from "../../../mocks/mockWarehouses";
|
||||
import React, { useState, useEffect, useCallback } from 'react'
|
||||
import { useNavigate, useParams } from 'react-router-dom'
|
||||
import { FaSave, FaTimes, FaBox, FaMapMarkerAlt, FaLayerGroup, FaBuilding } from 'react-icons/fa'
|
||||
import LoadingSpinner from '../../../components/common/LoadingSpinner'
|
||||
import { WmWarehouse, WarehouseTypeEnum } from '../../../types/wm'
|
||||
import { SecurityLevelEnum } from '../../../types/mrp'
|
||||
import { mockWarehouses } from '../../../mocks/mockWarehouses'
|
||||
import { Container } from '@/components/shared'
|
||||
|
||||
interface ValidationErrors {
|
||||
[key: string]: string;
|
||||
[key: string]: string
|
||||
}
|
||||
|
||||
const WarehouseForm: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const isEdit = Boolean(id);
|
||||
const navigate = useNavigate()
|
||||
const { id } = useParams<{ id: string }>()
|
||||
const isEdit = Boolean(id)
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [errors, setErrors] = useState<ValidationErrors>({});
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [saving, setSaving] = useState(false)
|
||||
const [errors, setErrors] = useState<ValidationErrors>({})
|
||||
|
||||
const [formData, setFormData] = useState<WmWarehouse>({
|
||||
id: "",
|
||||
code: "",
|
||||
name: "",
|
||||
description: "",
|
||||
id: '',
|
||||
code: '',
|
||||
name: '',
|
||||
description: '',
|
||||
address: {
|
||||
street: "",
|
||||
city: "",
|
||||
state: "",
|
||||
postalCode: "",
|
||||
country: "Türkiye",
|
||||
street: '',
|
||||
city: '',
|
||||
state: '',
|
||||
postalCode: '',
|
||||
country: 'Türkiye',
|
||||
},
|
||||
warehouseType: WarehouseTypeEnum.RawMaterials,
|
||||
locations: [],
|
||||
|
|
@ -50,132 +44,129 @@ const WarehouseForm: React.FC = () => {
|
|||
managerId: 0,
|
||||
creationTime: new Date(),
|
||||
lastModificationTime: new Date(),
|
||||
});
|
||||
})
|
||||
|
||||
const loadFormData = useCallback(async () => {
|
||||
setLoading(true);
|
||||
setLoading(true)
|
||||
try {
|
||||
if (isEdit && id) {
|
||||
// Simulate API call
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
const warehouse = mockWarehouses.find((w) => w.id === id);
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
||||
const warehouse = mockWarehouses.find((w) => w.id === id)
|
||||
if (warehouse) {
|
||||
setFormData(warehouse);
|
||||
setFormData(warehouse)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading form data:", error);
|
||||
console.error('Error loading form data:', error)
|
||||
} finally {
|
||||
setLoading(false);
|
||||
setLoading(false)
|
||||
}
|
||||
}, [isEdit, id]);
|
||||
}, [isEdit, id])
|
||||
|
||||
useEffect(() => {
|
||||
loadFormData();
|
||||
}, [loadFormData]);
|
||||
loadFormData()
|
||||
}, [loadFormData])
|
||||
|
||||
const validateForm = (): boolean => {
|
||||
const newErrors: ValidationErrors = {};
|
||||
const newErrors: ValidationErrors = {}
|
||||
|
||||
if (!formData.code.trim()) {
|
||||
newErrors.code = "Depo kodu zorunludur";
|
||||
newErrors.code = 'Depo kodu zorunludur'
|
||||
}
|
||||
if (!formData.name.trim()) {
|
||||
newErrors.name = "Depo adı zorunludur";
|
||||
newErrors.name = 'Depo adı zorunludur'
|
||||
}
|
||||
if (!formData.warehouseType) {
|
||||
newErrors.warehouseType = "Depo tipi seçilmelidir";
|
||||
newErrors.warehouseType = 'Depo tipi seçilmelidir'
|
||||
}
|
||||
if (!formData.address?.street.trim()) {
|
||||
newErrors["address.street"] = "Adres zorunludur";
|
||||
newErrors['address.street'] = 'Adres zorunludur'
|
||||
}
|
||||
if (!formData.address?.city.trim()) {
|
||||
newErrors["address.city"] = "Şehir zorunludur";
|
||||
newErrors['address.city'] = 'Şehir zorunludur'
|
||||
}
|
||||
if (!formData.address?.country.trim()) {
|
||||
newErrors["address.country"] = "Ülke zorunludur";
|
||||
newErrors['address.country'] = 'Ülke zorunludur'
|
||||
}
|
||||
if (formData.capacity <= 0) {
|
||||
newErrors.capacity = "Kapasite 0'dan büyük olmalıdır";
|
||||
newErrors.capacity = "Kapasite 0'dan büyük olmalıdır"
|
||||
}
|
||||
|
||||
setErrors(newErrors);
|
||||
return Object.keys(newErrors).length === 0;
|
||||
};
|
||||
setErrors(newErrors)
|
||||
return Object.keys(newErrors).length === 0
|
||||
}
|
||||
|
||||
const handleInputChange = (field: string, value: any) => {
|
||||
setFormData((prev) => {
|
||||
const updated = { ...prev };
|
||||
const keys = field.split(".");
|
||||
let current: any = updated;
|
||||
const updated = { ...prev }
|
||||
const keys = field.split('.')
|
||||
let current: any = updated
|
||||
keys.forEach((key, index) => {
|
||||
if (index === keys.length - 1) {
|
||||
current[key] = value;
|
||||
current[key] = value
|
||||
} else {
|
||||
current[key] = { ...current[key] };
|
||||
current = current[key];
|
||||
current[key] = { ...current[key] }
|
||||
current = current[key]
|
||||
}
|
||||
});
|
||||
return updated;
|
||||
});
|
||||
})
|
||||
return updated
|
||||
})
|
||||
|
||||
if (errors[field]) {
|
||||
setErrors((prev) => ({ ...prev, [field]: "" }));
|
||||
setErrors((prev) => ({ ...prev, [field]: '' }))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
e.preventDefault()
|
||||
|
||||
if (!validateForm()) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
setSaving(true);
|
||||
setSaving(true)
|
||||
try {
|
||||
// Simulate API call
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000))
|
||||
|
||||
console.log("Warehouse data:", {
|
||||
console.log('Warehouse data:', {
|
||||
...formData,
|
||||
id: isEdit ? id : undefined,
|
||||
});
|
||||
})
|
||||
|
||||
// Show success message
|
||||
alert(
|
||||
isEdit ? "Depo başarıyla güncellendi!" : "Depo başarıyla oluşturuldu!"
|
||||
);
|
||||
alert(isEdit ? 'Depo başarıyla güncellendi!' : 'Depo başarıyla oluşturuldu!')
|
||||
|
||||
// Navigate back to list
|
||||
navigate("/admin/warehouse");
|
||||
navigate('/admin/warehouse')
|
||||
} catch (error) {
|
||||
console.error("Error saving warehouse:", error);
|
||||
alert("Bir hata oluştu. Lütfen tekrar deneyin.");
|
||||
console.error('Error saving warehouse:', error)
|
||||
alert('Bir hata oluştu. Lütfen tekrar deneyin.')
|
||||
} finally {
|
||||
setSaving(false);
|
||||
setSaving(false)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
navigate("/admin/warehouse");
|
||||
};
|
||||
navigate('/admin/warehouse')
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
return <LoadingSpinner />;
|
||||
return <LoadingSpinner />
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4 pt-2">
|
||||
<Container>
|
||||
<div className="space-y-2">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold text-gray-900">
|
||||
{isEdit ? "Depo Düzenle" : "Yeni Depo"}
|
||||
{isEdit ? 'Depo Düzenle' : 'Yeni Depo'}
|
||||
</h2>
|
||||
<p className="text-sm text-gray-600 mt-1">
|
||||
{isEdit
|
||||
? "Mevcut depo bilgilerini güncelleyin"
|
||||
: "Yeni depo bilgilerini girin"}
|
||||
{isEdit ? 'Mevcut depo bilgilerini güncelleyin' : 'Yeni depo bilgilerini girin'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -200,17 +191,15 @@ const WarehouseForm: React.FC = () => {
|
|||
<input
|
||||
type="text"
|
||||
value={formData.code}
|
||||
onChange={(e) => handleInputChange("code", e.target.value)}
|
||||
onChange={(e) => handleInputChange('code', e.target.value)}
|
||||
className={`block w-full px-3 py-1.5 text-sm border rounded-md shadow-sm focus:outline-none focus:ring-1 ${
|
||||
errors.code
|
||||
? "border-red-300 focus:border-red-500 focus:ring-red-500"
|
||||
: "border-gray-300 focus:border-blue-500 focus:ring-blue-500"
|
||||
? 'border-red-300 focus:border-red-500 focus:ring-red-500'
|
||||
: 'border-gray-300 focus:border-blue-500 focus:ring-blue-500'
|
||||
}`}
|
||||
placeholder="Örn: WH001"
|
||||
/>
|
||||
{errors.code && (
|
||||
<p className="mt-1 text-sm text-red-600">{errors.code}</p>
|
||||
)}
|
||||
{errors.code && <p className="mt-1 text-sm text-red-600">{errors.code}</p>}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
|
@ -219,13 +208,11 @@ const WarehouseForm: React.FC = () => {
|
|||
</label>
|
||||
<select
|
||||
value={formData.warehouseType}
|
||||
onChange={(e) =>
|
||||
handleInputChange("warehouseType", e.target.value)
|
||||
}
|
||||
onChange={(e) => handleInputChange('warehouseType', e.target.value)}
|
||||
className={`block w-full px-3 py-1.5 text-sm border rounded-md shadow-sm focus:outline-none focus:ring-1 ${
|
||||
errors.warehouseType
|
||||
? "border-red-300 focus:border-red-500 focus:ring-red-500"
|
||||
: "border-gray-300 focus:border-blue-500 focus:ring-blue-500"
|
||||
? 'border-red-300 focus:border-red-500 focus:ring-red-500'
|
||||
: 'border-gray-300 focus:border-blue-500 focus:ring-blue-500'
|
||||
}`}
|
||||
>
|
||||
<option value="">Tip seçin</option>
|
||||
|
|
@ -239,42 +226,32 @@ const WarehouseForm: React.FC = () => {
|
|||
<option value="HAZARDOUS">Tehlikeli Madde Deposu</option>
|
||||
</select>
|
||||
{errors.warehouseType && (
|
||||
<p className="mt-1 text-sm text-red-600">
|
||||
{errors.warehouseType}
|
||||
</p>
|
||||
<p className="mt-1 text-sm text-red-600">{errors.warehouseType}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Depo Adı *
|
||||
</label>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Depo Adı *</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.name}
|
||||
onChange={(e) => handleInputChange("name", e.target.value)}
|
||||
onChange={(e) => handleInputChange('name', e.target.value)}
|
||||
className={`block w-full px-3 py-1.5 text-sm border rounded-md shadow-sm focus:outline-none focus:ring-1 ${
|
||||
errors.name
|
||||
? "border-red-300 focus:border-red-500 focus:ring-red-500"
|
||||
: "border-gray-300 focus:border-blue-500 focus:ring-blue-500"
|
||||
? 'border-red-300 focus:border-red-500 focus:ring-red-500'
|
||||
: 'border-gray-300 focus:border-blue-500 focus:ring-blue-500'
|
||||
}`}
|
||||
placeholder="Depo adı"
|
||||
/>
|
||||
{errors.name && (
|
||||
<p className="mt-1 text-sm text-red-600">{errors.name}</p>
|
||||
)}
|
||||
{errors.name && <p className="mt-1 text-sm text-red-600">{errors.name}</p>}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Açıklama
|
||||
</label>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Açıklama</label>
|
||||
<textarea
|
||||
value={formData.description}
|
||||
onChange={(e) =>
|
||||
handleInputChange("description", e.target.value)
|
||||
}
|
||||
onChange={(e) => handleInputChange('description', e.target.value)}
|
||||
rows={3}
|
||||
className="block w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
|
||||
placeholder="Depo açıklaması"
|
||||
|
|
@ -291,16 +268,11 @@ const WarehouseForm: React.FC = () => {
|
|||
min="0"
|
||||
step="0.01"
|
||||
value={formData.capacity}
|
||||
onChange={(e) =>
|
||||
handleInputChange(
|
||||
"capacity",
|
||||
parseFloat(e.target.value) || 0
|
||||
)
|
||||
}
|
||||
onChange={(e) => handleInputChange('capacity', parseFloat(e.target.value) || 0)}
|
||||
className={`block w-full px-3 py-1.5 text-sm border rounded-md shadow-sm focus:outline-none focus:ring-1 ${
|
||||
errors.capacity
|
||||
? "border-red-300 focus:border-red-500 focus:ring-red-500"
|
||||
: "border-gray-300 focus:border-blue-500 focus:ring-blue-500"
|
||||
? 'border-red-300 focus:border-red-500 focus:ring-red-500'
|
||||
: 'border-gray-300 focus:border-blue-500 focus:ring-blue-500'
|
||||
}`}
|
||||
placeholder="0"
|
||||
/>
|
||||
|
|
@ -315,15 +287,10 @@ const WarehouseForm: React.FC = () => {
|
|||
type="checkbox"
|
||||
id="isMainWarehouse"
|
||||
checked={formData.isMainWarehouse}
|
||||
onChange={(e) =>
|
||||
handleInputChange("isMainWarehouse", e.target.checked)
|
||||
}
|
||||
onChange={(e) => handleInputChange('isMainWarehouse', e.target.checked)}
|
||||
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
|
||||
/>
|
||||
<label
|
||||
htmlFor="isMainWarehouse"
|
||||
className="ml-2 block text-sm text-gray-900"
|
||||
>
|
||||
<label htmlFor="isMainWarehouse" className="ml-2 block text-sm text-gray-900">
|
||||
Ana Depo
|
||||
</label>
|
||||
</div>
|
||||
|
|
@ -333,15 +300,10 @@ const WarehouseForm: React.FC = () => {
|
|||
type="checkbox"
|
||||
id="isActive"
|
||||
checked={formData.isActive}
|
||||
onChange={(e) =>
|
||||
handleInputChange("isActive", e.target.checked)
|
||||
}
|
||||
onChange={(e) => handleInputChange('isActive', e.target.checked)}
|
||||
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
|
||||
/>
|
||||
<label
|
||||
htmlFor="isActive"
|
||||
className="ml-2 block text-sm text-gray-900"
|
||||
>
|
||||
<label htmlFor="isActive" className="ml-2 block text-sm text-gray-900">
|
||||
Aktif
|
||||
</label>
|
||||
</div>
|
||||
|
|
@ -361,105 +323,79 @@ const WarehouseForm: React.FC = () => {
|
|||
|
||||
<div className="p-4 space-y-3">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Adres *
|
||||
</label>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Adres *</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.address?.street}
|
||||
onChange={(e) =>
|
||||
handleInputChange("address.street", e.target.value)
|
||||
}
|
||||
onChange={(e) => handleInputChange('address.street', e.target.value)}
|
||||
className={`block w-full px-3 py-1.5 text-sm border rounded-md shadow-sm focus:outline-none focus:ring-1 ${
|
||||
errors["address.street"]
|
||||
? "border-red-300 focus:border-red-500 focus:ring-red-500"
|
||||
: "border-gray-300 focus:border-blue-500 focus:ring-blue-500"
|
||||
errors['address.street']
|
||||
? 'border-red-300 focus:border-red-500 focus:ring-red-500'
|
||||
: 'border-gray-300 focus:border-blue-500 focus:ring-blue-500'
|
||||
}`}
|
||||
placeholder="Sokak, cadde, mahalle"
|
||||
/>
|
||||
{errors["address.street"] && (
|
||||
<p className="mt-1 text-sm text-red-600">
|
||||
{errors["address.street"]}
|
||||
</p>
|
||||
{errors['address.street'] && (
|
||||
<p className="mt-1 text-sm text-red-600">{errors['address.street']}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Şehir *
|
||||
</label>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Şehir *</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.address?.city}
|
||||
onChange={(e) =>
|
||||
handleInputChange("address.city", e.target.value)
|
||||
}
|
||||
onChange={(e) => handleInputChange('address.city', e.target.value)}
|
||||
className={`block w-full px-3 py-1.5 text-sm border rounded-md shadow-sm focus:outline-none focus:ring-1 ${
|
||||
errors["address.city"]
|
||||
? "border-red-300 focus:border-red-500 focus:ring-red-500"
|
||||
: "border-gray-300 focus:border-blue-500 focus:ring-blue-500"
|
||||
errors['address.city']
|
||||
? 'border-red-300 focus:border-red-500 focus:ring-red-500'
|
||||
: 'border-gray-300 focus:border-blue-500 focus:ring-blue-500'
|
||||
}`}
|
||||
placeholder="Şehir"
|
||||
/>
|
||||
{errors["address.city"] && (
|
||||
<p className="mt-1 text-sm text-red-600">
|
||||
{errors["address.city"]}
|
||||
</p>
|
||||
{errors['address.city'] && (
|
||||
<p className="mt-1 text-sm text-red-600">{errors['address.city']}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
İl/Bölge
|
||||
</label>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">İl/Bölge</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.address?.state}
|
||||
onChange={(e) =>
|
||||
handleInputChange("address.state", e.target.value)
|
||||
}
|
||||
onChange={(e) => handleInputChange('address.state', e.target.value)}
|
||||
className="block w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
|
||||
placeholder="İl/Bölge"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Posta Kodu
|
||||
</label>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Posta Kodu</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.address?.postalCode}
|
||||
onChange={(e) =>
|
||||
handleInputChange("address.postalCode", e.target.value)
|
||||
}
|
||||
onChange={(e) => handleInputChange('address.postalCode', e.target.value)}
|
||||
className="block w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
|
||||
placeholder="34000"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Ülke *
|
||||
</label>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Ülke *</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.address?.country}
|
||||
onChange={(e) =>
|
||||
handleInputChange("address.country", e.target.value)
|
||||
}
|
||||
onChange={(e) => handleInputChange('address.country', e.target.value)}
|
||||
className={`block w-full px-3 py-1.5 text-sm border rounded-md shadow-sm focus:outline-none focus:ring-1 ${
|
||||
errors["address.country"]
|
||||
? "border-red-300 focus:border-red-500 focus:ring-red-500"
|
||||
: "border-gray-300 focus:border-blue-500 focus:ring-blue-500"
|
||||
errors['address.country']
|
||||
? 'border-red-300 focus:border-red-500 focus:ring-red-500'
|
||||
: 'border-gray-300 focus:border-blue-500 focus:ring-blue-500'
|
||||
}`}
|
||||
placeholder="Ülke"
|
||||
/>
|
||||
{errors["address.country"] && (
|
||||
<p className="mt-1 text-sm text-red-600">
|
||||
{errors["address.country"]}
|
||||
</p>
|
||||
{errors['address.country'] && (
|
||||
<p className="mt-1 text-sm text-red-600">{errors['address.country']}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -479,25 +415,17 @@ const WarehouseForm: React.FC = () => {
|
|||
<div className="bg-gray-50 rounded-lg p-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 text-sm">
|
||||
<div>
|
||||
<span className="font-medium text-gray-700">
|
||||
Toplam Kapasite:
|
||||
</span>
|
||||
<span className="font-medium text-gray-700">Toplam Kapasite:</span>
|
||||
<div className="text-lg font-semibold text-blue-600">
|
||||
{formData.capacity.toLocaleString()} m³
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-medium text-gray-700">
|
||||
Mevcut Kullanım:
|
||||
</span>
|
||||
<div className="text-lg font-semibold text-green-600">
|
||||
0 m³ (0%)
|
||||
</div>
|
||||
<span className="font-medium text-gray-700">Mevcut Kullanım:</span>
|
||||
<div className="text-lg font-semibold text-green-600">0 m³ (0%)</div>
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-medium text-gray-700">
|
||||
Müsait Kapasite:
|
||||
</span>
|
||||
<span className="font-medium text-gray-700">Müsait Kapasite:</span>
|
||||
<div className="text-lg font-semibold text-gray-600">
|
||||
{formData.capacity.toLocaleString()} m³
|
||||
</div>
|
||||
|
|
@ -506,10 +434,7 @@ const WarehouseForm: React.FC = () => {
|
|||
|
||||
<div className="mt-4">
|
||||
<div className="w-full bg-gray-200 rounded-full h-2">
|
||||
<div
|
||||
className="bg-blue-600 h-2 rounded-full"
|
||||
style={{ width: "0%" }}
|
||||
/>
|
||||
<div className="bg-blue-600 h-2 rounded-full" style={{ width: '0%' }} />
|
||||
</div>
|
||||
<p className="text-xs text-gray-600 mt-1">Kullanım oranı: 0%</p>
|
||||
</div>
|
||||
|
|
@ -532,25 +457,20 @@ const WarehouseForm: React.FC = () => {
|
|||
<div className="flex">
|
||||
<div className="ml-3">
|
||||
<p className="text-sm text-blue-700">
|
||||
{formData.warehouseType ===
|
||||
WarehouseTypeEnum.RawMaterials &&
|
||||
"Hammadde depolama için özel koşullara sahip olacaktır."}
|
||||
{formData.warehouseType ===
|
||||
WarehouseTypeEnum.FinishedGoods &&
|
||||
"Mamul depolama ve sevkiyat operasyonları için optimize edilecektir."}
|
||||
{formData.warehouseType ===
|
||||
WarehouseTypeEnum.SpareParts &&
|
||||
"Yedek parça yönetimi için özel raflar ve kategorilendirme yapılacaktır."}
|
||||
{formData.warehouseType === WarehouseTypeEnum.RawMaterials &&
|
||||
'Hammadde depolama için özel koşullara sahip olacaktır.'}
|
||||
{formData.warehouseType === WarehouseTypeEnum.FinishedGoods &&
|
||||
'Mamul depolama ve sevkiyat operasyonları için optimize edilecektir.'}
|
||||
{formData.warehouseType === WarehouseTypeEnum.SpareParts &&
|
||||
'Yedek parça yönetimi için özel raflar ve kategorilendirme yapılacaktır.'}
|
||||
{formData.warehouseType === WarehouseTypeEnum.Returns &&
|
||||
"Konsinye stok yönetimi için ayrı izleme sistemi kullanılacaktır."}
|
||||
{formData.warehouseType ===
|
||||
WarehouseTypeEnum.Quarantine &&
|
||||
"Karantina prosedürleri ve onay mekanizmaları aktif olacaktır."}
|
||||
'Konsinye stok yönetimi için ayrı izleme sistemi kullanılacaktır.'}
|
||||
{formData.warehouseType === WarehouseTypeEnum.Quarantine &&
|
||||
'Karantina prosedürleri ve onay mekanizmaları aktif olacaktır.'}
|
||||
{formData.warehouseType === WarehouseTypeEnum.Transit &&
|
||||
"Sıcaklık kontrollü depolama sistemi kurulacaktır."}
|
||||
{formData.warehouseType ===
|
||||
WarehouseTypeEnum.WorkInProgress &&
|
||||
"Tehlikeli madde depolama güvenlik protokolleri uygulanacaktır."}
|
||||
'Sıcaklık kontrollü depolama sistemi kurulacaktır.'}
|
||||
{formData.warehouseType === WarehouseTypeEnum.WorkInProgress &&
|
||||
'Tehlikeli madde depolama güvenlik protokolleri uygulanacaktır.'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -582,14 +502,15 @@ const WarehouseForm: React.FC = () => {
|
|||
) : (
|
||||
<>
|
||||
<FaSave className="w-4 h-4 mr-2" />
|
||||
{isEdit ? "Güncelle" : "Kaydet"}
|
||||
{isEdit ? 'Güncelle' : 'Kaydet'}
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
export default WarehouseForm;
|
||||
export default WarehouseForm
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import React, { useState } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import {
|
||||
FaBuilding,
|
||||
FaPlus,
|
||||
|
|
@ -14,45 +14,42 @@ import {
|
|||
FaArrowUp,
|
||||
FaExclamationTriangle,
|
||||
FaChartLine,
|
||||
} from "react-icons/fa";
|
||||
import classNames from "classnames";
|
||||
import { WarehouseTypeEnum } from "../../../types/wm";
|
||||
import { mockWarehouses } from "../../../mocks/mockWarehouses";
|
||||
import {
|
||||
getWarehouseTypeColor,
|
||||
getWarehouseTypeText,
|
||||
} from "../../../utils/erp";
|
||||
} from 'react-icons/fa'
|
||||
import classNames from 'classnames'
|
||||
import { WarehouseTypeEnum } from '../../../types/wm'
|
||||
import { mockWarehouses } from '../../../mocks/mockWarehouses'
|
||||
import { getWarehouseTypeColor, getWarehouseTypeText } from '../../../utils/erp'
|
||||
import { Container } from '@/components/shared'
|
||||
|
||||
const WarehouseList: React.FC = () => {
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [filterType, setFilterType] = useState("all");
|
||||
const [showFilters, setShowFilters] = useState(false);
|
||||
const [searchTerm, setSearchTerm] = useState('')
|
||||
const [filterType, setFilterType] = useState('all')
|
||||
const [showFilters, setShowFilters] = useState(false)
|
||||
|
||||
const {
|
||||
data: warehouses,
|
||||
isLoading,
|
||||
error,
|
||||
} = useQuery({
|
||||
queryKey: ["warehouses", searchTerm, filterType],
|
||||
queryKey: ['warehouses', searchTerm, filterType],
|
||||
queryFn: async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
await new Promise((resolve) => setTimeout(resolve, 500))
|
||||
return mockWarehouses.filter((warehouse) => {
|
||||
const matchesSearch =
|
||||
warehouse.code.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
warehouse.name.toLowerCase().includes(searchTerm.toLowerCase());
|
||||
const matchesType =
|
||||
filterType === "all" || warehouse.warehouseType === filterType;
|
||||
return matchesSearch && matchesType;
|
||||
});
|
||||
warehouse.name.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
const matchesType = filterType === 'all' || warehouse.warehouseType === filterType
|
||||
return matchesSearch && matchesType
|
||||
})
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
const getUtilizationColor = (utilization: number) => {
|
||||
const percentage = utilization;
|
||||
if (percentage >= 90) return "text-red-600";
|
||||
if (percentage >= 75) return "text-yellow-600";
|
||||
return "text-green-600";
|
||||
};
|
||||
const percentage = utilization
|
||||
if (percentage >= 90) return 'text-red-600'
|
||||
if (percentage >= 75) return 'text-yellow-600'
|
||||
return 'text-green-600'
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
|
|
@ -60,7 +57,7 @@ const WarehouseList: React.FC = () => {
|
|||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
|
||||
<span className="ml-3 text-gray-600">Depolar yükleniyor...</span>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
if (error) {
|
||||
|
|
@ -71,11 +68,12 @@ const WarehouseList: React.FC = () => {
|
|||
<span className="text-red-800">Depolar yüklenirken hata oluştu.</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4 pt-2">
|
||||
<Container>
|
||||
<div className="space-y-2">
|
||||
{/* Header Actions */}
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3">
|
||||
<div className="flex items-center space-x-2">
|
||||
|
|
@ -96,10 +94,10 @@ const WarehouseList: React.FC = () => {
|
|||
<button
|
||||
onClick={() => setShowFilters(!showFilters)}
|
||||
className={classNames(
|
||||
"flex items-center px-3 py-1.5 text-sm border rounded-lg transition-colors",
|
||||
'flex items-center px-3 py-1.5 text-sm border rounded-lg transition-colors',
|
||||
showFilters
|
||||
? "border-blue-500 bg-blue-50 text-blue-700"
|
||||
: "border-gray-300 bg-white text-gray-700 hover:bg-gray-50"
|
||||
? 'border-blue-500 bg-blue-50 text-blue-700'
|
||||
: 'border-gray-300 bg-white text-gray-700 hover:bg-gray-50',
|
||||
)}
|
||||
>
|
||||
<FaFilter size={14} className="mr-2" />
|
||||
|
|
@ -109,7 +107,7 @@ const WarehouseList: React.FC = () => {
|
|||
|
||||
<div className="flex items-center space-x-2">
|
||||
<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 text-sm border border-gray-300 bg-white text-gray-700 rounded-lg hover:bg-gray-50 transition-colors"
|
||||
>
|
||||
<FaDownload size={14} className="mr-2" />
|
||||
|
|
@ -131,9 +129,7 @@ const WarehouseList: React.FC = () => {
|
|||
<div className="bg-white border border-gray-200 rounded-lg p-3">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-gray-700 mb-1">
|
||||
Depo Türü
|
||||
</label>
|
||||
<label className="block text-xs font-medium text-gray-700 mb-1">Depo Türü</label>
|
||||
<select
|
||||
value={filterType}
|
||||
onChange={(e) => setFilterType(e.target.value)}
|
||||
|
|
@ -142,12 +138,8 @@ const WarehouseList: React.FC = () => {
|
|||
<option value="all">Tümü</option>
|
||||
<option value={WarehouseTypeEnum.RawMaterials}>Hammadde</option>
|
||||
<option value={WarehouseTypeEnum.FinishedGoods}>Mamul</option>
|
||||
<option value={WarehouseTypeEnum.WorkInProgress}>
|
||||
Yarı Mamul
|
||||
</option>
|
||||
<option value={WarehouseTypeEnum.SpareParts}>
|
||||
Yedek Parça
|
||||
</option>
|
||||
<option value={WarehouseTypeEnum.WorkInProgress}>Yarı Mamul</option>
|
||||
<option value={WarehouseTypeEnum.SpareParts}>Yedek Parça</option>
|
||||
<option value={WarehouseTypeEnum.Quarantine}>Karantina</option>
|
||||
</select>
|
||||
</div>
|
||||
|
|
@ -155,8 +147,8 @@ const WarehouseList: React.FC = () => {
|
|||
<div className="flex items-end">
|
||||
<button
|
||||
onClick={() => {
|
||||
setFilterType("all");
|
||||
setSearchTerm("");
|
||||
setFilterType('all')
|
||||
setSearchTerm('')
|
||||
}}
|
||||
className="w-full px-4 py-1.5 text-sm border border-gray-300 bg-white text-gray-700 rounded-lg hover:bg-gray-50 transition-colors"
|
||||
>
|
||||
|
|
@ -173,9 +165,7 @@ const WarehouseList: React.FC = () => {
|
|||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-600">Toplam Depo</p>
|
||||
<p className="text-xl font-bold text-gray-900">
|
||||
{warehouses?.length || 0}
|
||||
</p>
|
||||
<p className="text-xl font-bold text-gray-900">{warehouses?.length || 0}</p>
|
||||
</div>
|
||||
<FaBuilding className="h-6 w-6 text-blue-500" />
|
||||
</div>
|
||||
|
|
@ -184,13 +174,9 @@ const WarehouseList: React.FC = () => {
|
|||
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-600">
|
||||
Toplam Kapasite
|
||||
</p>
|
||||
<p className="text-sm font-medium text-gray-600">Toplam Kapasite</p>
|
||||
<p className="text-xl font-bold text-green-600">
|
||||
{warehouses
|
||||
?.reduce((acc, w) => acc + w.capacity, 0)
|
||||
.toLocaleString() || 0}
|
||||
{warehouses?.reduce((acc, w) => acc + w.capacity, 0).toLocaleString() || 0}
|
||||
</p>
|
||||
</div>
|
||||
<FaBox className="h-6 w-6 text-green-500" />
|
||||
|
|
@ -200,19 +186,13 @@ const WarehouseList: React.FC = () => {
|
|||
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-600">
|
||||
Kullanım Oranı
|
||||
</p>
|
||||
<p className="text-sm font-medium text-gray-600">Kullanım Oranı</p>
|
||||
<p className="text-xl font-bold text-yellow-600">
|
||||
{warehouses?.length
|
||||
? Math.round(
|
||||
(warehouses.reduce(
|
||||
(acc, w) => acc + w.currentUtilization,
|
||||
0
|
||||
) /
|
||||
(warehouses.reduce((acc, w) => acc + w.capacity, 0) ||
|
||||
1)) *
|
||||
100
|
||||
(warehouses.reduce((acc, w) => acc + w.currentUtilization, 0) /
|
||||
(warehouses.reduce((acc, w) => acc + w.capacity, 0) || 1)) *
|
||||
100,
|
||||
)
|
||||
: 0}
|
||||
%
|
||||
|
|
@ -272,14 +252,11 @@ const WarehouseList: React.FC = () => {
|
|||
<tbody className="bg-white divide-y divide-gray-200">
|
||||
{warehouses?.map((warehouse) => {
|
||||
const utilizationPercentage = Math.round(
|
||||
(warehouse.currentUtilization / warehouse.capacity) * 100
|
||||
);
|
||||
(warehouse.currentUtilization / warehouse.capacity) * 100,
|
||||
)
|
||||
|
||||
return (
|
||||
<tr
|
||||
key={warehouse.id}
|
||||
className="hover:bg-gray-50 transition-colors"
|
||||
>
|
||||
<tr key={warehouse.id} className="hover:bg-gray-50 transition-colors">
|
||||
<td className="px-4 py-3">
|
||||
<div className="flex items-center">
|
||||
<div className="flex-shrink-0 h-8 w-8">
|
||||
|
|
@ -307,16 +284,15 @@ const WarehouseList: React.FC = () => {
|
|||
<div className="space-y-1">
|
||||
<span
|
||||
className={classNames(
|
||||
"inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium",
|
||||
getWarehouseTypeColor(warehouse.warehouseType)
|
||||
'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium',
|
||||
getWarehouseTypeColor(warehouse.warehouseType),
|
||||
)}
|
||||
>
|
||||
{getWarehouseTypeText(warehouse.warehouseType)}
|
||||
</span>
|
||||
<div className="flex items-center text-xs text-gray-500">
|
||||
<FaMapMarkerAlt size={14} className="mr-1" />
|
||||
{warehouse.address?.city},{" "}
|
||||
{warehouse.address?.country}
|
||||
{warehouse.address?.city}, {warehouse.address?.country}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
|
|
@ -325,17 +301,13 @@ const WarehouseList: React.FC = () => {
|
|||
<div className="text-sm font-medium text-gray-900">
|
||||
{warehouse.capacity.toLocaleString()} m³
|
||||
</div>
|
||||
<div className="text-sm text-gray-500">
|
||||
Toplam Kapasite
|
||||
</div>
|
||||
<div className="text-sm text-gray-500">Toplam Kapasite</div>
|
||||
</td>
|
||||
|
||||
<td className="px-4 py-3">
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-xs text-gray-600">
|
||||
Kullanılan:
|
||||
</span>
|
||||
<span className="text-xs text-gray-600">Kullanılan:</span>
|
||||
<span className="text-sm font-medium text-gray-900">
|
||||
{warehouse.currentUtilization.toLocaleString()} m³
|
||||
</span>
|
||||
|
|
@ -343,12 +315,12 @@ const WarehouseList: React.FC = () => {
|
|||
<div className="w-full bg-gray-200 rounded-full h-2">
|
||||
<div
|
||||
className={classNames(
|
||||
"h-2 rounded-full",
|
||||
'h-2 rounded-full',
|
||||
utilizationPercentage >= 90
|
||||
? "bg-red-500"
|
||||
? 'bg-red-500'
|
||||
: utilizationPercentage >= 75
|
||||
? "bg-yellow-500"
|
||||
: "bg-green-500"
|
||||
? 'bg-yellow-500'
|
||||
: 'bg-green-500',
|
||||
)}
|
||||
style={{
|
||||
width: `${Math.min(utilizationPercentage, 100)}%`,
|
||||
|
|
@ -357,8 +329,8 @@ const WarehouseList: React.FC = () => {
|
|||
</div>
|
||||
<div
|
||||
className={classNames(
|
||||
"text-sm font-medium",
|
||||
getUtilizationColor(utilizationPercentage)
|
||||
'text-sm font-medium',
|
||||
getUtilizationColor(utilizationPercentage),
|
||||
)}
|
||||
>
|
||||
%{utilizationPercentage}
|
||||
|
|
@ -369,13 +341,8 @@ const WarehouseList: React.FC = () => {
|
|||
<td className="px-4 py-3">
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center">
|
||||
<FaChartLine
|
||||
size={14}
|
||||
className="text-green-500 mr-1"
|
||||
/>
|
||||
<span className="text-sm text-green-600">
|
||||
Verimli
|
||||
</span>
|
||||
<FaChartLine size={14} className="text-green-500 mr-1" />
|
||||
<span className="text-sm text-green-600">Verimli</span>
|
||||
</div>
|
||||
<div className="text-xs text-gray-500">Son 30 gün</div>
|
||||
</div>
|
||||
|
|
@ -384,13 +351,13 @@ const WarehouseList: React.FC = () => {
|
|||
<td className="px-4 py-3">
|
||||
<span
|
||||
className={classNames(
|
||||
"inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium",
|
||||
'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium',
|
||||
warehouse.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',
|
||||
)}
|
||||
>
|
||||
{warehouse.isActive ? "Aktif" : "Pasif"}
|
||||
{warehouse.isActive ? 'Aktif' : 'Pasif'}
|
||||
</span>
|
||||
</td>
|
||||
|
||||
|
|
@ -414,7 +381,7 @@ const WarehouseList: React.FC = () => {
|
|||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
@ -423,12 +390,8 @@ const WarehouseList: React.FC = () => {
|
|||
{(!warehouses || warehouses.length === 0) && (
|
||||
<div className="text-center py-12">
|
||||
<FaBuilding className="mx-auto h-12 w-12 text-gray-400" />
|
||||
<h3 className="mt-2 text-sm font-medium text-gray-900">
|
||||
Depo bulunamadı
|
||||
</h3>
|
||||
<p className="mt-1 text-sm text-gray-500">
|
||||
Yeni depo ekleyerek başlayın.
|
||||
</p>
|
||||
<h3 className="mt-2 text-sm font-medium text-gray-900">Depo bulunamadı</h3>
|
||||
<p className="mt-1 text-sm text-gray-500">Yeni depo ekleyerek başlayın.</p>
|
||||
<div className="mt-6">
|
||||
<Link
|
||||
to="/admin/warehouse/new"
|
||||
|
|
@ -442,7 +405,8 @@ const WarehouseList: React.FC = () => {
|
|||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
export default WarehouseList;
|
||||
export default WarehouseList
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue