|
- "use client";
-
- import { InventoryLotLineResult, InventoryResult } from "@/app/api/inventory";
- import { updateInventoryLotLineStatus } from "@/app/api/inventory/actions";
- import { arrayToDateString } from "@/app/utils/formatUtil";
- import { msg, msgError } from "@/components/Swal/CustomAlerts";
- import HighlightOffIcon from "@mui/icons-material/HighlightOff";
- import {
- Box,
- FormControl,
- IconButton,
- InputLabel,
- MenuItem,
- Select,
- SelectChangeEvent,
- Typography,
- } from "@mui/material";
- import { useCallback, useMemo, useRef, useState } from "react";
- import { useTranslation } from "react-i18next";
- import { Column } from "../SearchResults";
- import SearchResults, {
- defaultPagingController,
- defaultSetPagingController,
- } from "../SearchResults/SearchResults";
- import BadItemHandleModal from "./BadItemHandleModal";
-
- const LOT_STATUSES = ["available", "unavailable"] as const;
-
- interface Props {
- inventoryLotLines: InventoryLotLineResult[] | null;
- setPagingController: defaultSetPagingController;
- pagingController: typeof defaultPagingController;
- totalCount: number;
- inventory: InventoryResult | null;
- currentUserId?: number;
- onBadItemHandleSuccess?: (payload: {
- inventoryLotLineId: number;
- qty: number;
- }) => void | Promise<void>;
- onLotLinesChanged?: () => void | Promise<void>;
- }
-
- const StockIssueLotLineTable: React.FC<Props> = ({
- inventoryLotLines,
- pagingController,
- setPagingController,
- totalCount,
- inventory,
- currentUserId,
- onBadItemHandleSuccess,
- onLotLinesChanged,
- }) => {
- const { t } = useTranslation(["stockIssue", "common"]);
- const [modalOpen, setModalOpen] = useState(false);
- const [selectedLotLine, setSelectedLotLine] =
- useState<InventoryLotLineResult | null>(null);
- const [statusUpdatingIds, setStatusUpdatingIds] = useState<Set<number>>(
- new Set(),
- );
- const statusInFlightRef = useRef<Set<number>>(new Set());
-
- const displayLotLines = useMemo(
- () => inventoryLotLines ?? [],
- [inventoryLotLines],
- );
-
- const isBadItemEnabled = useCallback((line: InventoryLotLineResult) => {
- const qty = line.availableQty ?? 0;
- return qty > 0;
- }, []);
-
- const handleBadItemClick = useCallback((lotLine: InventoryLotLineResult) => {
- if (!isBadItemEnabled(lotLine)) return;
- setSelectedLotLine(lotLine);
- setModalOpen(true);
- }, [isBadItemEnabled]);
-
- const handleStatusChange = useCallback(
- async (line: InventoryLotLineResult, event: SelectChangeEvent<string>) => {
- const nextStatus = event.target.value;
- if (!nextStatus || nextStatus === line.status) return;
- if (statusInFlightRef.current.has(line.id)) return;
-
- statusInFlightRef.current.add(line.id);
- setStatusUpdatingIds((prev) => new Set(prev).add(line.id));
- try {
- const res = await updateInventoryLotLineStatus({
- inventoryLotLineId: line.id,
- status: nextStatus,
- });
- if (res?.code && res.code !== "SUCCESS") {
- throw new Error(res.message ?? t("Failed to submit"));
- }
- msg(t("Saved successfully"));
- await onLotLinesChanged?.();
- } catch (e: unknown) {
- msgError(e instanceof Error ? e.message : t("Failed to submit"));
- } finally {
- statusInFlightRef.current.delete(line.id);
- setStatusUpdatingIds((prev) => {
- const next = new Set(prev);
- next.delete(line.id);
- return next;
- });
- }
- },
- [t, onLotLinesChanged],
- );
-
- const formatStatusLabel = useCallback(
- (status: string) => {
- const key = status?.toLowerCase();
- if (key === "available") return t("available");
- if (key === "unavailable") return t("unavailable");
- return status;
- },
- [t],
- );
-
- const columns = useMemo<Column<InventoryLotLineResult>[]>(
- () => [
- { name: "lotNo", label: t("Lot No") },
- {
- name: "availableQty",
- label: t("Available Qty"),
- align: "right",
- headerAlign: "right",
- type: "integer",
- },
- { name: "uom", label: t("Stock UoM") },
- {
- name: "expiryDate",
- label: t("Expiry Date"),
- renderCell: (params) => arrayToDateString(params.expiryDate),
- },
- {
- name: "warehouse",
- label: t("Warehouse"),
- renderCell: (params) => params.warehouse?.code ?? "",
- },
- {
- name: "status",
- label: t("Status"),
- renderCell: (row) => (
- <FormControl
- size="small"
- fullWidth
- disabled={statusUpdatingIds.has(row.id)}
- >
- <InputLabel id={`lot-status-${row.id}`}>{t("Status")}</InputLabel>
- <Select
- labelId={`lot-status-${row.id}`}
- label={t("Status")}
- value={
- LOT_STATUSES.includes(
- row.status?.toLowerCase() as (typeof LOT_STATUSES)[number],
- )
- ? row.status!.toLowerCase()
- : "unavailable"
- }
- onChange={(e) => handleStatusChange(row, e)}
- >
- {LOT_STATUSES.map((s) => (
- <MenuItem key={s} value={s}>
- {formatStatusLabel(s)}
- </MenuItem>
- ))}
- </Select>
- </FormControl>
- ),
- },
- {
- name: "id",
- label: t("Bad Item Handle"),
- align: "center",
- headerAlign: "center",
- renderCell: (row) => (
- <IconButton
- color="error"
- disabled={!isBadItemEnabled(row) || !currentUserId}
- onClick={() => handleBadItemClick(row)}
- title={t("Bad Item Handle")}
- >
- <HighlightOffIcon />
- </IconButton>
- ),
- },
- ],
- [
- t,
- handleStatusChange,
- formatStatusLabel,
- statusUpdatingIds,
- isBadItemEnabled,
- handleBadItemClick,
- currentUserId,
- ],
- );
-
- return (
- <>
- <Box sx={{ mb: 2 }}>
- <Typography variant="h6">
- {inventory
- ? `${t("Item selected")}: ${inventory.itemCode} | ${inventory.itemName} (${t(inventory.itemType, { ns: "common", defaultValue: inventory.itemType })})`
- : t("No items are selected yet.")}
- </Typography>
- </Box>
- <SearchResults<InventoryLotLineResult>
- items={displayLotLines}
- columns={columns}
- pagingController={pagingController}
- setPagingController={setPagingController}
- totalCount={totalCount}
- />
- <BadItemHandleModal
- open={modalOpen}
- onClose={() => setModalOpen(false)}
- lotLine={selectedLotLine}
- inventory={inventory}
- currentUserId={currentUserId}
- onSuccess={async (payload) => {
- await onBadItemHandleSuccess?.(payload);
- }}
- />
- </>
- );
- };
-
- export default StockIssueLotLineTable;
|