erp-platform/ui/src/views/warehouse/components/WarehouseReceipt.tsx
2025-09-17 12:46:58 +03:00

1352 lines
63 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

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

import React, { useState } from 'react'
import { MovementStatusEnum, MovementTypeEnum } from '../../../types/mm'
import {
FaSearch,
FaPlus,
FaEdit,
FaTrash,
FaBox,
FaDownload,
FaUpload,
FaEye,
FaArrowCircleDown,
FaSave,
FaTimes,
FaInfoCircle,
FaWarehouse,
FaBarcode,
FaCalculator,
} from 'react-icons/fa'
import { mockWarehouses } from '../../../mocks/mockWarehouses'
import { mockStockMovements } from '../../../mocks/mockStockMovements'
import { mockMaterials } from '../../../mocks/mockMaterials'
import { mockEmployees } from '../../../mocks/mockEmployees'
import Widget from '../../../components/common/Widget'
import {
getMovementStatusColor,
getMovementStatusText,
getMovementStatusIcon,
} from '../../../utils/erp'
import { Container } from '@/components/shared'
const WarehouseReceipt: React.FC = () => {
const [searchTerm, setSearchTerm] = useState('')
const [statusFilter, setStatusFilter] = useState<MovementStatusEnum | ''>('')
const [selectedMovement, setSelectedMovement] = useState<string>('')
const [showReceiptForm, setShowReceiptForm] = useState(false)
const [editingMovement, setEditingMovement] = useState<string | null>(null)
// Form state - Header data for the entire movement
const [headerData, setHeaderData] = useState({
movementNumber: '',
referenceDocument: '',
referenceType: 'Purchase Order',
reason: '',
movementDate: new Date().toISOString().split('T')[0],
performedBy: '',
approvedBy: '',
description: '',
status: MovementStatusEnum.Planned,
})
// Materials list for the movement
const [materialsList, setMaterialsList] = useState([
{
id: Date.now().toString(),
materialId: '',
warehouseId: '',
zoneId: '',
locationId: '',
quantity: '',
lotNumber: '',
unitPrice: '',
},
])
const resetForm = () => {
setHeaderData({
movementNumber: '',
referenceDocument: '',
referenceType: 'Purchase Order',
reason: '',
movementDate: new Date().toISOString().split('T')[0],
performedBy: '',
approvedBy: '',
description: '',
status: MovementStatusEnum.Planned,
})
setMaterialsList([
{
id: Date.now().toString(),
materialId: '',
warehouseId: '',
zoneId: '',
locationId: '',
quantity: '',
lotNumber: '',
unitPrice: '',
},
])
setEditingMovement(null)
}
const handleHeaderInputChange = (field: string, value: string) => {
setHeaderData((prev) => ({
...prev,
[field]: value,
}))
}
const handleMaterialInputChange = (materialId: string, field: string, value: string) => {
setMaterialsList((prev) =>
prev.map((material) =>
material.id === materialId ? { ...material, [field]: value } : material,
),
)
}
const addMaterial = () => {
const newMaterial = {
id: Date.now().toString(),
materialId: '',
warehouseId: '',
zoneId: '',
locationId: '',
quantity: '',
lotNumber: '',
unitPrice: '',
}
setMaterialsList((prev) => [...prev, newMaterial])
}
const removeMaterial = (materialId: string) => {
setMaterialsList((prev) => prev.filter((material) => material.id !== materialId))
}
const handleSubmit = () => {
// In a real application, this would submit to an API
console.log('Submitting receipt form:', { headerData, materialsList })
setShowReceiptForm(false)
resetForm()
// Here you would typically update the mock data or call an API
}
const handleEdit = (movementId: string) => {
// Find all movements with the same movement number
const movement = mockStockMovements.find((m) => m.id === movementId)
if (movement) {
const movementsWithSameNumber = mockStockMovements.filter(
(m) => m.movementNumber === movement.movementNumber,
)
// Set header data from the first movement
setHeaderData({
movementNumber: movement.movementNumber,
referenceDocument: movement.referenceDocument || '',
referenceType: movement.referenceType || 'Purchase Order',
reason: movement.reason || '',
movementDate: movement.movementDate.toISOString().split('T')[0],
performedBy: movement.performedBy || '',
approvedBy: movement.approvedBy || '',
description: movement.description || '',
status: movement.status || MovementStatusEnum.Planned,
})
// Set materials list from all movements with the same number
const materialsData = movementsWithSameNumber.map((m, index) => ({
id: (Date.now() + index).toString(),
materialId: m.materialId,
warehouseId: m.toWarehouseId || '',
zoneId: m.toZoneId || '',
locationId: m.toLocationId || '',
quantity: m.quantity.toString(),
lotNumber: m.lotNumber || '',
unitPrice: (m.material?.costPrice || 0).toString(),
}))
setMaterialsList(materialsData)
setEditingMovement(movementId)
setShowReceiptForm(true)
}
}
const filteredMovements = mockStockMovements.filter((movement) => {
const material = movement.material
const matchesSearch =
movement.movementNumber.toLowerCase().includes(searchTerm.toLowerCase()) ||
material?.name?.toLowerCase().includes(searchTerm.toLowerCase()) ||
material?.code?.toLowerCase().includes(searchTerm.toLowerCase())
const matchesStatus = statusFilter === '' || movement.status === statusFilter
const matchesType = movement.movementType === MovementTypeEnum.GoodsReceipt
return matchesSearch && matchesStatus && matchesType
})
const MovementDetailModal = () => {
const movement = mockStockMovements.find((m) => m.id === selectedMovement)
if (!selectedMovement || !movement) return null
// Aynı hareket numarasına sahip tüm malzemeleri bul
const relatedMovements = mockStockMovements.filter(
(m) => m.movementNumber === movement.movementNumber,
)
const StatusIcon = getMovementStatusIcon(movement.status!)
return (
<div className="fixed inset-0 z-50 overflow-y-auto">
<div className="flex items-center justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div
className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"
onClick={() => setSelectedMovement('')}
/>
<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 w-full max-w-7xl mx-4 sm:mx-auto">
<div className="bg-white ">
{/* Modal Header */}
<div className="bg-gradient-to-r from-blue-600 to-blue-700 px-4 sm:px-6 py-3">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<div className="p-2 bg-white bg-opacity-20 rounded-lg">
<FaArrowCircleDown className="w-6 h-6 text-white" />
</div>
<div>
<h3 className="text-lg sm:text-xl font-semibold text-white">
{movement.movementNumber} - Giriş Detayları
</h3>
<p className="text-blue-100 text-sm">
{relatedMovements.length} malzeme {' '}
{movement.movementDate.toLocaleDateString('tr-TR')}
</p>
</div>
</div>
<button
onClick={() => setSelectedMovement('')}
className="text-white hover:text-blue-100 transition-colors p-2 hover:bg-white hover:bg-opacity-20 rounded-lg"
>
<FaTimes className="w-6 h-6" />
</button>
</div>
</div>
{/* Modal Content */}
<div className="px-4 sm:px-6 py-4 max-h-[70vh] overflow-y-auto">
{/* Movement Summary */}
<div className="bg-gradient-to-r from-gray-50 to-gray-100 rounded-xl p-3 sm:p-3 mb-4 border border-gray-200">
<div className="flex items-center justify-between mb-3">
<h4 className="text-base font-semibold text-gray-900 flex items-center">
<FaInfoCircle className="w-5 h-5 text-gray-600 mr-2" />
Hareket Özeti
</h4>
<div
className={`inline-flex items-center px-3 py-1 rounded-full text-sm font-medium ${getMovementStatusColor(
movement.status!,
)}`}
>
<StatusIcon className="w-4 h-4 mr-1" />
{getMovementStatusText(movement.status!)}
</div>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3 text-sm">
<div className="bg-white rounded-lg p-3 border border-gray-200">
<div className="text-gray-600 font-medium mb-1">Hareket No</div>
<div className="text-gray-900 font-semibold">{movement.movementNumber}</div>
</div>
<div className="bg-white rounded-lg p-3 border border-gray-200">
<div className="text-gray-600 font-medium mb-1">Tarih</div>
<div className="text-gray-900 font-semibold">
{movement.movementDate.toLocaleDateString('tr-TR')}
</div>
</div>
<div className="bg-white rounded-lg p-3 border border-gray-200">
<div className="text-gray-600 font-medium mb-1">Referans</div>
<div className="text-gray-900 font-semibold">
{movement.referenceDocument || '-'}
</div>
</div>
<div className="bg-white rounded-lg p-3 border border-gray-200">
<div className="text-gray-600 font-medium mb-1">İşlem Yapan</div>
<div className="text-gray-900 font-semibold">
{movement.performedBy || '-'}
</div>
</div>
</div>
{movement.description && (
<div className="mt-4 bg-white rounded-lg p-3 border border-gray-200">
<div className="text-gray-600 font-medium mb-1">ıklama</div>
<div className="text-gray-900">{movement.description}</div>
</div>
)}
</div>
{/* Materials List */}
<div className="space-y-4">
<h4 className="text-base font-semibold text-gray-900 flex items-center">
<FaBox className="w-4 h-4 text-gray-600 mr-2" />
Giriş Yapılan Malzemeler ({relatedMovements.length})
</h4>
<div className="grid gap-4">
{relatedMovements.map((mov, index) => {
const warehouse = mockWarehouses.find((w) => w.id === mov.toWarehouseId)
const zone = mockWarehouses
.flatMap((w) => w.zones || [])
.find((z) => z.id === mov.toZoneId)
const location = mockWarehouses
.flatMap((w) => w.locations || [])
.find((l) => l.id === mov.toLocationId)
return (
<div
key={mov.id}
className="bg-white border border-gray-200 rounded-xl overflow-hidden hover:shadow-lg transition-shadow"
>
{/* Material Header */}
<div className="bg-gray-50 px-4 py-2 border-b border-gray-200">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-3">
<div className="w-8 h-8 bg-blue-100 rounded-lg flex items-center justify-center">
<span className="text-blue-600 font-semibold text-sm">
{index + 1}
</span>
</div>
<div>
<h5 className="font-semibold text-gray-900">
{mov.material?.code} - {mov.material?.name}
</h5>
<p className="text-sm text-gray-600">
{mov.material?.materialType?.name}
</p>
</div>
</div>
<div className="text-right">
<div className="text-lg font-bold text-blue-600">
{mov.quantity} Adet
</div>
<div className="text-sm text-gray-600">
Birim Fiyat: {mov.material?.costPrice?.toFixed(2)}
</div>
</div>
</div>
</div>
{/* Material Details */}
<div className="p-3">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
{/* Warehouse Info */}
<div className="space-y-2">
<h6 className="font-medium text-sm text-gray-700 flex items-center">
<FaWarehouse className="w-4 h-4 mr-2 text-gray-500" />
Depo Bilgileri
</h6>
<div className="bg-gray-50 rounded-lg p-3 space-y-1 text-sm">
<div>
<span className="font-medium">Depo:</span> {warehouse?.name}
</div>
<div>
<span className="font-medium">Bölge:</span> {zone?.name || '-'}
</div>
<div>
<span className="font-medium">Lokasyon:</span>{' '}
{location?.name || '-'}
</div>
</div>
</div>
{/* Stock Info */}
<div className="space-y-2">
<h6 className="font-medium text-sm text-gray-700 flex items-center">
<FaBarcode className="w-4 h-4 mr-2 text-gray-500" />
Stok Bilgileri
</h6>
<div className="bg-gray-50 rounded-lg p-3 space-y-1 text-sm">
<div>
<span className="font-medium">Lot No:</span>{' '}
{mov.lotNumber || '-'}
</div>
<div>
<span className="font-medium">Miktar:</span> {mov.quantity} Adet
</div>
<div>
<span className="font-medium">Toplam Değer:</span>
{((mov.material?.costPrice || 0) * mov.quantity).toFixed(2)}
</div>
</div>
</div>
{/* Additional Info */}
<div className="space-y-2">
<h6 className="font-medium text-sm text-gray-700 flex items-center">
<FaInfoCircle className="w-4 h-4 mr-2 text-gray-500" />
Ek Bilgiler
</h6>
<div className="bg-gray-50 rounded-lg p-3 space-y-1 text-sm">
<div>
<span className="font-medium">Malzeme Grubu:</span>{' '}
{mov.material?.materialGroup?.name || '-'}
</div>
<div>
<span className="font-medium">Referans Tipi:</span>{' '}
{mov.referenceType || '-'}
</div>
<div>
<span className="font-medium">Sebep:</span> {mov.reason || '-'}
</div>
</div>
</div>
</div>
</div>
</div>
)
})}
</div>
</div>
</div>
{/* Modal Footer */}
<div className="bg-gray-50 px-4 sm:px-6 py-3 border-t border-gray-200">
<div className="flex flex-col sm:flex-row justify-between items-center space-y-3 sm:space-y-0">
<div className="flex items-center space-x-4 text-sm text-gray-600">
<div className="flex items-center">
<FaBox className="w-4 h-4 mr-1" />
<span>{relatedMovements.length} malzeme</span>
</div>
<div className="flex items-center">
<FaCalculator className="w-4 h-4 mr-1" />
<span>
Toplam:
{relatedMovements
.reduce(
(sum, mov) => sum + (mov.material?.costPrice || 0) * mov.quantity,
0,
)
.toFixed(2)}
</span>
</div>
</div>
<button
onClick={() => setSelectedMovement('')}
className="bg-gray-600 text-white px-4 py-1.5 text-sm rounded-lg hover:bg-gray-700 transition-colors flex items-center space-x-2"
>
<span>Kapat</span>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
)
}
return (
<Container>
<div className="space-y-2">
<div className="flex items-center justify-between">
<div>
<h2 className="text-2xl font-bold text-gray-900">Stok Giriş</h2>
<p className="text-gray-600">Depoya malzeme giriş hareketlerini yönetin</p>
</div>
<div className="flex gap-2">
<button
onClick={() => setShowReceiptForm(true)}
className="bg-blue-600 text-white px-4 py-1.5 text-sm rounded-lg hover:bg-blue-700 flex items-center gap-2"
>
<FaPlus className="w-4 h-4" />
Yeni Giriş
</button>
<button className="bg-green-600 text-white px-4 py-1.5 text-sm rounded-lg hover:bg-green-700 flex items-center gap-2">
<FaUpload className="w-4 h-4" />
İçe Aktar
</button>
<button className="bg-gray-600 text-white px-4 py-1.5 text-sm rounded-lg hover:bg-gray-700 flex items-center gap-2">
<FaDownload className="w-4 h-4" />
Dışa Aktar
</button>
</div>
</div>
{/* Stats Cards */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-3">
<Widget
title="Bugün Giriş"
value={
mockStockMovements.filter(
(m) =>
m.movementDate.toDateString() === new Date().toDateString() &&
m.status === MovementStatusEnum.Completed &&
m.movementType === MovementTypeEnum.GoodsReceipt,
).length
}
color="blue"
icon="FaArrowCircleDown"
/>
<Widget
title="Bekleyen"
value={
mockStockMovements.filter(
(m) =>
m.status === MovementStatusEnum.Planned &&
m.movementType === MovementTypeEnum.GoodsReceipt,
).length
}
color="yellow"
icon="FaClock"
/>
<Widget
title="İşlemde"
value={
mockStockMovements.filter(
(m) =>
m.status === MovementStatusEnum.InProgress &&
m.movementType === MovementTypeEnum.GoodsReceipt,
).length
}
color="blue"
icon="FaBox"
/>
<Widget
title="Tamamlanan"
value={
mockStockMovements.filter(
(m) =>
m.status === MovementStatusEnum.Completed &&
m.movementType === MovementTypeEnum.GoodsReceipt,
).length
}
color="green"
icon="FaCheckCircle"
/>
</div>
{/* Filters */}
<div className="flex flex-col sm:flex-row gap-4">
<div className="relative flex-1">
<FaSearch className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
<input
type="text"
placeholder="Giriş 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={statusFilter}
onChange={(e) => setStatusFilter(e.target.value as MovementStatusEnum | '')}
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 Durumlar</option>
{Object.values(MovementStatusEnum).map((status) => (
<option key={status} value={status}>
{getMovementStatusText(status)}
</option>
))}
</select>
</div>
{/* Movements Table */}
<div className="bg-white rounded-lg shadow-sm border border-gray-200">
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Hareket No
</th>
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Malzeme
</th>
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Depo / Bölge / Lokasyon
</th>
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Miktar / Lot No
</th>
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Referans
</th>
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Tarih
</th>
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Durum
</th>
<th className="px-3 py-2 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">
İşlemler
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{filteredMovements.map((movement) => {
const StatusIcon = getMovementStatusIcon(movement.status!)
return (
<tr key={movement.id} className="hover:bg-gray-50">
<td className="px-3 py-2 whitespace-nowrap">
<div className="flex items-center">
<div className="p-2 bg-blue-100 rounded-lg mr-3">
<FaArrowCircleDown className="w-4 h-4 text-blue-600" />
</div>
<div>
<div className="text-sm font-medium text-gray-900">
{movement.movementNumber}
</div>
<div className="text-sm text-gray-500">{movement.performedBy}</div>
</div>
</div>
</td>
<td className="px-3 py-2 whitespace-nowrap">
<div>
<div className="text-sm font-medium text-gray-900">
{movement.material?.name}
</div>
<div className="text-sm text-gray-500">{movement.material?.code}</div>
</div>
</td>
<td className="px-3 py-2 whitespace-nowrap">
<div>
<div className="text-sm font-medium text-gray-900">
{movement.toWarehouse?.name || movement.toWarehouse?.code}
</div>
<div className="text-sm text-gray-500">
{movement.toZone?.name && `${movement.toZone.name}`}
{movement.toLocation?.name && ` / ${movement.toLocation.name}`}
</div>
{movement.toLocation?.locationCode && (
<div className="text-xs text-gray-400">
Kod: {movement.toLocation.locationCode}
</div>
)}
</div>
</td>
<td className="px-3 py-2 whitespace-nowrap">
<div className="text-sm text-gray-900">
{movement.quantity} {movement.unit?.code}
</div>
{movement.lotNumber && (
<div className="text-sm text-gray-500">Lot: {movement.lotNumber}</div>
)}
</td>
<td className="px-3 py-2 whitespace-nowrap">
<div className="text-sm text-gray-900">{movement.referenceDocument}</div>
<div className="text-sm text-gray-500">{movement.referenceType}</div>
</td>
<td className="px-3 py-2 whitespace-nowrap text-sm text-gray-900">
{movement.movementDate.toLocaleDateString('tr-TR')}
</td>
<td className="px-3 py-2 whitespace-nowrap">
<span
className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${getMovementStatusColor(
movement.status!,
)}`}
>
<StatusIcon className="w-3 h-3 mr-1" />
{getMovementStatusText(movement.status!)}
</span>
</td>
<td className="px-3 py-2 whitespace-nowrap text-right text-sm font-medium">
<div className="flex items-center justify-end gap-2">
<button
onClick={() => setSelectedMovement(movement.id)}
className="text-blue-600 hover:text-blue-900"
>
<FaEye className="w-4 h-4" />
</button>
<button
onClick={() => handleEdit(movement.id)}
className="text-blue-600 hover:text-blue-900"
>
<FaEdit className="w-4 h-4" />
</button>
<button className="text-red-600 hover:text-red-900">
<FaTrash className="w-4 h-4" />
</button>
</div>
</td>
</tr>
)
})}
</tbody>
</table>
</div>
</div>
</div>
{/* Movement Detail Modal */}
<MovementDetailModal />
{/* Receipt Form Modal */}
{showReceiptForm && (
<div className="fixed inset-0 z-50 overflow-y-auto">
<div className="flex items-center justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div
className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"
onClick={() => {
setShowReceiptForm(false)
resetForm()
}}
/>
<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 w-full max-w-7xl mx-4 sm:mx-auto">
<div className="bg-white px-4 sm:px-6 pt-5 pb-4 sm:p-6">
<div className="flex items-center justify-between mb-3">
<h3 className="text-base font-medium text-gray-900">
{editingMovement ? 'Stok Giriş Düzenle' : 'Yeni Stok Giriş'}
</h3>
<button
onClick={() => {
setShowReceiptForm(false)
resetForm()
}}
className="text-gray-400 hover:text-gray-600"
>
<FaTimes className="w-6 h-6" />
</button>
</div>
{/* Header Section - Compact and Responsive */}
<div className="bg-gray-50 rounded-lg p-3 mb-3">
<h4 className="text-sm font-medium text-gray-900 mb-2">Genel Bilgiler</h4>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-6 gap-3 text-sm">
{/* Movement Number */}
<div className="sm:col-span-1">
<label className="block text-xs font-medium text-gray-700 mb-1">
Hareket No
</label>
<input
type="text"
value={headerData.movementNumber}
onChange={(e) => handleHeaderInputChange('movementNumber', e.target.value)}
className="w-full px-2 py-1.5 text-sm border border-gray-300 rounded focus:ring-1 focus:ring-blue-500 focus:border-transparent"
placeholder="Otomatik"
disabled={!!editingMovement}
/>
</div>
{/* Movement Date */}
<div className="sm:col-span-1">
<label className="block text-xs font-medium text-gray-700 mb-1">
Tarih *
</label>
<input
type="date"
value={headerData.movementDate}
onChange={(e) => handleHeaderInputChange('movementDate', e.target.value)}
className="w-full px-2 py-1.5 text-sm border border-gray-300 rounded focus:ring-1 focus:ring-blue-500 focus:border-transparent"
required
/>
</div>
{/* Reference Document */}
<div className="sm:col-span-1">
<label className="block text-xs font-medium text-gray-700 mb-1">
Referans Belge
</label>
<input
type="text"
value={headerData.referenceDocument}
onChange={(e) =>
handleHeaderInputChange('referenceDocument', e.target.value)
}
className="w-full px-2 py-1.5 text-sm border border-gray-300 rounded focus:ring-1 focus:ring-blue-500 focus:border-transparent"
placeholder="Belge no"
/>
</div>
{/* Reference Type */}
<div className="sm:col-span-1">
<label className="block text-xs font-medium text-gray-700 mb-1">
Referans Tipi
</label>
<select
value={headerData.referenceType}
onChange={(e) => handleHeaderInputChange('referenceType', e.target.value)}
className="w-full px-2 py-1.5 text-sm border border-gray-300 rounded focus:ring-1 focus:ring-blue-500 focus:border-transparent"
>
<option value="Purchase Order">Satın Alma Siparişi</option>
<option value="Production Order">Üretim Emri</option>
<option value="Transfer Order">Transfer Emri</option>
<option value="Return">İade</option>
<option value="Adjustment">Düzeltme</option>
<option value="Other">Diğer</option>
</select>
</div>
{/* Performed By */}
<div className="sm:col-span-1">
<label className="block text-xs font-medium text-gray-700 mb-1">
İşlem Yapan *
</label>
<select
value={headerData.performedBy}
onChange={(e) => handleHeaderInputChange('performedBy', e.target.value)}
className="w-full px-2 py-1.5 text-sm border border-gray-300 rounded focus:ring-1 focus:ring-blue-500 focus:border-transparent"
required
>
<option value="">Çalışan Seçiniz</option>
{mockEmployees.map((employee) => (
<option key={employee.id} value={employee.fullName}>
{employee.fullName} - {employee.code}
</option>
))}
</select>
</div>
{/* Status */}
<div className="sm:col-span-1">
<label className="block text-xs font-medium text-gray-700 mb-1">Durum</label>
<select
value={headerData.status}
onChange={(e) =>
handleHeaderInputChange('status', e.target.value as MovementStatusEnum)
}
className="w-full px-2 py-1.5 text-sm border border-gray-300 rounded focus:ring-1 focus:ring-blue-500 focus:border-transparent"
>
{Object.values(MovementStatusEnum).map((status) => (
<option key={status} value={status}>
{getMovementStatusText(status)}
</option>
))}
</select>
</div>
{/* Description - Full width */}
<div className="col-span-1 sm:col-span-2 lg:col-span-3 xl:col-span-6">
<label className="block text-xs font-medium text-gray-700 mb-1">
ıklama
</label>
<textarea
value={headerData.description}
onChange={(e) => handleHeaderInputChange('description', e.target.value)}
className="w-full px-2 py-1.5 text-sm border border-gray-300 rounded focus:ring-1 focus:ring-blue-500 focus:border-transparent"
placeholder="Hareket açıklaması"
rows={2}
/>
</div>
</div>
</div>
{/* Materials Section - Responsive */}
<div className="mb-2">
<div className="flex items-center justify-between mb-2">
<h4 className="text-sm font-medium text-gray-900">
Giriş Malzemeleri
{materialsList.length > 0 && (
<span className="ml-2 text-xs text-blue-600">
({materialsList.length} malzeme)
</span>
)}
</h4>
</div>
{/* Desktop Table View */}
<div className="hidden lg:block border border-gray-200 rounded-lg overflow-hidden">
<div className="bg-gray-50 px-2 py-2 border-b border-gray-200">
<div className="grid grid-cols-12 gap-2 text-xs font-medium text-gray-500 uppercase tracking-wider">
<div className="col-span-2">Malzeme</div>
<div className="col-span-2">Depo</div>
<div className="col-span-2">Zone</div>
<div className="col-span-2">Lokasyon</div>
<div className="col-span-1">Miktar</div>
<div className="col-span-1">B.Fiyat</div>
<div className="col-span-1">Lot No</div>
<div className="col-span-1">İşlem</div>
</div>
</div>
<div className="max-h-64 overflow-y-auto">
{materialsList.map((material) => {
const selectedMaterial = mockMaterials.find(
(m) => m.id === material.materialId,
)
const selectedWarehouse = mockWarehouses.find(
(w) => w.id === material.warehouseId,
)
const availableZones = selectedWarehouse?.zones || []
const availableLocations =
selectedWarehouse?.locations?.filter(
(location) => !material.zoneId || location.zoneId === material.zoneId,
) || []
return (
<div
key={material.id}
className="px-2 py-2 border-b border-gray-100 last:border-b-0"
>
<div className="grid grid-cols-12 gap-2 items-center">
{/* Material Selection */}
<div className="col-span-2">
<select
value={material.materialId}
onChange={(e) =>
handleMaterialInputChange(
material.id,
'materialId',
e.target.value,
)
}
className="w-full px-2 py-1.5 text-xs border border-gray-300 rounded focus:ring-1 focus:ring-blue-500 focus:border-transparent"
required
>
<option value="">Malzeme Seçiniz</option>
{mockMaterials.map((mat) => (
<option key={mat.id} value={mat.id}>
{mat.code} - {mat.name}
</option>
))}
</select>
</div>
{/* Warehouse Selection */}
<div className="col-span-2">
<select
value={material.warehouseId}
onChange={(e) => {
handleMaterialInputChange(
material.id,
'warehouseId',
e.target.value,
)
handleMaterialInputChange(material.id, 'zoneId', '')
handleMaterialInputChange(material.id, 'locationId', '')
}}
className="w-full px-2 py-1.5 text-xs border border-gray-300 rounded focus:ring-1 focus:ring-blue-500 focus:border-transparent"
required
>
<option value="">Depo Seçiniz</option>
{mockWarehouses.map((warehouse) => (
<option key={warehouse.id} value={warehouse.id}>
{warehouse.code}
</option>
))}
</select>
</div>
{/* Zone Selection */}
<div className="col-span-2">
<select
value={material.zoneId}
onChange={(e) => {
handleMaterialInputChange(material.id, 'zoneId', e.target.value)
handleMaterialInputChange(material.id, 'locationId', '')
}}
className="w-full px-2 py-1.5 text-xs border border-gray-300 rounded focus:ring-1 focus:ring-blue-500 focus:border-transparent"
disabled={!material.warehouseId}
>
<option value="">Zone Seç</option>
{availableZones.map((zone) => (
<option key={zone.id} value={zone.id}>
{zone.zoneCode} - {zone.name}
</option>
))}
</select>
</div>
{/* Location Selection */}
<div className="col-span-2">
<select
value={material.locationId}
onChange={(e) =>
handleMaterialInputChange(
material.id,
'locationId',
e.target.value,
)
}
className="w-full px-2 py-1.5 text-xs border border-gray-300 rounded focus:ring-1 focus:ring-blue-500 focus:border-transparent"
required
disabled={!material.warehouseId}
>
<option value="">Lokasyon Seç</option>
{availableLocations.map((location) => (
<option key={location.id} value={location.id}>
{location.locationCode} - {location.name}
</option>
))}
</select>
</div>
{/* Quantity */}
<div className="col-span-1">
<input
type="number"
value={material.quantity}
onChange={(e) =>
handleMaterialInputChange(
material.id,
'quantity',
e.target.value,
)
}
className="w-full px-2 py-1.5 text-xs border border-gray-300 rounded focus:ring-1 focus:ring-blue-500 focus:border-transparent"
placeholder="Miktar"
min="0"
step="0.01"
required
/>
</div>
{/* Unit Price */}
<div className="col-span-1">
<input
type="number"
value={material.unitPrice}
onChange={(e) =>
handleMaterialInputChange(
material.id,
'unitPrice',
e.target.value,
)
}
className="w-full px-2 py-1.5 text-xs border border-gray-300 rounded focus:ring-1 focus:ring-blue-500 focus:border-transparent"
placeholder="Fiyat"
min="0"
step="0.01"
/>
</div>
{/* Lot Number */}
<div className="col-span-1">
<input
type="text"
value={material.lotNumber}
onChange={(e) =>
handleMaterialInputChange(
material.id,
'lotNumber',
e.target.value,
)
}
className="w-full px-2 py-1.5 text-xs border border-gray-300 rounded focus:ring-1 focus:ring-blue-500 focus:border-transparent"
placeholder="Lot"
/>
</div>
{/* Actions */}
<div className="col-span-1 flex justify-center">
<button
onClick={() => removeMaterial(material.id)}
className="text-red-600 hover:text-red-800 p-1"
title="Malzemeyi Sil"
>
<FaTrash className="w-3 h-3" />
</button>
</div>
</div>
{/* Additional info row for desktop */}
{selectedMaterial && (
<div className="mt-1 text-xs text-gray-500 grid grid-cols-12 gap-2">
<div className="col-span-2">
Birim: {selectedMaterial.baseUnit?.code} | Stok:{' '}
{selectedMaterial.totalStock}
</div>
<div className="col-span-2">{selectedWarehouse?.name}</div>
<div className="col-span-2">
{availableZones.find((z) => z.id === material.zoneId)?.name}
</div>
<div className="col-span-2">
{
availableLocations.find((l) => l.id === material.locationId)
?.name
}
</div>
<div className="col-span-1">{selectedMaterial.baseUnit?.code}</div>
<div className="col-span-1">{selectedMaterial.costPrice}</div>
<div className="col-span-2">
{material.quantity &&
material.unitPrice &&
`Toplam: ₺${(
parseFloat(material.quantity) * parseFloat(material.unitPrice)
).toFixed(2)}`}
</div>
</div>
)}
</div>
)
})}
</div>
</div>
{/* Mobile/Tablet Card View */}
<div className="lg:hidden space-y-3 max-h-80 overflow-y-auto">
{materialsList.map((material) => {
const selectedMaterial = mockMaterials.find(
(m) => m.id === material.materialId,
)
const selectedWarehouse = mockWarehouses.find(
(w) => w.id === material.warehouseId,
)
const availableZones = selectedWarehouse?.zones || []
const availableLocations =
selectedWarehouse?.locations?.filter(
(location) => !material.zoneId || location.zoneId === material.zoneId,
) || []
return (
<div
key={material.id}
className="border border-gray-200 rounded-lg p-3 bg-white"
>
<div className="flex items-center justify-between mb-3">
<span className="text-sm font-medium text-gray-900">
Malzeme #{materialsList.indexOf(material) + 1}
</span>
<button
onClick={() => removeMaterial(material.id)}
className="text-red-600 hover:text-red-800 p-1"
title="Malzemeyi Sil"
>
<FaTrash className="w-4 h-4" />
</button>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
{/* Material Selection */}
<div className="sm:col-span-2">
<label className="block text-xs font-medium text-gray-700 mb-1">
Malzeme *
</label>
<select
value={material.materialId}
onChange={(e) =>
handleMaterialInputChange(
material.id,
'materialId',
e.target.value,
)
}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded focus:ring-1 focus:ring-blue-500 focus:border-transparent"
required
>
<option value="">Malzeme Seçiniz</option>
{mockMaterials.map((mat) => (
<option key={mat.id} value={mat.id}>
{mat.code} - {mat.name}
</option>
))}
</select>
{selectedMaterial && (
<div className="mt-1 text-xs text-gray-500">
Birim: {selectedMaterial.baseUnit?.code} | Stok:{' '}
{selectedMaterial.totalStock}
</div>
)}
</div>
{/* Warehouse Selection */}
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">
Depo *
</label>
<select
value={material.warehouseId}
onChange={(e) => {
handleMaterialInputChange(
material.id,
'warehouseId',
e.target.value,
)
handleMaterialInputChange(material.id, 'zoneId', '')
handleMaterialInputChange(material.id, 'locationId', '')
}}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded focus:ring-1 focus:ring-blue-500 focus:border-transparent"
required
>
<option value="">Depo Seçiniz</option>
{mockWarehouses.map((warehouse) => (
<option key={warehouse.id} value={warehouse.id}>
{warehouse.code} - {warehouse.name}
</option>
))}
</select>
</div>
{/* Zone Selection */}
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">
Zone
</label>
<select
value={material.zoneId}
onChange={(e) => {
handleMaterialInputChange(material.id, 'zoneId', e.target.value)
handleMaterialInputChange(material.id, 'locationId', '')
}}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded focus:ring-1 focus:ring-blue-500 focus:border-transparent"
disabled={!material.warehouseId}
>
<option value="">Zone Seç</option>
{availableZones.map((zone) => (
<option key={zone.id} value={zone.id}>
{zone.zoneCode} - {zone.name}
</option>
))}
</select>
</div>
{/* Location Selection */}
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">
Lokasyon *
</label>
<select
value={material.locationId}
onChange={(e) =>
handleMaterialInputChange(
material.id,
'locationId',
e.target.value,
)
}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded focus:ring-1 focus:ring-blue-500 focus:border-transparent"
required
disabled={!material.warehouseId}
>
<option value="">Lokasyon Seç</option>
{availableLocations.map((location) => (
<option key={location.id} value={location.id}>
{location.locationCode} - {location.name}
</option>
))}
</select>
</div>
{/* Quantity */}
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">
Miktar *
</label>
<input
type="number"
value={material.quantity}
onChange={(e) =>
handleMaterialInputChange(material.id, 'quantity', e.target.value)
}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded focus:ring-1 focus:ring-blue-500 focus:border-transparent"
placeholder="Miktar"
min="0"
step="0.01"
required
/>
{selectedMaterial && (
<div className="mt-1 text-xs text-gray-500">
Birim: {selectedMaterial.baseUnit?.code}
</div>
)}
</div>
{/* Unit Price */}
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">
Birim Fiyat
</label>
<input
type="number"
value={material.unitPrice}
onChange={(e) =>
handleMaterialInputChange(
material.id,
'unitPrice',
e.target.value,
)
}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded focus:ring-1 focus:ring-blue-500 focus:border-transparent"
placeholder={`${selectedMaterial?.costPrice || 0}`}
min="0"
step="0.01"
/>
</div>
{/* Lot Number */}
<div className="sm:col-span-2">
<label className="block text-xs font-medium text-gray-700 mb-1">
Lot Numarası
</label>
<input
type="text"
value={material.lotNumber}
onChange={(e) =>
handleMaterialInputChange(
material.id,
'lotNumber',
e.target.value,
)
}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded focus:ring-1 focus:ring-blue-500 focus:border-transparent"
placeholder="Lot numarası"
/>
</div>
</div>
{/* Total calculation */}
{selectedMaterial && material.quantity && (
<div className="mt-3 pt-3 border-t border-gray-200">
<div className="text-sm font-medium text-gray-900">
Toplam Tutar:
{(
parseFloat(material.quantity || '0') *
parseFloat(
material.unitPrice ||
selectedMaterial.costPrice?.toString() ||
'0',
)
).toFixed(2)}
</div>
</div>
)}
</div>
)
})}
</div>
</div>
{/* Form Actions */}
<div className="flex justify-between items-center gap-2 pt-4 border-t border-gray-200">
<button
onClick={addMaterial}
className="bg-blue-600 text-white px-3 py-1.5 rounded-lg text-sm hover:bg-blue-700 flex items-center gap-1"
>
<FaPlus className="w-3 h-3" />
Malzeme Ekle
</button>
<div className="flex gap-2">
<button
onClick={() => {
setShowReceiptForm(false)
resetForm()
}}
className="px-4 py-1.5 text-sm text-gray-700 bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors"
>
İptal
</button>
<button
onClick={handleSubmit}
className="px-4 py-1.5 text-sm bg-blue-600 text-white hover:bg-blue-700 rounded-lg transition-colors flex items-center gap-2"
>
<FaSave className="w-4 h-4" />
{editingMovement ? 'Güncelle' : 'Kaydet'}
</button>
</div>
</div>
</div>
</div>
</div>
</div>
)}
</Container>
)
}
export default WarehouseReceipt