|
- "use client";
-
- import {
- Autocomplete,
- Badge,
- Box,
- Button,
- CircularProgress,
- Tab,
- Tabs,
- TextField,
- Tooltip,
- Typography,
- } from "@mui/material";
- import React, { Suspense } from "react";
- import { usePathname, useRouter, useSearchParams } from "next/navigation";
- import DoWorkbenchPickShell from "./DoWorkbenchPickShell";
- import type { PrinterCombo } from "@/app/api/settings/printer";
- import GoodPickExecutionWorkbenchRecord from "./GoodPickExecutionWorkbenchRecord";
- import { useTranslation } from "react-i18next";
- import WorkbenchTicketReleaseTableTab from "./WorkbenchTicketReleaseTable";
- import { Stack } from "@mui/system";
- import Swal from "sweetalert2";
- import { printDNWorkbench } from "@/app/api/do/actions";
- import {
- fetchWorkbenchEtraLaneSummary,
- fetchWorkbenchReleasedDoPickOrdersForSelectionToday,
- type WorkbenchEtraShopLaneGroup,
- } from "@/app/api/doworkbench/actions";
- import FinishedGoodCartonDashboardTab from "../FinishedGoodSearch/FinishedGoodCartonDashboardTab";
- import TruckRoutingSummaryTabWorkbench from "./TruckRoutingSummaryTabWorkbench";
-
- const ALLOWED_WORKBENCH_TABS = new Set([0, 1, 2, 3, 4, 5, 6]);
-
- /** Backend Etra summary: each lane `total` = distinct incomplete (`pending`/`released`) `delivery_order_pick_order` rows for that day. */
- function sumIncompleteEtraDopoTickets(groups: WorkbenchEtraShopLaneGroup[]): number {
- let n = 0;
- for (const g of groups) {
- for (const lane of g.lanes) {
- n += Number(lane.total) || 0;
- }
- }
- return n;
- }
-
- type Props = {
- defaultTabIndex?: 0 | 1;
- printerCombo?: PrinterCombo[];
- };
-
- function TabPanel(props: { value: number; index: number; children: React.ReactNode }) {
- const { value, index, children } = props;
- if (value !== index) return null;
- return <Box sx={{ pt: 2 }}>{children}</Box>;
- }
-
- const DoWorkbenchTabsInner: React.FC<Props> = ({ defaultTabIndex = 0, printerCombo = [] }) => {
- const searchParams = useSearchParams();
- const router = useRouter();
- const pathname = usePathname();
-
- const urlTabStr = searchParams.get("tab");
- const urlTicketRaw = searchParams.get("ticketNo");
- const urlTicketNo =
- urlTicketRaw && urlTicketRaw.trim() !== ""
- ? decodeURIComponent(urlTicketRaw.trim())
- : null;
- const urlTargetDateRaw = searchParams.get("targetDate");
- const urlTargetDate =
- urlTargetDateRaw && urlTargetDateRaw.trim() !== ""
- ? decodeURIComponent(urlTargetDateRaw.trim())
- : null;
-
- const [tab, setTab] = React.useState<number>(defaultTabIndex);
- const [a4Printer, setA4Printer] = React.useState<PrinterCombo | null>(null);
- const [labelPrinter, setLabelPrinter] = React.useState<PrinterCombo | null>(null);
- const [releasedOrderCount, setReleasedOrderCount] = React.useState(0);
- const [etraIncompleteDopoCount, setEtraIncompleteDopoCount] = React.useState(0);
- const { t } = useTranslation( );
- const a4Printers = React.useMemo(
- () => (printerCombo || []).filter((printer) => printer.type === "A4"),
- [printerCombo],
- );
- const labelPrinters = React.useMemo(
- () => (printerCombo || []).filter((printer) => printer.type === "Label"),
- [printerCombo],
- );
-
- const refreshWorkbenchCounts = React.useCallback(async () => {
- const [releasedRes, etraRes] = await Promise.allSettled([
- fetchWorkbenchReleasedDoPickOrdersForSelectionToday(),
- fetchWorkbenchEtraLaneSummary(),
- ]);
- if (releasedRes.status === "fulfilled") {
- setReleasedOrderCount(releasedRes.value.length);
- } else {
- console.error("Error fetching workbench released order count:", releasedRes.reason);
- setReleasedOrderCount(0);
- }
- if (etraRes.status === "fulfilled") {
- setEtraIncompleteDopoCount(sumIncompleteEtraDopoTickets(etraRes.value));
- } else {
- console.error("Error fetching workbench Etra incomplete count:", etraRes.reason);
- setEtraIncompleteDopoCount(0);
- }
- }, []);
-
- React.useEffect(() => {
- void refreshWorkbenchCounts();
- }, [refreshWorkbenchCounts]);
-
- React.useEffect(() => {
- const onAssigned = () => {
- void refreshWorkbenchCounts();
- };
- window.addEventListener("pickOrderAssigned", onAssigned);
- return () => window.removeEventListener("pickOrderAssigned", onAssigned);
- }, [refreshWorkbenchCounts]);
-
- /** Opening Etra tab refreshes badge (completion does not always dispatch `pickOrderAssigned`). */
- const etraTabMountSkipRef = React.useRef(false);
- React.useEffect(() => {
- if (!etraTabMountSkipRef.current) {
- etraTabMountSkipRef.current = true;
- return;
- }
- if (tab === 1) void refreshWorkbenchCounts();
- }, [tab, refreshWorkbenchCounts]);
-
- React.useEffect(() => {
- if (urlTabStr == null || urlTabStr === "") return;
- const n = parseInt(urlTabStr, 10);
- if (!Number.isNaN(n) && ALLOWED_WORKBENCH_TABS.has(n)) {
- setTab(n);
- }
- }, [urlTabStr]);
-
- const handleTabChange = React.useCallback(
- (_: React.SyntheticEvent, newTab: number) => {
- setTab(newTab);
- const params = new URLSearchParams(searchParams.toString());
- params.set("tab", String(newTab));
- /* ticketNo / targetDate deep-link only for "Finished Good Record" (mine) */
- if (newTab !== 2) {
- params.delete("ticketNo");
- params.delete("targetDate");
- }
- const qs = params.toString();
- router.replace(qs ? `${pathname}?${qs}` : pathname, { scroll: false });
- },
- [pathname, router, searchParams],
- );
-
- const handleAllDraft = React.useCallback(async () => {
- try {
- if (!a4Printer) {
- await Swal.fire({
- position: "bottom-end",
- icon: "warning",
- text: t("Please select a printer first"),
- showConfirmButton: false,
- timer: 1500,
- });
- return;
- }
-
- const releasedOrders = await fetchWorkbenchReleasedDoPickOrdersForSelectionToday();
- if (releasedOrders.length === 0) {
- await Swal.fire({
- title: "",
- text: t("No released pick order records found."),
- icon: "info",
- });
- return;
- }
-
- const confirmResult = await Swal.fire({
- title: t("Batch Print"),
- text: `${t("Confirm print: (")}${releasedOrders.length}${t("piece(s))")}`,
- icon: "question",
- showCancelButton: true,
- confirmButtonText: t("Confirm"),
- cancelButtonText: t("Cancel"),
- confirmButtonColor: "#8dba00",
- cancelButtonColor: "#F04438",
- });
- if (!confirmResult.isConfirmed) return;
-
- Swal.fire({
- title: t("Printing..."),
- text: t("Please wait..."),
- allowOutsideClick: false,
- allowEscapeKey: false,
- didOpen: () => Swal.showLoading(),
- });
-
- for (const order of releasedOrders) {
- const printRequest = {
- printerId: a4Printer.id,
- printQty: 1,
- isDraft: true,
- numOfCarton: 0,
- deliveryOrderPickOrderId: order.id,
- };
- const response = await printDNWorkbench(printRequest);
- if (!response.success) {
- console.error(`Workbench print draft failed for deliveryOrderPickOrderId ${order.id}:`, response.message);
- }
- }
-
- Swal.close();
- await Swal.fire({
- position: "bottom-end",
- icon: "success",
- text: t("Printed Successfully."),
- showConfirmButton: false,
- timer: 1500,
- });
- await refreshWorkbenchCounts();
- } catch (error) {
- Swal.close();
- console.error("Error in workbench handleAllDraft:", error);
- await Swal.fire({
- icon: "error",
- text: t("An error occurred during batch print"),
- });
- }
- }, [a4Printer, t, refreshWorkbenchCounts]);
-
- return (
- <Box>
- <Stack
- direction="row"
- spacing={2}
- sx={{
- alignItems: "center",
- justifyContent: "flex-end",
- flexWrap: "wrap",
- rowGap: 1,
- mb: 1,
- }}
- >
- <Typography variant="body2" sx={{ minWidth: "fit-content" }}>
- {t("A4 Printer")}:
- </Typography>
- <Autocomplete
- options={a4Printers}
- getOptionLabel={(option) => option.name || option.label || option.code || `Printer ${option.id}`}
- value={a4Printer}
- onChange={(_, newValue) => setA4Printer(newValue)}
- sx={{ minWidth: 200 }}
- size="small"
- renderInput={(params) => (
- <TextField
- {...params}
- placeholder={t("A4 Printer")}
- inputProps={{ ...params.inputProps, readOnly: true }}
- />
- )}
- />
- <Typography variant="body2" sx={{ minWidth: "fit-content" }}>
- {t("Label Printer")}:
- </Typography>
- <Autocomplete
- options={labelPrinters}
- getOptionLabel={(option) => option.name || option.label || option.code || `Printer ${option.id}`}
- value={labelPrinter}
- onChange={(_, newValue) => setLabelPrinter(newValue)}
- sx={{ minWidth: 200 }}
- size="small"
- renderInput={(params) => (
- <TextField
- {...params}
- placeholder={t("Label Printer")}
- inputProps={{ ...params.inputProps, readOnly: true }}
- />
- )}
- />
- <Button
- variant="contained"
- onClick={() => void handleAllDraft()}
- >
- {`${t("Print All Draft")} (${releasedOrderCount})`}
- </Button>
- </Stack>
- <Tabs
- value={tab}
- onChange={handleTabChange}
- sx={{
- borderBottom: 1,
- borderColor: "divider",
- "& .MuiTabs-flexContainer": {
- columnGap: 2,
- rowGap: 1,
- },
- /* 否則 Tab 內 overflow:hidden 會把 Badge 數字裁成紅點 */
- "& .MuiTab-root": {
- overflow: "visible",
- minWidth: "auto",
- px: 2,
- },
- }}
- >
- <Tab label={t("Pick Order Detail")} value={0} />
- <Tab
- value={1}
- sx={{
- overflow: "visible",
- /* 徽章在標籤右側外凸,預留空間避免與下一個 Tab 貼死 */
- pr: etraIncompleteDopoCount > 99 ? 5 : etraIncompleteDopoCount > 0 ? 4 : 2,
- }}
- label={
- <Tooltip
- title={
- etraIncompleteDopoCount > 0
- ? t("Etra incomplete badge tooltip", { count: etraIncompleteDopoCount })
- : t("Etra incomplete badge tooltip none")
- }
- >
- <Box component="span" sx={{ display: "inline-flex", alignItems: "center" }}>
- <Badge
- color="error"
- variant="standard"
- badgeContent={etraIncompleteDopoCount > 99 ? "99+" : etraIncompleteDopoCount}
- invisible={etraIncompleteDopoCount === 0}
- sx={{
- "& .MuiBadge-badge": {
- fontWeight: 800,
- fontSize: "0.7rem",
- minWidth: 18,
- height: 18,
- lineHeight: "18px",
- px: 0.5,
- right: -8,
- top: 2,
- },
- }}
- >
- <Typography
- component="span"
- variant="inherit"
- sx={{ pr: etraIncompleteDopoCount > 0 ? 1 : 0 }}
- >
- {t("Etra Pick Order Detail")}
- </Typography>
- </Badge>
- </Box>
- </Tooltip>
- }
- />
- <Tab label={t("Finished Good Record")} value={2} />
- <Tab label={t("Finished Good Record (All)")} value={3} />
- <Tab label={t("Ticket Release Table")} value={4} />
- <Tab label={t("成品出倉出箱數量")} value={5} />
- <Tab label={t("送貨路線摘要")} value={6} />
- </Tabs>
-
- <TabPanel value={tab} index={0}>
- <DoWorkbenchPickShell laneMode="normal" />
- </TabPanel>
- <TabPanel value={tab} index={1}>
- <DoWorkbenchPickShell laneMode="etra" />
- </TabPanel>
- <TabPanel value={tab} index={2}>
- <GoodPickExecutionWorkbenchRecord
- key={`workbench-record-mine-${urlTicketNo ?? ""}-${urlTargetDate ?? ""}`}
- printerCombo={printerCombo}
- listScope="mine"
- a4Printer={a4Printer}
- labelPrinter={labelPrinter}
- initialTicketNo={urlTicketNo}
- initialTargetDate={urlTargetDate}
- />
- </TabPanel>
- <TabPanel value={tab} index={3}>
- <GoodPickExecutionWorkbenchRecord
- printerCombo={printerCombo}
- listScope="all"
- a4Printer={a4Printer}
- labelPrinter={labelPrinter}
- />
- </TabPanel>
- <TabPanel value={tab} index={4}>
- <WorkbenchTicketReleaseTableTab />
- </TabPanel>
- <TabPanel value={tab} index={5}>
- <FinishedGoodCartonDashboardTab mode="workbench" />
- </TabPanel>
- <TabPanel value={tab} index={6}>
- <TruckRoutingSummaryTabWorkbench />
- </TabPanel>
-
- </Box>
- );
- };
-
- const DoWorkbenchTabs: React.FC<Props> = (props) => (
- <Suspense
- fallback={
- <Box sx={{ display: "flex", justifyContent: "center", p: 4 }}>
- <CircularProgress />
- </Box>
- }
- >
- <DoWorkbenchTabsInner {...props} />
- </Suspense>
- );
-
- export default DoWorkbenchTabs;
|