diff --git a/src/components/InventorySearch/InventoryLotLineTable.tsx b/src/components/InventorySearch/InventoryLotLineTable.tsx index f293ea4..b325aff 100644 --- a/src/components/InventorySearch/InventoryLotLineTable.tsx +++ b/src/components/InventorySearch/InventoryLotLineTable.tsx @@ -1,16 +1,21 @@ import { InventoryLotLineResult, InventoryResult } from "@/app/api/inventory"; -import { Dispatch, SetStateAction, useCallback, useMemo } from "react"; +import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { Column } from "../SearchResults"; import SearchResults, { defaultPagingController, defaultSetPagingController } from "../SearchResults/SearchResults"; -import { CheckCircleOutline, DoDisturb, EditNote } from "@mui/icons-material"; import { arrayToDateString } from "@/app/utils/formatUtil"; -import { Typography } from "@mui/material"; -import { isFinite } from "lodash"; +import { Box, Card, Grid, IconButton, Modal, TextField, Typography, Button } from "@mui/material"; import useUploadContext from "../UploadProvider/useUploadContext"; import { downloadFile } from "@/app/utils/commonUtil"; import { fetchQrCodeByLotLineId, LotLineToQrcode } from "@/app/api/pdf/actions"; import QrCodeIcon from "@mui/icons-material/QrCode"; +import PrintIcon from "@mui/icons-material/Print"; +import SwapHoriz from "@mui/icons-material/SwapHoriz"; +import CloseIcon from "@mui/icons-material/Close"; +import { Autocomplete } from "@mui/material"; +import { WarehouseResult } from "@/app/api/warehouse"; +import { fetchWarehouseListClient } from "@/app/api/warehouse/client"; +import { updateInventoryLotLineQuantities } from "@/app/api/inventory/actions"; interface Props { inventoryLotLines: InventoryLotLineResult[] | null; @@ -23,8 +28,26 @@ interface Props { const InventoryLotLineTable: React.FC = ({ inventoryLotLines, pagingController, setPagingController, totalCount, inventory }) => { const { t } = useTranslation(["inventory"]); const { setIsUploading } = useUploadContext(); + const [stockTransferModalOpen, setStockTransferModalOpen] = useState(false); + const [selectedLotLine, setSelectedLotLine] = useState(null); + const [startLocation, setStartLocation] = useState(""); + const [targetLocation, setTargetLocation] = useState(""); + const [targetLocationInput, setTargetLocationInput] = useState(""); + const [qtyToBeTransferred, setQtyToBeTransferred] = useState(0); + const [warehouses, setWarehouses] = useState([]); - const printQrcode = useCallback(async (lotLineId: number) => { + useEffect(() => { + if (stockTransferModalOpen) { + fetchWarehouseListClient() + .then(setWarehouses) + .catch(console.error); + } + }, [stockTransferModalOpen]); + + const originalQty = selectedLotLine?.availableQty || 0; + const remainingQty = originalQty - qtyToBeTransferred; + + const downloadQrCode = useCallback(async (lotLineId: number) => { setIsUploading(true); // const postData = { stockInLineIds: [42,43,44] }; const postData: LotLineToQrcode = { @@ -37,12 +60,24 @@ const InventoryLotLineTable: React.FC = ({ inventoryLotLines, pagingContr setIsUploading(false); }, [setIsUploading]); + const handleStockTransfer = useCallback( + (lotLine: InventoryLotLineResult) => { + setSelectedLotLine(lotLine); + setStockTransferModalOpen(true); + setStartLocation(lotLine.warehouse.code || ""); + setTargetLocation(""); + setTargetLocationInput(""); + setQtyToBeTransferred(0); + }, + [], + ); + const onDetailClick = useCallback( (lotLine: InventoryLotLineResult) => { - printQrcode(lotLine.id) + downloadQrCode(lotLine.id) // lot line id to find stock in line }, - [printQrcode], + [downloadQrCode], ); const columns = useMemo[]>( () => [ @@ -108,14 +143,32 @@ const InventoryLotLineTable: React.FC = ({ inventoryLotLines, pagingContr name: "warehouse", label: t("Warehouse"), renderCell: (params) => { - return `${params.warehouse.code} - ${params.warehouse.name}` + return `${params.warehouse.code}` }, }, { name: "id", - label: t("qrcode"), + label: t("Download QR Code"), onClick: onDetailClick, buttonIcon: , + align: "center", + headerAlign: "center", + }, + { + name: "id", + label: t("Print QR Code"), + onClick: () => {}, + buttonIcon: , + align: "center", + headerAlign: "center", + }, + { + name: "id", + label: t("Stock Transfer"), + onClick: handleStockTransfer, + buttonIcon: , + align: "center", + headerAlign: "center", }, // { // name: "status", @@ -131,8 +184,39 @@ const InventoryLotLineTable: React.FC = ({ inventoryLotLines, pagingContr // } // }, ], - [t], + [t, onDetailClick, downloadQrCode, handleStockTransfer], ); + + const handleCloseStockTransferModal = useCallback(() => { + setStockTransferModalOpen(false); + setSelectedLotLine(null); + setStartLocation(""); + setTargetLocation(""); + setTargetLocationInput(""); + setQtyToBeTransferred(0); + }, []); + + const handleSubmitStockTransfer = useCallback(async () => { + try { + setIsUploading(true); + + // Decrease the inQty (availableQty) in the source inventory lot line + + + // TODO: Add logic to increase qty in target location warehouse + + alert(t("Stock transfer successful")); + handleCloseStockTransferModal(); + + // TODO: Refresh the inventory lot lines list + } catch (error: any) { + console.error("Error transferring stock:", error); + alert(error?.message || t("Failed to transfer stock. Please try again.")); + } finally { + setIsUploading(false); + } + }, [selectedLotLine, targetLocation, qtyToBeTransferred, originalQty, handleCloseStockTransferModal, setIsUploading, t]); + return <> {inventory ? `${t("Item selected")}: ${inventory.itemCode} | ${inventory.itemName} (${t(inventory.itemType)})` : t("No items are selected yet.")} @@ -142,6 +226,191 @@ const InventoryLotLineTable: React.FC = ({ inventoryLotLines, pagingContr setPagingController={setPagingController} totalCount={totalCount} /> + + + + + + {inventory && selectedLotLine + ? `${inventory.itemCode} ${inventory.itemName} (${selectedLotLine.lotNo})` + : t("Stock Transfer") + } + + + + + + + + + + + {t("to")} + + + w.code !== startLocation)} + getOptionLabel={(option) => option.code || ""} + value={targetLocation ? warehouses.find(w => w.code === targetLocation) || null : null} + inputValue={targetLocationInput} + onInputChange={(event, newInputValue) => { + setTargetLocationInput(newInputValue); + if (targetLocation && newInputValue !== targetLocation) { + setTargetLocation(""); + } + }} + onChange={(event, newValue) => { + if (newValue) { + setTargetLocation(newValue.code); + setTargetLocationInput(newValue.code); + } else { + setTargetLocation(""); + setTargetLocationInput(""); + } + }} + filterOptions={(options, { inputValue }) => { + if (!inputValue || inputValue.trim() === "") return options; + const searchTerm = inputValue.toLowerCase().trim(); + return options.filter((option) => + (option.code || "").toLowerCase().includes(searchTerm) || + (option.name || "").toLowerCase().includes(searchTerm) || + (option.description || "").toLowerCase().includes(searchTerm) + ); + }} + isOptionEqualToValue={(option, value) => option.code === value.code} + autoHighlight={false} + autoSelect={false} + clearOnBlur={false} + renderOption={(props, option) => ( +
  • + {option.code} +
  • + )} + renderInput={(params) => ( + + )} + /> +
    +
    + + + + + + - + + + { + const value = parseInt(e.target.value) || 0; + const maxValue = Math.max(0, originalQty); + setQtyToBeTransferred(Math.min(Math.max(0, value), maxValue)); + }} + inputProps={{ min: 0, max: originalQty, step: 1 }} + InputLabelProps={{ + shrink: true, + sx: { fontSize: "0.9375rem" }, + }} + /> + + + = + + + + + + + + + + + +
    +
    + }