1147 lines
45 KiB
TypeScript
1147 lines
45 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'
|
||
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')
|
||
|
||
// 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-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-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>
|
||
<div className="text-xs font-medium text-gray-900">{location.name}</div>
|
||
<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 (
|
||
<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>
|
||
</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 />}
|
||
</div>
|
||
|
||
{/* 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}
|
||
/>
|
||
</Container>
|
||
)
|
||
}
|
||
|
||
export default WarehouseDefinitions
|