|
- import { InventoryLotLineResult, InventoryResult } from "@/app/api/inventory";
- 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 { arrayToDateString } from "@/app/utils/formatUtil";
- 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;
- setPagingController: defaultSetPagingController;
- pagingController: typeof defaultPagingController;
- totalCount: number;
- inventory: InventoryResult | null;
- }
-
- const InventoryLotLineTable: React.FC<Props> = ({ inventoryLotLines, pagingController, setPagingController, totalCount, inventory }) => {
- const { t } = useTranslation(["inventory"]);
- const { setIsUploading } = useUploadContext();
- const [stockTransferModalOpen, setStockTransferModalOpen] = useState(false);
- const [selectedLotLine, setSelectedLotLine] = useState<InventoryLotLineResult | null>(null);
- const [startLocation, setStartLocation] = useState<string>("");
- const [targetLocation, setTargetLocation] = useState<string>("");
- const [targetLocationInput, setTargetLocationInput] = useState<string>("");
- const [qtyToBeTransferred, setQtyToBeTransferred] = useState<number>(0);
- const [warehouses, setWarehouses] = useState<WarehouseResult[]>([]);
-
- 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 = {
- inventoryLotLineId: lotLineId
- }
- const response = await fetchQrCodeByLotLineId(postData);
- if (response) {
- downloadFile(new Uint8Array(response.blobValue), response.filename!);
- }
- 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) => {
- downloadQrCode(lotLine.id)
- // lot line id to find stock in line
- },
- [downloadQrCode],
- );
- const columns = useMemo<Column<InventoryLotLineResult>[]>(
- () => [
- // {
- // name: "item",
- // label: t("Code"),
- // renderCell: (params) => {
- // return params.item.code;
- // },
- // },
- // {
- // name: "item",
- // label: t("Name"),
- // renderCell: (params) => {
- // return params.item.name;
- // },
- // },
- {
- name: "lotNo",
- label: t("Lot No"),
- },
- // {
- // name: "item",
- // label: t("Type"),
- // renderCell: (params) => {
- // return t(params.item.type);
- // },
- // },
- {
- name: "availableQty",
- label: t("Available Qty"),
- align: "right",
- headerAlign: "right",
- type: "integer",
- },
- {
- name: "uom",
- label: t("Stock UoM"),
- align: "left",
- headerAlign: "left",
- },
- // {
- // name: "qtyPerSmallestUnit",
- // label: t("Available Qty Per Smallest Unit"),
- // align: "right",
- // headerAlign: "right",
- // type: "integer",
- // },
- // {
- // name: "baseUom",
- // label: t("Base UoM"),
- // align: "left",
- // headerAlign: "left",
- // },
- {
- name: "expiryDate",
- label: t("Expiry Date"),
- renderCell: (params) => {
- return arrayToDateString(params.expiryDate)
- },
- },
- {
- name: "warehouse",
- label: t("Warehouse"),
- renderCell: (params) => {
- return `${params.warehouse.code}`
- },
- },
- {
- name: "id",
- label: t("Download QR Code"),
- onClick: onDetailClick,
- buttonIcon: <QrCodeIcon />,
- align: "center",
- headerAlign: "center",
- },
- {
- name: "id",
- label: t("Print QR Code"),
- onClick: () => {},
- buttonIcon: <PrintIcon />,
- align: "center",
- headerAlign: "center",
- },
- {
- name: "id",
- label: t("Stock Transfer"),
- onClick: handleStockTransfer,
- buttonIcon: <SwapHoriz />,
- align: "center",
- headerAlign: "center",
- },
- // {
- // name: "status",
- // label: t("Status"),
- // type: "icon",
- // icons: {
- // available: <CheckCircleOutline fontSize="small"/>,
- // unavailable: <DoDisturb fontSize="small"/>,
- // },
- // colors: {
- // available: "success",
- // unavailable: "error",
- // }
- // },
- ],
- [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 <>
- <Typography variant="h6">{inventory ? `${t("Item selected")}: ${inventory.itemCode} | ${inventory.itemName} (${t(inventory.itemType)})` : t("No items are selected yet.")}</Typography>
- <SearchResults<InventoryLotLineResult>
- items={inventoryLotLines ?? []}
- columns={columns}
- pagingController={pagingController}
- setPagingController={setPagingController}
- totalCount={totalCount}
- />
-
- <Modal
- open={stockTransferModalOpen}
- onClose={handleCloseStockTransferModal}
- sx={{
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'center',
- }}
- >
- <Card
- sx={{
- position: 'relative',
- width: '95%',
- maxWidth: '1200px',
- maxHeight: '90vh',
- overflow: 'auto',
- p: 3,
- }}
- >
- <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
- <Typography variant="h6">
- {inventory && selectedLotLine
- ? `${inventory.itemCode} ${inventory.itemName} (${selectedLotLine.lotNo})`
- : t("Stock Transfer")
- }
- </Typography>
- <IconButton onClick={handleCloseStockTransferModal}>
- <CloseIcon />
- </IconButton>
- </Box>
- <Grid container spacing={1} sx={{ mt: 2 }}>
- <Grid item xs={5.5}>
- <TextField
- label={t("Start Location")}
- fullWidth
- variant="outlined"
- value={startLocation}
- disabled
- InputLabelProps={{
- shrink: !!startLocation,
- sx: { fontSize: "0.9375rem" },
- }}
- />
- </Grid>
- <Grid item xs={1} sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
- <Typography variant="body1">{t("to")}</Typography>
- </Grid>
- <Grid item xs={5.5}>
- <Autocomplete
- options={warehouses.filter(w => 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) => (
- <li {...props}>
- {option.code}
- </li>
- )}
- renderInput={(params) => (
- <TextField
- {...params}
- label={t("Target Location")}
- variant="outlined"
- fullWidth
- InputLabelProps={{
- shrink: !!targetLocation || !!targetLocationInput,
- sx: { fontSize: "0.9375rem" },
- }}
- />
- )}
- />
- </Grid>
- </Grid>
- <Grid container spacing={1} sx={{ mt: 2 }}>
- <Grid item xs={2}>
- <TextField
- label={t("Original Qty")}
- fullWidth
- variant="outlined"
- value={originalQty}
- disabled
- InputLabelProps={{
- shrink: true,
- sx: { fontSize: "0.9375rem" },
- }}
- />
- </Grid>
- <Grid item xs={1} sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
- <Typography variant="body1">-</Typography>
- </Grid>
- <Grid item xs={2}>
- <TextField
- label={t("Qty To Be Transferred")}
- fullWidth
- variant="outlined"
- type="number"
- value={qtyToBeTransferred}
- onChange={(e) => {
- 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" },
- }}
- />
- </Grid>
- <Grid item xs={1} sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
- <Typography variant="body1">=</Typography>
- </Grid>
- <Grid item xs={2}>
- <TextField
- label={t("Remaining Qty")}
- fullWidth
- variant="outlined"
- value={remainingQty}
- disabled
- InputLabelProps={{
- shrink: true,
- sx: { fontSize: "0.9375rem" },
- }}
- />
- </Grid>
- <Grid item xs={2}>
- <TextField
- label={t("Stock UoM")}
- fullWidth
- variant="outlined"
- value={selectedLotLine?.uom || ""}
- disabled
- InputLabelProps={{
- shrink: true,
- sx: { fontSize: "0.9375rem" },
- }}
- />
- </Grid>
- <Grid item xs={2} sx={{ display: 'flex', alignItems: 'center' }}>
- <Button
- variant="contained"
- fullWidth
- sx={{
- height: '56px',
- fontSize: '0.9375rem',
- }}
- onClick={handleSubmitStockTransfer}
- disabled={!selectedLotLine || !targetLocation || qtyToBeTransferred <= 0 || qtyToBeTransferred > originalQty}
- >
- {t("Submit")}
- </Button>
- </Grid>
- </Grid>
- </Card>
- </Modal>
-
- </>
- }
-
- export default InventoryLotLineTable;
|