1225 lines
46 KiB
TypeScript
1225 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;
|