|
- "use client";
-
- import SearchBox, { Criterion } from "../SearchBox";
- import { useCallback, useMemo, useState } from "react";
- import { useTranslation } from "react-i18next";
- import SearchResults, { Column } from "../SearchResults/index";
- import { SessionWithTokens } from "@/config/authConfig";
- import {
- batchSubmitBadItem,
- batchSubmitExpiryItem,
- batchSubmitMissItem,
- ExpiryItemResult,
- StockIssueLists,
- StockIssueResult,
- submitBadItem,
- submitExpiryItem,
- submitMissItem,
- } from "@/app/api/stockIssue/actions";
- import { Box, Button, Tab, Tabs } from "@mui/material";
- import { useSession } from "next-auth/react";
-
- interface Props {
- dataList: StockIssueLists;
- }
-
- type SearchQuery = {
- lotNo: string;
- };
- type SearchParamNames = keyof SearchQuery;
-
- const SearchPage: React.FC<Props> = ({ dataList }) => {
- const { t } = useTranslation("inventory");
- const [tab, setTab] = useState<"miss" | "bad" | "expiry">("miss");
- const [search, setSearch] = useState<SearchQuery>({ lotNo: "" });
- const { data: session } = useSession() as { data: SessionWithTokens | null };
- const currentUserId = session?.id ? parseInt(session.id) : undefined;
- const [missItems, setMissItems] = useState<StockIssueResult[]>(
- dataList.missItems,
- );
- const [badItems, setBadItems] = useState<StockIssueResult[]>(
- dataList.badItems,
- );
- const [expiryItems, setExpiryItems] = useState<ExpiryItemResult[]>(
- dataList.expiryItems,
- );
- const [selectedIds, setSelectedIds] = useState<(string | number)[]>([]);
- const [submittingIds, setSubmittingIds] = useState<Set<number>>(new Set());
- const [batchSubmitting, setBatchSubmitting] = useState(false);
-
- const searchCriteria: Criterion<SearchParamNames>[] = useMemo(
- () => [
- {
- label: t("Lot No."),
- paramName: "lotNo",
- type: "text",
- },
- ],
- [t],
- );
-
- const filterBySearch = useCallback(
- <T extends { lotNo: string | null }>(items: T[]): T[] => {
- if (!search.lotNo) return items;
- const keyword = search.lotNo.toLowerCase();
- return items.filter(
- (i) => i.lotNo && i.lotNo.toLowerCase().includes(keyword),
- );
- },
- [search.lotNo],
- );
-
- const handleSubmitSingle = useCallback(
- async (id: number) => {
- if (!currentUserId) {
- alert(t("User ID is required"));
- return;
- }
-
- setSubmittingIds((prev) => new Set(prev).add(id));
- try {
- if (tab === "miss") {
- await submitMissItem(id, currentUserId);
- setMissItems((prev) => prev.filter((i) => i.id !== id));
- } else if (tab === "bad") {
- await submitBadItem(id, currentUserId);
- setBadItems((prev) => prev.filter((i) => i.id !== id));
- } else {
- await submitExpiryItem(id, currentUserId);
- setExpiryItems((prev) => prev.filter((i) => i.id !== id));
- }
- // Remove from selectedIds if it was selected
- setSelectedIds((prev) => prev.filter((selectedId) => selectedId !== id));
- } catch (error) {
- console.error("Failed to submit item:", error);
- alert(`Failed to submit: ${error instanceof Error ? error.message : "Unknown error"}`);
- } finally {
- setSubmittingIds((prev) => {
- const newSet = new Set(prev);
- newSet.delete(id);
- return newSet;
- });
- }
- },
- [tab, currentUserId, t],
- );
-
- const handleSubmitSelected = useCallback(async () => {
- if (!currentUserId) return;
-
- // Get all IDs from the current tab's filtered items
- let allIds: number[] = [];
- if (tab === "miss") {
- const items = filterBySearch(missItems);
- allIds = items.map((item) => item.id);
- } else if (tab === "bad") {
- const items = filterBySearch(badItems);
- allIds = items.map((item) => item.id);
- } else {
- const items = filterBySearch(expiryItems);
- allIds = items.map((item) => item.id);
- }
-
- if (allIds.length === 0) return;
-
- setBatchSubmitting(true);
- try {
- if (tab === "miss") {
- await batchSubmitMissItem(allIds, currentUserId);
- setMissItems((prev) => prev.filter((i) => !allIds.includes(i.id)));
- } else if (tab === "bad") {
- await batchSubmitBadItem(allIds, currentUserId);
- setBadItems((prev) => prev.filter((i) => !allIds.includes(i.id)));
- } else {
- await batchSubmitExpiryItem(allIds, currentUserId);
- setExpiryItems((prev) => prev.filter((i) => !allIds.includes(i.id)));
- }
-
- setSelectedIds([]);
- } catch (error) {
- console.error("Failed to submit selected items:", error);
- alert(`Failed to submit: ${error instanceof Error ? error.message : "Unknown error"}`);
- } finally {
- setBatchSubmitting(false);
- }
- }, [tab, currentUserId, missItems, badItems, expiryItems, filterBySearch]);
-
- const missColumns = useMemo<Column<StockIssueResult>[]>(
- () => [
- { name: "itemCode", label: t("Item Code") },
- { name: "itemDescription", label: t("Item") },
- { name: "lotNo", label: t("Lot No.") },
- { name: "storeLocation", label: t("Location") },
- { name: "missQty", label: t("Miss Qty") },
- {
- name: "id",
- label: t("Action"),
- renderCell: (item) => (
- <Button
- size="small"
- variant="contained"
- color="primary"
- onClick={() => handleSubmitSingle(item.id)}
- disabled={submittingIds.has(item.id) || !currentUserId}
- >
- {submittingIds.has(item.id) ? t("Processing...") : t("Looked")}
- </Button>
- ),
- },
- ],
- [t, handleSubmitSingle, submittingIds, currentUserId],
- );
-
- const badColumns = useMemo<Column<StockIssueResult>[]>(
- () => [
- { name: "itemCode", label: t("Item Code") },
- { name: "itemDescription", label: t("Item") },
- { name: "lotNo", label: t("Lot No.") },
- { name: "storeLocation", label: t("Location") },
- { name: "badItemQty", label: t("Defective Qty") },
- {
- name: "id",
- label: t("Action"),
- renderCell: (item) => (
- <Button
- size="small"
- variant="contained"
- color="primary"
- onClick={() => handleSubmitSingle(item.id)}
- disabled={submittingIds.has(item.id) || !currentUserId}
- >
- {submittingIds.has(item.id) ? t("Disposing...") : t("Disposed")}
- </Button>
- ),
- },
- ],
- [t, handleSubmitSingle, submittingIds, currentUserId],
- );
-
- const expiryColumns = useMemo<Column<ExpiryItemResult>[]>(
- () => [
- { name: "itemCode", label: t("Item Code") },
- { name: "itemDescription", label: t("Item") },
- { name: "lotNo", label: t("Lot No.") },
- { name: "storeLocation", label: t("Location") },
- { name: "expiryDate", label: t("Expiry Date") },
- { name: "remainingQty", label: t("Remaining Qty") },
- {
- name: "id",
- label: t("Action"),
- renderCell: (item) => (
- <Button
- size="small"
- variant="contained"
- color="primary"
- onClick={() => handleSubmitSingle(item.id)}
- disabled={submittingIds.has(item.id) || !currentUserId}
- >
- {submittingIds.has(item.id) ? t("Disposing...") : t("Disposed")}
- </Button>
- ),
- },
- ],
- [t, handleSubmitSingle, submittingIds, currentUserId],
- );
-
- const handleSearch = useCallback((query: Record<SearchParamNames, string>) => {
- setSearch(query);
- }, []);
-
- const handleTabChange = useCallback(
- (_: React.SyntheticEvent, value: string) => {
- setTab(value as "miss" | "bad" | "expiry");
- setSelectedIds([]);
- },
- [],
- );
-
- const renderCurrentTab = () => {
- if (tab === "miss") {
- const items = filterBySearch(missItems);
- return (
- <SearchResults<StockIssueResult>
- items={items}
- columns={missColumns}
- pagingController={{ pageNum: 1, pageSize: 10 }}
- checkboxIds={selectedIds}
- setCheckboxIds={setSelectedIds}
- />
- );
- }
-
- if (tab === "bad") {
- const items = filterBySearch(badItems);
- return (
- <SearchResults<StockIssueResult>
- items={items}
- columns={badColumns}
- pagingController={{ pageNum: 1, pageSize: 10 }}
- checkboxIds={selectedIds}
- setCheckboxIds={setSelectedIds}
- />
- );
- }
-
- const items = filterBySearch(expiryItems);
- return (
- <SearchResults<ExpiryItemResult>
- items={items}
- columns={expiryColumns}
- pagingController={{ pageNum: 1, pageSize: 10 }}
- checkboxIds={selectedIds}
- setCheckboxIds={setSelectedIds}
- />
- );
- };
-
- return (
- <Box>
- <Tabs value={tab} onChange={handleTabChange} sx={{ mb: 2 }}>
- <Tab value="miss" label={t("Miss Item")} />
- <Tab value="bad" label={t("Bad Item")} />
- <Tab value="expiry" label={t("Expiry Item")} />
- </Tabs>
-
- <SearchBox<SearchParamNames>
- criteria={searchCriteria}
- onSearch={handleSearch}
- />
-
- <Box sx={{ display: "flex", justifyContent: "flex-end", mb: 1 }}>
- <Button
- variant="contained"
- color="primary"
- onClick={handleSubmitSelected}
- disabled={batchSubmitting || !currentUserId}
- >
- {batchSubmitting ? tab === "miss" ? t("Processing...") : tab === "bad" ? t("Disposing...") : t("Disposing...") : t("Batch Disposed All")}
- </Button>
- </Box>
-
- {renderCurrentTab()}
- </Box>
- );
- };
-
- export default SearchPage;
|