erp-platform/ui/src/views/warehouse/components/WarehouseList.tsx
2025-09-18 10:24:36 +03:00

419 lines
18 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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