1226 lines
46 KiB
TypeScript
1226 lines
46 KiB
TypeScript
|
|
import React, { useState } from "react";
|
|||
|
|
import {
|
|||
|
|
WmWarehouse,
|
|||
|
|
WarehouseTypeEnum,
|
|||
|
|
WmZone,
|
|||
|
|
WmLocation,
|
|||
|
|
} from "../../../types/wm";
|
|||
|
|
import {
|
|||
|
|
FaPlus,
|
|||
|
|
FaSearch,
|
|||
|
|
FaEdit,
|
|||
|
|
FaTrash,
|
|||
|
|
FaBuilding,
|
|||
|
|
FaMapMarkerAlt,
|
|||
|
|
FaLayerGroup,
|
|||
|
|
FaExclamationCircle,
|
|||
|
|
FaCheckCircle,
|
|||
|
|
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";
|
|||
|
|
import {
|
|||
|
|
getWarehouseTypeColor,
|
|||
|
|
getWarehouseTypeText,
|
|||
|
|
getZoneTypeText,
|
|||
|
|
getLocationTypeText,
|
|||
|
|
} from "../../../utils/erp";
|
|||
|
|
|
|||
|
|
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");
|
|||
|
|
|
|||
|
|
// Modal states
|
|||
|
|
const [warehouseModal, setWarehouseModal] = useState<{
|
|||
|
|
isOpen: boolean;
|
|||
|
|
mode: "create" | "edit" | "view";
|
|||
|
|
warehouse: WmWarehouse | null;
|
|||
|
|
}>({
|
|||
|
|
isOpen: false,
|
|||
|
|
mode: "create",
|
|||
|
|
warehouse: null,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const [zoneModal, setZoneModal] = useState<{
|
|||
|
|
isOpen: boolean;
|
|||
|
|
mode: "create" | "edit" | "view";
|
|||
|
|
zone: WmZone | null;
|
|||
|
|
}>({
|
|||
|
|
isOpen: false,
|
|||
|
|
mode: "create",
|
|||
|
|
zone: null,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const [locationModal, setLocationModal] = useState<{
|
|||
|
|
isOpen: boolean;
|
|||
|
|
mode: "create" | "edit" | "view";
|
|||
|
|
location: WmLocation | null;
|
|||
|
|
}>({
|
|||
|
|
isOpen: false,
|
|||
|
|
mode: "create",
|
|||
|
|
location: null,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// Data states (in a real app, these would come from an API)
|
|||
|
|
const [warehouses, setWarehouses] = useState<WmWarehouse[]>(mockWarehouses);
|
|||
|
|
|
|||
|
|
const [zones, setZones] = useState<WmZone[]>(mockZones);
|
|||
|
|
|
|||
|
|
const [locations, setLocations] = useState<WmLocation[]>(mockLocations);
|
|||
|
|
|
|||
|
|
// Modal handlers
|
|||
|
|
const handleWarehouseAction = (
|
|||
|
|
mode: "create" | "edit" | "view",
|
|||
|
|
warehouse?: WmWarehouse
|
|||
|
|
) => {
|
|||
|
|
setWarehouseModal({
|
|||
|
|
isOpen: true,
|
|||
|
|
mode,
|
|||
|
|
warehouse: warehouse || null,
|
|||
|
|
});
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleZoneAction = (
|
|||
|
|
mode: "create" | "edit" | "view",
|
|||
|
|
zone?: WmZone
|
|||
|
|
) => {
|
|||
|
|
setZoneModal({
|
|||
|
|
isOpen: true,
|
|||
|
|
mode,
|
|||
|
|
zone: zone || null,
|
|||
|
|
});
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleLocationAction = (
|
|||
|
|
mode: "create" | "edit" | "view",
|
|||
|
|
location?: WmLocation
|
|||
|
|
) => {
|
|||
|
|
setLocationModal({
|
|||
|
|
isOpen: true,
|
|||
|
|
mode,
|
|||
|
|
location: location || null,
|
|||
|
|
});
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleWarehouseSave = (warehouseData: Partial<WmWarehouse>) => {
|
|||
|
|
if (warehouseModal.mode === "create") {
|
|||
|
|
const newWarehouse: WmWarehouse = {
|
|||
|
|
...warehouseData,
|
|||
|
|
id: Date.now().toString(),
|
|||
|
|
locations: [],
|
|||
|
|
zones: [],
|
|||
|
|
creationTime: new Date(),
|
|||
|
|
lastModificationTime: new Date(),
|
|||
|
|
} as WmWarehouse;
|
|||
|
|
setWarehouses([...warehouses, newWarehouse]);
|
|||
|
|
} else if (warehouseModal.mode === "edit" && warehouseModal.warehouse) {
|
|||
|
|
setWarehouses(
|
|||
|
|
warehouses.map((w) =>
|
|||
|
|
w.id === warehouseModal.warehouse!.id
|
|||
|
|
? ({
|
|||
|
|
...w,
|
|||
|
|
...warehouseData,
|
|||
|
|
lastModificationTime: new Date(),
|
|||
|
|
} as WmWarehouse)
|
|||
|
|
: w
|
|||
|
|
)
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleZoneSave = (zoneData: Partial<WmZone>) => {
|
|||
|
|
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) {
|
|||
|
|
setZones(
|
|||
|
|
zones.map((z) =>
|
|||
|
|
z.id === zoneModal.zone!.id ? ({ ...z, ...zoneData } as WmZone) : z
|
|||
|
|
)
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleLocationSave = (locationData: Partial<WmLocation>) => {
|
|||
|
|
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) {
|
|||
|
|
setLocations(
|
|||
|
|
locations.map((l) =>
|
|||
|
|
l.id === locationModal.location!.id
|
|||
|
|
? ({ ...l, ...locationData } as WmLocation)
|
|||
|
|
: l
|
|||
|
|
)
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
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?`
|
|||
|
|
)
|
|||
|
|
) {
|
|||
|
|
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;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 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;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const filteredZones = zones.filter(
|
|||
|
|
(zone) => selectedWarehouse === "" || zone.warehouseId === selectedWarehouse
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
const filteredLocations = locations.filter(
|
|||
|
|
(location) =>
|
|||
|
|
selectedWarehouse === "" || location.warehouseId === selectedWarehouse
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// Warehouse Components
|
|||
|
|
const WarehousesGridView = () => (
|
|||
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
|
|||
|
|
{filteredWarehouses.map((warehouse) => (
|
|||
|
|
<div
|
|||
|
|
key={warehouse.id}
|
|||
|
|
className="bg-white rounded-lg shadow-sm border border-gray-200 p-3 flex flex-col"
|
|||
|
|
>
|
|||
|
|
<div className="flex items-start justify-between mb-2">
|
|||
|
|
<div className="flex items-center gap-2">
|
|||
|
|
<div className="p-2 bg-blue-100 rounded-lg">
|
|||
|
|
<FaBuilding className="w-4 h-4 text-blue-600" />
|
|||
|
|
</div>
|
|||
|
|
<div>
|
|||
|
|
<h3 className="font-medium text-sm text-gray-900">
|
|||
|
|
{warehouse.name}
|
|||
|
|
</h3>
|
|||
|
|
<p className="text-sm text-gray-600">{warehouse.code}</p>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
{warehouse.isMainWarehouse && (
|
|||
|
|
<span className="bg-green-100 text-green-800 text-xs px-2 py-1 rounded-full">
|
|||
|
|
Ana Depo
|
|||
|
|
</span>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="space-y-2 flex-grow">
|
|||
|
|
<div>
|
|||
|
|
<span
|
|||
|
|
className={`inline-flex px-2 py-1 text-xs font-medium rounded-full ${getWarehouseTypeColor(
|
|||
|
|
warehouse.warehouseType
|
|||
|
|
)}`}
|
|||
|
|
>
|
|||
|
|
{getWarehouseTypeText(warehouse.warehouseType)}
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="flex items-center gap-2 text-xs text-gray-600">
|
|||
|
|
<FaMapMarkerAlt className="w-4 h-4" />
|
|||
|
|
<span>
|
|||
|
|
{warehouse.address?.city}, {warehouse.address?.state}
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="text-xs text-gray-600 flex-grow">
|
|||
|
|
<p>{warehouse.description}</p>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Capacity */}
|
|||
|
|
<div className="space-y-1">
|
|||
|
|
<div className="flex justify-between text-xs">
|
|||
|
|
<span className="text-gray-600">Kapasite Kullanımı</span>
|
|||
|
|
<span className="font-medium">
|
|||
|
|
{warehouse.currentUtilization} / {warehouse.capacity}
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
<div className="w-full bg-gray-200 rounded-full h-2">
|
|||
|
|
<div
|
|||
|
|
className="bg-blue-600 h-2 rounded-full"
|
|||
|
|
style={{
|
|||
|
|
width: `${
|
|||
|
|
(warehouse.currentUtilization / warehouse.capacity) * 100
|
|||
|
|
}%`,
|
|||
|
|
}}
|
|||
|
|
></div>
|
|||
|
|
</div>
|
|||
|
|
<div className="text-xs text-gray-500">
|
|||
|
|
%
|
|||
|
|
{Math.round(
|
|||
|
|
(warehouse.currentUtilization / warehouse.capacity) * 100
|
|||
|
|
)}{" "}
|
|||
|
|
dolu
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="flex items-center justify-between pt-3 mt-3 border-t border-gray-100">
|
|||
|
|
<div className="flex items-center gap-2">
|
|||
|
|
{warehouse.isActive ? (
|
|||
|
|
<>
|
|||
|
|
<FaCheckCircle className="w-4 h-4 text-green-500" />
|
|||
|
|
<span className="text-xs text-green-600">Aktif</span>
|
|||
|
|
</>
|
|||
|
|
) : (
|
|||
|
|
<>
|
|||
|
|
<FaExclamationCircle className="w-4 h-4 text-red-500" />
|
|||
|
|
<span className="text-sm text-red-600">Pasif</span>
|
|||
|
|
</>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
<div className="flex items-center gap-1">
|
|||
|
|
<button
|
|||
|
|
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)}
|
|||
|
|
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)}
|
|||
|
|
className="p-1.5 text-gray-400 hover:text-red-600 hover:bg-red-50 rounded-md"
|
|||
|
|
title="Sil"
|
|||
|
|
>
|
|||
|
|
<FaTrash className="w-4 h-4" />
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
))}
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
const WarehousesListView = () => (
|
|||
|
|
<div className="bg-white rounded-lg shadow-sm border border-gray-200">
|
|||
|
|
<div className="overflow-x-auto">
|
|||
|
|
<table className="min-w-full divide-y divide-gray-200">
|
|||
|
|
<thead className="bg-gray-50">
|
|||
|
|
<tr>
|
|||
|
|
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|||
|
|
Depo
|
|||
|
|
</th>
|
|||
|
|
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|||
|
|
Tip
|
|||
|
|
</th>
|
|||
|
|
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|||
|
|
Adres
|
|||
|
|
</th>
|
|||
|
|
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|||
|
|
Kapasite
|
|||
|
|
</th>
|
|||
|
|
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|||
|
|
Kullanım
|
|||
|
|
</th>
|
|||
|
|
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|||
|
|
Durum
|
|||
|
|
</th>
|
|||
|
|
<th className="px-3 py-2 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|||
|
|
İşlemler
|
|||
|
|
</th>
|
|||
|
|
</tr>
|
|||
|
|
</thead>
|
|||
|
|
<tbody className="bg-white divide-y divide-gray-200">
|
|||
|
|
{filteredWarehouses.map((warehouse) => {
|
|||
|
|
const utilizationPercentage =
|
|||
|
|
(warehouse.currentUtilization / warehouse.capacity) * 100;
|
|||
|
|
return (
|
|||
|
|
<tr key={warehouse.id} className="hover:bg-gray-50">
|
|||
|
|
<td className="px-3 py-2 whitespace-nowrap">
|
|||
|
|
<div className="flex items-center ">
|
|||
|
|
<div className="p-2 bg-blue-100 rounded-lg mr-3">
|
|||
|
|
<FaBuilding className="w-4 h-4 text-blue-600" />
|
|||
|
|
</div>
|
|||
|
|
<div>
|
|||
|
|
<div className="text-sm font-medium text-gray-900 flex items-center gap-2">
|
|||
|
|
{warehouse.name}
|
|||
|
|
{warehouse.isMainWarehouse && (
|
|||
|
|
<span className="bg-green-100 text-green-800 text-xs px-2 py-1 rounded-full">
|
|||
|
|
Ana Depo
|
|||
|
|
</span>
|
|||
|
|
)}
|
|||
|
|
</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
|
|||
|
|
)}`}
|
|||
|
|
>
|
|||
|
|
{getWarehouseTypeText(warehouse.warehouseType)}
|
|||
|
|
</span>
|
|||
|
|
</td>
|
|||
|
|
<td className="px-3 py-2 whitespace-nowrap">
|
|||
|
|
<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>
|
|||
|
|
</td>
|
|||
|
|
<td className="px-3 py-2 whitespace-nowrap text-sm text-gray-900">
|
|||
|
|
{warehouse.capacity.toLocaleString()} birim
|
|||
|
|
</td>
|
|||
|
|
<td className="px-3 py-2 whitespace-nowrap">
|
|||
|
|
<div className="flex items-center">
|
|||
|
|
<div className="w-16 bg-gray-200 rounded-full h-2 mr-2">
|
|||
|
|
<div
|
|||
|
|
className={`h-2 rounded-full ${
|
|||
|
|
utilizationPercentage > 90
|
|||
|
|
? "bg-red-500"
|
|||
|
|
: utilizationPercentage > 70
|
|||
|
|
? "bg-yellow-500"
|
|||
|
|
: "bg-blue-500"
|
|||
|
|
}`}
|
|||
|
|
style={{ width: `${utilizationPercentage}%` }}
|
|||
|
|
></div>
|
|||
|
|
</div>
|
|||
|
|
<span className="text-xs text-gray-900">
|
|||
|
|
%{Math.round(utilizationPercentage)}
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
<div className="text-xs text-gray-500">
|
|||
|
|
{warehouse.currentUtilization.toLocaleString()} /{" "}
|
|||
|
|
{warehouse.capacity.toLocaleString()}
|
|||
|
|
</div>
|
|||
|
|
</td>
|
|||
|
|
<td className="px-3 py-2 whitespace-nowrap">
|
|||
|
|
{warehouse.isActive ? (
|
|||
|
|
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">
|
|||
|
|
<FaCheckCircle className="w-3 h-3 mr-1" />
|
|||
|
|
Aktif
|
|||
|
|
</span>
|
|||
|
|
) : (
|
|||
|
|
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800">
|
|||
|
|
<FaExclamationCircle className="w-3 h-3 mr-1" />
|
|||
|
|
Pasif
|
|||
|
|
</span>
|
|||
|
|
)}
|
|||
|
|
</td>
|
|||
|
|
<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)}
|
|||
|
|
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)}
|
|||
|
|
className="text-blue-600 hover:text-blue-900"
|
|||
|
|
title="Düzenle"
|
|||
|
|
>
|
|||
|
|
<FaEdit className="w-4 h-4" />
|
|||
|
|
</button>
|
|||
|
|
<button
|
|||
|
|
onClick={() => handleDelete("warehouse", warehouse.id)}
|
|||
|
|
className="text-red-600 hover:text-red-900"
|
|||
|
|
title="Sil"
|
|||
|
|
>
|
|||
|
|
<FaTrash className="w-4 h-4" />
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</td>
|
|||
|
|
</tr>
|
|||
|
|
);
|
|||
|
|
})}
|
|||
|
|
</tbody>
|
|||
|
|
</table>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
const WarehousesTab = () => (
|
|||
|
|
<div className="space-y-4 pt-2">
|
|||
|
|
{/* Filters */}
|
|||
|
|
<div className="flex flex-col sm:flex-row gap-4">
|
|||
|
|
<div className="relative flex-1">
|
|||
|
|
<FaSearch className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
|
|||
|
|
<input
|
|||
|
|
type="text"
|
|||
|
|
placeholder="Depo ara..."
|
|||
|
|
value={searchTerm}
|
|||
|
|
onChange={(e) => setSearchTerm(e.target.value)}
|
|||
|
|
className="pl-10 pr-4 py-1.5 text-sm w-full border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
<select
|
|||
|
|
value={selectedType}
|
|||
|
|
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>
|
|||
|
|
{Object.values(WarehouseTypeEnum).map((type) => (
|
|||
|
|
<option key={type} value={type}>
|
|||
|
|
{getWarehouseTypeText(type)}
|
|||
|
|
</option>
|
|||
|
|
))}
|
|||
|
|
</select>
|
|||
|
|
<button
|
|||
|
|
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" />
|
|||
|
|
Yeni Depo
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Content based on view mode */}
|
|||
|
|
{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);
|
|||
|
|
return (
|
|||
|
|
<div
|
|||
|
|
key={zone.id}
|
|||
|
|
className="bg-white rounded-lg shadow-sm border border-gray-200 p-3 flex flex-col"
|
|||
|
|
>
|
|||
|
|
<div className="flex items-start justify-between mb-2">
|
|||
|
|
<div className="flex items-center gap-2">
|
|||
|
|
<div className="p-2 bg-purple-100 rounded-lg">
|
|||
|
|
<FaLayerGroup className="w-4 h-4 text-purple-600" />
|
|||
|
|
</div>
|
|||
|
|
<div>
|
|||
|
|
<h3 className="font-medium text-sm text-gray-900">
|
|||
|
|
{zone.name}
|
|||
|
|
</h3>
|
|||
|
|
<p className="text-sm text-gray-600">{zone.zoneCode}</p>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<span className="inline-flex px-2 py-1 text-xs font-medium rounded-full bg-blue-100 text-blue-800">
|
|||
|
|
{getZoneTypeText(zone.zoneType)}
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="space-y-2 flex-grow">
|
|||
|
|
<div className="text-xs text-gray-600">
|
|||
|
|
<p>
|
|||
|
|
<strong>Depo:</strong> {warehouse?.name}
|
|||
|
|
</p>
|
|||
|
|
<p>
|
|||
|
|
<strong>Kod:</strong> {warehouse?.code}
|
|||
|
|
</p>
|
|||
|
|
<p>{zone.description}</p>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{(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>
|
|||
|
|
<div className="grid grid-cols-2 gap-2 text-xs text-gray-600">
|
|||
|
|
{zone.temperature && (
|
|||
|
|
<div>Sıcaklık: {zone.temperature}°C</div>
|
|||
|
|
)}
|
|||
|
|
{zone.humidity && <div>Nem: %{zone.humidity}</div>}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="flex items-center justify-between pt-3 mt-3 border-t border-gray-100">
|
|||
|
|
<div className="flex items-center gap-2">
|
|||
|
|
{zone.isActive ? (
|
|||
|
|
<>
|
|||
|
|
<FaCheckCircle className="w-4 h-4 text-green-500" />
|
|||
|
|
<span className="text-xs text-green-600">Aktif</span>
|
|||
|
|
</>
|
|||
|
|
) : (
|
|||
|
|
<>
|
|||
|
|
<FaExclamationCircle className="w-4 h-4 text-red-500" />
|
|||
|
|
<span className="text-sm text-red-600">Pasif</span>
|
|||
|
|
</>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
<div className="flex items-center gap-2">
|
|||
|
|
<button
|
|||
|
|
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)}
|
|||
|
|
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)}
|
|||
|
|
className="p-1 text-gray-400 hover:text-red-600"
|
|||
|
|
title="Sil"
|
|||
|
|
>
|
|||
|
|
<FaTrash className="w-4 h-4" />
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
})}
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
const ZonesListView = () => (
|
|||
|
|
<div className="bg-white rounded-lg shadow-sm border border-gray-200">
|
|||
|
|
<div className="overflow-x-auto">
|
|||
|
|
<table className="min-w-full divide-y divide-gray-200">
|
|||
|
|
<thead className="bg-gray-50">
|
|||
|
|
<tr>
|
|||
|
|
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|||
|
|
Bölge
|
|||
|
|
</th>
|
|||
|
|
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|||
|
|
Depo
|
|||
|
|
</th>
|
|||
|
|
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|||
|
|
Tip
|
|||
|
|
</th>
|
|||
|
|
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|||
|
|
Sıcaklık
|
|||
|
|
</th>
|
|||
|
|
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|||
|
|
Nem
|
|||
|
|
</th>
|
|||
|
|
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|||
|
|
Durum
|
|||
|
|
</th>
|
|||
|
|
<th className="px-3 py-2 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|||
|
|
İşlemler
|
|||
|
|
</th>
|
|||
|
|
</tr>
|
|||
|
|
</thead>
|
|||
|
|
<tbody className="bg-white divide-y divide-gray-200">
|
|||
|
|
{filteredZones.map((zone) => {
|
|||
|
|
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">
|
|||
|
|
<div className="flex items-center">
|
|||
|
|
<div className="p-2 bg-purple-100 rounded-lg mr-3">
|
|||
|
|
<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>
|
|||
|
|
</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>
|
|||
|
|
</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">
|
|||
|
|
{getZoneTypeText(zone.zoneType)}
|
|||
|
|
</span>
|
|||
|
|
</td>
|
|||
|
|
<td className="px-3 py-2 whitespace-nowrap text-sm text-gray-900">
|
|||
|
|
{zone.temperature ? `${zone.temperature}°C` : "-"}
|
|||
|
|
</td>
|
|||
|
|
<td className="px-3 py-2 whitespace-nowrap text-sm text-gray-900">
|
|||
|
|
{zone.humidity ? `%${zone.humidity}` : "-"}
|
|||
|
|
</td>
|
|||
|
|
<td className="px-3 py-2 whitespace-nowrap">
|
|||
|
|
{zone.isActive ? (
|
|||
|
|
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">
|
|||
|
|
<FaCheckCircle className="w-3 h-3 mr-1" />
|
|||
|
|
Aktif
|
|||
|
|
</span>
|
|||
|
|
) : (
|
|||
|
|
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800">
|
|||
|
|
<FaExclamationCircle className="w-3 h-3 mr-1" />
|
|||
|
|
Pasif
|
|||
|
|
</span>
|
|||
|
|
)}
|
|||
|
|
</td>
|
|||
|
|
<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)}
|
|||
|
|
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)}
|
|||
|
|
className="text-blue-600 hover:text-blue-900"
|
|||
|
|
title="Düzenle"
|
|||
|
|
>
|
|||
|
|
<FaEdit className="w-4 h-4" />
|
|||
|
|
</button>
|
|||
|
|
<button
|
|||
|
|
onClick={() => handleDelete("zone", zone.id)}
|
|||
|
|
className="text-red-600 hover:text-red-900"
|
|||
|
|
title="Sil"
|
|||
|
|
>
|
|||
|
|
<FaTrash className="w-4 h-4" />
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</td>
|
|||
|
|
</tr>
|
|||
|
|
);
|
|||
|
|
})}
|
|||
|
|
</tbody>
|
|||
|
|
</table>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
const ZonesTab = () => (
|
|||
|
|
<div className="space-y-4 pt-2">
|
|||
|
|
{/* Warehouse Filter */}
|
|||
|
|
<div className="flex flex-col sm:flex-row gap-4">
|
|||
|
|
<select
|
|||
|
|
value={selectedWarehouse}
|
|||
|
|
onChange={(e) => setSelectedWarehouse(e.target.value)}
|
|||
|
|
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 Depolar</option>
|
|||
|
|
{warehouses.map((warehouse) => (
|
|||
|
|
<option key={warehouse.id} value={warehouse.id}>
|
|||
|
|
{warehouse.name} ({warehouse.code})
|
|||
|
|
</option>
|
|||
|
|
))}
|
|||
|
|
</select>
|
|||
|
|
<button
|
|||
|
|
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" />
|
|||
|
|
Yeni Bölge
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Content based on view mode */}
|
|||
|
|
{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;
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<div
|
|||
|
|
key={location.id}
|
|||
|
|
className="bg-white rounded-lg shadow-sm border border-gray-200 p-3"
|
|||
|
|
>
|
|||
|
|
<div className="flex items-start justify-between mb-3">
|
|||
|
|
<div className="flex items-center gap-2">
|
|||
|
|
<div className="p-2 bg-orange-100 rounded-lg">
|
|||
|
|
<FaMapMarkerAlt className="w-5 h-5 text-orange-600" />
|
|||
|
|
</div>
|
|||
|
|
<div>
|
|||
|
|
<h3 className="font-medium text-gray-900">{location.name}</h3>
|
|||
|
|
<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">
|
|||
|
|
{getLocationTypeText(location.locationType)}
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="space-y-2 flex-grow">
|
|||
|
|
<div className="text-xs text-gray-600">
|
|||
|
|
<p>
|
|||
|
|
<strong>Depo:</strong> {warehouse?.name}
|
|||
|
|
</p>
|
|||
|
|
{zone && (
|
|||
|
|
<p>
|
|||
|
|
<strong>Bölge:</strong> {zone.name}
|
|||
|
|
</p>
|
|||
|
|
)}
|
|||
|
|
<p>{location.description}</p>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* 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>
|
|||
|
|
<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>
|
|||
|
|
<div>Yükseklik: {location.dimensions.height}m</div>
|
|||
|
|
<div>
|
|||
|
|
Maks. Ağırlık: {location.dimensions.weight}
|
|||
|
|
{location.dimensions.unit}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
{/* Capacity */}
|
|||
|
|
<div className="space-y-1">
|
|||
|
|
<div className="flex justify-between text-xs">
|
|||
|
|
<span className="text-gray-600">Kapasite Kullanımı</span>
|
|||
|
|
<span className="font-medium">
|
|||
|
|
{location.currentStock} / {location.capacity}
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
<div className="w-full bg-gray-200 rounded-full h-2">
|
|||
|
|
<div
|
|||
|
|
className={`h-2 rounded-full ${
|
|||
|
|
utilizationPercentage > 90
|
|||
|
|
? "bg-red-500"
|
|||
|
|
: utilizationPercentage > 70
|
|||
|
|
? "bg-yellow-500"
|
|||
|
|
: "bg-green-500"
|
|||
|
|
}`}
|
|||
|
|
style={{ width: `${utilizationPercentage}%` }}
|
|||
|
|
></div>
|
|||
|
|
</div>
|
|||
|
|
<div className="text-xs text-gray-500">
|
|||
|
|
%{Math.round(utilizationPercentage)} dolu
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* 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>
|
|||
|
|
<div className="flex flex-wrap gap-1">
|
|||
|
|
{location.restrictions.map((restriction, index) => (
|
|||
|
|
<span
|
|||
|
|
key={index}
|
|||
|
|
className="inline-flex px-2 py-1 text-xs bg-yellow-100 text-yellow-800 rounded"
|
|||
|
|
>
|
|||
|
|
{restriction}
|
|||
|
|
</span>
|
|||
|
|
))}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="flex items-center justify-between pt-3 mt-3 border-t border-gray-100">
|
|||
|
|
<div className="flex items-center gap-2">
|
|||
|
|
{location.isActive ? (
|
|||
|
|
<>
|
|||
|
|
<FaCheckCircle className="w-4 h-4 text-green-500" />
|
|||
|
|
<span className="text-xs text-green-600">Aktif</span>
|
|||
|
|
</>
|
|||
|
|
) : (
|
|||
|
|
<>
|
|||
|
|
<FaExclamationCircle className="w-4 h-4 text-red-500" />
|
|||
|
|
<span className="text-sm text-red-600">Pasif</span>
|
|||
|
|
</>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
<div className="flex items-center gap-2">
|
|||
|
|
<button
|
|||
|
|
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)}
|
|||
|
|
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)}
|
|||
|
|
className="p-1 text-gray-400 hover:text-red-600"
|
|||
|
|
title="Sil"
|
|||
|
|
>
|
|||
|
|
<FaTrash className="w-4 h-4" />
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
})}
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
const LocationsListView = () => (
|
|||
|
|
<div className="bg-white rounded-lg shadow-sm border border-gray-200">
|
|||
|
|
<div className="overflow-x-auto">
|
|||
|
|
<table className="min-w-full divide-y divide-gray-200">
|
|||
|
|
<thead className="bg-gray-50">
|
|||
|
|
<tr>
|
|||
|
|
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|||
|
|
Lokasyon
|
|||
|
|
</th>
|
|||
|
|
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|||
|
|
Depo
|
|||
|
|
</th>
|
|||
|
|
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|||
|
|
Bölge
|
|||
|
|
</th>
|
|||
|
|
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|||
|
|
Tip
|
|||
|
|
</th>
|
|||
|
|
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|||
|
|
Kapasite
|
|||
|
|
</th>
|
|||
|
|
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|||
|
|
Kullanım
|
|||
|
|
</th>
|
|||
|
|
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|||
|
|
Durum
|
|||
|
|
</th>
|
|||
|
|
<th className="px-3 py-2 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|||
|
|
İşlemler
|
|||
|
|
</th>
|
|||
|
|
</tr>
|
|||
|
|
</thead>
|
|||
|
|
<tbody className="bg-white divide-y divide-gray-200">
|
|||
|
|
{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;
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<tr key={location.id} className="hover:bg-gray-50">
|
|||
|
|
<td className="px-3 py-2 whitespace-nowrap">
|
|||
|
|
<div className="flex items-center">
|
|||
|
|
<div className="p-2 bg-orange-100 rounded-lg mr-3">
|
|||
|
|
<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>
|
|||
|
|
</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>
|
|||
|
|
</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>
|
|||
|
|
</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">
|
|||
|
|
{getLocationTypeText(location.locationType)}
|
|||
|
|
</span>
|
|||
|
|
</td>
|
|||
|
|
<td className="px-3 py-2 whitespace-nowrap text-sm text-gray-900">
|
|||
|
|
{location.capacity} birim
|
|||
|
|
</td>
|
|||
|
|
<td className="px-3 py-2 whitespace-nowrap">
|
|||
|
|
<div className="flex items-center">
|
|||
|
|
<div className="w-16 bg-gray-200 rounded-full h-2 mr-2">
|
|||
|
|
<div
|
|||
|
|
className={`h-2 rounded-full ${
|
|||
|
|
utilizationPercentage > 90
|
|||
|
|
? "bg-red-500"
|
|||
|
|
: utilizationPercentage > 70
|
|||
|
|
? "bg-yellow-500"
|
|||
|
|
: "bg-green-500"
|
|||
|
|
}`}
|
|||
|
|
style={{ width: `${utilizationPercentage}%` }}
|
|||
|
|
></div>
|
|||
|
|
</div>
|
|||
|
|
<span className="text-sm text-gray-900">
|
|||
|
|
%{Math.round(utilizationPercentage)}
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
<div className="text-xs text-gray-500">
|
|||
|
|
{location.currentStock} / {location.capacity}
|
|||
|
|
</div>
|
|||
|
|
</td>
|
|||
|
|
<td className="px-3 py-2 whitespace-nowrap">
|
|||
|
|
{location.isActive ? (
|
|||
|
|
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">
|
|||
|
|
<FaCheckCircle className="w-3 h-3 mr-1" />
|
|||
|
|
Aktif
|
|||
|
|
</span>
|
|||
|
|
) : (
|
|||
|
|
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800">
|
|||
|
|
<FaExclamationCircle className="w-3 h-3 mr-1" />
|
|||
|
|
Pasif
|
|||
|
|
</span>
|
|||
|
|
)}
|
|||
|
|
</td>
|
|||
|
|
<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)}
|
|||
|
|
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)}
|
|||
|
|
className="text-blue-600 hover:text-blue-900"
|
|||
|
|
title="Düzenle"
|
|||
|
|
>
|
|||
|
|
<FaEdit className="w-4 h-4" />
|
|||
|
|
</button>
|
|||
|
|
<button
|
|||
|
|
onClick={() => handleDelete("location", location.id)}
|
|||
|
|
className="text-red-600 hover:text-red-900"
|
|||
|
|
title="Sil"
|
|||
|
|
>
|
|||
|
|
<FaTrash className="w-4 h-4" />
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</td>
|
|||
|
|
</tr>
|
|||
|
|
);
|
|||
|
|
})}
|
|||
|
|
</tbody>
|
|||
|
|
</table>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
const LocationsTab = () => (
|
|||
|
|
<div className="space-y-4 pt-2">
|
|||
|
|
{/* Warehouse Filter */}
|
|||
|
|
<div className="flex flex-col sm:flex-row gap-4">
|
|||
|
|
<select
|
|||
|
|
value={selectedWarehouse}
|
|||
|
|
onChange={(e) => setSelectedWarehouse(e.target.value)}
|
|||
|
|
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 Depolar</option>
|
|||
|
|
{warehouses.map((warehouse) => (
|
|||
|
|
<option key={warehouse.id} value={warehouse.id}>
|
|||
|
|
{warehouse.name} ({warehouse.code})
|
|||
|
|
</option>
|
|||
|
|
))}
|
|||
|
|
</select>
|
|||
|
|
<button
|
|||
|
|
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" />
|
|||
|
|
Yeni Lokasyon
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Content based on view mode */}
|
|||
|
|
{viewMode === "grid" ? <LocationsGridView /> : <LocationsListView />}
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<div className="space-y-4 pt-2">
|
|||
|
|
<div className="flex items-center justify-between">
|
|||
|
|
<div>
|
|||
|
|
<h2 className="text-lg font-bold text-gray-900">Depo Tanımları</h2>
|
|||
|
|
<p className="text-sm text-gray-600">
|
|||
|
|
Depolar, bölgeler ve lokasyonları yönetin
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* View Toggle Buttons */}
|
|||
|
|
{(activeTab === "warehouses" ||
|
|||
|
|
activeTab === "zones" ||
|
|||
|
|
activeTab === "locations") && (
|
|||
|
|
<div className="flex items-center gap-2">
|
|||
|
|
<button
|
|||
|
|
onClick={() => setViewMode("grid")}
|
|||
|
|
className={`p-1.5 rounded-lg ${
|
|||
|
|
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")}
|
|||
|
|
className={`p-1.5 rounded-lg ${
|
|||
|
|
viewMode === "list"
|
|||
|
|
? "bg-blue-100 text-blue-600"
|
|||
|
|
: "text-gray-400 hover:text-gray-600"
|
|||
|
|
}`}
|
|||
|
|
title="Liste Görünümü"
|
|||
|
|
>
|
|||
|
|
<FaList className="w-4 h-4" />
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Tabs */}
|
|||
|
|
<div className="border-b border-gray-200">
|
|||
|
|
<nav className="-mb-px flex space-x-8" aria-label="Tabs">
|
|||
|
|
<button
|
|||
|
|
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"
|
|||
|
|
}`}
|
|||
|
|
>
|
|||
|
|
<div className="flex items-center gap-2">
|
|||
|
|
<FaBuilding className="w-4 h-4" />
|
|||
|
|
Depolar
|
|||
|
|
</div>
|
|||
|
|
</button>
|
|||
|
|
<button
|
|||
|
|
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"
|
|||
|
|
}`}
|
|||
|
|
>
|
|||
|
|
<div className="flex items-center gap-2">
|
|||
|
|
<FaLayerGroup className="w-4 h-4" />
|
|||
|
|
Bölgeler
|
|||
|
|
</div>
|
|||
|
|
</button>
|
|||
|
|
<button
|
|||
|
|
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"
|
|||
|
|
}`}
|
|||
|
|
>
|
|||
|
|
<div className="flex items-center gap-2">
|
|||
|
|
<FaMapMarkerAlt className="w-4 h-4" />
|
|||
|
|
Lokasyonlar
|
|||
|
|
</div>
|
|||
|
|
</button>
|
|||
|
|
</nav>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Tab Content */}
|
|||
|
|
{activeTab === "warehouses" && <WarehousesTab />}
|
|||
|
|
{activeTab === "zones" && <ZonesTab />}
|
|||
|
|
{activeTab === "locations" && <LocationsTab />}
|
|||
|
|
|
|||
|
|
{/* Modals */}
|
|||
|
|
<WarehouseModal
|
|||
|
|
isOpen={warehouseModal.isOpen}
|
|||
|
|
onClose={() => setWarehouseModal({ ...warehouseModal, isOpen: false })}
|
|||
|
|
onSave={handleWarehouseSave}
|
|||
|
|
warehouse={warehouseModal.warehouse}
|
|||
|
|
mode={warehouseModal.mode}
|
|||
|
|
/>
|
|||
|
|
|
|||
|
|
<ZoneModal
|
|||
|
|
isOpen={zoneModal.isOpen}
|
|||
|
|
onClose={() => setZoneModal({ ...zoneModal, isOpen: false })}
|
|||
|
|
onSave={handleZoneSave}
|
|||
|
|
zone={zoneModal.zone}
|
|||
|
|
mode={zoneModal.mode}
|
|||
|
|
warehouses={warehouses}
|
|||
|
|
/>
|
|||
|
|
|
|||
|
|
<LocationModal
|
|||
|
|
isOpen={locationModal.isOpen}
|
|||
|
|
onClose={() => setLocationModal({ ...locationModal, isOpen: false })}
|
|||
|
|
onSave={handleLocationSave}
|
|||
|
|
location={locationModal.location}
|
|||
|
|
mode={locationModal.mode}
|
|||
|
|
warehouses={warehouses}
|
|||
|
|
zones={zones}
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
export default WarehouseDefinitions;
|