2025-09-15 21:11:40 +00:00
|
|
|
|
import React, { useState } from 'react'
|
|
|
|
|
|
import { MovementStatusEnum, MovementTypeEnum } from '../../../types/mm'
|
2025-09-15 09:31:47 +00:00
|
|
|
|
import {
|
|
|
|
|
|
FaSearch,
|
|
|
|
|
|
FaPlus,
|
|
|
|
|
|
FaEdit,
|
|
|
|
|
|
FaTrash,
|
|
|
|
|
|
FaBox,
|
|
|
|
|
|
FaDownload,
|
|
|
|
|
|
FaUpload,
|
|
|
|
|
|
FaEye,
|
|
|
|
|
|
FaArrowCircleDown,
|
|
|
|
|
|
FaSave,
|
|
|
|
|
|
FaTimes,
|
|
|
|
|
|
FaInfoCircle,
|
|
|
|
|
|
FaWarehouse,
|
|
|
|
|
|
FaBarcode,
|
|
|
|
|
|
FaCalculator,
|
2025-09-15 21:11:40 +00:00
|
|
|
|
} 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'
|
2025-09-15 09:31:47 +00:00
|
|
|
|
import {
|
|
|
|
|
|
getMovementStatusColor,
|
|
|
|
|
|
getMovementStatusText,
|
|
|
|
|
|
getMovementStatusIcon,
|
2025-09-15 21:11:40 +00:00
|
|
|
|
} from '../../../utils/erp'
|
|
|
|
|
|
import { Container } from '@/components/shared'
|
2025-09-15 09:31:47 +00:00
|
|
|
|
|
|
|
|
|
|
const WarehouseReceipt: React.FC = () => {
|
2025-09-15 21:11:40 +00:00
|
|
|
|
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)
|
2025-09-15 09:31:47 +00:00
|
|
|
|
|
|
|
|
|
|
// Form state - Header data for the entire movement
|
|
|
|
|
|
const [headerData, setHeaderData] = useState({
|
2025-09-15 21:11:40 +00:00
|
|
|
|
movementNumber: '',
|
|
|
|
|
|
referenceDocument: '',
|
|
|
|
|
|
referenceType: 'Purchase Order',
|
|
|
|
|
|
reason: '',
|
|
|
|
|
|
movementDate: new Date().toISOString().split('T')[0],
|
|
|
|
|
|
performedBy: '',
|
|
|
|
|
|
approvedBy: '',
|
|
|
|
|
|
description: '',
|
2025-09-15 09:31:47 +00:00
|
|
|
|
status: MovementStatusEnum.Planned,
|
2025-09-15 21:11:40 +00:00
|
|
|
|
})
|
2025-09-15 09:31:47 +00:00
|
|
|
|
|
|
|
|
|
|
// Materials list for the movement
|
|
|
|
|
|
const [materialsList, setMaterialsList] = useState([
|
|
|
|
|
|
{
|
|
|
|
|
|
id: Date.now().toString(),
|
2025-09-15 21:11:40 +00:00
|
|
|
|
materialId: '',
|
|
|
|
|
|
warehouseId: '',
|
|
|
|
|
|
zoneId: '',
|
|
|
|
|
|
locationId: '',
|
|
|
|
|
|
quantity: '',
|
|
|
|
|
|
lotNumber: '',
|
|
|
|
|
|
unitPrice: '',
|
2025-09-15 09:31:47 +00:00
|
|
|
|
},
|
2025-09-15 21:11:40 +00:00
|
|
|
|
])
|
2025-09-15 09:31:47 +00:00
|
|
|
|
|
|
|
|
|
|
const resetForm = () => {
|
|
|
|
|
|
setHeaderData({
|
2025-09-15 21:11:40 +00:00
|
|
|
|
movementNumber: '',
|
|
|
|
|
|
referenceDocument: '',
|
|
|
|
|
|
referenceType: 'Purchase Order',
|
|
|
|
|
|
reason: '',
|
|
|
|
|
|
movementDate: new Date().toISOString().split('T')[0],
|
|
|
|
|
|
performedBy: '',
|
|
|
|
|
|
approvedBy: '',
|
|
|
|
|
|
description: '',
|
2025-09-15 09:31:47 +00:00
|
|
|
|
status: MovementStatusEnum.Planned,
|
2025-09-15 21:11:40 +00:00
|
|
|
|
})
|
2025-09-15 09:31:47 +00:00
|
|
|
|
setMaterialsList([
|
|
|
|
|
|
{
|
|
|
|
|
|
id: Date.now().toString(),
|
2025-09-15 21:11:40 +00:00
|
|
|
|
materialId: '',
|
|
|
|
|
|
warehouseId: '',
|
|
|
|
|
|
zoneId: '',
|
|
|
|
|
|
locationId: '',
|
|
|
|
|
|
quantity: '',
|
|
|
|
|
|
lotNumber: '',
|
|
|
|
|
|
unitPrice: '',
|
2025-09-15 09:31:47 +00:00
|
|
|
|
},
|
2025-09-15 21:11:40 +00:00
|
|
|
|
])
|
|
|
|
|
|
setEditingMovement(null)
|
|
|
|
|
|
}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
|
|
|
|
|
|
const handleHeaderInputChange = (field: string, value: string) => {
|
|
|
|
|
|
setHeaderData((prev) => ({
|
|
|
|
|
|
...prev,
|
|
|
|
|
|
[field]: value,
|
2025-09-15 21:11:40 +00:00
|
|
|
|
}))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const handleMaterialInputChange = (materialId: string, field: string, value: string) => {
|
2025-09-15 09:31:47 +00:00
|
|
|
|
setMaterialsList((prev) =>
|
|
|
|
|
|
prev.map((material) =>
|
2025-09-15 21:11:40 +00:00
|
|
|
|
material.id === materialId ? { ...material, [field]: value } : material,
|
|
|
|
|
|
),
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
|
|
|
|
|
|
const addMaterial = () => {
|
|
|
|
|
|
const newMaterial = {
|
|
|
|
|
|
id: Date.now().toString(),
|
2025-09-15 21:11:40 +00:00
|
|
|
|
materialId: '',
|
|
|
|
|
|
warehouseId: '',
|
|
|
|
|
|
zoneId: '',
|
|
|
|
|
|
locationId: '',
|
|
|
|
|
|
quantity: '',
|
|
|
|
|
|
lotNumber: '',
|
|
|
|
|
|
unitPrice: '',
|
|
|
|
|
|
}
|
|
|
|
|
|
setMaterialsList((prev) => [...prev, newMaterial])
|
|
|
|
|
|
}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
|
|
|
|
|
|
const removeMaterial = (materialId: string) => {
|
2025-09-15 21:11:40 +00:00
|
|
|
|
setMaterialsList((prev) => prev.filter((material) => material.id !== materialId))
|
|
|
|
|
|
}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
|
|
|
|
|
|
const handleSubmit = () => {
|
|
|
|
|
|
// In a real application, this would submit to an API
|
2025-09-15 21:11:40 +00:00
|
|
|
|
console.log('Submitting receipt form:', { headerData, materialsList })
|
|
|
|
|
|
setShowReceiptForm(false)
|
|
|
|
|
|
resetForm()
|
2025-09-15 09:31:47 +00:00
|
|
|
|
// Here you would typically update the mock data or call an API
|
2025-09-15 21:11:40 +00:00
|
|
|
|
}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
|
|
|
|
|
|
const handleEdit = (movementId: string) => {
|
|
|
|
|
|
// Find all movements with the same movement number
|
2025-09-15 21:11:40 +00:00
|
|
|
|
const movement = mockStockMovements.find((m) => m.id === movementId)
|
2025-09-15 09:31:47 +00:00
|
|
|
|
if (movement) {
|
|
|
|
|
|
const movementsWithSameNumber = mockStockMovements.filter(
|
2025-09-15 21:11:40 +00:00
|
|
|
|
(m) => m.movementNumber === movement.movementNumber,
|
|
|
|
|
|
)
|
2025-09-15 09:31:47 +00:00
|
|
|
|
|
|
|
|
|
|
// Set header data from the first movement
|
|
|
|
|
|
setHeaderData({
|
|
|
|
|
|
movementNumber: movement.movementNumber,
|
2025-09-15 21:11:40 +00:00
|
|
|
|
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 || '',
|
2025-09-15 09:31:47 +00:00
|
|
|
|
status: movement.status || MovementStatusEnum.Planned,
|
2025-09-15 21:11:40 +00:00
|
|
|
|
})
|
2025-09-15 09:31:47 +00:00
|
|
|
|
|
|
|
|
|
|
// Set materials list from all movements with the same number
|
|
|
|
|
|
const materialsData = movementsWithSameNumber.map((m, index) => ({
|
|
|
|
|
|
id: (Date.now() + index).toString(),
|
|
|
|
|
|
materialId: m.materialId,
|
2025-09-15 21:11:40 +00:00
|
|
|
|
warehouseId: m.toWarehouseId || '',
|
|
|
|
|
|
zoneId: m.toZoneId || '',
|
|
|
|
|
|
locationId: m.toLocationId || '',
|
2025-09-15 09:31:47 +00:00
|
|
|
|
quantity: m.quantity.toString(),
|
2025-09-15 21:11:40 +00:00
|
|
|
|
lotNumber: m.lotNumber || '',
|
2025-09-15 09:31:47 +00:00
|
|
|
|
unitPrice: (m.material?.costPrice || 0).toString(),
|
2025-09-15 21:11:40 +00:00
|
|
|
|
}))
|
2025-09-15 09:31:47 +00:00
|
|
|
|
|
2025-09-15 21:11:40 +00:00
|
|
|
|
setMaterialsList(materialsData)
|
|
|
|
|
|
setEditingMovement(movementId)
|
|
|
|
|
|
setShowReceiptForm(true)
|
2025-09-15 09:31:47 +00:00
|
|
|
|
}
|
2025-09-15 21:11:40 +00:00
|
|
|
|
}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
|
|
|
|
|
|
const filteredMovements = mockStockMovements.filter((movement) => {
|
2025-09-15 21:11:40 +00:00
|
|
|
|
const material = movement.material
|
2025-09-15 09:31:47 +00:00
|
|
|
|
|
|
|
|
|
|
const matchesSearch =
|
2025-09-15 21:11:40 +00:00
|
|
|
|
movement.movementNumber.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
2025-09-15 09:31:47 +00:00
|
|
|
|
material?.name?.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
2025-09-15 21:11:40 +00:00
|
|
|
|
material?.code?.toLowerCase().includes(searchTerm.toLowerCase())
|
2025-09-15 09:31:47 +00:00
|
|
|
|
|
2025-09-15 21:11:40 +00:00
|
|
|
|
const matchesStatus = statusFilter === '' || movement.status === statusFilter
|
2025-09-15 09:31:47 +00:00
|
|
|
|
|
2025-09-15 21:11:40 +00:00
|
|
|
|
const matchesType = movement.movementType === MovementTypeEnum.GoodsReceipt
|
2025-09-15 09:31:47 +00:00
|
|
|
|
|
2025-09-15 21:11:40 +00:00
|
|
|
|
return matchesSearch && matchesStatus && matchesType
|
|
|
|
|
|
})
|
2025-09-15 09:31:47 +00:00
|
|
|
|
|
|
|
|
|
|
const MovementDetailModal = () => {
|
2025-09-15 21:11:40 +00:00
|
|
|
|
const movement = mockStockMovements.find((m) => m.id === selectedMovement)
|
2025-09-15 09:31:47 +00:00
|
|
|
|
|
2025-09-15 21:11:40 +00:00
|
|
|
|
if (!selectedMovement || !movement) return null
|
2025-09-15 09:31:47 +00:00
|
|
|
|
|
|
|
|
|
|
// Aynı hareket numarasına sahip tüm malzemeleri bul
|
|
|
|
|
|
const relatedMovements = mockStockMovements.filter(
|
2025-09-15 21:11:40 +00:00
|
|
|
|
(m) => m.movementNumber === movement.movementNumber,
|
|
|
|
|
|
)
|
2025-09-15 09:31:47 +00:00
|
|
|
|
|
2025-09-15 21:11:40 +00:00
|
|
|
|
const StatusIcon = getMovementStatusIcon(movement.status!)
|
2025-09-15 09:31:47 +00:00
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="fixed inset-0 z-50 overflow-y-auto">
|
|
|
|
|
|
<div className="flex items-center justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
|
|
|
|
|
|
<div
|
|
|
|
|
|
className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"
|
2025-09-15 21:11:40 +00:00
|
|
|
|
onClick={() => setSelectedMovement('')}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle 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">
|
2025-09-15 21:11:40 +00:00
|
|
|
|
{relatedMovements.length} malzeme •{' '}
|
|
|
|
|
|
{movement.movementDate.toLocaleDateString('tr-TR')}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<button
|
2025-09-15 21:11:40 +00:00
|
|
|
|
onClick={() => setSelectedMovement('')}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
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(
|
2025-09-15 21:11:40 +00:00
|
|
|
|
movement.status!,
|
2025-09-15 09:31:47 +00:00
|
|
|
|
)}`}
|
|
|
|
|
|
>
|
|
|
|
|
|
<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">
|
2025-09-15 21:11:40 +00:00
|
|
|
|
<div className="text-gray-600 font-medium mb-1">Hareket No</div>
|
|
|
|
|
|
<div className="text-gray-900 font-semibold">{movement.movementNumber}</div>
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
<div className="bg-white rounded-lg p-3 border border-gray-200">
|
2025-09-15 21:11:40 +00:00
|
|
|
|
<div className="text-gray-600 font-medium mb-1">Tarih</div>
|
2025-09-15 09:31:47 +00:00
|
|
|
|
<div className="text-gray-900 font-semibold">
|
2025-09-15 21:11:40 +00:00
|
|
|
|
{movement.movementDate.toLocaleDateString('tr-TR')}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="bg-white rounded-lg p-3 border border-gray-200">
|
2025-09-15 21:11:40 +00:00
|
|
|
|
<div className="text-gray-600 font-medium mb-1">Referans</div>
|
2025-09-15 09:31:47 +00:00
|
|
|
|
<div className="text-gray-900 font-semibold">
|
2025-09-15 21:11:40 +00:00
|
|
|
|
{movement.referenceDocument || '-'}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="bg-white rounded-lg p-3 border border-gray-200">
|
2025-09-15 21:11:40 +00:00
|
|
|
|
<div className="text-gray-600 font-medium mb-1">İşlem Yapan</div>
|
2025-09-15 09:31:47 +00:00
|
|
|
|
<div className="text-gray-900 font-semibold">
|
2025-09-15 21:11:40 +00:00
|
|
|
|
{movement.performedBy || '-'}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{movement.description && (
|
|
|
|
|
|
<div className="mt-4 bg-white rounded-lg p-3 border border-gray-200">
|
2025-09-15 21:11:40 +00:00
|
|
|
|
<div className="text-gray-600 font-medium mb-1">Açıklama</div>
|
|
|
|
|
|
<div className="text-gray-900">{movement.description}</div>
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</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) => {
|
2025-09-15 21:11:40 +00:00
|
|
|
|
const warehouse = mockWarehouses.find((w) => w.id === mov.toWarehouseId)
|
2025-09-15 09:31:47 +00:00
|
|
|
|
const zone = mockWarehouses
|
|
|
|
|
|
.flatMap((w) => w.zones || [])
|
2025-09-15 21:11:40 +00:00
|
|
|
|
.find((z) => z.id === mov.toZoneId)
|
2025-09-15 09:31:47 +00:00
|
|
|
|
const location = mockWarehouses
|
|
|
|
|
|
.flatMap((w) => w.locations || [])
|
2025-09-15 21:11:40 +00:00
|
|
|
|
.find((l) => l.id === mov.toLocationId)
|
2025-09-15 09:31:47 +00:00
|
|
|
|
|
|
|
|
|
|
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">
|
2025-09-15 21:11:40 +00:00
|
|
|
|
Birim Fiyat: ₺{mov.material?.costPrice?.toFixed(2)}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</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>
|
2025-09-15 21:11:40 +00:00
|
|
|
|
<span className="font-medium">Depo:</span> {warehouse?.name}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
2025-09-15 21:11:40 +00:00
|
|
|
|
<span className="font-medium">Bölge:</span> {zone?.name || '-'}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
2025-09-15 21:11:40 +00:00
|
|
|
|
<span className="font-medium">Lokasyon:</span>{' '}
|
|
|
|
|
|
{location?.name || '-'}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</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>
|
2025-09-15 21:11:40 +00:00
|
|
|
|
<span className="font-medium">Lot No:</span>{' '}
|
|
|
|
|
|
{mov.lotNumber || '-'}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
2025-09-15 21:11:40 +00:00
|
|
|
|
<span className="font-medium">Miktar:</span> {mov.quantity} Adet
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
2025-09-15 21:11:40 +00:00
|
|
|
|
<span className="font-medium">Toplam Değer:</span> ₺
|
|
|
|
|
|
{((mov.material?.costPrice || 0) * mov.quantity).toFixed(2)}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</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>
|
2025-09-15 21:11:40 +00:00
|
|
|
|
<span className="font-medium">Malzeme Grubu:</span>{' '}
|
|
|
|
|
|
{mov.material?.materialGroup?.name || '-'}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
2025-09-15 21:11:40 +00:00
|
|
|
|
<span className="font-medium">Referans Tipi:</span>{' '}
|
|
|
|
|
|
{mov.referenceType || '-'}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
2025-09-15 21:11:40 +00:00
|
|
|
|
<span className="font-medium">Sebep:</span> {mov.reason || '-'}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-09-15 21:11:40 +00:00
|
|
|
|
)
|
2025-09-15 09:31:47 +00:00
|
|
|
|
})}
|
|
|
|
|
|
</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(
|
2025-09-15 21:11:40 +00:00
|
|
|
|
(sum, mov) => sum + (mov.material?.costPrice || 0) * mov.quantity,
|
|
|
|
|
|
0,
|
2025-09-15 09:31:47 +00:00
|
|
|
|
)
|
|
|
|
|
|
.toFixed(2)}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<button
|
2025-09-15 21:11:40 +00:00
|
|
|
|
onClick={() => setSelectedMovement('')}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
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>
|
2025-09-15 21:11:40 +00:00
|
|
|
|
)
|
|
|
|
|
|
}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
|
|
|
|
|
|
return (
|
2025-09-15 21:11:40 +00:00
|
|
|
|
<Container>
|
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
|
<div className="flex items-center justify-between">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<h2 className="text-2xl font-bold text-gray-900">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>
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2025-09-15 21:11:40 +00:00
|
|
|
|
{/* 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"
|
|
|
|
|
|
/>
|
2025-09-15 09:31:47 +00:00
|
|
|
|
|
2025-09-15 21:11:40 +00:00
|
|
|
|
<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"
|
2025-09-15 09:31:47 +00:00
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2025-09-15 21:11:40 +00:00
|
|
|
|
{/* Filters */}
|
|
|
|
|
|
<div className="flex flex-col sm:flex-row gap-4">
|
|
|
|
|
|
<div className="relative flex-1">
|
|
|
|
|
|
<FaSearch className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
placeholder="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>
|
2025-09-17 09:46:58 +00:00
|
|
|
|
{Object.values(MovementStatusEnum).map((status) => (
|
|
|
|
|
|
<option key={status} value={status}>
|
|
|
|
|
|
{getMovementStatusText(status)}
|
|
|
|
|
|
</option>
|
|
|
|
|
|
))}
|
2025-09-15 21:11:40 +00:00
|
|
|
|
</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>
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</div>
|
2025-09-15 21:11:40 +00:00
|
|
|
|
</td>
|
|
|
|
|
|
<td className="px-3 py-2 whitespace-nowrap">
|
2025-09-15 09:31:47 +00:00
|
|
|
|
<div>
|
|
|
|
|
|
<div className="text-sm font-medium text-gray-900">
|
2025-09-15 21:11:40 +00:00
|
|
|
|
{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}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
<div className="text-sm text-gray-500">
|
2025-09-15 21:11:40 +00:00
|
|
|
|
{movement.toZone?.name && `${movement.toZone.name}`}
|
|
|
|
|
|
{movement.toLocation?.name && ` / ${movement.toLocation.name}`}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</div>
|
2025-09-15 21:11:40 +00:00
|
|
|
|
{movement.toLocation?.locationCode && (
|
|
|
|
|
|
<div className="text-xs text-gray-400">
|
|
|
|
|
|
Kod: {movement.toLocation.locationCode}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</div>
|
2025-09-15 21:11:40 +00:00
|
|
|
|
</td>
|
|
|
|
|
|
<td className="px-3 py-2 whitespace-nowrap">
|
|
|
|
|
|
<div className="text-sm text-gray-900">
|
|
|
|
|
|
{movement.quantity} {movement.unit?.code}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</div>
|
2025-09-15 21:11:40 +00:00
|
|
|
|
{movement.lotNumber && (
|
|
|
|
|
|
<div className="text-sm text-gray-500">Lot: {movement.lotNumber}</div>
|
2025-09-15 09:31:47 +00:00
|
|
|
|
)}
|
2025-09-15 21:11:40 +00:00
|
|
|
|
</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!,
|
|
|
|
|
|
)}`}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
>
|
2025-09-15 21:11:40 +00:00
|
|
|
|
<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>
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</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={() => {
|
2025-09-15 21:11:40 +00:00
|
|
|
|
setShowReceiptForm(false)
|
|
|
|
|
|
resetForm()
|
2025-09-15 09:31:47 +00:00
|
|
|
|
}}
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle 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">
|
2025-09-15 21:11:40 +00:00
|
|
|
|
{editingMovement ? 'Stok Giriş Düzenle' : 'Yeni Stok Giriş'}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</h3>
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => {
|
2025-09-15 21:11:40 +00:00
|
|
|
|
setShowReceiptForm(false)
|
|
|
|
|
|
resetForm()
|
2025-09-15 09:31:47 +00:00
|
|
|
|
}}
|
|
|
|
|
|
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">
|
2025-09-15 21:11:40 +00:00
|
|
|
|
<h4 className="text-sm font-medium text-gray-900 mb-2">Genel Bilgiler</h4>
|
2025-09-15 09:31:47 +00:00
|
|
|
|
<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}
|
2025-09-15 21:11:40 +00:00
|
|
|
|
onChange={(e) => handleHeaderInputChange('movementNumber', e.target.value)}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
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}
|
2025-09-15 21:11:40 +00:00
|
|
|
|
onChange={(e) => handleHeaderInputChange('movementDate', e.target.value)}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
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) =>
|
2025-09-15 21:11:40 +00:00
|
|
|
|
handleHeaderInputChange('referenceDocument', e.target.value)
|
2025-09-15 09:31:47 +00:00
|
|
|
|
}
|
|
|
|
|
|
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}
|
2025-09-15 21:11:40 +00:00
|
|
|
|
onChange={(e) => handleHeaderInputChange('referenceType', e.target.value)}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
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"
|
|
|
|
|
|
>
|
2025-09-15 21:11:40 +00:00
|
|
|
|
<option value="Purchase Order">Satın Alma Siparişi</option>
|
2025-09-15 09:31:47 +00:00
|
|
|
|
<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}
|
2025-09-15 21:11:40 +00:00
|
|
|
|
onChange={(e) => handleHeaderInputChange('performedBy', e.target.value)}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
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">
|
2025-09-15 21:11:40 +00:00
|
|
|
|
<label className="block text-xs font-medium text-gray-700 mb-1">Durum</label>
|
2025-09-15 09:31:47 +00:00
|
|
|
|
<select
|
|
|
|
|
|
value={headerData.status}
|
|
|
|
|
|
onChange={(e) =>
|
2025-09-15 21:11:40 +00:00
|
|
|
|
handleHeaderInputChange('status', e.target.value as MovementStatusEnum)
|
2025-09-15 09:31:47 +00:00
|
|
|
|
}
|
|
|
|
|
|
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"
|
|
|
|
|
|
>
|
2025-09-17 09:46:58 +00:00
|
|
|
|
{Object.values(MovementStatusEnum).map((status) => (
|
|
|
|
|
|
<option key={status} value={status}>
|
|
|
|
|
|
{getMovementStatusText(status)}
|
|
|
|
|
|
</option>
|
|
|
|
|
|
))}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</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">
|
|
|
|
|
|
Açıklama
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<textarea
|
|
|
|
|
|
value={headerData.description}
|
2025-09-15 21:11:40 +00:00
|
|
|
|
onChange={(e) => handleHeaderInputChange('description', e.target.value)}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
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(
|
2025-09-15 21:11:40 +00:00
|
|
|
|
(m) => m.id === material.materialId,
|
|
|
|
|
|
)
|
2025-09-15 09:31:47 +00:00
|
|
|
|
const selectedWarehouse = mockWarehouses.find(
|
2025-09-15 21:11:40 +00:00
|
|
|
|
(w) => w.id === material.warehouseId,
|
|
|
|
|
|
)
|
|
|
|
|
|
const availableZones = selectedWarehouse?.zones || []
|
2025-09-15 09:31:47 +00:00
|
|
|
|
const availableLocations =
|
|
|
|
|
|
selectedWarehouse?.locations?.filter(
|
2025-09-15 21:11:40 +00:00
|
|
|
|
(location) => !material.zoneId || location.zoneId === material.zoneId,
|
|
|
|
|
|
) || []
|
2025-09-15 09:31:47 +00:00
|
|
|
|
|
|
|
|
|
|
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,
|
2025-09-15 21:11:40 +00:00
|
|
|
|
'materialId',
|
|
|
|
|
|
e.target.value,
|
2025-09-15 09:31:47 +00:00
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
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,
|
2025-09-15 21:11:40 +00:00
|
|
|
|
'warehouseId',
|
|
|
|
|
|
e.target.value,
|
|
|
|
|
|
)
|
|
|
|
|
|
handleMaterialInputChange(material.id, 'zoneId', '')
|
|
|
|
|
|
handleMaterialInputChange(material.id, 'locationId', '')
|
2025-09-15 09:31:47 +00:00
|
|
|
|
}}
|
|
|
|
|
|
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) => (
|
2025-09-15 21:11:40 +00:00
|
|
|
|
<option key={warehouse.id} value={warehouse.id}>
|
2025-09-15 09:31:47 +00:00
|
|
|
|
{warehouse.code}
|
|
|
|
|
|
</option>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Zone Selection */}
|
|
|
|
|
|
<div className="col-span-2">
|
|
|
|
|
|
<select
|
|
|
|
|
|
value={material.zoneId}
|
|
|
|
|
|
onChange={(e) => {
|
2025-09-15 21:11:40 +00:00
|
|
|
|
handleMaterialInputChange(material.id, 'zoneId', e.target.value)
|
|
|
|
|
|
handleMaterialInputChange(material.id, 'locationId', '')
|
2025-09-15 09:31:47 +00:00
|
|
|
|
}}
|
|
|
|
|
|
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,
|
2025-09-15 21:11:40 +00:00
|
|
|
|
'locationId',
|
|
|
|
|
|
e.target.value,
|
2025-09-15 09:31:47 +00:00
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
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) => (
|
2025-09-15 21:11:40 +00:00
|
|
|
|
<option key={location.id} value={location.id}>
|
2025-09-15 09:31:47 +00:00
|
|
|
|
{location.locationCode} - {location.name}
|
|
|
|
|
|
</option>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Quantity */}
|
|
|
|
|
|
<div className="col-span-1">
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="number"
|
|
|
|
|
|
value={material.quantity}
|
|
|
|
|
|
onChange={(e) =>
|
|
|
|
|
|
handleMaterialInputChange(
|
|
|
|
|
|
material.id,
|
2025-09-15 21:11:40 +00:00
|
|
|
|
'quantity',
|
|
|
|
|
|
e.target.value,
|
2025-09-15 09:31:47 +00:00
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
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,
|
2025-09-15 21:11:40 +00:00
|
|
|
|
'unitPrice',
|
|
|
|
|
|
e.target.value,
|
2025-09-15 09:31:47 +00:00
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
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,
|
2025-09-15 21:11:40 +00:00
|
|
|
|
'lotNumber',
|
|
|
|
|
|
e.target.value,
|
2025-09-15 09:31:47 +00:00
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
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">
|
2025-09-15 21:11:40 +00:00
|
|
|
|
Birim: {selectedMaterial.baseUnit?.code} | Stok:{' '}
|
|
|
|
|
|
{selectedMaterial.totalStock}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</div>
|
2025-09-15 21:11:40 +00:00
|
|
|
|
<div className="col-span-2">{selectedWarehouse?.name}</div>
|
2025-09-15 09:31:47 +00:00
|
|
|
|
<div className="col-span-2">
|
2025-09-15 21:11:40 +00:00
|
|
|
|
{availableZones.find((z) => z.id === material.zoneId)?.name}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
<div className="col-span-2">
|
|
|
|
|
|
{
|
2025-09-15 21:11:40 +00:00
|
|
|
|
availableLocations.find((l) => l.id === material.locationId)
|
|
|
|
|
|
?.name
|
2025-09-15 09:31:47 +00:00
|
|
|
|
}
|
|
|
|
|
|
</div>
|
2025-09-15 21:11:40 +00:00
|
|
|
|
<div className="col-span-1">{selectedMaterial.baseUnit?.code}</div>
|
|
|
|
|
|
<div className="col-span-1">₺{selectedMaterial.costPrice}</div>
|
2025-09-15 09:31:47 +00:00
|
|
|
|
<div className="col-span-2">
|
|
|
|
|
|
{material.quantity &&
|
|
|
|
|
|
material.unitPrice &&
|
|
|
|
|
|
`Toplam: ₺${(
|
2025-09-15 21:11:40 +00:00
|
|
|
|
parseFloat(material.quantity) * parseFloat(material.unitPrice)
|
2025-09-15 09:31:47 +00:00
|
|
|
|
).toFixed(2)}`}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
2025-09-15 21:11:40 +00:00
|
|
|
|
)
|
2025-09-15 09:31:47 +00:00
|
|
|
|
})}
|
|
|
|
|
|
</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(
|
2025-09-15 21:11:40 +00:00
|
|
|
|
(m) => m.id === material.materialId,
|
|
|
|
|
|
)
|
2025-09-15 09:31:47 +00:00
|
|
|
|
const selectedWarehouse = mockWarehouses.find(
|
2025-09-15 21:11:40 +00:00
|
|
|
|
(w) => w.id === material.warehouseId,
|
|
|
|
|
|
)
|
|
|
|
|
|
const availableZones = selectedWarehouse?.zones || []
|
2025-09-15 09:31:47 +00:00
|
|
|
|
const availableLocations =
|
|
|
|
|
|
selectedWarehouse?.locations?.filter(
|
2025-09-15 21:11:40 +00:00
|
|
|
|
(location) => !material.zoneId || location.zoneId === material.zoneId,
|
|
|
|
|
|
) || []
|
2025-09-15 09:31:47 +00:00
|
|
|
|
|
|
|
|
|
|
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,
|
2025-09-15 21:11:40 +00:00
|
|
|
|
'materialId',
|
|
|
|
|
|
e.target.value,
|
2025-09-15 09:31:47 +00:00
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
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">
|
2025-09-15 21:11:40 +00:00
|
|
|
|
Birim: {selectedMaterial.baseUnit?.code} | Stok:{' '}
|
|
|
|
|
|
{selectedMaterial.totalStock}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</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,
|
2025-09-15 21:11:40 +00:00
|
|
|
|
'warehouseId',
|
|
|
|
|
|
e.target.value,
|
|
|
|
|
|
)
|
|
|
|
|
|
handleMaterialInputChange(material.id, 'zoneId', '')
|
|
|
|
|
|
handleMaterialInputChange(material.id, 'locationId', '')
|
2025-09-15 09:31:47 +00:00
|
|
|
|
}}
|
|
|
|
|
|
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) => (
|
2025-09-15 21:11:40 +00:00
|
|
|
|
<option key={warehouse.id} value={warehouse.id}>
|
2025-09-15 09:31:47 +00:00
|
|
|
|
{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) => {
|
2025-09-15 21:11:40 +00:00
|
|
|
|
handleMaterialInputChange(material.id, 'zoneId', e.target.value)
|
|
|
|
|
|
handleMaterialInputChange(material.id, 'locationId', '')
|
2025-09-15 09:31:47 +00:00
|
|
|
|
}}
|
|
|
|
|
|
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,
|
2025-09-15 21:11:40 +00:00
|
|
|
|
'locationId',
|
|
|
|
|
|
e.target.value,
|
2025-09-15 09:31:47 +00:00
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
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) =>
|
2025-09-15 21:11:40 +00:00
|
|
|
|
handleMaterialInputChange(material.id, 'quantity', e.target.value)
|
2025-09-15 09:31:47 +00:00
|
|
|
|
}
|
|
|
|
|
|
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,
|
2025-09-15 21:11:40 +00:00
|
|
|
|
'unitPrice',
|
|
|
|
|
|
e.target.value,
|
2025-09-15 09:31:47 +00:00
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
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"
|
2025-09-15 21:11:40 +00:00
|
|
|
|
placeholder={`₺${selectedMaterial?.costPrice || 0}`}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
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,
|
2025-09-15 21:11:40 +00:00
|
|
|
|
'lotNumber',
|
|
|
|
|
|
e.target.value,
|
2025-09-15 09:31:47 +00:00
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
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: ₺
|
|
|
|
|
|
{(
|
2025-09-15 21:11:40 +00:00
|
|
|
|
parseFloat(material.quantity || '0') *
|
2025-09-15 09:31:47 +00:00
|
|
|
|
parseFloat(
|
|
|
|
|
|
material.unitPrice ||
|
|
|
|
|
|
selectedMaterial.costPrice?.toString() ||
|
2025-09-15 21:11:40 +00:00
|
|
|
|
'0',
|
2025-09-15 09:31:47 +00:00
|
|
|
|
)
|
|
|
|
|
|
).toFixed(2)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
2025-09-15 21:11:40 +00:00
|
|
|
|
)
|
2025-09-15 09:31:47 +00:00
|
|
|
|
})}
|
|
|
|
|
|
</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={() => {
|
2025-09-15 21:11:40 +00:00
|
|
|
|
setShowReceiptForm(false)
|
|
|
|
|
|
resetForm()
|
2025-09-15 09:31:47 +00:00
|
|
|
|
}}
|
|
|
|
|
|
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" />
|
2025-09-15 21:11:40 +00:00
|
|
|
|
{editingMovement ? 'Güncelle' : 'Kaydet'}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
2025-09-15 21:11:40 +00:00
|
|
|
|
</Container>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
|
2025-09-15 21:11:40 +00:00
|
|
|
|
export default WarehouseReceipt
|