Warehouse Management

This commit is contained in:
Sedat Öztürk 2025-09-16 00:11:40 +03:00
parent 8b88970fe2
commit bb2e39c2c3
13 changed files with 4751 additions and 5982 deletions

View file

@ -1,4 +1,4 @@
import React, { useState } from "react"; import React, { useState } from 'react'
import { import {
FaBox, FaBox,
FaMapMarkerAlt, FaMapMarkerAlt,
@ -8,521 +8,474 @@ import {
FaEye, FaEye,
FaEdit, FaEdit,
FaPlus, FaPlus,
} from "react-icons/fa"; } from 'react-icons/fa'
// using modals for create actions; no navigation needed here // using modals for create actions; no navigation needed here
import { import { MmLotNumber, MmSerialNumber, QualityStatusEnum, SerialStatusEnum } from '../../../types/mm'
MmLotNumber, import LotForm from './LotForm'
MmSerialNumber, import SerialForm from './SerialForm'
QualityStatusEnum, import { mockLotNumbers } from '../../../mocks/mockLotNumbers'
SerialStatusEnum, import { mockSerialNumbers } from '../../../mocks/mockSerialNumbers'
} from "../../../types/mm"; import { mockMaterials } from '../../../mocks/mockMaterials'
import LotForm from "./LotForm"; import { mockUnits } from '../../../mocks/mockUnits'
import SerialForm from "./SerialForm"; import { mockBusinessParties } from '../../../mocks/mockBusinessParties'
import { mockLotNumbers } from "../../../mocks/mockLotNumbers"; import Widget from '../../../components/common/Widget'
import { mockSerialNumbers } from "../../../mocks/mockSerialNumbers"; import { PartyType } from '../../../types/common'
import { mockMaterials } from "../../../mocks/mockMaterials"; import { getQualityStatusInfo, getSerialStatusInfo } from '../../../utils/erp'
import { mockUnits } from "../../../mocks/mockUnits"; import { Container } from '@/components/shared'
import { mockBusinessParties } from "../../../mocks/mockBusinessParties";
import Widget from "../../../components/common/Widget";
import { PartyType } from "../../../types/common";
import { getQualityStatusInfo, getSerialStatusInfo } from "../../../utils/erp";
const InventoryTracking: React.FC = () => { const InventoryTracking: React.FC = () => {
const [activeTab, setActiveTab] = useState<"lots" | "serials">("lots"); const [activeTab, setActiveTab] = useState<'lots' | 'serials'>('lots')
const [searchTerm, setSearchTerm] = useState(""); const [searchTerm, setSearchTerm] = useState('')
const [showCreateMenu, setShowCreateMenu] = useState(false); const [showCreateMenu, setShowCreateMenu] = useState(false)
// Mock data için // Mock data için
const [lotNumbers, setLotNumbers] = useState<MmLotNumber[]>(mockLotNumbers); const [lotNumbers, setLotNumbers] = useState<MmLotNumber[]>(mockLotNumbers)
const [serialNumbers, setSerialNumbers] = const [serialNumbers, setSerialNumbers] = useState<MmSerialNumber[]>(mockSerialNumbers)
useState<MmSerialNumber[]>(mockSerialNumbers);
// Modal control state // Modal control state
const [openLotModal, setOpenLotModal] = useState(false); const [openLotModal, setOpenLotModal] = useState(false)
const [openSerialModal, setOpenSerialModal] = useState(false); const [openSerialModal, setOpenSerialModal] = useState(false)
const [currentLot, setCurrentLot] = useState<MmLotNumber | null>(null); const [currentLot, setCurrentLot] = useState<MmLotNumber | null>(null)
const [currentSerial, setCurrentSerial] = useState<MmSerialNumber | null>( const [currentSerial, setCurrentSerial] = useState<MmSerialNumber | null>(null)
null
);
const filteredLots = lotNumbers.filter( const filteredLots = lotNumbers.filter(
(lot) => (lot) =>
lot.lotNumber.toLowerCase().includes(searchTerm.toLowerCase()) || lot.lotNumber.toLowerCase().includes(searchTerm.toLowerCase()) ||
lot.material?.name.toLowerCase().includes(searchTerm.toLowerCase()) lot.material?.name.toLowerCase().includes(searchTerm.toLowerCase()),
); )
const filteredSerials = serialNumbers.filter( const filteredSerials = serialNumbers.filter(
(serial) => (serial) =>
serial.serialNumber.toLowerCase().includes(searchTerm.toLowerCase()) || serial.serialNumber.toLowerCase().includes(searchTerm.toLowerCase()) ||
serial.material?.name.toLowerCase().includes(searchTerm.toLowerCase()) serial.material?.name.toLowerCase().includes(searchTerm.toLowerCase()),
); )
return ( return (
<div className="space-y-3 py-2"> <Container>
{/* Header */} <div className="space-y-2">
<div className="flex items-center justify-between"> {/* Header */}
<div> <div className="flex items-center justify-between">
<h2 className="text-2xl font-bold text-gray-900">Envanter Takibi</h2> <div>
<p className="text-gray-600"> <h2 className="text-2xl font-bold text-gray-900">Envanter Takibi</h2>
Lot ve seri numarası takiplerini yönetin <p className="text-gray-600">Lot ve seri numarası takiplerini yönetin</p>
</p> </div>
</div> <div className="relative">
<div className="relative"> <button
<button onClick={() => setShowCreateMenu((s) => !s)} /* Kontrol küçültüldü */
onClick={() => className="bg-blue-600 text-white px-2.5 py-1 rounded-md hover:bg-blue-700 flex items-center space-x-1.5 text-sm"
setShowCreateMenu((s) => !s) >
} /* Kontrol küçültüldü */ <FaPlus className="h-4 w-4" />
className="bg-blue-600 text-white px-2.5 py-1 rounded-md hover:bg-blue-700 flex items-center space-x-1.5 text-sm" <span>Yeni Kayıt</span>
> </button>
<FaPlus className="h-4 w-4" />
<span>Yeni Kayıt</span>
</button>
{/* only modal-based create menu (route-based entries removed) */} {/* only modal-based create menu (route-based entries removed) */}
{showCreateMenu && ( {showCreateMenu && (
<div className="absolute right-0 mt-2 w-44 bg-white border rounded shadow-lg z-10"> <div className="absolute right-0 mt-2 w-44 bg-white border rounded shadow-lg z-10">
<button <button
onClick={() => { onClick={() => {
setOpenLotModal(true); setOpenLotModal(true)
setShowCreateMenu(false); setShowCreateMenu(false)
}} }}
className="w-full text-left px-2.5 py-1 text-sm hover:bg-gray-50" className="w-full text-left px-2.5 py-1 text-sm hover:bg-gray-50"
> >
Yeni Lot Yeni Lot
</button> </button>
<button <button
onClick={() => { onClick={() => {
setOpenSerialModal(true); setOpenSerialModal(true)
setShowCreateMenu(false); setShowCreateMenu(false)
}} }}
className="w-full text-left px-2.5 py-1 text-sm hover:bg-gray-50" className="w-full text-left px-2.5 py-1 text-sm hover:bg-gray-50"
> >
Yeni Seri Yeni Seri
</button> </button>
</div> </div>
)}
</div>
</div>
{/* Summary Statistics */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-2">
<Widget
title="Toplam Lot"
value={lotNumbers.length}
color="blue"
icon="FaBox"
/>
<Widget
title="Onaylı Lot"
value={
lotNumbers.filter(
(lot) => lot.qualityStatus === QualityStatusEnum.Approved
).length
}
color="green"
icon="FaBox"
/>
<Widget
title="Toplam Seri"
value={serialNumbers.length}
color="purple"
icon="FaHashtag"
/>
<Widget
title="Müsait Seri"
value={
serialNumbers.filter(
(serial) => serial.status === SerialStatusEnum.Available
).length
}
color="orange"
icon="FaHashtag"
/>
</div>
{/* Search */}
<div className="relative">
<FaSearch className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-3.5 w-3.5" />
<input
type="text"
placeholder="Lot/Seri numarası veya malzeme kodu ara..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full pl-8 pr-3 py-1 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
</div>
{/* Tabs */}
<div className="border-b border-gray-200">
<nav className="flex -mb-px">
<button
onClick={() => setActiveTab("lots")}
className={`flex items-center px-2.5 py-1 text-sm font-medium border-b-2 ${
activeTab === "lots"
? "border-blue-500 text-blue-600"
: "border-transparent text-gray-500 hover:text-gray-700"
}`}
>
<FaBox className="h-4 w-4 mr-2" />
Lot Numaraları ({filteredLots.length})
</button>
<button
onClick={() => setActiveTab("serials")}
className={`flex items-center px-2.5 py-1 text-sm font-medium border-b-2 ${
activeTab === "serials"
? "border-blue-500 text-blue-600"
: "border-transparent text-gray-500 hover:text-gray-700"
}`}
>
<FaHashtag className="h-4 w-4 mr-2" />
Seri Numaraları ({filteredSerials.length})
</button>
</nav>
</div>
{/* Lot Numbers Tab */}
{activeTab === "lots" && (
<div className="bg-white shadow-sm rounded-lg overflow-hidden">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Lot Numarası
</th>
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Malzeme
</th>
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Miktar
</th>
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Üretim Tarihi
</th>
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Son Kullanma
</th>
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Tedarikçi
</th>
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Kalite Durumu
</th>
<th className="px-2 py-1.5 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">
{filteredLots.map((lot) => {
const qualityInfo = getQualityStatusInfo(lot.qualityStatus);
const isExpiringSoon =
lot.expiryDate &&
new Date(lot.expiryDate).getTime() - new Date().getTime() <
30 * 24 * 60 * 60 * 1000;
return (
<tr key={lot.id} className="hover:bg-gray-50 text-xs">
<td className="px-2 py-1.5 whitespace-nowrap">
<div className="flex items-center">
<FaBox className="h-4 w-4 text-gray-400 mr-2" />
<div className="text-xs font-medium text-gray-900">
{lot.lotNumber}
</div>
</div>
</td>
<td className="px-2 py-1.5 whitespace-nowrap">
<div className="text-xs font-medium text-gray-900">
{lot.material?.name}
</div>
</td>
<td className="px-2 py-1.5 whitespace-nowrap">
<div className="text-xs text-gray-900">
{lot.quantity} {lot.unitId}
</div>
</td>
<td className="px-2 py-1.5 whitespace-nowrap">
<div className="flex items-center">
<FaCalendar className="h-4 w-4 text-gray-400 mr-2" />
<div className="text-xs text-gray-900">
{new Date(lot.productionDate).toLocaleDateString(
"tr-TR"
)}
</div>
</div>
</td>
<td className="px-2 py-1.5 whitespace-nowrap">
{lot.expiryDate ? (
<div
className={`text-xs ${
isExpiringSoon
? "text-red-600 font-medium"
: "text-gray-900"
}`}
>
{new Date(lot.expiryDate).toLocaleDateString("tr-TR")}
{isExpiringSoon && (
<div className="text-xs text-red-500">
Yakında Dolacak
</div>
)}
</div>
) : (
<span className="text-gray-400">-</span>
)}
</td>
<td className="px-2 py-1.5 whitespace-nowrap">
<div className="text-xs text-gray-900">
{lot.supplierId || "-"}
</div>
</td>
<td className="px-2 py-1.5 whitespace-nowrap">
<span
className={`px-2 py-0.5 text-xs font-medium rounded-full ${qualityInfo.color}`}
>
{qualityInfo.label}
</span>
</td>
<td className="px-2 py-1.5 whitespace-nowrap text-right text-sm font-medium">
<div className="flex items-center justify-end space-x-2">
<button
onClick={() => {
setCurrentLot(lot);
setOpenLotModal(true);
}}
className="text-blue-600 hover:text-blue-900"
>
<FaEye className="h-3.5 w-3.5" />
</button>
<button
onClick={() => {
setCurrentLot(lot);
setOpenLotModal(true);
}}
className="text-green-600 hover:text-green-900"
>
<FaEdit className="h-3.5 w-3.5" />
</button>
</div>
</td>
</tr>
);
})}
</tbody>
</table>
<LotForm
isOpen={openLotModal}
onClose={() => {
setOpenLotModal(false);
setCurrentLot(null);
}}
onSave={(lot) => setLotNumbers((prev) => [lot, ...prev])}
onUpdate={(updated) =>
setLotNumbers((prev) =>
prev.map((l) => (l.id === updated.id ? updated : l))
)
}
materials={mockMaterials}
units={mockUnits}
suppliers={mockBusinessParties.filter(
(bp) => bp.partyType === PartyType.Supplier
)} )}
initial={currentLot} </div>
mode={currentLot ? "edit" : "create"}
/>
{filteredLots.length === 0 && (
<div className="text-center py-12">
<FaBox className="mx-auto h-12 w-12 text-gray-400" />
<h3 className="mt-2 text-sm font-medium text-gray-900">
Lot bulunamadı
</h3>
<p className="mt-1 text-sm text-gray-500">
Arama kriterlerinize uygun lot kaydı bulunmuyor.
</p>
</div>
)}
</div> </div>
)}
{/* Serial Numbers Tab */} {/* Summary Statistics */}
{activeTab === "serials" && ( <div className="grid grid-cols-1 md:grid-cols-4 gap-2">
<div className="bg-white shadow-sm rounded-lg overflow-hidden"> <Widget title="Toplam Lot" value={lotNumbers.length} color="blue" icon="FaBox" />
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Seri Numarası
</th>
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Malzeme
</th>
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Lot
</th>
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Üretim Tarihi
</th>
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Garanti Bitiş
</th>
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Mevcut Lokasyon
</th>
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Durum
</th>
<th className="px-2 py-1.5 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">
{filteredSerials.map((serial) => {
const statusInfo = getSerialStatusInfo(serial.status);
const isWarrantyExpiring =
serial.warrantyExpiryDate &&
new Date(serial.warrantyExpiryDate).getTime() -
new Date().getTime() <
30 * 24 * 60 * 60 * 1000;
return ( <Widget
<tr key={serial.id} className="hover:bg-gray-50 text-xs"> title="Onaylı Lot"
<td className="px-2 py-1.5 whitespace-nowrap"> value={
<div className="flex items-center"> lotNumbers.filter((lot) => lot.qualityStatus === QualityStatusEnum.Approved).length
<FaHashtag className="h-4 w-4 text-gray-400 mr-2" />
<div className="text-xs font-medium text-gray-900">
{serial.serialNumber}
</div>
</div>
</td>
<td className="px-2 py-1.5 whitespace-nowrap">
<div className="text-xs font-medium text-gray-900">
{serial.material?.name}
</div>
</td>
<td className="px-2 py-1.5 whitespace-nowrap">
<div className="text-xs text-gray-900">
{serial.lotId ? (
<span className="bg-blue-100 text-blue-800 px-2 py-1 rounded text-xs">
LOT-{serial.lotId}
</span>
) : (
"-"
)}
</div>
</td>
<td className="px-2 py-1.5 whitespace-nowrap">
<div className="flex items-center">
<FaCalendar className="h-4 w-4 text-gray-400 mr-2" />
<div className="text-xs text-gray-900">
{new Date(serial.productionDate).toLocaleDateString(
"tr-TR"
)}
</div>
</div>
</td>
<td className="px-2 py-1.5 whitespace-nowrap">
{serial.warrantyExpiryDate ? (
<div
className={`text-xs ${
isWarrantyExpiring
? "text-red-600 font-medium"
: "text-gray-900"
}`}
>
{new Date(
serial.warrantyExpiryDate
).toLocaleDateString("tr-TR")}
{isWarrantyExpiring && (
<div className="text-xs text-red-500">
Yakında Dolacak
</div>
)}
</div>
) : (
<span className="text-gray-400">-</span>
)}
</td>
<td className="px-2 py-1.5 whitespace-nowrap">
<div className="flex items-center">
<FaMapMarkerAlt className="h-4 w-4 text-gray-400 mr-2" />
<div className="text-xs text-gray-900">
{serial.currentLocationId || "-"}
</div>
</div>
</td>
<td className="px-2 py-1.5 whitespace-nowrap">
<span
className={`px-2 py-0.5 text-xs font-medium rounded-full ${statusInfo.color}`}
>
{statusInfo.label}
</span>
</td>
<td className="px-2 py-1.5 whitespace-nowrap text-right text-sm font-medium">
<div className="flex items-center justify-end space-x-2">
<button
onClick={() => {
setCurrentSerial(serial);
setOpenSerialModal(true);
}}
className="text-blue-600 hover:text-blue-900"
>
<FaEye className="h-3.5 w-3.5" />
</button>
<button
onClick={() => {
setCurrentSerial(serial);
setOpenSerialModal(true);
}}
className="text-green-600 hover:text-green-900"
>
<FaEdit className="h-3.5 w-3.5" />
</button>
</div>
</td>
</tr>
);
})}
</tbody>
</table>
<SerialForm
isOpen={openSerialModal}
onClose={() => {
setOpenSerialModal(false);
setCurrentSerial(null);
}}
onSave={(serial) => setSerialNumbers((prev) => [serial, ...prev])}
onUpdate={(updated) =>
setSerialNumbers((prev) =>
prev.map((s) => (s.id === updated.id ? updated : s))
)
} }
materials={mockMaterials} color="green"
lots={lotNumbers.map((l) => ({ id: l.id, label: l.lotNumber }))} icon="FaBox"
initial={currentSerial}
mode={currentSerial ? "edit" : "create"}
/> />
{filteredSerials.length === 0 && ( <Widget
<div className="text-center py-12"> title="Toplam Seri"
<FaHashtag className="mx-auto h-12 w-12 text-gray-400" /> value={serialNumbers.length}
<h3 className="mt-2 text-sm font-medium text-gray-900"> color="purple"
Seri numarası bulunamadı icon="FaHashtag"
</h3> />
<p className="mt-1 text-sm text-gray-500">
Arama kriterlerinize uygun seri numarası bulunmuyor.
</p>
</div>
)}
</div>
)}
</div>
);
};
export default InventoryTracking; <Widget
title="Müsait Seri"
value={
serialNumbers.filter((serial) => serial.status === SerialStatusEnum.Available).length
}
color="orange"
icon="FaHashtag"
/>
</div>
{/* Search */}
<div className="relative">
<FaSearch className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-3.5 w-3.5" />
<input
type="text"
placeholder="Lot/Seri numarası veya malzeme kodu ara..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full pl-8 pr-3 py-1 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
</div>
{/* Tabs */}
<div className="border-b border-gray-200">
<nav className="flex -mb-px">
<button
onClick={() => setActiveTab('lots')}
className={`flex items-center px-2.5 py-1 text-sm font-medium border-b-2 ${
activeTab === 'lots'
? 'border-blue-500 text-blue-600'
: 'border-transparent text-gray-500 hover:text-gray-700'
}`}
>
<FaBox className="h-4 w-4 mr-2" />
Lot Numaraları ({filteredLots.length})
</button>
<button
onClick={() => setActiveTab('serials')}
className={`flex items-center px-2.5 py-1 text-sm font-medium border-b-2 ${
activeTab === 'serials'
? 'border-blue-500 text-blue-600'
: 'border-transparent text-gray-500 hover:text-gray-700'
}`}
>
<FaHashtag className="h-4 w-4 mr-2" />
Seri Numaraları ({filteredSerials.length})
</button>
</nav>
</div>
{/* Lot Numbers Tab */}
{activeTab === 'lots' && (
<div className="bg-white shadow-sm rounded-lg overflow-hidden">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Lot Numarası
</th>
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Malzeme
</th>
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Miktar
</th>
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Üretim Tarihi
</th>
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Son Kullanma
</th>
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Tedarikçi
</th>
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Kalite Durumu
</th>
<th className="px-2 py-1.5 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">
{filteredLots.map((lot) => {
const qualityInfo = getQualityStatusInfo(lot.qualityStatus)
const isExpiringSoon =
lot.expiryDate &&
new Date(lot.expiryDate).getTime() - new Date().getTime() <
30 * 24 * 60 * 60 * 1000
return (
<tr key={lot.id} className="hover:bg-gray-50 text-xs">
<td className="px-2 py-1.5 whitespace-nowrap">
<div className="flex items-center">
<FaBox className="h-4 w-4 text-gray-400 mr-2" />
<div className="text-xs font-medium text-gray-900">{lot.lotNumber}</div>
</div>
</td>
<td className="px-2 py-1.5 whitespace-nowrap">
<div className="text-xs font-medium text-gray-900">
{lot.material?.name}
</div>
</td>
<td className="px-2 py-1.5 whitespace-nowrap">
<div className="text-xs text-gray-900">
{lot.quantity} {lot.unitId}
</div>
</td>
<td className="px-2 py-1.5 whitespace-nowrap">
<div className="flex items-center">
<FaCalendar className="h-4 w-4 text-gray-400 mr-2" />
<div className="text-xs text-gray-900">
{new Date(lot.productionDate).toLocaleDateString('tr-TR')}
</div>
</div>
</td>
<td className="px-2 py-1.5 whitespace-nowrap">
{lot.expiryDate ? (
<div
className={`text-xs ${
isExpiringSoon ? 'text-red-600 font-medium' : 'text-gray-900'
}`}
>
{new Date(lot.expiryDate).toLocaleDateString('tr-TR')}
{isExpiringSoon && (
<div className="text-xs text-red-500">Yakında Dolacak</div>
)}
</div>
) : (
<span className="text-gray-400">-</span>
)}
</td>
<td className="px-2 py-1.5 whitespace-nowrap">
<div className="text-xs text-gray-900">{lot.supplierId || '-'}</div>
</td>
<td className="px-2 py-1.5 whitespace-nowrap">
<span
className={`px-2 py-0.5 text-xs font-medium rounded-full ${qualityInfo.color}`}
>
{qualityInfo.label}
</span>
</td>
<td className="px-2 py-1.5 whitespace-nowrap text-right text-sm font-medium">
<div className="flex items-center justify-end space-x-2">
<button
onClick={() => {
setCurrentLot(lot)
setOpenLotModal(true)
}}
className="text-blue-600 hover:text-blue-900"
>
<FaEye className="h-3.5 w-3.5" />
</button>
<button
onClick={() => {
setCurrentLot(lot)
setOpenLotModal(true)
}}
className="text-green-600 hover:text-green-900"
>
<FaEdit className="h-3.5 w-3.5" />
</button>
</div>
</td>
</tr>
)
})}
</tbody>
</table>
<LotForm
isOpen={openLotModal}
onClose={() => {
setOpenLotModal(false)
setCurrentLot(null)
}}
onSave={(lot) => setLotNumbers((prev) => [lot, ...prev])}
onUpdate={(updated) =>
setLotNumbers((prev) => prev.map((l) => (l.id === updated.id ? updated : l)))
}
materials={mockMaterials}
units={mockUnits}
suppliers={mockBusinessParties.filter((bp) => bp.partyType === PartyType.Supplier)}
initial={currentLot}
mode={currentLot ? 'edit' : 'create'}
/>
{filteredLots.length === 0 && (
<div className="text-center py-12">
<FaBox className="mx-auto h-12 w-12 text-gray-400" />
<h3 className="mt-2 text-sm font-medium text-gray-900">Lot bulunamadı</h3>
<p className="mt-1 text-sm text-gray-500">
Arama kriterlerinize uygun lot kaydı bulunmuyor.
</p>
</div>
)}
</div>
)}
{/* Serial Numbers Tab */}
{activeTab === 'serials' && (
<div className="bg-white shadow-sm rounded-lg overflow-hidden">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Seri Numarası
</th>
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Malzeme
</th>
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Lot
</th>
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Üretim Tarihi
</th>
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Garanti Bitiş
</th>
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Mevcut Lokasyon
</th>
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Durum
</th>
<th className="px-2 py-1.5 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">
{filteredSerials.map((serial) => {
const statusInfo = getSerialStatusInfo(serial.status)
const isWarrantyExpiring =
serial.warrantyExpiryDate &&
new Date(serial.warrantyExpiryDate).getTime() - new Date().getTime() <
30 * 24 * 60 * 60 * 1000
return (
<tr key={serial.id} className="hover:bg-gray-50 text-xs">
<td className="px-2 py-1.5 whitespace-nowrap">
<div className="flex items-center">
<FaHashtag className="h-4 w-4 text-gray-400 mr-2" />
<div className="text-xs font-medium text-gray-900">
{serial.serialNumber}
</div>
</div>
</td>
<td className="px-2 py-1.5 whitespace-nowrap">
<div className="text-xs font-medium text-gray-900">
{serial.material?.name}
</div>
</td>
<td className="px-2 py-1.5 whitespace-nowrap">
<div className="text-xs text-gray-900">
{serial.lotId ? (
<span className="bg-blue-100 text-blue-800 px-2 py-1 rounded text-xs">
LOT-{serial.lotId}
</span>
) : (
'-'
)}
</div>
</td>
<td className="px-2 py-1.5 whitespace-nowrap">
<div className="flex items-center">
<FaCalendar className="h-4 w-4 text-gray-400 mr-2" />
<div className="text-xs text-gray-900">
{new Date(serial.productionDate).toLocaleDateString('tr-TR')}
</div>
</div>
</td>
<td className="px-2 py-1.5 whitespace-nowrap">
{serial.warrantyExpiryDate ? (
<div
className={`text-xs ${
isWarrantyExpiring ? 'text-red-600 font-medium' : 'text-gray-900'
}`}
>
{new Date(serial.warrantyExpiryDate).toLocaleDateString('tr-TR')}
{isWarrantyExpiring && (
<div className="text-xs text-red-500">Yakında Dolacak</div>
)}
</div>
) : (
<span className="text-gray-400">-</span>
)}
</td>
<td className="px-2 py-1.5 whitespace-nowrap">
<div className="flex items-center">
<FaMapMarkerAlt className="h-4 w-4 text-gray-400 mr-2" />
<div className="text-xs text-gray-900">
{serial.currentLocationId || '-'}
</div>
</div>
</td>
<td className="px-2 py-1.5 whitespace-nowrap">
<span
className={`px-2 py-0.5 text-xs font-medium rounded-full ${statusInfo.color}`}
>
{statusInfo.label}
</span>
</td>
<td className="px-2 py-1.5 whitespace-nowrap text-right text-sm font-medium">
<div className="flex items-center justify-end space-x-2">
<button
onClick={() => {
setCurrentSerial(serial)
setOpenSerialModal(true)
}}
className="text-blue-600 hover:text-blue-900"
>
<FaEye className="h-3.5 w-3.5" />
</button>
<button
onClick={() => {
setCurrentSerial(serial)
setOpenSerialModal(true)
}}
className="text-green-600 hover:text-green-900"
>
<FaEdit className="h-3.5 w-3.5" />
</button>
</div>
</td>
</tr>
)
})}
</tbody>
</table>
<SerialForm
isOpen={openSerialModal}
onClose={() => {
setOpenSerialModal(false)
setCurrentSerial(null)
}}
onSave={(serial) => setSerialNumbers((prev) => [serial, ...prev])}
onUpdate={(updated) =>
setSerialNumbers((prev) => prev.map((s) => (s.id === updated.id ? updated : s)))
}
materials={mockMaterials}
lots={lotNumbers.map((l) => ({ id: l.id, label: l.lotNumber }))}
initial={currentSerial}
mode={currentSerial ? 'edit' : 'create'}
/>
{filteredSerials.length === 0 && (
<div className="text-center py-12">
<FaHashtag className="mx-auto h-12 w-12 text-gray-400" />
<h3 className="mt-2 text-sm font-medium text-gray-900">Seri numarası bulunamadı</h3>
<p className="mt-1 text-sm text-gray-500">
Arama kriterlerinize uygun seri numarası bulunmuyor.
</p>
</div>
)}
</div>
)}
</div>
</Container>
)
}
export default InventoryTracking

View file

@ -1,4 +1,4 @@
import React, { useState } from "react"; import React, { useState } from 'react'
import { import {
FaSearch, FaSearch,
FaMapMarkerAlt, FaMapMarkerAlt,
@ -7,45 +7,43 @@ import {
FaEye, FaEye,
FaTh, FaTh,
FaList, FaList,
} from "react-icons/fa"; } from 'react-icons/fa'
import { mockWarehouses } from "../../../mocks/mockWarehouses"; import { mockWarehouses } from '../../../mocks/mockWarehouses'
import { mockLocations } from "../../../mocks/mockLocations"; import { mockLocations } from '../../../mocks/mockLocations'
import { mockStockItems } from "../../../mocks/mockStockItems"; import { mockStockItems } from '../../../mocks/mockStockItems'
import { getStockStatusColor, getStockStatusText } from "../../../utils/erp"; import { getStockStatusColor, getStockStatusText } from '../../../utils/erp'
import { Container } from '@/components/shared'
const LocationTracking: React.FC = () => { const LocationTracking: React.FC = () => {
const [searchTerm, setSearchTerm] = useState(""); const [searchTerm, setSearchTerm] = useState('')
const [selectedWarehouse, setSelectedWarehouse] = useState<string>(""); const [selectedWarehouse, setSelectedWarehouse] = useState<string>('')
const [selectedLocation, setSelectedLocation] = useState<string>(""); const [selectedLocation, setSelectedLocation] = useState<string>('')
const [viewMode, setViewMode] = useState<"grid" | "list">("grid"); const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid')
const getLocationUtilization = (locationId: string) => { const getLocationUtilization = (locationId: string) => {
const location = mockLocations.find((l) => l.id === locationId); const location = mockLocations.find((l) => l.id === locationId)
if (!location) return 0; if (!location) return 0
return (location.currentStock / location.capacity) * 100; return (location.currentStock / location.capacity) * 100
}; }
const getLocationStockItems = (locationId: string) => { const getLocationStockItems = (locationId: string) => {
return mockStockItems.filter((item) => item.locationId === locationId); return mockStockItems.filter((item) => item.locationId === locationId)
}; }
const filteredLocations = mockLocations.filter((location) => { const filteredLocations = mockLocations.filter((location) => {
const matchesSearch = const matchesSearch =
location.name.toLowerCase().includes(searchTerm.toLowerCase()) || location.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
location.locationCode.toLowerCase().includes(searchTerm.toLowerCase()); location.locationCode.toLowerCase().includes(searchTerm.toLowerCase())
const matchesWarehouse = const matchesWarehouse = selectedWarehouse === '' || location.warehouseId === selectedWarehouse
selectedWarehouse === "" || location.warehouseId === selectedWarehouse; return matchesSearch && matchesWarehouse
return matchesSearch && matchesWarehouse; })
});
const GridView = () => ( const GridView = () => (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{filteredLocations.map((location) => { {filteredLocations.map((location) => {
const warehouse = mockWarehouses.find( const warehouse = mockWarehouses.find((w) => w.id === location.warehouseId)
(w) => w.id === location.warehouseId const locationStockItems = getLocationStockItems(location.id)
); const utilization = getLocationUtilization(location.id)
const locationStockItems = getLocationStockItems(location.id);
const utilization = getLocationUtilization(location.id);
return ( return (
<div <div
@ -59,9 +57,7 @@ const LocationTracking: React.FC = () => {
</div> </div>
<div> <div>
<h3 className="font-medium text-gray-900">{location.name}</h3> <h3 className="font-medium text-gray-900">{location.name}</h3>
<p className="text-sm text-gray-500"> <p className="text-sm text-gray-500">{location.locationCode}</p>
{location.locationCode}
</p>
</div> </div>
</div> </div>
<button <button
@ -84,18 +80,16 @@ const LocationTracking: React.FC = () => {
<div className="space-y-1"> <div className="space-y-1">
<div className="flex justify-between text-sm"> <div className="flex justify-between text-sm">
<span className="text-gray-600">Doluluk Oranı</span> <span className="text-gray-600">Doluluk Oranı</span>
<span className="font-medium"> <span className="font-medium">%{Math.round(utilization)}</span>
%{Math.round(utilization)}
</span>
</div> </div>
<div className="w-full bg-gray-200 rounded-full h-2"> <div className="w-full bg-gray-200 rounded-full h-2">
<div <div
className={`h-2 rounded-full ${ className={`h-2 rounded-full ${
utilization > 90 utilization > 90
? "bg-red-500" ? 'bg-red-500'
: utilization > 70 : utilization > 70
? "bg-yellow-500" ? 'bg-yellow-500'
: "bg-green-500" : 'bg-green-500'
}`} }`}
style={{ width: `${utilization}%` }} style={{ width: `${utilization}%` }}
/> />
@ -108,29 +102,22 @@ const LocationTracking: React.FC = () => {
{/* Stock Items Summary */} {/* Stock Items Summary */}
<div className="bg-gray-50 rounded-lg p-2"> <div className="bg-gray-50 rounded-lg p-2">
<div className="flex items-center justify-between mb-2"> <div className="flex items-center justify-between mb-2">
<h4 className="text-sm font-medium text-gray-900"> <h4 className="text-sm font-medium text-gray-900">Malzemeler</h4>
Malzemeler <span className="text-xs text-gray-500">{locationStockItems.length} çeşit</span>
</h4>
<span className="text-xs text-gray-500">
{locationStockItems.length} çeşit
</span>
</div> </div>
{locationStockItems.length > 0 ? ( {locationStockItems.length > 0 ? (
<div className="space-y-1"> <div className="space-y-1">
{locationStockItems.slice(0, 3).map((item) => ( {locationStockItems.slice(0, 3).map((item) => (
<div <div key={item.id} className="flex justify-between items-center text-xs">
key={item.id}
className="flex justify-between items-center text-xs"
>
<span className="text-gray-700 truncate"> <span className="text-gray-700 truncate">
{item.material?.code || "N/A"} {item.material?.code || 'N/A'}
</span> </span>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="font-medium">{item.quantity}</span> <span className="font-medium">{item.quantity}</span>
<span <span
className={`px-1.5 py-0.5 rounded-full text-xs ${getStockStatusColor( className={`px-1.5 py-0.5 rounded-full text-xs ${getStockStatusColor(
item.status item.status,
)}`} )}`}
> >
{getStockStatusText(item.status)} {getStockStatusText(item.status)}
@ -145,29 +132,23 @@ const LocationTracking: React.FC = () => {
)} )}
</div> </div>
) : ( ) : (
<div className="text-xs text-gray-500 text-center py-2"> <div className="text-xs text-gray-500 text-center py-2">Malzeme bulunmuyor</div>
Malzeme bulunmuyor
</div>
)} )}
</div> </div>
{/* Restrictions */} {/* Restrictions */}
{location.restrictions && location.restrictions.length > 0 && ( {location.restrictions && location.restrictions.length > 0 && (
<div> <div>
<h4 className="text-sm font-medium text-gray-900 mb-1"> <h4 className="text-sm font-medium text-gray-900 mb-1">Kısıtlamalar</h4>
Kısıtlamalar
</h4>
<div className="flex flex-wrap gap-1"> <div className="flex flex-wrap gap-1">
{location.restrictions {location.restrictions.slice(0, 2).map((restriction, index) => (
.slice(0, 2) <span
.map((restriction, index) => ( key={index}
<span className="inline-flex px-2 py-1 text-xs bg-yellow-100 text-yellow-800 rounded"
key={index} >
className="inline-flex px-2 py-1 text-xs bg-yellow-100 text-yellow-800 rounded" {restriction}
> </span>
{restriction} ))}
</span>
))}
{location.restrictions.length > 2 && ( {location.restrictions.length > 2 && (
<span className="inline-flex px-2 py-1 text-xs bg-gray-100 text-gray-600 rounded"> <span className="inline-flex px-2 py-1 text-xs bg-gray-100 text-gray-600 rounded">
+{location.restrictions.length - 2} +{location.restrictions.length - 2}
@ -193,23 +174,21 @@ const LocationTracking: React.FC = () => {
)} )}
</div> </div>
<div className="text-xs text-gray-500"> <div className="text-xs text-gray-500">
Son hareket:{" "} Son hareket:{' '}
{locationStockItems.length > 0 {locationStockItems.length > 0
? new Date( ? new Date(
Math.max( Math.max(
...locationStockItems.map((item) => ...locationStockItems.map((item) => item.lastMovementDate.getTime()),
item.lastMovementDate.getTime() ),
) ).toLocaleDateString('tr-TR')
) : 'N/A'}
).toLocaleDateString("tr-TR")
: "N/A"}
</div> </div>
</div> </div>
</div> </div>
); )
})} })}
</div> </div>
); )
const ListView = () => ( const ListView = () => (
<div className="bg-white rounded-lg shadow-sm border border-gray-200"> <div className="bg-white rounded-lg shadow-sm border border-gray-200">
@ -242,11 +221,9 @@ const LocationTracking: React.FC = () => {
</thead> </thead>
<tbody className="bg-white divide-y divide-gray-200"> <tbody className="bg-white divide-y divide-gray-200">
{filteredLocations.map((location) => { {filteredLocations.map((location) => {
const warehouse = mockWarehouses.find( const warehouse = mockWarehouses.find((w) => w.id === location.warehouseId)
(w) => w.id === location.warehouseId const locationStockItems = getLocationStockItems(location.id)
); const utilization = getLocationUtilization(location.id)
const locationStockItems = getLocationStockItems(location.id);
const utilization = getLocationUtilization(location.id);
return ( return (
<tr key={location.id} className="hover:bg-gray-50"> <tr key={location.id} className="hover:bg-gray-50">
@ -256,22 +233,14 @@ const LocationTracking: React.FC = () => {
<FaMapMarkerAlt className="w-4 h-4 text-blue-600" /> <FaMapMarkerAlt className="w-4 h-4 text-blue-600" />
</div> </div>
<div> <div>
<div className="text-sm font-medium text-gray-900"> <div className="text-sm font-medium text-gray-900">{location.name}</div>
{location.name} <div className="text-sm text-gray-500">{location.locationCode}</div>
</div>
<div className="text-sm text-gray-500">
{location.locationCode}
</div>
</div> </div>
</div> </div>
</td> </td>
<td className="px-3 py-2 whitespace-nowrap"> <td className="px-3 py-2 whitespace-nowrap">
<div className="text-sm text-gray-900"> <div className="text-sm text-gray-900">{warehouse?.name}</div>
{warehouse?.name} <div className="text-sm text-gray-500">{warehouse?.code}</div>
</div>
<div className="text-sm text-gray-500">
{warehouse?.code}
</div>
</td> </td>
<td className="px-3 py-2 whitespace-nowrap"> <td className="px-3 py-2 whitespace-nowrap">
<div className="flex items-center"> <div className="flex items-center">
@ -279,17 +248,15 @@ const LocationTracking: React.FC = () => {
<div <div
className={`h-2 rounded-full ${ className={`h-2 rounded-full ${
utilization > 90 utilization > 90
? "bg-red-500" ? 'bg-red-500'
: utilization > 70 : utilization > 70
? "bg-yellow-500" ? 'bg-yellow-500'
: "bg-green-500" : 'bg-green-500'
}`} }`}
style={{ width: `${utilization}%` }} style={{ width: `${utilization}%` }}
/> />
</div> </div>
<span className="text-sm text-gray-900"> <span className="text-sm text-gray-900">%{Math.round(utilization)}</span>
%{Math.round(utilization)}
</span>
</div> </div>
<div className="text-xs text-gray-500"> <div className="text-xs text-gray-500">
{location.currentStock} / {location.capacity} {location.currentStock} / {location.capacity}
@ -302,12 +269,10 @@ const LocationTracking: React.FC = () => {
{locationStockItems.length > 0 {locationStockItems.length > 0
? new Date( ? new Date(
Math.max( Math.max(
...locationStockItems.map((item) => ...locationStockItems.map((item) => item.lastMovementDate.getTime()),
item.lastMovementDate.getTime() ),
) ).toLocaleDateString('tr-TR')
) : 'N/A'}
).toLocaleDateString("tr-TR")
: "N/A"}
</td> </td>
<td className="px-3 py-2 whitespace-nowrap"> <td className="px-3 py-2 whitespace-nowrap">
{location.isActive ? ( {location.isActive ? (
@ -331,44 +296,37 @@ const LocationTracking: React.FC = () => {
</button> </button>
</td> </td>
</tr> </tr>
); )
})} })}
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>
); )
const LocationDetailModal = () => { const LocationDetailModal = () => {
const location = mockLocations.find((l) => l.id === selectedLocation); const location = mockLocations.find((l) => l.id === selectedLocation)
const locationStockItems = getLocationStockItems(selectedLocation); const locationStockItems = getLocationStockItems(selectedLocation)
if (!selectedLocation || !location) return null; if (!selectedLocation || !location) return null
return ( return (
<div className="fixed inset-0 z-50 overflow-y-auto"> <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="flex items-center justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div <div
className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"
onClick={() => setSelectedLocation("")} onClick={() => setSelectedLocation('')}
/> />
<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="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="bg-white px-4 pt-4 pb-4 sm:p-4">
<div className="flex items-center justify-between mb-4"> <div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-medium text-gray-900"> <h3 className="text-lg font-medium text-gray-900">{location.name} - Detaylar</h3>
{location.name} - Detaylar
</h3>
<button <button
onClick={() => setSelectedLocation("")} onClick={() => setSelectedLocation('')}
className="text-gray-400 hover:text-gray-600" className="text-gray-400 hover:text-gray-600"
> >
<svg <svg className="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
className="w-6 h-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path <path
strokeLinecap="round" strokeLinecap="round"
strokeLinejoin="round" strokeLinejoin="round"
@ -382,9 +340,7 @@ const LocationTracking: React.FC = () => {
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6"> <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Location Info */} {/* Location Info */}
<div className="bg-gray-50 rounded-lg p-3"> <div className="bg-gray-50 rounded-lg p-3">
<h4 className="font-medium text-gray-900 mb-3"> <h4 className="font-medium text-gray-900 mb-3">Lokasyon Bilgileri</h4>
Lokasyon Bilgileri
</h4>
<div className="space-y-2 text-sm"> <div className="space-y-2 text-sm">
<div> <div>
<strong>Kod:</strong> {location.locationCode} <strong>Kod:</strong> {location.locationCode}
@ -399,14 +355,12 @@ const LocationTracking: React.FC = () => {
<strong>Kapasite:</strong> {location.capacity} birim <strong>Kapasite:</strong> {location.capacity} birim
</div> </div>
<div> <div>
<strong>Mevcut Stok:</strong> {location.currentStock}{" "} <strong>Mevcut Stok:</strong> {location.currentStock} birim
birim
</div> </div>
{location.dimensions && ( {location.dimensions && (
<div> <div>
<strong>Boyutlar:</strong> {location.dimensions.length}x <strong>Boyutlar:</strong> {location.dimensions.length}x
{location.dimensions.width}x{location.dimensions.height} {location.dimensions.width}x{location.dimensions.height}m
m
</div> </div>
)} )}
</div> </div>
@ -419,22 +373,15 @@ const LocationTracking: React.FC = () => {
</h4> </h4>
<div className="space-y-2 max-h-64 overflow-y-auto"> <div className="space-y-2 max-h-64 overflow-y-auto">
{locationStockItems.map((item) => ( {locationStockItems.map((item) => (
<div <div key={item.id} className="bg-white rounded p-3 border">
key={item.id}
className="bg-white rounded p-3 border"
>
<div className="flex justify-between items-start mb-2"> <div className="flex justify-between items-start mb-2">
<div> <div>
<div className="font-medium text-sm"> <div className="font-medium text-sm">{item.material?.code}</div>
{item.material?.code} <div className="text-xs text-gray-500">{item.material?.code}</div>
</div>
<div className="text-xs text-gray-500">
{item.material?.code}
</div>
</div> </div>
<span <span
className={`px-2 py-1 rounded-full text-xs ${getStockStatusColor( className={`px-2 py-1 rounded-full text-xs ${getStockStatusColor(
item.status item.status,
)}`} )}`}
> >
{getStockStatusText(item.status)} {getStockStatusText(item.status)}
@ -450,11 +397,10 @@ const LocationTracking: React.FC = () => {
<div> <div>
Rezerve: {item.reservedQuantity} {item.unitId} Rezerve: {item.reservedQuantity} {item.unitId}
</div> </div>
<div>Lot: {item.lotNumber || "N/A"}</div> <div>Lot: {item.lotNumber || 'N/A'}</div>
</div> </div>
<div className="mt-2 text-xs text-gray-500"> <div className="mt-2 text-xs text-gray-500">
Son hareket:{" "} Son hareket: {item.lastMovementDate.toLocaleDateString('tr-TR')}
{item.lastMovementDate.toLocaleDateString("tr-TR")}
</div> </div>
</div> </div>
))} ))}
@ -470,78 +416,76 @@ const LocationTracking: React.FC = () => {
</div> </div>
</div> </div>
</div> </div>
); )
}; }
return ( return (
<div className="space-y-4 pt-2"> <Container>
<div className="flex items-center justify-between"> <div className="space-y-2">
<div> <div className="flex items-center justify-between">
<h2 className="text-2xl font-bold text-gray-900"> <div>
Raf/Lokasyon Bazlı Takip <h2 className="text-2xl font-bold text-gray-900">Raf/Lokasyon Bazlı Takip</h2>
</h2> <p className="text-gray-600">Lokasyonlardaki stok durumunu takip edin</p>
<p className="text-gray-600"> </div>
Lokasyonlardaki stok durumunu takip edin
</p> <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>
</div> </div>
<div className="flex items-center gap-2"> {/* Filters */}
<button <div className="flex flex-col sm:flex-row gap-4">
onClick={() => setViewMode("grid")} <div className="relative flex-1">
className={`p-1.5 rounded-lg ${ <FaSearch className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
viewMode === "grid" <input
? "bg-blue-100 text-blue-600" type="text"
: "text-gray-400 hover:text-gray-600" 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"
> >
<FaTh className="w-4 h-4" /> <option value="">Tüm Depolar</option>
</button> {mockWarehouses.map((warehouse) => (
<button <option key={warehouse.id} value={warehouse.id}>
onClick={() => setViewMode("list")} {warehouse.name} ({warehouse.code})
className={`p-1.5 rounded-lg ${ </option>
viewMode === "list" ))}
? "bg-blue-100 text-blue-600" </select>
: "text-gray-400 hover:text-gray-600"
}`}
>
<FaList className="w-4 h-4" />
</button>
</div> </div>
</div>
{/* Filters */} {/* Content */}
<div className="flex flex-col sm:flex-row gap-4"> {viewMode === 'grid' ? <GridView /> : <ListView />}
<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"
>
<option value="">Tüm Depolar</option>
{mockWarehouses.map((warehouse) => (
<option key={warehouse.id} value={warehouse.id}>
{warehouse.name} ({warehouse.code})
</option>
))}
</select>
</div> </div>
{/* Content */}
{viewMode === "grid" ? <GridView /> : <ListView />}
{/* Location Detail Modal */} {/* Location Detail Modal */}
<LocationDetailModal /> <LocationDetailModal />
</div> </Container>
); )
}; }
export default LocationTracking; export default LocationTracking

View file

@ -1,31 +1,26 @@
import React from "react"; import React from 'react'
import { useFormik } from "formik"; import { useFormik } from 'formik'
import * as Yup from "yup"; import * as Yup from 'yup'
import { FaSave, FaTimes } from "react-icons/fa"; import { FaSave, FaTimes } from 'react-icons/fa'
import { import { QualityStatusEnum, MmLotNumber, MmUnit, MmMaterial } from '../../../types/mm'
QualityStatusEnum, import { BusinessParty } from '../../../types/common'
MmLotNumber,
MmUnit,
MmMaterial,
} from "../../../types/mm";
import { BusinessParty } from "../../../types/common";
const validationSchema = Yup.object({ const validationSchema = Yup.object({
materialId: Yup.string().required("Malzeme seçimi zorunlu"), materialId: Yup.string().required('Malzeme seçimi zorunlu'),
lotNumber: Yup.string().required("Lot numarası zorunlu"), lotNumber: Yup.string().required('Lot numarası zorunlu'),
quantity: Yup.number().min(0, "Miktar 0'dan büyük olmalıdır"), quantity: Yup.number().min(0, "Miktar 0'dan büyük olmalıdır"),
}); })
interface LotFormProps { interface LotFormProps {
isOpen: boolean; isOpen: boolean
onClose: () => void; onClose: () => void
onSave: (lot: MmLotNumber) => void; onSave: (lot: MmLotNumber) => void
onUpdate?: (lot: MmLotNumber) => void; onUpdate?: (lot: MmLotNumber) => void
materials: MmMaterial[]; materials: MmMaterial[]
suppliers?: BusinessParty[]; suppliers?: BusinessParty[]
units?: MmUnit[]; units?: MmUnit[]
initial?: MmLotNumber | null; initial?: MmLotNumber | null
mode?: "create" | "edit" | "view"; mode?: 'create' | 'edit' | 'view'
} }
const LotForm: React.FC<LotFormProps> = ({ const LotForm: React.FC<LotFormProps> = ({
@ -37,17 +32,17 @@ const LotForm: React.FC<LotFormProps> = ({
suppliers = [], suppliers = [],
units = [], units = [],
initial = null, initial = null,
mode = "create", mode = 'create',
}) => { }) => {
const formik = useFormik({ const formik = useFormik({
initialValues: { initialValues: {
materialId: "", materialId: '',
lotNumber: "", lotNumber: '',
productionDate: "", productionDate: '',
expiryDate: "", expiryDate: '',
quantity: 0, quantity: 0,
unitId: "KG", unitId: 'KG',
supplierId: suppliers.length ? suppliers[0].id : "", supplierId: suppliers.length ? suppliers[0].id : '',
qualityStatus: QualityStatusEnum.Pending, qualityStatus: QualityStatusEnum.Pending,
isActive: true, isActive: true,
}, },
@ -57,52 +52,48 @@ const LotForm: React.FC<LotFormProps> = ({
id: (initial && initial.id) || Date.now().toString(), id: (initial && initial.id) || Date.now().toString(),
materialId: values.materialId, materialId: values.materialId,
lotNumber: values.lotNumber, lotNumber: values.lotNumber,
productionDate: values.productionDate productionDate: values.productionDate ? new Date(values.productionDate) : new Date(),
? new Date(values.productionDate)
: new Date(),
expiryDate: values.expiryDate ? new Date(values.expiryDate) : undefined, expiryDate: values.expiryDate ? new Date(values.expiryDate) : undefined,
quantity: Number(values.quantity), quantity: Number(values.quantity),
unitId: values.unitId, unitId: values.unitId,
supplierId: values.supplierId || undefined, supplierId: values.supplierId || undefined,
qualityStatus: values.qualityStatus, qualityStatus: values.qualityStatus,
isActive: !!values.isActive, isActive: !!values.isActive,
}; }
// simulate API // simulate API
await new Promise((r) => setTimeout(r, 300)); await new Promise((r) => setTimeout(r, 300))
if (mode === "edit" && onUpdate) { if (mode === 'edit' && onUpdate) {
onUpdate(newLot); onUpdate(newLot)
} else { } else {
onSave(newLot); onSave(newLot)
} }
onClose(); onClose()
}, },
}); })
// sync initial values when editing/viewing // sync initial values when editing/viewing
React.useEffect(() => { React.useEffect(() => {
if (initial) { if (initial) {
const src = initial; const src = initial
formik.setValues({ formik.setValues({
materialId: src.materialId || "", materialId: src.materialId || '',
lotNumber: src.lotNumber || "", lotNumber: src.lotNumber || '',
productionDate: src.productionDate productionDate: src.productionDate
? new Date(src.productionDate).toISOString().slice(0, 10) ? new Date(src.productionDate).toISOString().slice(0, 10)
: "", : '',
expiryDate: src.expiryDate expiryDate: src.expiryDate ? new Date(src.expiryDate).toISOString().slice(0, 10) : '',
? new Date(src.expiryDate).toISOString().slice(0, 10)
: "",
quantity: src.quantity || 0, quantity: src.quantity || 0,
unitId: src.unitId || (units.length ? units[0].id : ""), unitId: src.unitId || (units.length ? units[0].id : ''),
supplierId: src.supplierId || (suppliers.length ? suppliers[0].id : ""), supplierId: src.supplierId || (suppliers.length ? suppliers[0].id : ''),
qualityStatus: src.qualityStatus || QualityStatusEnum.Pending, qualityStatus: src.qualityStatus || QualityStatusEnum.Pending,
isActive: !!src.isActive, isActive: !!src.isActive,
}); })
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [initial]); }, [initial])
if (!isOpen) return null; if (!isOpen) return null
return ( return (
<div className="fixed inset-0 z-50 flex items-center justify-center"> <div className="fixed inset-0 z-50 flex items-center justify-center">
@ -112,18 +103,18 @@ const LotForm: React.FC<LotFormProps> = ({
<div className="flex items-center justify-between p-2 border-b"> <div className="flex items-center justify-between p-2 border-b">
<div> <div>
<h2 className="text-2xl font-bold text-gray-900"> <h2 className="text-2xl font-bold text-gray-900">
{mode === "create" {mode === 'create'
? "Yeni Lot Kaydı" ? 'Yeni Lot Kaydı'
: mode === "edit" : mode === 'edit'
? "Lot Düzenle" ? 'Lot Düzenle'
: "Lot Detayı"} : 'Lot Detayı'}
</h2> </h2>
<p className="text-gray-600"> <p className="text-gray-600">
{mode === "create" {mode === 'create'
? "Lot bilgilerini girin" ? 'Lot bilgilerini girin'
: mode === "edit" : mode === 'edit'
? "Mevcut lot bilgilerini güncelleyin" ? 'Mevcut lot bilgilerini güncelleyin'
: "Lot bilgileri (sadece gösterim)"} : 'Lot bilgileri (sadece gösterim)'}
</p> </p>
</div> </div>
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
@ -134,7 +125,7 @@ const LotForm: React.FC<LotFormProps> = ({
> >
<FaTimes className="inline mr-1 h-3 w-3" /> Kapat <FaTimes className="inline mr-1 h-3 w-3" /> Kapat
</button> </button>
{mode !== "view" && ( {mode !== 'view' && (
<button <button
type="submit" type="submit"
className="px-2.5 py-1 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700 flex items-center" className="px-2.5 py-1 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700 flex items-center"
@ -147,12 +138,10 @@ const LotForm: React.FC<LotFormProps> = ({
<div className="grid grid-cols-1 md:grid-cols-2 p-2 gap-x-3 gap-y-2"> <div className="grid grid-cols-1 md:grid-cols-2 p-2 gap-x-3 gap-y-2">
<div> <div>
<label className="block text-sm font-medium text-gray-700"> <label className="block text-sm font-medium text-gray-700">Malzeme *</label>
Malzeme *
</label>
<select <select
{...formik.getFieldProps("materialId")} {...formik.getFieldProps('materialId')}
disabled={mode === "view"} disabled={mode === 'view'}
className="w-full border rounded-md px-2 py-1 text-sm" className="w-full border rounded-md px-2 py-1 text-sm"
> >
<option value="">Seçiniz...</option> <option value="">Seçiniz...</option>
@ -165,61 +154,51 @@ const LotForm: React.FC<LotFormProps> = ({
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-gray-700"> <label className="block text-sm font-medium text-gray-700">Lot Numarası *</label>
Lot Numarası *
</label>
<input <input
type="text" type="text"
{...formik.getFieldProps("lotNumber")} {...formik.getFieldProps('lotNumber')}
disabled={mode === "view"} disabled={mode === 'view'}
className="w-full border rounded-md px-2 py-1 text-sm" className="w-full border rounded-md px-2 py-1 text-sm"
/> />
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-gray-700"> <label className="block text-sm font-medium text-gray-700">Üretim Tarihi</label>
Üretim Tarihi
</label>
<input <input
type="date" type="date"
{...formik.getFieldProps("productionDate")} {...formik.getFieldProps('productionDate')}
disabled={mode === "view"} disabled={mode === 'view'}
className="w-full border rounded-md px-2 py-1 text-sm" className="w-full border rounded-md px-2 py-1 text-sm"
/> />
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-gray-700"> <label className="block text-sm font-medium text-gray-700">Son Kullanma Tarihi</label>
Son Kullanma Tarihi
</label>
<input <input
type="date" type="date"
{...formik.getFieldProps("expiryDate")} {...formik.getFieldProps('expiryDate')}
disabled={mode === "view"} disabled={mode === 'view'}
className="w-full border rounded-md px-2 py-1 text-sm" className="w-full border rounded-md px-2 py-1 text-sm"
/> />
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-gray-700"> <label className="block text-sm font-medium text-gray-700">Miktar</label>
Miktar
</label>
<input <input
type="number" type="number"
step="0.01" step="0.01"
{...formik.getFieldProps("quantity")} {...formik.getFieldProps('quantity')}
disabled={mode === "view"} disabled={mode === 'view'}
className="w-full border rounded-md px-2 py-1 text-sm" className="w-full border rounded-md px-2 py-1 text-sm"
/> />
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-gray-700"> <label className="block text-sm font-medium text-gray-700">Birim</label>
Birim
</label>
<select <select
{...formik.getFieldProps("unitId")} {...formik.getFieldProps('unitId')}
disabled={mode === "view"} disabled={mode === 'view'}
className="w-full border rounded-md px-2 py-1 text-sm" className="w-full border rounded-md px-2 py-1 text-sm"
> >
{units.map((u) => ( {units.map((u) => (
@ -231,12 +210,10 @@ const LotForm: React.FC<LotFormProps> = ({
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-gray-700"> <label className="block text-sm font-medium text-gray-700">Tedarikçi</label>
Tedarikçi
</label>
<select <select
{...formik.getFieldProps("supplierId")} {...formik.getFieldProps('supplierId')}
disabled={mode === "view"} disabled={mode === 'view'}
className="w-full border rounded-md px-2 py-1 text-sm" className="w-full border rounded-md px-2 py-1 text-sm"
> >
<option value="">Seçiniz...</option> <option value="">Seçiniz...</option>
@ -249,11 +226,9 @@ const LotForm: React.FC<LotFormProps> = ({
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-gray-700"> <label className="block text-sm font-medium text-gray-700">Kalite Durumu</label>
Kalite Durumu
</label>
<select <select
{...formik.getFieldProps("qualityStatus")} {...formik.getFieldProps('qualityStatus')}
className="w-full border rounded-md px-2 py-1 text-sm" className="w-full border rounded-md px-2 py-1 text-sm"
> >
<option value={QualityStatusEnum.Pending}>Beklemede</option> <option value={QualityStatusEnum.Pending}>Beklemede</option>
@ -266,7 +241,7 @@ const LotForm: React.FC<LotFormProps> = ({
</form> </form>
</div> </div>
</div> </div>
); )
}; }
export default LotForm; export default LotForm

View file

@ -1,382 +1,346 @@
import React, { useState, useEffect, useCallback } from "react"; import React, { useState, useEffect, useCallback } from 'react'
import { useParams } from "react-router-dom"; import { useParams } from 'react-router-dom'
import { FaDownload, FaCalendar, FaBox } from "react-icons/fa"; import { FaDownload, FaCalendar, FaBox } from 'react-icons/fa'
import { MmStockMovement, MovementTypeEnum } from "../../../types/mm"; import { MmStockMovement, MovementTypeEnum } from '../../../types/mm'
import { FaRepeat } from "react-icons/fa6"; import { FaRepeat } from 'react-icons/fa6'
import { getMovementTypeInfo } from "../../../utils/erp"; import { getMovementTypeInfo } from '../../../utils/erp'
import { mockStockMovements } from "../../../mocks/mockStockMovements"; import { mockStockMovements } from '../../../mocks/mockStockMovements'
import Widget, { colorType } from "../../../components/common/Widget"; import Widget, { colorType } from '../../../components/common/Widget'
import { Container } from '@/components/shared'
const MaterialMovements: React.FC<{ materialId?: string }> = ({ const MaterialMovements: React.FC<{ materialId?: string }> = ({ materialId }) => {
materialId, const params = useParams<Record<string, string | undefined>>()
}) => { const routeMaterialId = materialId || params.materialId || params.id || undefined
const params = useParams<Record<string, string | undefined>>(); const [movements, setMovements] = useState<MmStockMovement[]>([])
const routeMaterialId = const [loading, setLoading] = useState(false)
materialId || params.materialId || params.id || undefined;
const [movements, setMovements] = useState<MmStockMovement[]>([]);
const [loading, setLoading] = useState(false);
const [filters, setFilters] = useState({ const [filters, setFilters] = useState({
startDate: "", startDate: '',
endDate: "", endDate: '',
movementType: "", movementType: '',
warehouseId: "", warehouseId: '',
}); })
const loadMovements = useCallback(() => { const loadMovements = useCallback(() => {
setLoading(true); setLoading(true)
// Simulate API call // Simulate API call
setTimeout(() => { setTimeout(() => {
let filteredMovements = mockStockMovements; let filteredMovements = mockStockMovements
const activeMaterialId = routeMaterialId; const activeMaterialId = routeMaterialId
if (activeMaterialId) { if (activeMaterialId) {
filteredMovements = filteredMovements.filter( filteredMovements = filteredMovements.filter((m) => m.materialId === activeMaterialId)
(m) => m.materialId === activeMaterialId
);
} }
if (filters.movementType) { if (filters.movementType) {
filteredMovements = filteredMovements.filter( filteredMovements = filteredMovements.filter(
(m) => (m) => m.movementType === (filters.movementType as unknown as MovementTypeEnum),
m.movementType === )
(filters.movementType as unknown as MovementTypeEnum)
);
} }
setMovements(filteredMovements); setMovements(filteredMovements)
setLoading(false); setLoading(false)
}, 1000); }, 1000)
}, [routeMaterialId, filters]); }, [routeMaterialId, filters])
useEffect(() => { useEffect(() => {
loadMovements(); loadMovements()
}, [loadMovements]); }, [loadMovements])
const calculateRunningBalance = () => { const calculateRunningBalance = () => {
let balance = 0; let balance = 0
return movements return movements
.sort( .sort((a, b) => new Date(a.movementDate).getTime() - new Date(b.movementDate).getTime())
(a, b) =>
new Date(a.movementDate).getTime() -
new Date(b.movementDate).getTime()
)
.map((movement) => { .map((movement) => {
balance += movement.quantity; balance += movement.quantity
return { ...movement, runningBalance: balance }; return { ...movement, runningBalance: balance }
}); })
}; }
const movementsWithBalance = calculateRunningBalance(); const movementsWithBalance = calculateRunningBalance()
return ( return (
<div className="space-y-3 py-2"> <Container>
{/* Header */} <div className="space-y-2">
<div className="flex items-center justify-between"> {/* Header */}
<div> <div className="flex items-center justify-between">
<h2 className="text-2xl font-bold text-gray-900"> <div>
{materialId ? "Stok Hareketleri" : "Tüm Stok Hareketleri"} <h2 className="text-2xl font-bold text-gray-900">
</h2> {materialId ? 'Stok Hareketleri' : 'Tüm Stok Hareketleri'}
<p className="text-gray-600"> </h2>
{materialId <p className="text-gray-600">
? `${materialId} kodlu malzeme için` {materialId ? `${materialId} kodlu malzeme için` : 'Tüm malzemeler için'} stok
: "Tüm malzemeler için"}{" "} hareketlerini görüntüleyin
stok hareketlerini görüntüleyin
</p>
</div>
<div className="flex items-center space-x-1.5">
<button
onClick={() => alert("Dışa aktarma özelliği yakında eklenecek")}
className="bg-gray-600 text-white px-2.5 py-1 rounded-md hover:bg-gray-700 flex items-center space-x-1.5 text-sm"
>
<FaDownload className="h-4 w-4" />
<span>Dışa Aktar</span>
</button>
<button
onClick={loadMovements}
className="bg-blue-600 text-white px-2.5 py-1 rounded-md hover:bg-blue-700 flex items-center space-x-1.5 text-sm"
>
<FaRepeat className="h-4 w-4" />
<span>Yenile</span>
</button>
</div>
</div>
{/* Summary Cards */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-2">
{[
{
title: "Toplam Giriş",
value: "125 KG",
color: "green",
icon: "FaArrowDown",
},
{
title: "Toplam Çıkış",
value: "50 KG",
color: "red",
icon: "FaArrowUp",
},
{
title: "Net Bakiye",
value: "75 KG",
color: "blue",
icon: "FaBalanceScale",
},
{
title: "Hareket Sayısı",
value: movements.length,
color: "gray",
icon: "FaExchangeAlt",
},
].map((stat) => (
<Widget
key={stat.title}
title={stat.title}
value={stat.value}
color={stat.color as colorType}
icon={stat.icon}
/>
))}
</div>
{/* Filters */}
<div className="bg-white p-2 rounded-lg shadow-sm">
<div className="grid grid-cols-1 md:grid-cols-4 gap-2">
<div className="flex-1">
<label className="block text-xs font-medium text-gray-700 mb-1">
Başlangıç Tarihi
</label>
<input
type="date"
value={filters.startDate}
onChange={(e) =>
setFilters({ ...filters, startDate: e.target.value })
}
className="w-full px-2 py-1 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
</div>
<div className="flex-1">
<label className="block text-xs font-medium text-gray-700 mb-1">
Bitiş Tarihi
</label>
<input
type="date"
value={filters.endDate}
onChange={(e) =>
setFilters({ ...filters, endDate: e.target.value })
}
className="w-full px-2 py-1 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
</div>
<div className="flex-1">
<label className="block text-xs font-medium text-gray-700 mb-1">
Hareket Tipi
</label>
<select
value={filters.movementType}
onChange={(e) =>
setFilters({ ...filters, movementType: e.target.value })
}
className="w-full px-2 py-1 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
>
<option value="">Tümü</option>
<option value={MovementTypeEnum.GoodsReceipt}>Mal Girişi</option>
<option value={MovementTypeEnum.GoodsIssue}>Mal Çıkışı</option>
<option value={MovementTypeEnum.Transfer}>Transfer</option>
</select>
</div>
<div className="flex-1">
<label className="block text-xs font-medium text-gray-700 mb-1">
Depo
</label>
<select
value={filters.warehouseId}
onChange={(e) =>
setFilters({ ...filters, warehouseId: e.target.value })
}
className="w-full px-2 py-1 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
>
<option value="">Tümü</option>
<option value="WH-001">Ana Depo</option>
<option value="WH-002">Üretim Deposu</option>
<option value="WH-003">Satış Deposu</option>
</select>
</div>
</div>
</div>
{/* Movements Table */}
<div className="bg-white shadow-sm rounded-lg overflow-hidden">
{loading ? (
<div className="p-8 text-center">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 mx-auto"></div>
<p className="mt-2 text-gray-500">Hareketler yükleniyor...</p>
</div>
) : (
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Tarih/Saat
</th>
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Hareket Tipi
</th>
{!materialId && (
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Malzeme
</th>
)}
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Miktar
</th>
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Bakiye
</th>
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Depo/Lokasyon
</th>
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Lot/Seri
</th>
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Belge
</th>
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
ıklama
</th>
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Oluşturan
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{movementsWithBalance.map((movement) => {
const typeInfo = getMovementTypeInfo(movement.movementType);
const Icon = typeInfo.icon;
return (
<tr key={movement.id} className="hover:bg-gray-50 text-xs">
<td className="px-2 py-1.5 whitespace-nowrap">
<div className="flex items-center space-x-2">
<FaCalendar className="h-4 w-4 text-gray-400" />
<div>
<div className="text-xs font-medium text-gray-900">
{new Date(
movement.movementDate
).toLocaleDateString("tr-TR")}
</div>
<div className="text-xs text-gray-500">
{new Date(
movement.movementDate
).toLocaleTimeString("tr-TR")}
</div>
</div>
</div>
</td>
<td className="px-2 py-1.5 whitespace-nowrap">
<div
className={`inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium ${typeInfo.color}`}
>
<Icon className="h-3 w-3 mr-1" />
{typeInfo.label}
</div>
</td>
{!materialId && (
<td className="px-2 py-1.5 whitespace-nowrap">
<div className="text-xs font-medium text-gray-900">
{movement.material.code} - {movement.material.name}
</div>
</td>
)}
<td className="px-2 py-1.5 whitespace-nowrap">
<div className="flex items-center space-x-1">
<span
className={`text-sm font-medium ${
movement.quantity > 0
? "text-green-600"
: "text-red-600"
}`}
>
{movement.quantity > 0 ? "+" : ""}
{movement.quantity}
</span>
<span className="text-xs text-gray-500">
{movement.unit?.code}
</span>
</div>
</td>
<td className="px-2 py-1.5 whitespace-nowrap">
<div className="text-xs font-medium text-gray-900">
{movement.runningBalance} {movement.unit?.code}
</div>
</td>
<td className="px-2 py-1.5 whitespace-nowrap">
<div className="text-xs text-gray-900">
{movement.toWarehouse?.code}
{movement.toZone?.zoneCode && (
<div className="text-xs text-gray-500">
{movement.toZone?.zoneCode}
</div>
)}
{movement.toLocation?.locationCode && (
<div className="text-xs text-gray-500">
{movement.toLocation?.locationCode}
</div>
)}
</div>
</td>
<td className="px-2 py-1.5 whitespace-nowrap">
<div className="text-xs text-gray-900">
{movement.lotNumber && (
<div className="text-xs bg-blue-100 text-blue-800 px-2 py-1 rounded">
{movement.lotNumber}
</div>
)}
</div>
</td>
<td className="px-2 py-1.5 whitespace-nowrap">
{movement.referenceDocument && (
<div className="text-xs text-gray-900">
<div>{movement.referenceDocument}</div>
<div className="text-xs text-gray-500">
{movement.referenceDocumentType}
</div>
</div>
)}
</td>
<td className="px-2 py-1.5">
<div className="text-xs text-gray-900">
{movement.description}
</div>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
)}
{!loading && movements.length === 0 && (
<div className="text-center py-10">
<FaBox className="mx-auto h-10 w-10 text-gray-400" />
<h3 className="mt-2 text-base font-medium text-gray-900">
Hareket bulunamadı
</h3>
<p className="mt-1 text-xs text-gray-500">
Seçilen kriterlere uygun hareket kaydı bulunmuyor.
</p> </p>
</div> </div>
)}
</div>
</div>
);
};
export default MaterialMovements; <div className="flex items-center space-x-1.5">
<button
onClick={() => alert('Dışa aktarma özelliği yakında eklenecek')}
className="bg-gray-600 text-white px-2.5 py-1 rounded-md hover:bg-gray-700 flex items-center space-x-1.5 text-sm"
>
<FaDownload className="h-4 w-4" />
<span>Dışa Aktar</span>
</button>
<button
onClick={loadMovements}
className="bg-blue-600 text-white px-2.5 py-1 rounded-md hover:bg-blue-700 flex items-center space-x-1.5 text-sm"
>
<FaRepeat className="h-4 w-4" />
<span>Yenile</span>
</button>
</div>
</div>
{/* Summary Cards */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-2">
{[
{
title: 'Toplam Giriş',
value: '125 KG',
color: 'green',
icon: 'FaArrowDown',
},
{
title: 'Toplam Çıkış',
value: '50 KG',
color: 'red',
icon: 'FaArrowUp',
},
{
title: 'Net Bakiye',
value: '75 KG',
color: 'blue',
icon: 'FaBalanceScale',
},
{
title: 'Hareket Sayısı',
value: movements.length,
color: 'gray',
icon: 'FaExchangeAlt',
},
].map((stat) => (
<Widget
key={stat.title}
title={stat.title}
value={stat.value}
color={stat.color as colorType}
icon={stat.icon}
/>
))}
</div>
{/* Filters */}
<div className="bg-white p-2 rounded-lg shadow-sm">
<div className="grid grid-cols-1 md:grid-cols-4 gap-2">
<div className="flex-1">
<label className="block text-xs font-medium text-gray-700 mb-1">
Başlangıç Tarihi
</label>
<input
type="date"
value={filters.startDate}
onChange={(e) => setFilters({ ...filters, startDate: e.target.value })}
className="w-full px-2 py-1 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
</div>
<div className="flex-1">
<label className="block text-xs font-medium text-gray-700 mb-1">Bitiş Tarihi</label>
<input
type="date"
value={filters.endDate}
onChange={(e) => setFilters({ ...filters, endDate: e.target.value })}
className="w-full px-2 py-1 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
</div>
<div className="flex-1">
<label className="block text-xs font-medium text-gray-700 mb-1">Hareket Tipi</label>
<select
value={filters.movementType}
onChange={(e) => setFilters({ ...filters, movementType: e.target.value })}
className="w-full px-2 py-1 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
>
<option value="">Tümü</option>
<option value={MovementTypeEnum.GoodsReceipt}>Mal Girişi</option>
<option value={MovementTypeEnum.GoodsIssue}>Mal Çıkışı</option>
<option value={MovementTypeEnum.Transfer}>Transfer</option>
</select>
</div>
<div className="flex-1">
<label className="block text-xs font-medium text-gray-700 mb-1">Depo</label>
<select
value={filters.warehouseId}
onChange={(e) => setFilters({ ...filters, warehouseId: e.target.value })}
className="w-full px-2 py-1 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
>
<option value="">Tümü</option>
<option value="WH-001">Ana Depo</option>
<option value="WH-002">Üretim Deposu</option>
<option value="WH-003">Satış Deposu</option>
</select>
</div>
</div>
</div>
{/* Movements Table */}
<div className="bg-white shadow-sm rounded-lg overflow-hidden">
{loading ? (
<div className="p-8 text-center">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 mx-auto"></div>
<p className="mt-2 text-gray-500">Hareketler yükleniyor...</p>
</div>
) : (
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Tarih/Saat
</th>
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Hareket Tipi
</th>
{!materialId && (
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Malzeme
</th>
)}
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Miktar
</th>
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Bakiye
</th>
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Depo/Lokasyon
</th>
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Lot/Seri
</th>
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Belge
</th>
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
ıklama
</th>
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Oluşturan
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{movementsWithBalance.map((movement) => {
const typeInfo = getMovementTypeInfo(movement.movementType)
const Icon = typeInfo.icon
return (
<tr key={movement.id} className="hover:bg-gray-50 text-xs">
<td className="px-2 py-1.5 whitespace-nowrap">
<div className="flex items-center space-x-2">
<FaCalendar className="h-4 w-4 text-gray-400" />
<div>
<div className="text-xs font-medium text-gray-900">
{new Date(movement.movementDate).toLocaleDateString('tr-TR')}
</div>
<div className="text-xs text-gray-500">
{new Date(movement.movementDate).toLocaleTimeString('tr-TR')}
</div>
</div>
</div>
</td>
<td className="px-2 py-1.5 whitespace-nowrap">
<div
className={`inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium ${typeInfo.color}`}
>
<Icon className="h-3 w-3 mr-1" />
{typeInfo.label}
</div>
</td>
{!materialId && (
<td className="px-2 py-1.5 whitespace-nowrap">
<div className="text-xs font-medium text-gray-900">
{movement.material.code} - {movement.material.name}
</div>
</td>
)}
<td className="px-2 py-1.5 whitespace-nowrap">
<div className="flex items-center space-x-1">
<span
className={`text-sm font-medium ${
movement.quantity > 0 ? 'text-green-600' : 'text-red-600'
}`}
>
{movement.quantity > 0 ? '+' : ''}
{movement.quantity}
</span>
<span className="text-xs text-gray-500">{movement.unit?.code}</span>
</div>
</td>
<td className="px-2 py-1.5 whitespace-nowrap">
<div className="text-xs font-medium text-gray-900">
{movement.runningBalance} {movement.unit?.code}
</div>
</td>
<td className="px-2 py-1.5 whitespace-nowrap">
<div className="text-xs text-gray-900">
{movement.toWarehouse?.code}
{movement.toZone?.zoneCode && (
<div className="text-xs text-gray-500">
{movement.toZone?.zoneCode}
</div>
)}
{movement.toLocation?.locationCode && (
<div className="text-xs text-gray-500">
{movement.toLocation?.locationCode}
</div>
)}
</div>
</td>
<td className="px-2 py-1.5 whitespace-nowrap">
<div className="text-xs text-gray-900">
{movement.lotNumber && (
<div className="text-xs bg-blue-100 text-blue-800 px-2 py-1 rounded">
{movement.lotNumber}
</div>
)}
</div>
</td>
<td className="px-2 py-1.5 whitespace-nowrap">
{movement.referenceDocument && (
<div className="text-xs text-gray-900">
<div>{movement.referenceDocument}</div>
<div className="text-xs text-gray-500">
{movement.referenceDocumentType}
</div>
</div>
)}
</td>
<td className="px-2 py-1.5">
<div className="text-xs text-gray-900">{movement.description}</div>
</td>
</tr>
)
})}
</tbody>
</table>
</div>
)}
{!loading && movements.length === 0 && (
<div className="text-center py-10">
<FaBox className="mx-auto h-10 w-10 text-gray-400" />
<h3 className="mt-2 text-base font-medium text-gray-900">Hareket bulunamadı</h3>
<p className="mt-1 text-xs text-gray-500">
Seçilen kriterlere uygun hareket kaydı bulunmuyor.
</p>
</div>
)}
</div>
</div>
</Container>
)
}
export default MaterialMovements

File diff suppressed because it is too large Load diff

View file

@ -1,24 +1,24 @@
import React from "react"; import React from 'react'
import { useFormik } from "formik"; import { useFormik } from 'formik'
import * as Yup from "yup"; import * as Yup from 'yup'
import { FaSave, FaTimes } from "react-icons/fa"; import { FaSave, FaTimes } from 'react-icons/fa'
import { SerialStatusEnum, MmSerialNumber, MmMaterial } from "../../../types/mm"; import { SerialStatusEnum, MmSerialNumber, MmMaterial } from '../../../types/mm'
export interface SerialFormProps { export interface SerialFormProps {
isOpen: boolean; isOpen: boolean
onClose: () => void; onClose: () => void
onSave: (serial: MmSerialNumber) => void; onSave: (serial: MmSerialNumber) => void
onUpdate?: (serial: MmSerialNumber) => void; onUpdate?: (serial: MmSerialNumber) => void
materials: MmMaterial[]; materials: MmMaterial[]
lots?: { id: string; label: string }[]; lots?: { id: string; label: string }[]
initial?: MmSerialNumber | null; initial?: MmSerialNumber | null
mode?: "create" | "edit" | "view"; mode?: 'create' | 'edit' | 'view'
} }
const validationSchema = Yup.object({ const validationSchema = Yup.object({
materialId: Yup.string().required("Malzeme seçimi zorunlu"), materialId: Yup.string().required('Malzeme seçimi zorunlu'),
serialNumber: Yup.string().required("Seri numarası zorunlu"), serialNumber: Yup.string().required('Seri numarası zorunlu'),
}); })
const SerialForm: React.FC<SerialFormProps> = ({ const SerialForm: React.FC<SerialFormProps> = ({
isOpen, isOpen,
@ -28,16 +28,16 @@ const SerialForm: React.FC<SerialFormProps> = ({
materials, materials,
lots = [], lots = [],
initial = null, initial = null,
mode = "create", mode = 'create',
}) => { }) => {
const formik = useFormik({ const formik = useFormik({
initialValues: { initialValues: {
materialId: "", materialId: '',
serialNumber: "", serialNumber: '',
lotId: lots.length ? lots[0].id : "", lotId: lots.length ? lots[0].id : '',
productionDate: "", productionDate: '',
warrantyExpiryDate: "", warrantyExpiryDate: '',
currentLocationId: "", currentLocationId: '',
status: SerialStatusEnum.Available, status: SerialStatusEnum.Available,
isActive: true, isActive: true,
}, },
@ -48,51 +48,49 @@ const SerialForm: React.FC<SerialFormProps> = ({
materialId: values.materialId, materialId: values.materialId,
serialNumber: values.serialNumber, serialNumber: values.serialNumber,
lotId: values.lotId || undefined, lotId: values.lotId || undefined,
productionDate: values.productionDate productionDate: values.productionDate ? new Date(values.productionDate) : new Date(),
? new Date(values.productionDate)
: new Date(),
warrantyExpiryDate: values.warrantyExpiryDate warrantyExpiryDate: values.warrantyExpiryDate
? new Date(values.warrantyExpiryDate) ? new Date(values.warrantyExpiryDate)
: undefined, : undefined,
currentLocationId: values.currentLocationId || undefined, currentLocationId: values.currentLocationId || undefined,
status: values.status, status: values.status,
isActive: !!values.isActive, isActive: !!values.isActive,
}; }
// simulate API // simulate API
await new Promise((r) => setTimeout(r, 300)); await new Promise((r) => setTimeout(r, 300))
if (mode === "edit" && onUpdate) { if (mode === 'edit' && onUpdate) {
onUpdate(newSerial); onUpdate(newSerial)
} else { } else {
onSave(newSerial); onSave(newSerial)
} }
onClose(); onClose()
}, },
}); })
// sync initial values when editing/viewing // sync initial values when editing/viewing
React.useEffect(() => { React.useEffect(() => {
if (initial) { if (initial) {
const src = initial; const src = initial
formik.setValues({ formik.setValues({
materialId: src.materialId || "", materialId: src.materialId || '',
serialNumber: src.serialNumber || "", serialNumber: src.serialNumber || '',
lotId: src.lotId || (lots.length ? lots[0].id : ""), lotId: src.lotId || (lots.length ? lots[0].id : ''),
productionDate: src.productionDate productionDate: src.productionDate
? new Date(src.productionDate).toISOString().slice(0, 10) ? new Date(src.productionDate).toISOString().slice(0, 10)
: "", : '',
warrantyExpiryDate: src.warrantyExpiryDate warrantyExpiryDate: src.warrantyExpiryDate
? new Date(src.warrantyExpiryDate).toISOString().slice(0, 10) ? new Date(src.warrantyExpiryDate).toISOString().slice(0, 10)
: "", : '',
currentLocationId: src.currentLocationId || "", currentLocationId: src.currentLocationId || '',
status: src.status || SerialStatusEnum.Available, status: src.status || SerialStatusEnum.Available,
isActive: !!src.isActive, isActive: !!src.isActive,
}); })
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [initial]); }, [initial])
if (!isOpen) return null; if (!isOpen) return null
return ( return (
<div className="fixed inset-0 z-50 flex items-center justify-center"> <div className="fixed inset-0 z-50 flex items-center justify-center">
@ -102,18 +100,18 @@ const SerialForm: React.FC<SerialFormProps> = ({
<div className="flex items-center justify-between p-2 border-b"> <div className="flex items-center justify-between p-2 border-b">
<div> <div>
<h2 className="text-xl font-bold text-gray-900"> <h2 className="text-xl font-bold text-gray-900">
{mode === "create" {mode === 'create'
? "Yeni Seri Kaydı" ? 'Yeni Seri Kaydı'
: mode === "edit" : mode === 'edit'
? "Seri Düzenle" ? 'Seri Düzenle'
: "Seri Detayı"} : 'Seri Detayı'}
</h2> </h2>
<p className="text-sm text-gray-600"> <p className="text-sm text-gray-600">
{mode === "create" {mode === 'create'
? "Seri numarası girin" ? 'Seri numarası girin'
: mode === "edit" : mode === 'edit'
? "Mevcut seri bilgisini güncelleyin" ? 'Mevcut seri bilgisini güncelleyin'
: "Seri bilgileri (sadece gösterim)"} : 'Seri bilgileri (sadece gösterim)'}
</p> </p>
</div> </div>
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
@ -124,7 +122,7 @@ const SerialForm: React.FC<SerialFormProps> = ({
> >
<FaTimes className="inline mr-1 h-3 w-3" /> Kapat <FaTimes className="inline mr-1 h-3 w-3" /> Kapat
</button> </button>
{mode !== "view" && ( {mode !== 'view' && (
<button <button
type="submit" type="submit"
className="px-2.5 py-1 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700 flex items-center" className="px-2.5 py-1 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700 flex items-center"
@ -137,12 +135,10 @@ const SerialForm: React.FC<SerialFormProps> = ({
<div className="grid grid-cols-1 md:grid-cols-2 p-2 gap-x-3 gap-y-2"> <div className="grid grid-cols-1 md:grid-cols-2 p-2 gap-x-3 gap-y-2">
<div> <div>
<label className="block text-sm font-medium text-gray-700"> <label className="block text-sm font-medium text-gray-700">Malzeme *</label>
Malzeme *
</label>
<select <select
{...formik.getFieldProps("materialId")} {...formik.getFieldProps('materialId')}
disabled={mode === "view"} disabled={mode === 'view'}
className="w-full border rounded-md px-2 py-1 text-sm" className="w-full border rounded-md px-2 py-1 text-sm"
> >
<option value="">Seçiniz...</option> <option value="">Seçiniz...</option>
@ -155,24 +151,20 @@ const SerialForm: React.FC<SerialFormProps> = ({
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-gray-700"> <label className="block text-sm font-medium text-gray-700">Seri Numarası *</label>
Seri Numarası *
</label>
<input <input
type="text" type="text"
{...formik.getFieldProps("serialNumber")} {...formik.getFieldProps('serialNumber')}
disabled={mode === "view"} disabled={mode === 'view'}
className="w-full border rounded-md px-2 py-1 text-sm" className="w-full border rounded-md px-2 py-1 text-sm"
/> />
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-gray-700"> <label className="block text-sm font-medium text-gray-700">Lot</label>
Lot
</label>
<select <select
{...formik.getFieldProps("lotId")} {...formik.getFieldProps('lotId')}
disabled={mode === "view"} disabled={mode === 'view'}
className="w-full border rounded-md px-2 py-1 text-sm" className="w-full border rounded-md px-2 py-1 text-sm"
> >
<option value="">Seçiniz...</option> <option value="">Seçiniz...</option>
@ -185,36 +177,30 @@ const SerialForm: React.FC<SerialFormProps> = ({
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-gray-700"> <label className="block text-sm font-medium text-gray-700">Üretim Tarihi</label>
Üretim Tarihi
</label>
<input <input
type="date" type="date"
{...formik.getFieldProps("productionDate")} {...formik.getFieldProps('productionDate')}
disabled={mode === "view"} disabled={mode === 'view'}
className="w-full border rounded-md px-2 py-1 text-sm" className="w-full border rounded-md px-2 py-1 text-sm"
/> />
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-gray-700"> <label className="block text-sm font-medium text-gray-700">Garanti Bitiş</label>
Garanti Bitiş
</label>
<input <input
type="date" type="date"
{...formik.getFieldProps("warrantyExpiryDate")} {...formik.getFieldProps('warrantyExpiryDate')}
disabled={mode === "view"} disabled={mode === 'view'}
className="w-full border rounded-md px-2 py-1 text-sm" className="w-full border rounded-md px-2 py-1 text-sm"
/> />
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-gray-700"> <label className="block text-sm font-medium text-gray-700">Durum</label>
Durum
</label>
<select <select
{...formik.getFieldProps("status")} {...formik.getFieldProps('status')}
disabled={mode === "view"} disabled={mode === 'view'}
className="w-full border rounded-md px-2 py-1 text-sm" className="w-full border rounded-md px-2 py-1 text-sm"
> >
<option value={SerialStatusEnum.Available}>Müsait</option> <option value={SerialStatusEnum.Available}>Müsait</option>
@ -227,7 +213,7 @@ const SerialForm: React.FC<SerialFormProps> = ({
</form> </form>
</div> </div>
</div> </div>
); )
}; }
export default SerialForm; export default SerialForm

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
import React, { useState } from "react"; import React, { useState } from 'react'
import { Link } from "react-router-dom"; import { Link } from 'react-router-dom'
import { useQuery } from "@tanstack/react-query"; import { useQuery } from '@tanstack/react-query'
import { import {
FaBuilding, FaBuilding,
FaPlus, FaPlus,
@ -14,45 +14,42 @@ import {
FaArrowUp, FaArrowUp,
FaExclamationTriangle, FaExclamationTriangle,
FaChartLine, FaChartLine,
} from "react-icons/fa"; } from 'react-icons/fa'
import classNames from "classnames"; import classNames from 'classnames'
import { WarehouseTypeEnum } from "../../../types/wm"; import { WarehouseTypeEnum } from '../../../types/wm'
import { mockWarehouses } from "../../../mocks/mockWarehouses"; import { mockWarehouses } from '../../../mocks/mockWarehouses'
import { import { getWarehouseTypeColor, getWarehouseTypeText } from '../../../utils/erp'
getWarehouseTypeColor, import { Container } from '@/components/shared'
getWarehouseTypeText,
} from "../../../utils/erp";
const WarehouseList: React.FC = () => { const WarehouseList: React.FC = () => {
const [searchTerm, setSearchTerm] = useState(""); const [searchTerm, setSearchTerm] = useState('')
const [filterType, setFilterType] = useState("all"); const [filterType, setFilterType] = useState('all')
const [showFilters, setShowFilters] = useState(false); const [showFilters, setShowFilters] = useState(false)
const { const {
data: warehouses, data: warehouses,
isLoading, isLoading,
error, error,
} = useQuery({ } = useQuery({
queryKey: ["warehouses", searchTerm, filterType], queryKey: ['warehouses', searchTerm, filterType],
queryFn: async () => { queryFn: async () => {
await new Promise((resolve) => setTimeout(resolve, 500)); await new Promise((resolve) => setTimeout(resolve, 500))
return mockWarehouses.filter((warehouse) => { return mockWarehouses.filter((warehouse) => {
const matchesSearch = const matchesSearch =
warehouse.code.toLowerCase().includes(searchTerm.toLowerCase()) || warehouse.code.toLowerCase().includes(searchTerm.toLowerCase()) ||
warehouse.name.toLowerCase().includes(searchTerm.toLowerCase()); warehouse.name.toLowerCase().includes(searchTerm.toLowerCase())
const matchesType = const matchesType = filterType === 'all' || warehouse.warehouseType === filterType
filterType === "all" || warehouse.warehouseType === filterType; return matchesSearch && matchesType
return matchesSearch && matchesType; })
});
}, },
}); })
const getUtilizationColor = (utilization: number) => { const getUtilizationColor = (utilization: number) => {
const percentage = utilization; const percentage = utilization
if (percentage >= 90) return "text-red-600"; if (percentage >= 90) return 'text-red-600'
if (percentage >= 75) return "text-yellow-600"; if (percentage >= 75) return 'text-yellow-600'
return "text-green-600"; return 'text-green-600'
}; }
if (isLoading) { if (isLoading) {
return ( return (
@ -60,7 +57,7 @@ const WarehouseList: React.FC = () => {
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div> <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> <span className="ml-3 text-gray-600">Depolar yükleniyor...</span>
</div> </div>
); )
} }
if (error) { if (error) {
@ -71,378 +68,345 @@ const WarehouseList: React.FC = () => {
<span className="text-red-800">Depolar yüklenirken hata oluştu.</span> <span className="text-red-800">Depolar yüklenirken hata oluştu.</span>
</div> </div>
</div> </div>
); )
} }
return ( return (
<div className="space-y-4 pt-2"> <Container>
{/* Header Actions */} <div className="space-y-2">
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3"> {/* Header Actions */}
<div className="flex items-center space-x-2"> <div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3">
<div className="relative"> <div className="flex items-center space-x-2">
<FaSearch <div className="relative">
size={16} <FaSearch
className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" size={16}
/> className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4"
<input />
type="text" <input
placeholder="Depo kodu veya adı..." type="text"
value={searchTerm} placeholder="Depo kodu veya adı..."
onChange={(e) => setSearchTerm(e.target.value)} value={searchTerm}
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" 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>
<button <div className="flex items-center space-x-2">
onClick={() => setShowFilters(!showFilters)} <button
className={classNames( onClick={() => alert('Dışa aktarma özelliği yakında eklenecek')}
"flex items-center px-3 py-1.5 text-sm border rounded-lg transition-colors", 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"
showFilters >
? "border-blue-500 bg-blue-50 text-blue-700" <FaDownload size={14} className="mr-2" />
: "border-gray-300 bg-white text-gray-700 hover:bg-gray-50" Dışa Aktar
)} </button>
>
<FaFilter size={14} className="mr-2" />
Filtreler
</button>
</div>
<div className="flex items-center space-x-2"> <Link
<button to="/admin/warehouse/new"
onClick={() => alert("Dışa aktarma özelliği yakında eklenecek")} className="flex items-center px-3 py-1.5 text-sm bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
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" >
> <FaPlus size={14} className="mr-2" />
<FaDownload size={14} className="mr-2" /> Yeni Depo
Dışa Aktar </Link>
</button>
<Link
to="/admin/warehouse/new"
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>
<option value={WarehouseTypeEnum.RawMaterials}>Hammadde</option>
<option value={WarehouseTypeEnum.FinishedGoods}>Mamul</option>
<option value={WarehouseTypeEnum.WorkInProgress}>
Yarı Mamul
</option>
<option value={WarehouseTypeEnum.SpareParts}>
Yedek Parça
</option>
<option value={WarehouseTypeEnum.Quarantine}>Karantina</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> </div>
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-3"> {/* Filters Panel */}
<div className="flex items-center justify-between"> {showFilters && (
<div> <div className="bg-white border border-gray-200 rounded-lg p-3">
<p className="text-sm font-medium text-gray-600"> <div className="grid grid-cols-1 md:grid-cols-3 gap-3">
Toplam Kapasite <div>
</p> <label className="block text-xs font-medium text-gray-700 mb-1">Depo Türü</label>
<p className="text-xl font-bold text-green-600"> <select
{warehouses value={filterType}
?.reduce((acc, w) => acc + w.capacity, 0) onChange={(e) => setFilterType(e.target.value)}
.toLocaleString() || 0} 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"
</p> >
</div> <option value="all">Tümü</option>
<FaBox className="h-6 w-6 text-green-500" /> <option value={WarehouseTypeEnum.RawMaterials}>Hammadde</option>
</div> <option value={WarehouseTypeEnum.FinishedGoods}>Mamul</option>
</div> <option value={WarehouseTypeEnum.WorkInProgress}>Yarı Mamul</option>
<option value={WarehouseTypeEnum.SpareParts}>Yedek Parça</option>
<option value={WarehouseTypeEnum.Quarantine}>Karantina</option>
</select>
</div>
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-3"> <div className="flex items-end">
<div className="flex items-center justify-between"> <button
<div> onClick={() => {
<p className="text-sm font-medium text-gray-600"> setFilterType('all')
Kullanım Oranı setSearchTerm('')
</p> }}
<p className="text-xl font-bold text-yellow-600"> 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"
{warehouses?.length >
? Math.round( Filtreleri Temizle
(warehouses.reduce( </button>
(acc, w) => acc + w.currentUtilization, </div>
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={`/admin/warehouse/warehouses/${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={`/admin/warehouse/edit/${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="/admin/warehouse/new"
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>
)} )}
</div>
</div>
);
};
export default WarehouseList; {/* 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={`/admin/warehouse/warehouses/${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={`/admin/warehouse/edit/${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="/admin/warehouse/new"
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

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff