2025-09-15 09:31:47 +00:00
|
|
|
|
import React, { useState, useRef, useMemo } from "react";
|
|
|
|
|
|
import { createPortal } from "react-dom";
|
|
|
|
|
|
import { MovementStatusEnum, MovementTypeEnum } from "../../../types/mm";
|
|
|
|
|
|
import {
|
|
|
|
|
|
FaSearch,
|
|
|
|
|
|
FaPlus,
|
|
|
|
|
|
FaEdit,
|
|
|
|
|
|
FaTrash,
|
|
|
|
|
|
FaBox,
|
|
|
|
|
|
FaCheckCircle,
|
|
|
|
|
|
FaClock,
|
|
|
|
|
|
FaExclamationCircle,
|
|
|
|
|
|
FaDownload,
|
|
|
|
|
|
FaUpload,
|
|
|
|
|
|
FaEye,
|
|
|
|
|
|
FaArrowCircleUp,
|
|
|
|
|
|
FaSave,
|
|
|
|
|
|
FaTimes,
|
|
|
|
|
|
FaMapMarkerAlt,
|
|
|
|
|
|
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";
|
|
|
|
|
|
|
|
|
|
|
|
interface StockLocation {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
warehouseId: string;
|
|
|
|
|
|
warehouseName: string;
|
|
|
|
|
|
warehouseCode: string;
|
|
|
|
|
|
zoneId: string;
|
|
|
|
|
|
zoneName: string;
|
|
|
|
|
|
zoneCode: string;
|
|
|
|
|
|
locationId: string;
|
|
|
|
|
|
locationName: string;
|
|
|
|
|
|
locationCode: string;
|
|
|
|
|
|
lotNumber: string;
|
|
|
|
|
|
availableQuantity: number;
|
|
|
|
|
|
requestedQuantity: number;
|
|
|
|
|
|
unitPrice: number;
|
|
|
|
|
|
selected?: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface MaterialRow {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
materialId: string;
|
|
|
|
|
|
selectedStockLocations: StockLocation[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Separate component for lot search input to prevent re-renders
|
|
|
|
|
|
const LotSearchInput = React.memo(
|
|
|
|
|
|
({
|
|
|
|
|
|
value,
|
|
|
|
|
|
onSearch,
|
|
|
|
|
|
}: {
|
|
|
|
|
|
value: string;
|
|
|
|
|
|
onSearch: (value: string) => void;
|
|
|
|
|
|
}) => {
|
|
|
|
|
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
|
|
|
|
|
|
|
|
|
|
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
|
|
|
|
if (e.key === "Enter") {
|
|
|
|
|
|
const inputValue = (e.target as HTMLInputElement).value;
|
|
|
|
|
|
onSearch(inputValue);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleClearSearch = () => {
|
|
|
|
|
|
if (inputRef.current) {
|
|
|
|
|
|
inputRef.current.value = "";
|
|
|
|
|
|
onSearch("");
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
|
|
|
|
Lot Numarası Arama (Enter ile arayın)
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<div className="relative">
|
|
|
|
|
|
<FaSearch className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
|
|
|
|
|
|
<input
|
|
|
|
|
|
ref={inputRef}
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
placeholder="Lot numarası yazıp Enter'a basın..."
|
|
|
|
|
|
defaultValue={value}
|
|
|
|
|
|
onKeyDown={handleKeyDown}
|
|
|
|
|
|
className="pl-10 pr-10 py-2 w-full text-sm border border-gray-300 rounded focus:ring-1 focus:ring-red-500 focus:border-transparent"
|
|
|
|
|
|
/>
|
|
|
|
|
|
{value && (
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={handleClearSearch}
|
|
|
|
|
|
className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600"
|
|
|
|
|
|
title="Aramayı temizle"
|
|
|
|
|
|
>
|
|
|
|
|
|
<FaTimes className="w-4 h-4" />
|
|
|
|
|
|
</button>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
const WarehouseIssue: React.FC = () => {
|
|
|
|
|
|
const [searchTerm, setSearchTerm] = useState("");
|
|
|
|
|
|
const [statusFilter, setStatusFilter] = useState<MovementStatusEnum | "">("");
|
|
|
|
|
|
const [selectedMovement, setSelectedMovement] = useState<string>("");
|
|
|
|
|
|
const [showIssueForm, setShowIssueForm] = useState(false);
|
|
|
|
|
|
const [editingMovement, setEditingMovement] = useState<string | null>(null);
|
|
|
|
|
|
|
|
|
|
|
|
// Form state - Header data for the entire movement
|
|
|
|
|
|
const [headerData, setHeaderData] = useState({
|
|
|
|
|
|
movementNumber: "",
|
|
|
|
|
|
referenceDocument: "",
|
|
|
|
|
|
referenceType: "Sales Order",
|
|
|
|
|
|
reason: "",
|
|
|
|
|
|
movementDate: new Date().toISOString().split("T")[0],
|
|
|
|
|
|
performedBy: "",
|
|
|
|
|
|
approvedBy: "",
|
|
|
|
|
|
description: "",
|
|
|
|
|
|
status: MovementStatusEnum.Planned,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// Materials list for the movement - New structure for stock-based selection
|
|
|
|
|
|
const [materialsList, setMaterialsList] = useState<MaterialRow[]>([]);
|
|
|
|
|
|
|
|
|
|
|
|
// Stock location modal state
|
|
|
|
|
|
const [showStockModal, setShowStockModal] = useState(false);
|
|
|
|
|
|
const [selectedMaterialForStock, setSelectedMaterialForStock] =
|
|
|
|
|
|
useState<string>("");
|
|
|
|
|
|
const [availableStockLocations, setAvailableStockLocations] = useState<
|
|
|
|
|
|
StockLocation[]
|
|
|
|
|
|
>([]);
|
|
|
|
|
|
const [lotSearchTerm, setLotSearchTerm] = useState("");
|
|
|
|
|
|
|
|
|
|
|
|
// Memoized filtered locations to prevent unnecessary re-renders
|
|
|
|
|
|
const filteredLocations = useMemo(() => {
|
|
|
|
|
|
return availableStockLocations.filter(
|
|
|
|
|
|
(location) =>
|
|
|
|
|
|
lotSearchTerm === "" ||
|
|
|
|
|
|
location.lotNumber.toLowerCase().includes(lotSearchTerm.toLowerCase())
|
|
|
|
|
|
);
|
|
|
|
|
|
}, [availableStockLocations, lotSearchTerm]);
|
|
|
|
|
|
|
|
|
|
|
|
const resetForm = () => {
|
|
|
|
|
|
setHeaderData({
|
|
|
|
|
|
movementNumber: "",
|
|
|
|
|
|
referenceDocument: "",
|
|
|
|
|
|
referenceType: "Sales Order",
|
|
|
|
|
|
reason: "",
|
|
|
|
|
|
movementDate: new Date().toISOString().split("T")[0],
|
|
|
|
|
|
performedBy: "",
|
|
|
|
|
|
approvedBy: "",
|
|
|
|
|
|
description: "",
|
|
|
|
|
|
status: MovementStatusEnum.Planned,
|
|
|
|
|
|
});
|
|
|
|
|
|
setMaterialsList([]);
|
|
|
|
|
|
setEditingMovement(null);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleHeaderInputChange = (field: string, value: string) => {
|
|
|
|
|
|
setHeaderData((prev) => ({
|
|
|
|
|
|
...prev,
|
|
|
|
|
|
[field]: value,
|
|
|
|
|
|
}));
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const removeMaterial = (materialId: string) => {
|
|
|
|
|
|
if (
|
|
|
|
|
|
window.confirm(
|
|
|
|
|
|
"Bu malzemeyi listeden kaldırmak istediğinizden emin misiniz?"
|
|
|
|
|
|
)
|
|
|
|
|
|
) {
|
|
|
|
|
|
setMaterialsList((prev) =>
|
|
|
|
|
|
prev.filter((material) => material.id !== materialId)
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Generate mock stock locations for a given material
|
|
|
|
|
|
const generateStockLocationsForMaterial = (
|
|
|
|
|
|
materialId: string
|
|
|
|
|
|
): StockLocation[] => {
|
|
|
|
|
|
const material = mockMaterials.find((m) => m.id === materialId);
|
|
|
|
|
|
if (!material || !material.totalStock || material.totalStock <= 0)
|
|
|
|
|
|
return [];
|
|
|
|
|
|
|
|
|
|
|
|
const stockLocations: StockLocation[] = [];
|
|
|
|
|
|
let remainingStock = material.totalStock;
|
|
|
|
|
|
|
|
|
|
|
|
// Generate some random stock distributions across warehouses/zones/locations
|
|
|
|
|
|
mockWarehouses.forEach((warehouse) => {
|
|
|
|
|
|
if (remainingStock <= 0) return;
|
|
|
|
|
|
|
|
|
|
|
|
warehouse.zones?.forEach((zone) => {
|
|
|
|
|
|
if (remainingStock <= 0) return;
|
|
|
|
|
|
|
|
|
|
|
|
const locationsInZone =
|
|
|
|
|
|
warehouse.locations?.filter((loc) => loc.zoneId === zone.id) || [];
|
|
|
|
|
|
|
|
|
|
|
|
locationsInZone.forEach((location) => {
|
|
|
|
|
|
if (remainingStock <= 0) return;
|
|
|
|
|
|
|
|
|
|
|
|
// Random distribution of stock (this would come from actual stock data in real app)
|
|
|
|
|
|
const stockAtLocation = Math.min(
|
|
|
|
|
|
Math.floor(Math.random() * 100) + 20, // Increased range for more realistic amounts
|
|
|
|
|
|
remainingStock
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (stockAtLocation > 0) {
|
|
|
|
|
|
// Generate 2-4 different lots per location with different dates
|
|
|
|
|
|
const lotsCount = Math.floor(Math.random() * 3) + 2; // 2-4 lots
|
|
|
|
|
|
|
|
|
|
|
|
// Split stock among lots with realistic distribution
|
|
|
|
|
|
const lotDistribution = [];
|
|
|
|
|
|
let tempStock = stockAtLocation;
|
|
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < lotsCount && tempStock > 5; i++) {
|
|
|
|
|
|
if (i === lotsCount - 1) {
|
|
|
|
|
|
// Last lot gets remaining stock
|
|
|
|
|
|
lotDistribution.push(tempStock);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// Distribute randomly but ensure each lot has at least 5 units
|
|
|
|
|
|
const maxForThisLot = Math.floor(tempStock * 0.6);
|
|
|
|
|
|
const minForThisLot = Math.min(5, tempStock);
|
|
|
|
|
|
const lotStock =
|
|
|
|
|
|
Math.floor(
|
|
|
|
|
|
Math.random() * (maxForThisLot - minForThisLot + 1)
|
|
|
|
|
|
) + minForThisLot;
|
|
|
|
|
|
lotDistribution.push(lotStock);
|
|
|
|
|
|
tempStock -= lotStock;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Create lot entries with different dates and batch numbers
|
|
|
|
|
|
lotDistribution.forEach((lotStock, index) => {
|
|
|
|
|
|
if (lotStock > 0 && remainingStock > 0) {
|
|
|
|
|
|
// Generate realistic lot numbers with dates
|
|
|
|
|
|
const lotDate = new Date();
|
|
|
|
|
|
lotDate.setDate(
|
|
|
|
|
|
lotDate.getDate() - Math.floor(Math.random() * 180)
|
|
|
|
|
|
); // Random date within last 6 months
|
|
|
|
|
|
const formattedDate = lotDate
|
|
|
|
|
|
.toISOString()
|
|
|
|
|
|
.slice(0, 10)
|
|
|
|
|
|
.replace(/-/g, "");
|
|
|
|
|
|
const batchNumber = String(index + 1).padStart(2, "0");
|
|
|
|
|
|
|
|
|
|
|
|
stockLocations.push({
|
|
|
|
|
|
id: `stock_${warehouse.id}_${zone.id}_${location.id}_${index}`,
|
|
|
|
|
|
warehouseId: warehouse.id,
|
|
|
|
|
|
warehouseName: warehouse.name,
|
|
|
|
|
|
warehouseCode: warehouse.code,
|
|
|
|
|
|
zoneId: zone.id,
|
|
|
|
|
|
zoneName: zone.name,
|
|
|
|
|
|
zoneCode: zone.zoneCode,
|
|
|
|
|
|
locationId: location.id,
|
|
|
|
|
|
locationName: location.name,
|
|
|
|
|
|
locationCode: location.locationCode,
|
|
|
|
|
|
lotNumber: `LOT${formattedDate}-${batchNumber}`,
|
|
|
|
|
|
availableQuantity: Math.min(lotStock, remainingStock),
|
|
|
|
|
|
requestedQuantity: 0,
|
|
|
|
|
|
unitPrice: material.costPrice || 0,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
remainingStock -= Math.min(lotStock, remainingStock);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// Sort by lot number (newest first - FIFO principle)
|
|
|
|
|
|
return stockLocations.sort((a, b) =>
|
|
|
|
|
|
b.lotNumber.localeCompare(a.lotNumber)
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const confirmStockSelection = () => {
|
|
|
|
|
|
const selectedLocations = availableStockLocations.filter(
|
|
|
|
|
|
(loc) => loc.selected
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (selectedLocations.length === 0 || !selectedMaterialForStock) return;
|
|
|
|
|
|
|
|
|
|
|
|
// Seçilen lokasyonlar için requestedQuantity'i availableQuantity'ye eşitle
|
|
|
|
|
|
const locationsWithQuantity = selectedLocations.map((loc) => ({
|
|
|
|
|
|
...loc,
|
|
|
|
|
|
requestedQuantity: loc.availableQuantity,
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
// Check if material already exists in the list
|
|
|
|
|
|
const existingMaterial = materialsList.find(
|
|
|
|
|
|
(m) => m.materialId === selectedMaterialForStock
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (existingMaterial) {
|
|
|
|
|
|
// Update existing material
|
|
|
|
|
|
setMaterialsList((prev) =>
|
|
|
|
|
|
prev.map((material) =>
|
|
|
|
|
|
material.id === existingMaterial.id
|
|
|
|
|
|
? { ...material, selectedStockLocations: locationsWithQuantity }
|
|
|
|
|
|
: material
|
|
|
|
|
|
)
|
|
|
|
|
|
);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// Add new material
|
|
|
|
|
|
const newMaterial: MaterialRow = {
|
|
|
|
|
|
id: Date.now().toString(),
|
|
|
|
|
|
materialId: selectedMaterialForStock,
|
|
|
|
|
|
selectedStockLocations: locationsWithQuantity,
|
|
|
|
|
|
};
|
|
|
|
|
|
setMaterialsList((prev) => [...prev, newMaterial]);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
setShowStockModal(false);
|
|
|
|
|
|
setSelectedMaterialForStock("");
|
|
|
|
|
|
setAvailableStockLocations([]);
|
|
|
|
|
|
setLotSearchTerm("");
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleSubmit = () => {
|
|
|
|
|
|
setShowIssueForm(false);
|
|
|
|
|
|
resetForm();
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleEdit = (movementId: string) => {
|
|
|
|
|
|
// Find the movement to edit
|
|
|
|
|
|
const movement = mockStockMovements.find((m) => m.id === movementId);
|
|
|
|
|
|
if (!movement) return;
|
|
|
|
|
|
|
|
|
|
|
|
// Load movement data into form
|
|
|
|
|
|
setHeaderData({
|
|
|
|
|
|
movementNumber: movement.movementNumber || "",
|
|
|
|
|
|
referenceDocument: movement.referenceDocument || "",
|
|
|
|
|
|
referenceType: movement.referenceType || "Sales 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,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// Create material row from movement data
|
|
|
|
|
|
if (movement.material && movement.materialId) {
|
|
|
|
|
|
// For goods issue, prioritize from fields (since we're issuing FROM the warehouse)
|
|
|
|
|
|
const locationId = movement.toLocationId || "";
|
|
|
|
|
|
const warehouse = movement.toWarehouse;
|
|
|
|
|
|
const zone = movement.toZone;
|
|
|
|
|
|
const location = movement.toLocation;
|
|
|
|
|
|
|
|
|
|
|
|
const materialRow: MaterialRow = {
|
|
|
|
|
|
id: Date.now().toString(),
|
|
|
|
|
|
materialId: movement.materialId,
|
|
|
|
|
|
selectedStockLocations: [
|
|
|
|
|
|
{
|
|
|
|
|
|
id: Date.now().toString(),
|
|
|
|
|
|
warehouseId: warehouse?.id || "",
|
|
|
|
|
|
warehouseName: warehouse?.name || "",
|
|
|
|
|
|
warehouseCode: warehouse?.code || "",
|
|
|
|
|
|
zoneId: zone?.id || "",
|
|
|
|
|
|
zoneName: zone?.name || "",
|
|
|
|
|
|
zoneCode: zone?.zoneCode || "",
|
|
|
|
|
|
locationId: locationId,
|
|
|
|
|
|
locationName: location?.name || "",
|
|
|
|
|
|
locationCode: location?.locationCode || "",
|
|
|
|
|
|
lotNumber: movement.lotNumber || "",
|
|
|
|
|
|
availableQuantity: movement.quantity || 0,
|
|
|
|
|
|
requestedQuantity: movement.quantity || 0,
|
|
|
|
|
|
unitPrice: 45.0,
|
|
|
|
|
|
selected: true,
|
|
|
|
|
|
},
|
|
|
|
|
|
],
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
setMaterialsList([materialRow]);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
setEditingMovement(movementId);
|
|
|
|
|
|
setShowIssueForm(true);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const getStatusLabel = (status: MovementStatusEnum) => {
|
|
|
|
|
|
const labels = {
|
|
|
|
|
|
[MovementStatusEnum.Planned]: "Planlandı",
|
|
|
|
|
|
[MovementStatusEnum.InProgress]: "Devam Ediyor",
|
|
|
|
|
|
[MovementStatusEnum.Completed]: "Tamamlandı",
|
|
|
|
|
|
[MovementStatusEnum.Cancelled]: "İptal Edildi",
|
|
|
|
|
|
};
|
|
|
|
|
|
return labels[status];
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const getStatusColor = (status: MovementStatusEnum) => {
|
|
|
|
|
|
const colors = {
|
|
|
|
|
|
[MovementStatusEnum.Planned]: "bg-gray-100 text-gray-800",
|
|
|
|
|
|
[MovementStatusEnum.InProgress]: "bg-blue-100 text-blue-800",
|
|
|
|
|
|
[MovementStatusEnum.Completed]: "bg-green-100 text-green-800",
|
|
|
|
|
|
[MovementStatusEnum.Cancelled]: "bg-red-100 text-red-800",
|
|
|
|
|
|
};
|
|
|
|
|
|
return colors[status];
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const getStatusIcon = (status: MovementStatusEnum) => {
|
|
|
|
|
|
const icons = {
|
|
|
|
|
|
[MovementStatusEnum.Planned]: FaClock,
|
|
|
|
|
|
[MovementStatusEnum.InProgress]: FaArrowCircleUp,
|
|
|
|
|
|
[MovementStatusEnum.Completed]: FaCheckCircle,
|
|
|
|
|
|
[MovementStatusEnum.Cancelled]: FaExclamationCircle,
|
|
|
|
|
|
};
|
|
|
|
|
|
return icons[status];
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Filter materials that have stock in warehouses (for goods issue)
|
|
|
|
|
|
const availableMaterials = mockMaterials.filter(
|
|
|
|
|
|
(material) => material.totalStock && material.totalStock > 0
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
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.GoodsIssue;
|
|
|
|
|
|
|
|
|
|
|
|
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 = getStatusIcon(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-red-600 to-red-700 px-4 sm:px-6 py-4">
|
|
|
|
|
|
<div className="flex items-center justify-between">
|
|
|
|
|
|
<div className="flex items-center space-x-3">
|
|
|
|
|
|
<div className="p-2 bg-white bg-opacity-20 rounded-lg">
|
|
|
|
|
|
<FaArrowCircleUp className="w-6 h-6 text-white" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<h3 className="text-lg sm:text-xl font-semibold text-white">
|
|
|
|
|
|
{movement.movementNumber} - Çıkış Detayları
|
|
|
|
|
|
</h3>
|
|
|
|
|
|
<p className="text-red-100 text-sm">
|
|
|
|
|
|
{relatedMovements.length} malzeme •{" "}
|
|
|
|
|
|
{movement.movementDate.toLocaleDateString("tr-TR")}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => setSelectedMovement("")}
|
|
|
|
|
|
className="text-white hover:text-red-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-6 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-2 border border-gray-200">
|
|
|
|
|
|
<div className="flex items-center justify-between mb-4">
|
|
|
|
|
|
<h4 className="text-lg 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 ${getStatusColor(
|
|
|
|
|
|
movement.status!
|
|
|
|
|
|
)}`}
|
|
|
|
|
|
>
|
|
|
|
|
|
<StatusIcon className="w-4 h-4 mr-1" />
|
|
|
|
|
|
{getStatusLabel(movement.status!)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 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">
|
|
|
|
|
|
Açıklama
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="text-gray-900">
|
|
|
|
|
|
{movement.description}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Materials List */}
|
|
|
|
|
|
<div className="space-y-4">
|
|
|
|
|
|
<h4 className="text-lg font-semibold text-gray-900 flex items-center">
|
|
|
|
|
|
<FaBox className="w-5 h-5 text-gray-600 mr-2" />
|
|
|
|
|
|
Çıkış 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-3 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-red-100 rounded-lg flex items-center justify-center">
|
|
|
|
|
|
<span className="text-red-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-red-600">
|
|
|
|
|
|
{mov.quantity}{" "}
|
|
|
|
|
|
{mov.material?.baseUnit?.name || "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-4">
|
|
|
|
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
|
|
|
|
{/* Warehouse Info */}
|
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
|
<h6 className="font-medium 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-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}{" "}
|
|
|
|
|
|
{mov.material?.baseUnit?.name || "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-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-4 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-6 py-2 rounded-lg hover:bg-gray-700 transition-colors flex items-center space-x-2"
|
|
|
|
|
|
>
|
|
|
|
|
|
<span>Kapat</span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Stock Selection Modal
|
|
|
|
|
|
const StockSelectionModal = () => {
|
|
|
|
|
|
if (!showStockModal) return null;
|
|
|
|
|
|
|
|
|
|
|
|
const selectedMaterial = availableMaterials.find(
|
|
|
|
|
|
(m) => m.id === selectedMaterialForStock
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
return createPortal(
|
|
|
|
|
|
<div className="fixed inset-0 overflow-y-auto" style={{ zIndex: 99999 }}>
|
|
|
|
|
|
<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-900 bg-opacity-75 transition-opacity"
|
|
|
|
|
|
onClick={() => setShowStockModal(false)}
|
|
|
|
|
|
style={{ zIndex: 99999 }}
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
|
|
<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-6xl mx-4 sm:mx-auto relative"
|
|
|
|
|
|
style={{ zIndex: 100000 }}
|
|
|
|
|
|
onClick={(e) => e.stopPropagation()}
|
|
|
|
|
|
>
|
|
|
|
|
|
<div className="bg-white px-4 sm:px-6 pt-5 pb-4">
|
|
|
|
|
|
<div className="flex items-center justify-between mb-4">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<h3 className="text-lg font-medium text-gray-900">
|
|
|
|
|
|
Malzeme ve Stok Lokasyonu Seçimi
|
|
|
|
|
|
</h3>
|
|
|
|
|
|
<p className="text-xs text-blue-600 mt-1">
|
|
|
|
|
|
💡 Önce malzeme seçin, sonra birden fazla lot ve lokasyondan
|
|
|
|
|
|
çıkış yapabilirsiniz.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => setShowStockModal(false)}
|
|
|
|
|
|
className="text-gray-400 hover:text-gray-600"
|
|
|
|
|
|
>
|
|
|
|
|
|
<FaTimes className="w-6 h-6" />
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Material Selection Section */}
|
|
|
|
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 mb-3">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<select
|
|
|
|
|
|
value={selectedMaterialForStock || ""}
|
|
|
|
|
|
onChange={(e) => {
|
|
|
|
|
|
const newValue = e.target.value;
|
|
|
|
|
|
if (newValue !== selectedMaterialForStock) {
|
|
|
|
|
|
setSelectedMaterialForStock(newValue);
|
|
|
|
|
|
// Reset stock locations when material changes
|
|
|
|
|
|
setAvailableStockLocations([]);
|
|
|
|
|
|
if (newValue) {
|
|
|
|
|
|
const stockLocations =
|
|
|
|
|
|
generateStockLocationsForMaterial(newValue);
|
|
|
|
|
|
setAvailableStockLocations(stockLocations);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}}
|
|
|
|
|
|
className="w-full px-3 py-2 text-sm border border-gray-300 rounded focus:ring-1 focus:ring-red-500 focus:border-transparent"
|
|
|
|
|
|
required
|
|
|
|
|
|
>
|
|
|
|
|
|
<option value="">Malzeme Seçiniz</option>
|
|
|
|
|
|
{availableMaterials.map((mat) => (
|
|
|
|
|
|
<option key={mat.id} value={mat.id}>
|
|
|
|
|
|
{mat.code} - {mat.name}
|
|
|
|
|
|
</option>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
{selectedMaterial && selectedMaterialForStock && (
|
|
|
|
|
|
<div className="flex items-center space-x-4 text-sm">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<span className="text-gray-600">Toplam Stok:</span>
|
|
|
|
|
|
<span className="ml-1 font-medium text-green-600">
|
|
|
|
|
|
{selectedMaterial.totalStock}{" "}
|
|
|
|
|
|
{selectedMaterial.baseUnit?.code}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<span className="text-gray-600">Birim:</span>
|
|
|
|
|
|
<span className="ml-1 font-medium">
|
|
|
|
|
|
{selectedMaterial.baseUnit?.code}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Lot Search */}
|
|
|
|
|
|
{selectedMaterialForStock &&
|
|
|
|
|
|
availableStockLocations.length > 0 && (
|
|
|
|
|
|
<LotSearchInput
|
|
|
|
|
|
value={lotSearchTerm}
|
|
|
|
|
|
onSearch={setLotSearchTerm}
|
|
|
|
|
|
/>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{/* Stock Locations Section */}
|
|
|
|
|
|
{selectedMaterialForStock &&
|
|
|
|
|
|
availableStockLocations.length > 0 && (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<div className="flex items-center justify-between m-1">
|
|
|
|
|
|
<h4 className="text-sm font-medium text-gray-900">
|
|
|
|
|
|
Stok Lokasyonları
|
|
|
|
|
|
{lotSearchTerm && (
|
|
|
|
|
|
<span className="ml-2 text-xs text-blue-600">
|
|
|
|
|
|
({filteredLocations.length} /{" "}
|
|
|
|
|
|
{availableStockLocations.length} kayıt)
|
|
|
|
|
|
</span>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</h4>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="max-h-96 overflow-y-auto border border-gray-200 rounded-lg">
|
|
|
|
|
|
<table className="min-w-full divide-y divide-gray-200">
|
|
|
|
|
|
<thead className="bg-gray-50 sticky top-0">
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th className="px-3 py-2 text-center text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
|
|
|
|
Seç
|
|
|
|
|
|
</th>
|
|
|
|
|
|
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
|
|
|
|
Depo
|
|
|
|
|
|
</th>
|
|
|
|
|
|
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
|
|
|
|
Zone/Lokasyon
|
|
|
|
|
|
</th>
|
|
|
|
|
|
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
|
|
|
|
Lot Numarası
|
|
|
|
|
|
</th>
|
|
|
|
|
|
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
|
|
|
|
Mevcut Stok
|
|
|
|
|
|
</th>
|
|
|
|
|
|
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
|
|
|
|
Birim Fiyat
|
|
|
|
|
|
</th>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</thead>
|
|
|
|
|
|
<tbody className="bg-white divide-y divide-gray-200">
|
|
|
|
|
|
{filteredLocations.map((location, index) => (
|
|
|
|
|
|
<tr
|
|
|
|
|
|
key={`stock-row-${location.id}-${location.lotNumber}`}
|
|
|
|
|
|
className={`hover:bg-gray-50 ${
|
|
|
|
|
|
index % 2 === 0 ? "bg-white" : "bg-gray-25"
|
|
|
|
|
|
}`}
|
|
|
|
|
|
>
|
|
|
|
|
|
<td className="px-3 py-3 whitespace-nowrap text-center">
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="checkbox"
|
|
|
|
|
|
checked={location.selected || false}
|
|
|
|
|
|
onChange={(e) => {
|
|
|
|
|
|
const updatedLocations =
|
|
|
|
|
|
availableStockLocations.map((loc) =>
|
|
|
|
|
|
loc.id === location.id
|
|
|
|
|
|
? {
|
|
|
|
|
|
...loc,
|
|
|
|
|
|
selected: e.target.checked,
|
|
|
|
|
|
requestedQuantity: e.target
|
|
|
|
|
|
.checked
|
|
|
|
|
|
? loc.availableQuantity
|
|
|
|
|
|
: 0,
|
|
|
|
|
|
}
|
|
|
|
|
|
: loc
|
|
|
|
|
|
);
|
|
|
|
|
|
setAvailableStockLocations(
|
|
|
|
|
|
updatedLocations
|
|
|
|
|
|
);
|
|
|
|
|
|
}}
|
|
|
|
|
|
className="w-4 h-4 text-red-600 bg-gray-100 border-gray-300 rounded focus:ring-red-500 focus:ring-2"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</td>
|
|
|
|
|
|
<td className="px-3 py-3 whitespace-nowrap text-sm">
|
|
|
|
|
|
<div className="flex items-center">
|
|
|
|
|
|
<FaBox className="w-4 h-4 text-blue-500 mr-2" />
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<div className="font-medium text-gray-900">
|
|
|
|
|
|
{location.warehouseCode}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="text-gray-500 text-xs">
|
|
|
|
|
|
{location.warehouseName}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</td>
|
|
|
|
|
|
<td className="px-3 py-3 whitespace-nowrap text-sm">
|
|
|
|
|
|
<div className="flex items-center">
|
|
|
|
|
|
<FaMapMarkerAlt className="w-3 h-3 text-green-500 mr-2" />
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<div className="font-medium text-gray-900">
|
|
|
|
|
|
{location.zoneCode} /{" "}
|
|
|
|
|
|
{location.locationCode}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="text-gray-500 text-xs">
|
|
|
|
|
|
{location.zoneName} /{" "}
|
|
|
|
|
|
{location.locationName}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</td>
|
|
|
|
|
|
<td className="px-3 py-3 whitespace-nowrap text-sm">
|
|
|
|
|
|
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
|
|
|
|
|
|
{location.lotNumber}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</td>
|
|
|
|
|
|
<td className="px-3 py-3 whitespace-nowrap text-sm">
|
|
|
|
|
|
<div className="font-medium text-gray-900">
|
|
|
|
|
|
{location.availableQuantity}{" "}
|
|
|
|
|
|
{selectedMaterial?.baseUnit?.code}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</td>
|
|
|
|
|
|
<td className="px-3 py-3 whitespace-nowrap text-sm text-gray-900">
|
|
|
|
|
|
₺{location.unitPrice.toFixed(2)}
|
|
|
|
|
|
</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</tbody>
|
|
|
|
|
|
</table>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="flex justify-between items-center pt-4 border-t border-gray-200 mt-4">
|
|
|
|
|
|
<div className="text-sm space-y-1">
|
|
|
|
|
|
<div className="text-gray-600">
|
|
|
|
|
|
<strong>Seçili Lokasyon Sayısı:</strong>{" "}
|
|
|
|
|
|
<span className="text-blue-600 font-medium">
|
|
|
|
|
|
{
|
|
|
|
|
|
availableStockLocations.filter(
|
|
|
|
|
|
(loc) => loc.selected
|
|
|
|
|
|
).length
|
|
|
|
|
|
}{" "}
|
|
|
|
|
|
adet
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="text-gray-600">
|
|
|
|
|
|
<strong>Toplam Stok Miktarı:</strong>{" "}
|
|
|
|
|
|
<span className="text-green-600 font-medium">
|
|
|
|
|
|
{availableStockLocations
|
|
|
|
|
|
.filter((loc) => loc.selected)
|
|
|
|
|
|
.reduce(
|
|
|
|
|
|
(sum, loc) => sum + loc.availableQuantity,
|
|
|
|
|
|
0
|
|
|
|
|
|
)}{" "}
|
|
|
|
|
|
{selectedMaterial?.baseUnit?.code}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="text-xs text-blue-600">
|
|
|
|
|
|
💡 Çıkış miktarlarını ana ekranda düzenleyebilirsiniz
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="flex gap-3">
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => setShowStockModal(false)}
|
|
|
|
|
|
className="px-4 py-2 text-sm text-gray-700 bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors"
|
|
|
|
|
|
>
|
|
|
|
|
|
İptal
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={confirmStockSelection}
|
|
|
|
|
|
className="px-4 py-2 text-sm bg-red-600 text-white hover:bg-red-700 rounded-lg transition-colors flex items-center gap-2"
|
|
|
|
|
|
disabled={
|
|
|
|
|
|
!selectedMaterialForStock ||
|
|
|
|
|
|
!availableStockLocations.some((loc) => loc.selected)
|
|
|
|
|
|
}
|
|
|
|
|
|
>
|
|
|
|
|
|
<FaCheckCircle className="w-4 h-4" />
|
|
|
|
|
|
Seçimi Onayla
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{/* No material selected message */}
|
|
|
|
|
|
{!selectedMaterialForStock && (
|
|
|
|
|
|
<div className="text-center py-8 text-gray-500">
|
|
|
|
|
|
<FaBox className="w-12 h-12 mx-auto mb-3 opacity-50" />
|
|
|
|
|
|
<p>Önce bir malzeme seçiniz</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>,
|
|
|
|
|
|
document.body
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="space-y-4 pt-2">
|
|
|
|
|
|
<div className="flex items-center justify-between">
|
|
|
|
|
|
<div>
|
2025-09-15 21:02:48 +00:00
|
|
|
|
<h2 className="text-2xl font-bold text-gray-900">Stok Çıkış</h2>
|
|
|
|
|
|
<p className="text-gray-600">
|
2025-09-15 09:31:47 +00:00
|
|
|
|
Depodan malzeme çıkış hareketlerini yönetin
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="flex gap-2">
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => setShowIssueForm(true)}
|
|
|
|
|
|
className="bg-red-600 text-white px-4 py-1.5 text-sm rounded-lg hover:bg-red-700 flex items-center gap-2"
|
|
|
|
|
|
>
|
|
|
|
|
|
<FaPlus className="w-4 h-4" />
|
|
|
|
|
|
Yeni Çıkış
|
|
|
|
|
|
</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 Çıkış"
|
|
|
|
|
|
value={
|
|
|
|
|
|
mockStockMovements.filter(
|
|
|
|
|
|
(m) =>
|
|
|
|
|
|
m.movementDate.toDateString() === new Date().toDateString() &&
|
|
|
|
|
|
m.status === MovementStatusEnum.Completed &&
|
|
|
|
|
|
m.movementType === MovementTypeEnum.GoodsIssue
|
|
|
|
|
|
).length
|
|
|
|
|
|
}
|
|
|
|
|
|
color="red"
|
|
|
|
|
|
icon="FaArrowCircleUp"
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
|
|
<Widget
|
|
|
|
|
|
title="Bekleyen"
|
|
|
|
|
|
value={
|
|
|
|
|
|
mockStockMovements.filter(
|
|
|
|
|
|
(m) =>
|
|
|
|
|
|
m.status === MovementStatusEnum.Planned &&
|
|
|
|
|
|
m.movementType === MovementTypeEnum.GoodsIssue
|
|
|
|
|
|
).length
|
|
|
|
|
|
}
|
|
|
|
|
|
color="yellow"
|
|
|
|
|
|
icon="FaClock"
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
|
|
<Widget
|
|
|
|
|
|
title="İşlemde"
|
|
|
|
|
|
value={
|
|
|
|
|
|
mockStockMovements.filter(
|
|
|
|
|
|
(m) =>
|
|
|
|
|
|
m.status === MovementStatusEnum.InProgress &&
|
|
|
|
|
|
m.movementType === MovementTypeEnum.GoodsIssue
|
|
|
|
|
|
).length
|
|
|
|
|
|
}
|
|
|
|
|
|
color="blue"
|
|
|
|
|
|
icon="FaBox"
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
|
|
<Widget
|
|
|
|
|
|
title="Tamamlanan"
|
|
|
|
|
|
value={
|
|
|
|
|
|
mockStockMovements.filter(
|
|
|
|
|
|
(m) =>
|
|
|
|
|
|
m.status === MovementStatusEnum.Completed &&
|
|
|
|
|
|
m.movementType === MovementTypeEnum.GoodsIssue
|
|
|
|
|
|
).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="Çıkış 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-red-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-red-500 focus:border-transparent"
|
|
|
|
|
|
>
|
|
|
|
|
|
<option value="">Tüm Durumlar</option>
|
|
|
|
|
|
<option value={MovementStatusEnum.Planned}>Planlandı</option>
|
|
|
|
|
|
<option value={MovementStatusEnum.InProgress}>Devam Ediyor</option>
|
|
|
|
|
|
<option value={MovementStatusEnum.Completed}>Tamamlandı</option>
|
|
|
|
|
|
<option value={MovementStatusEnum.Cancelled}>İptal Edildi</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 = getStatusIcon(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-red-100 rounded-lg mr-3">
|
|
|
|
|
|
<FaArrowCircleUp className="w-4 h-4 text-red-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 ${getStatusColor(
|
|
|
|
|
|
movement.status!
|
|
|
|
|
|
)}`}
|
|
|
|
|
|
>
|
|
|
|
|
|
<StatusIcon className="w-3 h-3 mr-1" />
|
|
|
|
|
|
{getStatusLabel(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-red-600 hover:text-red-900"
|
|
|
|
|
|
>
|
|
|
|
|
|
<FaEye className="w-4 h-4" />
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => handleEdit(movement.id)}
|
|
|
|
|
|
className="text-red-600 hover:text-red-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>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Movement Detail Modal */}
|
|
|
|
|
|
<MovementDetailModal />
|
|
|
|
|
|
|
|
|
|
|
|
{/* Stock Selection Modal */}
|
|
|
|
|
|
<StockSelectionModal />
|
|
|
|
|
|
|
|
|
|
|
|
{/* Issue Form Modal */}
|
|
|
|
|
|
{showIssueForm && (
|
|
|
|
|
|
<div className="fixed inset-0 overflow-y-auto" style={{ zIndex: 50 }}>
|
|
|
|
|
|
<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={() => {
|
|
|
|
|
|
setShowIssueForm(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 Çıkış Düzenle" : "Yeni Stok Çıkış"}
|
|
|
|
|
|
</h3>
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => {
|
|
|
|
|
|
setShowIssueForm(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-3">
|
|
|
|
|
|
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-red-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-red-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-3 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-red-500 focus:border-transparent"
|
|
|
|
|
|
>
|
|
|
|
|
|
<option value="Sales Order">Satış 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-red-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-red-500 focus:border-transparent"
|
|
|
|
|
|
>
|
|
|
|
|
|
<option value={MovementStatusEnum.Planned}>
|
|
|
|
|
|
Planlandı
|
|
|
|
|
|
</option>
|
|
|
|
|
|
<option value={MovementStatusEnum.InProgress}>
|
|
|
|
|
|
Devam Ediyor
|
|
|
|
|
|
</option>
|
|
|
|
|
|
<option value={MovementStatusEnum.Completed}>
|
|
|
|
|
|
Tamamlandı
|
|
|
|
|
|
</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">
|
|
|
|
|
|
Açı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-red-500 focus:border-transparent"
|
|
|
|
|
|
placeholder="Hareket açıklaması"
|
|
|
|
|
|
rows={2}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Materials Section - Improved Design */}
|
|
|
|
|
|
<div className="m-2">
|
|
|
|
|
|
<div className="flex items-center justify-between mb-2">
|
|
|
|
|
|
<h4 className="text-sm font-medium text-gray-900">
|
|
|
|
|
|
Çıkış Malzemeleri
|
|
|
|
|
|
{materialsList.length > 0 && (
|
|
|
|
|
|
<span className="ml-2 text-xs text-blue-600">
|
|
|
|
|
|
({materialsList.length} malzeme)
|
|
|
|
|
|
</span>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</h4>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Material List Container */}
|
|
|
|
|
|
<div className="border border-gray-200 rounded-lg bg-gray-50 min-h-[200px] max-h-[400px] overflow-y-auto">
|
|
|
|
|
|
{materialsList.length === 0 ? (
|
|
|
|
|
|
<div className="flex flex-col items-center justify-center h-[200px] text-gray-500">
|
|
|
|
|
|
<FaBox className="w-12 h-12 mb-3 opacity-50" />
|
|
|
|
|
|
<p className="text-sm font-medium">
|
|
|
|
|
|
Henüz malzeme seçilmedi
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<p className="text-xs text-gray-400 mt-1 text-center">
|
|
|
|
|
|
Aşağıdaki "Malzeme Seç" butonuna tıklayarak
|
|
|
|
|
|
<br />
|
|
|
|
|
|
malzeme ekleyebilirsiniz
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<div className="p-3 space-y-3">
|
|
|
|
|
|
{materialsList.map((materialRow, index) => {
|
|
|
|
|
|
const selectedMaterial = availableMaterials.find(
|
|
|
|
|
|
(m) => m.id === materialRow.materialId
|
|
|
|
|
|
);
|
|
|
|
|
|
const totalQuantity =
|
|
|
|
|
|
materialRow.selectedStockLocations.reduce(
|
|
|
|
|
|
(sum, loc) => sum + loc.requestedQuantity,
|
|
|
|
|
|
0
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div
|
|
|
|
|
|
key={materialRow.id}
|
|
|
|
|
|
className="bg-white border border-gray-200 rounded-lg p-3 shadow-sm hover:shadow-md transition-shadow"
|
|
|
|
|
|
>
|
|
|
|
|
|
{/* Material Header */}
|
|
|
|
|
|
<div className="flex items-center justify-between mb-3">
|
|
|
|
|
|
<div className="flex items-center space-x-3">
|
|
|
|
|
|
<div className="flex items-center justify-center w-8 h-8 bg-red-100 rounded-full">
|
|
|
|
|
|
<span className="text-xs font-bold text-red-600">
|
|
|
|
|
|
{index + 1}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<div className="flex items-center space-x-2">
|
|
|
|
|
|
<span className="text-sm font-semibold text-gray-900">
|
|
|
|
|
|
{selectedMaterial?.code}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span className="px-2 py-1 bg-gray-100 text-xs text-gray-600 rounded">
|
|
|
|
|
|
{selectedMaterial?.baseUnit?.code}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<p className="text-xs text-gray-600 mt-1">
|
|
|
|
|
|
{selectedMaterial?.name}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="flex items-center space-x-2">
|
|
|
|
|
|
<div className="text-right">
|
|
|
|
|
|
<div className="text-sm font-semibold text-red-600">
|
|
|
|
|
|
{totalQuantity}{" "}
|
|
|
|
|
|
{selectedMaterial?.baseUnit?.code}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="text-xs text-gray-500">
|
|
|
|
|
|
{
|
|
|
|
|
|
materialRow.selectedStockLocations
|
|
|
|
|
|
.length
|
|
|
|
|
|
}{" "}
|
|
|
|
|
|
lot
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="flex space-x-1">
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={() =>
|
|
|
|
|
|
removeMaterial(materialRow.id)
|
|
|
|
|
|
}
|
|
|
|
|
|
className="p-2 text-red-600 hover:bg-red-50 rounded-lg transition-colors"
|
|
|
|
|
|
title="Sil"
|
|
|
|
|
|
>
|
|
|
|
|
|
<FaTrash className="w-4 h-4" />
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Compact stock locations view with quantity inputs */}
|
|
|
|
|
|
{materialRow.selectedStockLocations.length >
|
|
|
|
|
|
0 && (
|
|
|
|
|
|
<div className="mt-3 pt-3 border-t border-gray-100">
|
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
|
{materialRow.selectedStockLocations.map(
|
|
|
|
|
|
(location) => (
|
|
|
|
|
|
<div
|
|
|
|
|
|
key={location.id}
|
|
|
|
|
|
className="flex items-center justify-between p-2 bg-gray-50 rounded text-xs border"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div className="flex items-center space-x-3 flex-1">
|
|
|
|
|
|
<span className="font-medium text-gray-900">
|
|
|
|
|
|
{location.warehouseCode} {" - "}
|
|
|
|
|
|
{location.warehouseName}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span className="text-gray-500">
|
|
|
|
|
|
{location.zoneCode}-
|
|
|
|
|
|
{location.zoneName} {" / "}
|
|
|
|
|
|
{location.locationCode}-
|
|
|
|
|
|
{location.locationName}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span className="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
|
|
|
|
|
|
{location.lotNumber}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span className="text-gray-500">
|
|
|
|
|
|
Mevcut:{" "}
|
|
|
|
|
|
{location.availableQuantity}{" "}
|
|
|
|
|
|
{selectedMaterial?.baseUnit?.code}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="flex items-center space-x-2">
|
|
|
|
|
|
<span className="text-gray-600 text-xs">
|
|
|
|
|
|
Çıkış:
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="number"
|
|
|
|
|
|
value={
|
|
|
|
|
|
location.requestedQuantity || ""
|
|
|
|
|
|
}
|
|
|
|
|
|
onChange={(e) => {
|
|
|
|
|
|
const value =
|
|
|
|
|
|
parseFloat(e.target.value) ||
|
|
|
|
|
|
0;
|
|
|
|
|
|
const updatedMaterials =
|
|
|
|
|
|
materialsList.map(
|
|
|
|
|
|
(material) =>
|
|
|
|
|
|
material.id ===
|
|
|
|
|
|
materialRow.id
|
|
|
|
|
|
? {
|
|
|
|
|
|
...material,
|
|
|
|
|
|
selectedStockLocations:
|
|
|
|
|
|
material.selectedStockLocations.map(
|
|
|
|
|
|
(loc) =>
|
|
|
|
|
|
loc.id ===
|
|
|
|
|
|
location.id
|
|
|
|
|
|
? {
|
|
|
|
|
|
...loc,
|
|
|
|
|
|
requestedQuantity:
|
|
|
|
|
|
Math.min(
|
|
|
|
|
|
value,
|
|
|
|
|
|
loc.availableQuantity
|
|
|
|
|
|
),
|
|
|
|
|
|
}
|
|
|
|
|
|
: loc
|
|
|
|
|
|
),
|
|
|
|
|
|
}
|
|
|
|
|
|
: material
|
|
|
|
|
|
);
|
|
|
|
|
|
setMaterialsList(
|
|
|
|
|
|
updatedMaterials
|
|
|
|
|
|
);
|
|
|
|
|
|
}}
|
|
|
|
|
|
className="w-20 px-2 py-1 text-xs border border-gray-300 rounded focus:ring-1 focus:ring-red-500"
|
|
|
|
|
|
min="0"
|
|
|
|
|
|
max={location.availableQuantity}
|
|
|
|
|
|
step="0.01"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<span className="text-xs text-gray-500">
|
|
|
|
|
|
{selectedMaterial?.baseUnit?.code}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="mt-2 text-xs text-gray-600 text-right">
|
|
|
|
|
|
Toplam Çıkış:{" "}
|
|
|
|
|
|
<span className="font-medium text-red-600">
|
|
|
|
|
|
{materialRow.selectedStockLocations.reduce(
|
|
|
|
|
|
(sum, loc) =>
|
|
|
|
|
|
sum + (loc.requestedQuantity || 0),
|
|
|
|
|
|
0
|
|
|
|
|
|
)}{" "}
|
|
|
|
|
|
{selectedMaterial?.baseUnit?.code}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
})}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Form Actions */}
|
|
|
|
|
|
<div className="flex justify-between items-center gap-3 pt-4 border-t border-gray-200">
|
|
|
|
|
|
{/* Left side - Add Material Button */}
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => setShowStockModal(true)}
|
|
|
|
|
|
className="bg-blue-600 text-white px-4 py-2 rounded-lg text-sm hover:bg-blue-700 flex items-center gap-2 shadow-sm transition-colors"
|
|
|
|
|
|
>
|
|
|
|
|
|
<FaPlus className="w-4 h-4" />
|
|
|
|
|
|
Malzeme Seç
|
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Right side - Form Actions */}
|
|
|
|
|
|
<div className="flex gap-3">
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => {
|
|
|
|
|
|
setShowIssueForm(false);
|
|
|
|
|
|
resetForm();
|
|
|
|
|
|
}}
|
|
|
|
|
|
className="px-4 py-2 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-2 text-sm bg-red-600 text-white hover:bg-red-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>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export default WarehouseIssue;
|