419 lines
18 KiB
TypeScript
419 lines
18 KiB
TypeScript
import React, { useState } from 'react'
|
||
import { Link } from 'react-router-dom'
|
||
import { useQuery } from '@tanstack/react-query'
|
||
import {
|
||
FaBuilding,
|
||
FaPlus,
|
||
FaSearch,
|
||
FaFilter,
|
||
FaDownload,
|
||
FaEdit,
|
||
FaEye,
|
||
FaMapMarkerAlt,
|
||
FaBox,
|
||
FaArrowUp,
|
||
FaExclamationTriangle,
|
||
FaChartLine,
|
||
} from 'react-icons/fa'
|
||
import classNames from 'classnames'
|
||
import { WarehouseTypeEnum } from '../../../types/wm'
|
||
import { mockWarehouses } from '../../../mocks/mockWarehouses'
|
||
import { getWarehouseTypeColor, getWarehouseTypeText } from '../../../utils/erp'
|
||
import { Container } from '@/components/shared'
|
||
import { ROUTES_ENUM } from '@/routes/route.constant'
|
||
|
||
const WarehouseList: React.FC = () => {
|
||
const [searchTerm, setSearchTerm] = useState('')
|
||
const [filterType, setFilterType] = useState('all')
|
||
const [showFilters, setShowFilters] = useState(false)
|
||
|
||
const {
|
||
data: warehouses,
|
||
isLoading,
|
||
error,
|
||
} = useQuery({
|
||
queryKey: ['warehouses', searchTerm, filterType],
|
||
queryFn: async () => {
|
||
await new Promise((resolve) => setTimeout(resolve, 500))
|
||
return mockWarehouses.filter((warehouse) => {
|
||
const matchesSearch =
|
||
warehouse.code.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||
warehouse.name.toLowerCase().includes(searchTerm.toLowerCase())
|
||
const matchesType = filterType === 'all' || warehouse.warehouseType === filterType
|
||
return matchesSearch && matchesType
|
||
})
|
||
},
|
||
})
|
||
|
||
const getUtilizationColor = (utilization: number) => {
|
||
const percentage = utilization
|
||
if (percentage >= 90) return 'text-red-600'
|
||
if (percentage >= 75) return 'text-yellow-600'
|
||
return 'text-green-600'
|
||
}
|
||
|
||
if (isLoading) {
|
||
return (
|
||
<div className="flex items-center justify-center py-12">
|
||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
|
||
<span className="ml-3 text-gray-600">Depolar yükleniyor...</span>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
if (error) {
|
||
return (
|
||
<div className="bg-red-50 border border-red-200 rounded-lg p-4">
|
||
<div className="flex items-center">
|
||
<FaExclamationTriangle className="h-5 w-5 text-red-600 mr-2" />
|
||
<span className="text-red-800">Depolar yüklenirken hata oluştu.</span>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
return (
|
||
<Container>
|
||
<div className="space-y-2">
|
||
{/* Header Actions */}
|
||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3">
|
||
<div className="flex items-center space-x-2">
|
||
<div className="relative">
|
||
<FaSearch
|
||
size={16}
|
||
className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4"
|
||
/>
|
||
<input
|
||
type="text"
|
||
placeholder="Depo kodu veya adı..."
|
||
value={searchTerm}
|
||
onChange={(e) => setSearchTerm(e.target.value)}
|
||
className="pl-10 pr-4 py-1.5 text-sm w-64 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||
/>
|
||
</div>
|
||
|
||
<button
|
||
onClick={() => setShowFilters(!showFilters)}
|
||
className={classNames(
|
||
'flex items-center px-3 py-1.5 text-sm border rounded-lg transition-colors',
|
||
showFilters
|
||
? 'border-blue-500 bg-blue-50 text-blue-700'
|
||
: 'border-gray-300 bg-white text-gray-700 hover:bg-gray-50',
|
||
)}
|
||
>
|
||
<FaFilter size={14} className="mr-2" />
|
||
Filtreler
|
||
</button>
|
||
</div>
|
||
|
||
<div className="flex items-center space-x-2">
|
||
<button
|
||
onClick={() => alert('Dışa aktarma özelliği yakında eklenecek')}
|
||
className="flex items-center px-3 py-1.5 text-sm border border-gray-300 bg-white text-gray-700 rounded-lg hover:bg-gray-50 transition-colors"
|
||
>
|
||
<FaDownload size={14} className="mr-2" />
|
||
Dışa Aktar
|
||
</button>
|
||
|
||
<Link
|
||
to={ROUTES_ENUM.protected.warehouse.warehouseNew}
|
||
className="flex items-center px-3 py-1.5 text-sm bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
|
||
>
|
||
<FaPlus size={14} className="mr-2" />
|
||
Yeni Depo
|
||
</Link>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Filters Panel */}
|
||
{showFilters && (
|
||
<div className="bg-white border border-gray-200 rounded-lg p-3">
|
||
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
|
||
<div>
|
||
<label className="block text-xs font-medium text-gray-700 mb-1">Depo Türü</label>
|
||
<select
|
||
value={filterType}
|
||
onChange={(e) => setFilterType(e.target.value)}
|
||
className="w-full border border-gray-300 rounded-lg px-3 py-1.5 text-sm focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||
>
|
||
<option value="all">Tümü</option>
|
||
{Object.values(WarehouseTypeEnum).map((type) => (
|
||
<option key={type} value={type}>
|
||
{getWarehouseTypeText(type)}
|
||
</option>
|
||
))}
|
||
</select>
|
||
</div>
|
||
|
||
<div className="flex items-end">
|
||
<button
|
||
onClick={() => {
|
||
setFilterType('all')
|
||
setSearchTerm('')
|
||
}}
|
||
className="w-full px-4 py-1.5 text-sm border border-gray-300 bg-white text-gray-700 rounded-lg hover:bg-gray-50 transition-colors"
|
||
>
|
||
Filtreleri Temizle
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Statistics Cards */}
|
||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-3">
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<p className="text-sm font-medium text-gray-600">Toplam Depo</p>
|
||
<p className="text-xl font-bold text-gray-900">{warehouses?.length || 0}</p>
|
||
</div>
|
||
<FaBuilding className="h-6 w-6 text-blue-500" />
|
||
</div>
|
||
</div>
|
||
|
||
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-3">
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<p className="text-sm font-medium text-gray-600">Toplam Kapasite</p>
|
||
<p className="text-xl font-bold text-green-600">
|
||
{warehouses?.reduce((acc, w) => acc + w.capacity, 0).toLocaleString() || 0}
|
||
</p>
|
||
</div>
|
||
<FaBox className="h-6 w-6 text-green-500" />
|
||
</div>
|
||
</div>
|
||
|
||
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-3">
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<p className="text-sm font-medium text-gray-600">Kullanım Oranı</p>
|
||
<p className="text-xl font-bold text-yellow-600">
|
||
{warehouses?.length
|
||
? Math.round(
|
||
(warehouses.reduce((acc, w) => acc + w.currentUtilization, 0) /
|
||
(warehouses.reduce((acc, w) => acc + w.capacity, 0) || 1)) *
|
||
100,
|
||
)
|
||
: 0}
|
||
%
|
||
</p>
|
||
</div>
|
||
<FaArrowUp className="h-6 w-6 text-yellow-500" />
|
||
</div>
|
||
</div>
|
||
|
||
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-3">
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<p className="text-sm font-medium text-gray-600">Aktif Depo</p>
|
||
<p className="text-xl font-bold text-purple-600">
|
||
{warehouses?.filter((w) => w.isActive).length || 0}
|
||
</p>
|
||
</div>
|
||
<FaChartLine className="h-6 w-6 text-purple-500" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Warehouses Table */}
|
||
<div className="bg-white rounded-lg shadow-sm border border-gray-200">
|
||
<div className="px-4 py-3 border-b border-gray-200">
|
||
<h2 className="text-xl font-bold text-gray-900">Depo Listesi</h2>
|
||
</div>
|
||
|
||
<div className="overflow-x-auto">
|
||
<table className="w-full">
|
||
<thead className="bg-gray-50">
|
||
<tr>
|
||
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||
Depo Bilgileri
|
||
</th>
|
||
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||
Tür / Konum
|
||
</th>
|
||
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||
Kapasite
|
||
</th>
|
||
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||
Kullanım Oranı
|
||
</th>
|
||
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||
Performans
|
||
</th>
|
||
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||
Durum
|
||
</th>
|
||
<th className="px-4 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">
|
||
{warehouses?.map((warehouse) => {
|
||
const utilizationPercentage = Math.round(
|
||
(warehouse.currentUtilization / warehouse.capacity) * 100,
|
||
)
|
||
|
||
return (
|
||
<tr key={warehouse.id} className="hover:bg-gray-50 transition-colors">
|
||
<td className="px-4 py-3">
|
||
<div className="flex items-center">
|
||
<div className="flex-shrink-0 h-8 w-8">
|
||
<div className="h-8 w-8 rounded-lg bg-blue-100 flex items-center justify-center">
|
||
<FaBuilding className="h-5 w-5 text-blue-600" />
|
||
</div>
|
||
</div>
|
||
<div className="ml-4">
|
||
<div className="text-xs font-medium text-gray-900">
|
||
{warehouse.code}
|
||
</div>
|
||
<div className="text-sm text-gray-500 max-w-xs truncate">
|
||
{warehouse.name}
|
||
</div>
|
||
{warehouse.isMainWarehouse && (
|
||
<span className="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-blue-100 text-blue-800 mt-1">
|
||
Ana Depo
|
||
</span>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</td>
|
||
|
||
<td className="px-4 py-3">
|
||
<div className="space-y-1">
|
||
<span
|
||
className={classNames(
|
||
'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium',
|
||
getWarehouseTypeColor(warehouse.warehouseType),
|
||
)}
|
||
>
|
||
{getWarehouseTypeText(warehouse.warehouseType)}
|
||
</span>
|
||
<div className="flex items-center text-xs text-gray-500">
|
||
<FaMapMarkerAlt size={14} className="mr-1" />
|
||
{warehouse.address?.city}, {warehouse.address?.country}
|
||
</div>
|
||
</div>
|
||
</td>
|
||
|
||
<td className="px-4 py-3">
|
||
<div className="text-sm font-medium text-gray-900">
|
||
{warehouse.capacity.toLocaleString()} m³
|
||
</div>
|
||
<div className="text-sm text-gray-500">Toplam Kapasite</div>
|
||
</td>
|
||
|
||
<td className="px-4 py-3">
|
||
<div className="space-y-1">
|
||
<div className="flex items-center justify-between">
|
||
<span className="text-xs text-gray-600">Kullanılan:</span>
|
||
<span className="text-sm font-medium text-gray-900">
|
||
{warehouse.currentUtilization.toLocaleString()} m³
|
||
</span>
|
||
</div>
|
||
<div className="w-full bg-gray-200 rounded-full h-2">
|
||
<div
|
||
className={classNames(
|
||
'h-2 rounded-full',
|
||
utilizationPercentage >= 90
|
||
? 'bg-red-500'
|
||
: utilizationPercentage >= 75
|
||
? 'bg-yellow-500'
|
||
: 'bg-green-500',
|
||
)}
|
||
style={{
|
||
width: `${Math.min(utilizationPercentage, 100)}%`,
|
||
}}
|
||
/>
|
||
</div>
|
||
<div
|
||
className={classNames(
|
||
'text-sm font-medium',
|
||
getUtilizationColor(utilizationPercentage),
|
||
)}
|
||
>
|
||
%{utilizationPercentage}
|
||
</div>
|
||
</div>
|
||
</td>
|
||
|
||
<td className="px-4 py-3">
|
||
<div className="space-y-1">
|
||
<div className="flex items-center">
|
||
<FaChartLine size={14} className="text-green-500 mr-1" />
|
||
<span className="text-sm text-green-600">Verimli</span>
|
||
</div>
|
||
<div className="text-xs text-gray-500">Son 30 gün</div>
|
||
</div>
|
||
</td>
|
||
|
||
<td className="px-4 py-3">
|
||
<span
|
||
className={classNames(
|
||
'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium',
|
||
warehouse.isActive
|
||
? 'bg-green-100 text-green-800'
|
||
: 'bg-red-100 text-red-800',
|
||
)}
|
||
>
|
||
{warehouse.isActive ? 'Aktif' : 'Pasif'}
|
||
</span>
|
||
</td>
|
||
|
||
<td className="px-4 py-3 text-right">
|
||
<div className="flex items-center justify-end space-x-2">
|
||
<Link
|
||
to={ROUTES_ENUM.protected.warehouse.warehouseDetail.replace(
|
||
':id',
|
||
warehouse.id,
|
||
)}
|
||
className="p-2 text-gray-600 hover:text-blue-600 hover:bg-blue-50 rounded-lg transition-colors"
|
||
title="Detayları Görüntüle"
|
||
>
|
||
<FaEye size={16} />
|
||
</Link>
|
||
|
||
<Link
|
||
to={ROUTES_ENUM.protected.warehouse.warehouseEdit.replace(
|
||
':id',
|
||
warehouse.id,
|
||
)}
|
||
className="p-2 text-gray-600 hover:text-yellow-600 hover:bg-yellow-50 rounded-lg transition-colors"
|
||
title="Düzenle"
|
||
>
|
||
<FaEdit size={16} />
|
||
</Link>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
)
|
||
})}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
{(!warehouses || warehouses.length === 0) && (
|
||
<div className="text-center py-12">
|
||
<FaBuilding className="mx-auto h-12 w-12 text-gray-400" />
|
||
<h3 className="mt-2 text-sm font-medium text-gray-900">Depo bulunamadı</h3>
|
||
<p className="mt-1 text-sm text-gray-500">Yeni depo ekleyerek başlayın.</p>
|
||
<div className="mt-6">
|
||
<Link
|
||
to={ROUTES_ENUM.protected.warehouse.warehouseNew}
|
||
className="inline-flex items-center px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
|
||
>
|
||
<FaPlus size={16} className="mr-2" />
|
||
Yeni Depo Ekle
|
||
</Link>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</Container>
|
||
)
|
||
}
|
||
|
||
export default WarehouseList
|