2025-09-15 21:11:40 +00:00
|
|
|
|
import React, { useState } from 'react'
|
2025-09-15 09:31:47 +00:00
|
|
|
|
import {
|
|
|
|
|
|
FaSearch,
|
|
|
|
|
|
FaMapMarkerAlt,
|
|
|
|
|
|
FaExclamationTriangle,
|
|
|
|
|
|
FaCheckCircle,
|
|
|
|
|
|
FaEye,
|
|
|
|
|
|
FaTh,
|
|
|
|
|
|
FaList,
|
2025-09-15 21:11:40 +00:00
|
|
|
|
} from 'react-icons/fa'
|
|
|
|
|
|
import { mockWarehouses } from '../../../mocks/mockWarehouses'
|
|
|
|
|
|
import { mockLocations } from '../../../mocks/mockLocations'
|
|
|
|
|
|
import { mockStockItems } from '../../../mocks/mockStockItems'
|
|
|
|
|
|
import { getStockStatusColor, getStockStatusText } from '../../../utils/erp'
|
|
|
|
|
|
import { Container } from '@/components/shared'
|
2025-09-15 09:31:47 +00:00
|
|
|
|
|
|
|
|
|
|
const LocationTracking: React.FC = () => {
|
2025-09-15 21:11:40 +00:00
|
|
|
|
const [searchTerm, setSearchTerm] = useState('')
|
|
|
|
|
|
const [selectedWarehouse, setSelectedWarehouse] = useState<string>('')
|
|
|
|
|
|
const [selectedLocation, setSelectedLocation] = useState<string>('')
|
|
|
|
|
|
const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid')
|
2025-09-15 09:31:47 +00:00
|
|
|
|
|
|
|
|
|
|
const getLocationUtilization = (locationId: string) => {
|
2025-09-15 21:11:40 +00:00
|
|
|
|
const location = mockLocations.find((l) => l.id === locationId)
|
|
|
|
|
|
if (!location) return 0
|
|
|
|
|
|
return (location.currentStock / location.capacity) * 100
|
|
|
|
|
|
}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
|
|
|
|
|
|
const getLocationStockItems = (locationId: string) => {
|
2025-09-15 21:11:40 +00:00
|
|
|
|
return mockStockItems.filter((item) => item.locationId === locationId)
|
|
|
|
|
|
}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
|
|
|
|
|
|
const filteredLocations = mockLocations.filter((location) => {
|
|
|
|
|
|
const matchesSearch =
|
|
|
|
|
|
location.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
2025-09-15 21:11:40 +00:00
|
|
|
|
location.locationCode.toLowerCase().includes(searchTerm.toLowerCase())
|
|
|
|
|
|
const matchesWarehouse = selectedWarehouse === '' || location.warehouseId === selectedWarehouse
|
|
|
|
|
|
return matchesSearch && matchesWarehouse
|
|
|
|
|
|
})
|
2025-09-15 09:31:47 +00:00
|
|
|
|
|
|
|
|
|
|
const GridView = () => (
|
|
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
|
|
|
|
{filteredLocations.map((location) => {
|
2025-09-15 21:11:40 +00:00
|
|
|
|
const warehouse = mockWarehouses.find((w) => w.id === location.warehouseId)
|
|
|
|
|
|
const locationStockItems = getLocationStockItems(location.id)
|
|
|
|
|
|
const utilization = getLocationUtilization(location.id)
|
2025-09-15 09:31:47 +00:00
|
|
|
|
|
|
|
|
|
|
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-3">
|
|
|
|
|
|
<div className="p-2 bg-blue-100 rounded-lg">
|
|
|
|
|
|
<FaMapMarkerAlt className="w-5 h-5 text-blue-600" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<h3 className="font-medium text-gray-900">{location.name}</h3>
|
2025-09-15 21:11:40 +00:00
|
|
|
|
<p className="text-sm text-gray-500">{location.locationCode}</p>
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => setSelectedLocation(location.id)}
|
|
|
|
|
|
className="text-blue-600 hover:text-blue-700"
|
|
|
|
|
|
>
|
|
|
|
|
|
<FaEye className="w-4 h-4" />
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="space-y-3 pt-2">
|
|
|
|
|
|
<div className="text-sm text-gray-600">
|
|
|
|
|
|
<p>
|
|
|
|
|
|
<strong>Depo:</strong> {warehouse?.name}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<p>{location.description}</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Utilization */}
|
|
|
|
|
|
<div className="space-y-1">
|
|
|
|
|
|
<div className="flex justify-between text-sm">
|
|
|
|
|
|
<span className="text-gray-600">Doluluk Oranı</span>
|
2025-09-15 21:11:40 +00:00
|
|
|
|
<span className="font-medium">%{Math.round(utilization)}</span>
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
<div className="w-full bg-gray-200 rounded-full h-2">
|
|
|
|
|
|
<div
|
|
|
|
|
|
className={`h-2 rounded-full ${
|
|
|
|
|
|
utilization > 90
|
2025-09-15 21:11:40 +00:00
|
|
|
|
? 'bg-red-500'
|
2025-09-15 09:31:47 +00:00
|
|
|
|
: utilization > 70
|
2025-09-15 21:11:40 +00:00
|
|
|
|
? 'bg-yellow-500'
|
|
|
|
|
|
: 'bg-green-500'
|
2025-09-15 09:31:47 +00:00
|
|
|
|
}`}
|
|
|
|
|
|
style={{ width: `${utilization}%` }}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="text-xs text-gray-500">
|
|
|
|
|
|
{location.currentStock} / {location.capacity} birim
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Stock Items Summary */}
|
|
|
|
|
|
<div className="bg-gray-50 rounded-lg p-2">
|
|
|
|
|
|
<div className="flex items-center justify-between mb-2">
|
2025-09-15 21:11:40 +00:00
|
|
|
|
<h4 className="text-sm font-medium text-gray-900">Malzemeler</h4>
|
|
|
|
|
|
<span className="text-xs text-gray-500">{locationStockItems.length} çeşit</span>
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{locationStockItems.length > 0 ? (
|
|
|
|
|
|
<div className="space-y-1">
|
|
|
|
|
|
{locationStockItems.slice(0, 3).map((item) => (
|
2025-09-15 21:11:40 +00:00
|
|
|
|
<div key={item.id} className="flex justify-between items-center text-xs">
|
2025-09-15 09:31:47 +00:00
|
|
|
|
<span className="text-gray-700 truncate">
|
2025-09-15 21:11:40 +00:00
|
|
|
|
{item.material?.code || 'N/A'}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</span>
|
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
|
<span className="font-medium">{item.quantity}</span>
|
|
|
|
|
|
<span
|
|
|
|
|
|
className={`px-1.5 py-0.5 rounded-full text-xs ${getStockStatusColor(
|
2025-09-15 21:11:40 +00:00
|
|
|
|
item.status,
|
2025-09-15 09:31:47 +00:00
|
|
|
|
)}`}
|
|
|
|
|
|
>
|
|
|
|
|
|
{getStockStatusText(item.status)}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
{locationStockItems.length > 3 && (
|
|
|
|
|
|
<div className="text-xs text-gray-500 text-center pt-1">
|
|
|
|
|
|
+{locationStockItems.length - 3} malzeme daha
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
) : (
|
2025-09-15 21:11:40 +00:00
|
|
|
|
<div className="text-xs text-gray-500 text-center py-2">Malzeme bulunmuyor</div>
|
2025-09-15 09:31:47 +00:00
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Restrictions */}
|
|
|
|
|
|
{location.restrictions && location.restrictions.length > 0 && (
|
|
|
|
|
|
<div>
|
2025-09-15 21:11:40 +00:00
|
|
|
|
<h4 className="text-sm font-medium text-gray-900 mb-1">Kısıtlamalar</h4>
|
2025-09-15 09:31:47 +00:00
|
|
|
|
<div className="flex flex-wrap gap-1">
|
2025-09-15 21:11:40 +00:00
|
|
|
|
{location.restrictions.slice(0, 2).map((restriction, index) => (
|
|
|
|
|
|
<span
|
|
|
|
|
|
key={index}
|
|
|
|
|
|
className="inline-flex px-2 py-1 text-xs bg-yellow-100 text-yellow-800 rounded"
|
|
|
|
|
|
>
|
|
|
|
|
|
{restriction}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
))}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
{location.restrictions.length > 2 && (
|
|
|
|
|
|
<span className="inline-flex px-2 py-1 text-xs bg-gray-100 text-gray-600 rounded">
|
|
|
|
|
|
+{location.restrictions.length - 2}
|
|
|
|
|
|
</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-sm text-green-600">Aktif</span>
|
|
|
|
|
|
</>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<FaExclamationTriangle className="w-4 h-4 text-red-500" />
|
|
|
|
|
|
<span className="text-sm text-red-600">Pasif</span>
|
|
|
|
|
|
</>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="text-xs text-gray-500">
|
2025-09-15 21:11:40 +00:00
|
|
|
|
Son hareket:{' '}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
{locationStockItems.length > 0
|
|
|
|
|
|
? new Date(
|
|
|
|
|
|
Math.max(
|
2025-09-15 21:11:40 +00:00
|
|
|
|
...locationStockItems.map((item) => item.lastMovementDate.getTime()),
|
|
|
|
|
|
),
|
|
|
|
|
|
).toLocaleDateString('tr-TR')
|
|
|
|
|
|
: 'N/A'}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-09-15 21:11:40 +00:00
|
|
|
|
)
|
2025-09-15 09:31:47 +00:00
|
|
|
|
})}
|
|
|
|
|
|
</div>
|
2025-09-15 21:11:40 +00:00
|
|
|
|
)
|
2025-09-15 09:31:47 +00:00
|
|
|
|
|
|
|
|
|
|
const ListView = () => (
|
|
|
|
|
|
<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">
|
|
|
|
|
|
Doluluk
|
|
|
|
|
|
</th>
|
|
|
|
|
|
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
|
|
|
|
Malzeme Çeşidi
|
|
|
|
|
|
</th>
|
|
|
|
|
|
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
|
|
|
|
Son Hareket
|
|
|
|
|
|
</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) => {
|
2025-09-15 21:11:40 +00:00
|
|
|
|
const warehouse = mockWarehouses.find((w) => w.id === location.warehouseId)
|
|
|
|
|
|
const locationStockItems = getLocationStockItems(location.id)
|
|
|
|
|
|
const utilization = getLocationUtilization(location.id)
|
2025-09-15 09:31:47 +00:00
|
|
|
|
|
|
|
|
|
|
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-blue-100 rounded-lg mr-3">
|
|
|
|
|
|
<FaMapMarkerAlt className="w-4 h-4 text-blue-600" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
2025-09-15 21:11:40 +00:00
|
|
|
|
<div className="text-sm font-medium text-gray-900">{location.name}</div>
|
|
|
|
|
|
<div className="text-sm text-gray-500">{location.locationCode}</div>
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</td>
|
|
|
|
|
|
<td className="px-3 py-2 whitespace-nowrap">
|
2025-09-15 21:11:40 +00:00
|
|
|
|
<div className="text-sm text-gray-900">{warehouse?.name}</div>
|
|
|
|
|
|
<div className="text-sm text-gray-500">{warehouse?.code}</div>
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</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 ${
|
|
|
|
|
|
utilization > 90
|
2025-09-15 21:11:40 +00:00
|
|
|
|
? 'bg-red-500'
|
2025-09-15 09:31:47 +00:00
|
|
|
|
: utilization > 70
|
2025-09-15 21:11:40 +00:00
|
|
|
|
? 'bg-yellow-500'
|
|
|
|
|
|
: 'bg-green-500'
|
2025-09-15 09:31:47 +00:00
|
|
|
|
}`}
|
|
|
|
|
|
style={{ width: `${utilization}%` }}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
2025-09-15 21:11:40 +00:00
|
|
|
|
<span className="text-sm text-gray-900">%{Math.round(utilization)}</span>
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
<div className="text-xs text-gray-500">
|
|
|
|
|
|
{location.currentStock} / {location.capacity}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</td>
|
|
|
|
|
|
<td className="px-3 py-2 whitespace-nowrap text-sm text-gray-900">
|
|
|
|
|
|
{locationStockItems.length} çeşit
|
|
|
|
|
|
</td>
|
|
|
|
|
|
<td className="px-3 py-2 whitespace-nowrap text-sm text-gray-900">
|
|
|
|
|
|
{locationStockItems.length > 0
|
|
|
|
|
|
? new Date(
|
|
|
|
|
|
Math.max(
|
2025-09-15 21:11:40 +00:00
|
|
|
|
...locationStockItems.map((item) => item.lastMovementDate.getTime()),
|
|
|
|
|
|
),
|
|
|
|
|
|
).toLocaleDateString('tr-TR')
|
|
|
|
|
|
: 'N/A'}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</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">
|
|
|
|
|
|
<FaExclamationTriangle className="w-3 h-3 mr-1" />
|
|
|
|
|
|
Pasif
|
|
|
|
|
|
</span>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</td>
|
|
|
|
|
|
<td className="px-3 py-2 whitespace-nowrap text-right text-sm font-medium">
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => setSelectedLocation(location.id)}
|
|
|
|
|
|
className="text-blue-600 hover:text-blue-900"
|
|
|
|
|
|
>
|
|
|
|
|
|
<FaEye className="w-4 h-4" />
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</td>
|
|
|
|
|
|
</tr>
|
2025-09-15 21:11:40 +00:00
|
|
|
|
)
|
2025-09-15 09:31:47 +00:00
|
|
|
|
})}
|
|
|
|
|
|
</tbody>
|
|
|
|
|
|
</table>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-09-15 21:11:40 +00:00
|
|
|
|
)
|
2025-09-15 09:31:47 +00:00
|
|
|
|
|
|
|
|
|
|
const LocationDetailModal = () => {
|
2025-09-15 21:11:40 +00:00
|
|
|
|
const location = mockLocations.find((l) => l.id === selectedLocation)
|
|
|
|
|
|
const locationStockItems = getLocationStockItems(selectedLocation)
|
2025-09-15 09:31:47 +00:00
|
|
|
|
|
2025-09-15 21:11:40 +00:00
|
|
|
|
if (!selectedLocation || !location) return null
|
2025-09-15 09:31:47 +00:00
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="fixed inset-0 z-50 overflow-y-auto">
|
|
|
|
|
|
<div className="flex items-center justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
|
|
|
|
|
|
<div
|
|
|
|
|
|
className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"
|
2025-09-15 21:11:40 +00:00
|
|
|
|
onClick={() => setSelectedLocation('')}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-4xl sm:w-full">
|
|
|
|
|
|
<div className="bg-white px-4 pt-4 pb-4 sm:p-4">
|
|
|
|
|
|
<div className="flex items-center justify-between mb-4">
|
2025-09-15 21:11:40 +00:00
|
|
|
|
<h3 className="text-lg font-medium text-gray-900">{location.name} - Detaylar</h3>
|
2025-09-15 09:31:47 +00:00
|
|
|
|
<button
|
2025-09-15 21:11:40 +00:00
|
|
|
|
onClick={() => setSelectedLocation('')}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
className="text-gray-400 hover:text-gray-600"
|
|
|
|
|
|
>
|
2025-09-15 21:11:40 +00:00
|
|
|
|
<svg className="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
2025-09-15 09:31:47 +00:00
|
|
|
|
<path
|
|
|
|
|
|
strokeLinecap="round"
|
|
|
|
|
|
strokeLinejoin="round"
|
|
|
|
|
|
strokeWidth={2}
|
|
|
|
|
|
d="M6 18L18 6M6 6l12 12"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
|
|
|
|
{/* Location Info */}
|
|
|
|
|
|
<div className="bg-gray-50 rounded-lg p-3">
|
2025-09-15 21:11:40 +00:00
|
|
|
|
<h4 className="font-medium text-gray-900 mb-3">Lokasyon Bilgileri</h4>
|
2025-09-15 09:31:47 +00:00
|
|
|
|
<div className="space-y-2 text-sm">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<strong>Kod:</strong> {location.locationCode}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<strong>Açıklama:</strong> {location.description}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<strong>Tip:</strong> {location.locationType}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<strong>Kapasite:</strong> {location.capacity} birim
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
2025-09-15 21:11:40 +00:00
|
|
|
|
<strong>Mevcut Stok:</strong> {location.currentStock} birim
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
{location.dimensions && (
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<strong>Boyutlar:</strong> {location.dimensions.length}x
|
2025-09-15 21:11:40 +00:00
|
|
|
|
{location.dimensions.width}x{location.dimensions.height}m
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Stock Items */}
|
|
|
|
|
|
<div className="bg-gray-50 rounded-lg p-3">
|
|
|
|
|
|
<h4 className="font-medium text-gray-900 mb-3">
|
|
|
|
|
|
Stok Malzemeleri ({locationStockItems.length})
|
|
|
|
|
|
</h4>
|
|
|
|
|
|
<div className="space-y-2 max-h-64 overflow-y-auto">
|
|
|
|
|
|
{locationStockItems.map((item) => (
|
2025-09-15 21:11:40 +00:00
|
|
|
|
<div key={item.id} className="bg-white rounded p-3 border">
|
2025-09-15 09:31:47 +00:00
|
|
|
|
<div className="flex justify-between items-start mb-2">
|
|
|
|
|
|
<div>
|
2025-09-15 21:11:40 +00:00
|
|
|
|
<div className="font-medium text-sm">{item.material?.code}</div>
|
|
|
|
|
|
<div className="text-xs text-gray-500">{item.material?.code}</div>
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
<span
|
|
|
|
|
|
className={`px-2 py-1 rounded-full text-xs ${getStockStatusColor(
|
2025-09-15 21:11:40 +00:00
|
|
|
|
item.status,
|
2025-09-15 09:31:47 +00:00
|
|
|
|
)}`}
|
|
|
|
|
|
>
|
|
|
|
|
|
{getStockStatusText(item.status)}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="grid grid-cols-2 gap-2 text-xs text-gray-600">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
Miktar: {item.quantity} {item.unitId}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
Mevcut: {item.availableQuantity} {item.unitId}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
Rezerve: {item.reservedQuantity} {item.unitId}
|
|
|
|
|
|
</div>
|
2025-09-15 21:11:40 +00:00
|
|
|
|
<div>Lot: {item.lotNumber || 'N/A'}</div>
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
<div className="mt-2 text-xs text-gray-500">
|
2025-09-15 21:11:40 +00:00
|
|
|
|
Son hareket: {item.lastMovementDate.toLocaleDateString('tr-TR')}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
{locationStockItems.length === 0 && (
|
|
|
|
|
|
<div className="text-sm text-gray-500 text-center py-4">
|
|
|
|
|
|
Bu lokasyonda malzeme bulunmuyor
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-09-15 21:11:40 +00:00
|
|
|
|
)
|
|
|
|
|
|
}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
|
|
|
|
|
|
return (
|
2025-09-15 21:11:40 +00:00
|
|
|
|
<Container>
|
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
|
<div className="flex items-center justify-between">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<h2 className="text-2xl font-bold text-gray-900">Raf/Lokasyon Bazlı Takip</h2>
|
|
|
|
|
|
<p className="text-gray-600">Lokasyonlardaki stok durumunu takip edin</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<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'
|
|
|
|
|
|
}`}
|
|
|
|
|
|
>
|
|
|
|
|
|
<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'
|
|
|
|
|
|
}`}
|
|
|
|
|
|
>
|
|
|
|
|
|
<FaList className="w-4 h-4" />
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2025-09-15 21:11:40 +00:00
|
|
|
|
{/* 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="Lokasyon 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={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"
|
2025-09-15 09:31:47 +00:00
|
|
|
|
>
|
2025-09-15 21:11:40 +00:00
|
|
|
|
<option value="">Tüm Depolar</option>
|
|
|
|
|
|
{mockWarehouses.map((warehouse) => (
|
|
|
|
|
|
<option key={warehouse.id} value={warehouse.id}>
|
|
|
|
|
|
{warehouse.name} ({warehouse.code})
|
|
|
|
|
|
</option>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</select>
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2025-09-15 21:11:40 +00:00
|
|
|
|
{/* Content */}
|
|
|
|
|
|
{viewMode === 'grid' ? <GridView /> : <ListView />}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Location Detail Modal */}
|
|
|
|
|
|
<LocationDetailModal />
|
2025-09-15 21:11:40 +00:00
|
|
|
|
</Container>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
|
2025-09-15 21:11:40 +00:00
|
|
|
|
export default LocationTracking
|