|
- "use client";
- import React, { useCallback, useEffect, useState } from "react";
- import {
- Box,
- Button,
- Card,
- Stack,
- Typography,
- Chip,
- CircularProgress,
- Grid,
- } from "@mui/material";
- import ArrowBackIcon from "@mui/icons-material/ArrowBack";
- import { useTranslation } from "react-i18next";
- import { fetchAllJoPickOrders, AllJoPickOrderResponse } from "@/app/api/jo/actions";
- import JobPickExecution from "./newJobPickExecution";
- import SearchBox, { Criterion } from "../SearchBox";
- import dayjs from "dayjs";
- import type { PrinterCombo } from "@/app/api/settings/printer";
-
- interface Props {
- /** Reserved for tabs parity with Jodetail; not used in workbench list yet. */
- onSwitchToRecordTab?: () => void;
- printerCombo: PrinterCombo[];
- }
-
- const chipSx = {
- flexShrink: 0,
- height: 28,
- borderRadius: "14px",
- "& .MuiChip-label": {
- typography: "body2",
- px: 1.25,
- lineHeight: 1.2,
- },
- } as const;
-
- /** Jo workbench: same list + detail flow as Jodetail `JoPickOrderList`, detail uses `JoWorkbench/newJobPickExecution`. */
- const JoPickOrderList: React.FC<Props> = ({ printerCombo }) => {
- const { t } = useTranslation(["common", "jo"]);
- const today = dayjs().format("YYYY-MM-DD");
- const [loading, setLoading] = useState(false);
- const [pickOrders, setPickOrders] = useState<AllJoPickOrderResponse[]>([]);
- const [selectedPickOrderId, setSelectedPickOrderId] = useState<number | undefined>(undefined);
- const [selectedJobOrderId, setSelectedJobOrderId] = useState<number | undefined>(undefined);
- const [searchQuery, setSearchQuery] = useState<Record<string, any>>({ planStart: today });
-
- type FloorFilter = "ALL" | "2F" | "3F" | "4F" | "NO_LOT";
-
- const searchCriteria: Criterion<any>[] = [
- { label: t("Job Order Code"), paramName: "jobOrderCode", type: "text" },
- { label: t("Pick Order"), paramName: "pickOrderCode", type: "text" },
- { label: t("Item Name"), paramName: "itemName", type: "text" },
- {
- label: t("Job Order Type"),
- paramName: "bomDescription",
- type: "select-labelled",
- options: [
- { label: t("FG"), value: "FG" },
- { label: t("WIP"), value: "WIP" },
- ],
- },
- { label: t("Plan Start"), paramName: "planStart", type: "date" },
- {
- label: t("BOM Type"),
- paramName: "bomType",
- type: "select-labelled",
- options: [
- { label: t("Drink"), value: "drink" },
- { label: t("Powder Mixture"), value: "Powder_Mixture" },
- { label: t("Other"), value: "other" },
- ],
- },
- {
- label: t("Floor"),
- paramName: "floor",
- type: "select-labelled",
- options: [
- { label: "2F", value: "2F" },
- { label: "3F", value: "3F" },
- { label: "4F", value: "4F" },
- { label: t("No Lot"), value: "NO_LOT" },
- ],
- },
- ];
-
- const selectedFloor: FloorFilter = (() => {
- const floor = String(searchQuery.floor || "ALL");
- if (floor === "2F" || floor === "3F" || floor === "4F" || floor === "NO_LOT") {
- return floor;
- }
- return "ALL";
- })();
-
- const fetchPickOrders = useCallback(async (query?: Record<string, any>) => {
- setLoading(true);
- try {
- const currentQuery = query ?? { planStart: today };
- const bomTypeValue = String(currentQuery.bomType || "All");
- const floorValue = String(currentQuery.floor || "ALL");
- const isAllOption = (value: string) => {
- const normalized = value.trim().toLowerCase();
- return normalized === "" || normalized === "all";
- };
- const typeParam = isAllOption(bomTypeValue) ? undefined : bomTypeValue;
- const floorParam = isAllOption(floorValue) ? undefined : floorValue;
- const jobOrderCode = String(currentQuery.jobOrderCode || "").trim();
- const pickOrderCode = String(currentQuery.pickOrderCode || "").trim();
- const itemName = String(currentQuery.itemName || "").trim();
- const bomDescription = String(currentQuery.bomDescription || "").trim();
- const planStart = String(currentQuery.planStart || "").trim();
-
- const data = await fetchAllJoPickOrders(typeParam, floorParam, {
- jobOrderCode: jobOrderCode || undefined,
- pickOrderCode: pickOrderCode || undefined,
- itemName: itemName || undefined,
- bomDescription: isAllOption(bomDescription) ? undefined : bomDescription,
- planStart: planStart || undefined,
- });
- setPickOrders(Array.isArray(data) ? data : []);
- } catch (e) {
- console.error(e);
- setPickOrders([]);
- } finally {
- setLoading(false);
- }
- }, [today]);
-
- const handleSearch = useCallback((query: Record<string, any>) => {
- const nextQuery = { ...query };
- setSearchQuery(nextQuery);
- void fetchPickOrders(nextQuery);
- }, [fetchPickOrders]);
-
- const handleSearchReset = useCallback(() => {
- const resetQuery = {};
- setSearchQuery(resetQuery);
- void fetchPickOrders(resetQuery);
- }, [fetchPickOrders]);
-
- useEffect(() => {
- void fetchPickOrders({ planStart: today });
- }, [fetchPickOrders, today]);
- const handleBackToList = useCallback(() => {
- setSelectedPickOrderId(undefined);
- setSelectedJobOrderId(undefined);
- void fetchPickOrders(searchQuery);
- }, [fetchPickOrders, searchQuery]);
- if (selectedPickOrderId !== undefined) {
- return (
- <Box>
- <Box sx={{ mb: 2 }}>
- <Button
- variant="outlined"
- onClick={handleBackToList}
- startIcon={<ArrowBackIcon />}
- >
- {t("Back to List")}
- </Button>
- </Box>
- <JobPickExecution
- filterArgs={{ pickOrderId: selectedPickOrderId, jobOrderId: selectedJobOrderId }}
- onBackToList={handleBackToList}
- printerCombo={printerCombo}
- />
- </Box>
- );
- }
-
- return (
- <Box>
- <Box sx={{ mb: 2 }}>
- <SearchBox
- criteria={searchCriteria}
- onSearch={handleSearch}
- onReset={handleSearchReset}
- />
- </Box>
- {loading ? (
- <Box sx={{ display: "flex", justifyContent: "center", p: 3 }}>
- <CircularProgress />
- </Box>
- ) : (
- <Box>
- <Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
- {t("Total pick orders")}: {pickOrders.length}
- </Typography>
-
- <Grid container spacing={2} alignItems="stretch">
- {pickOrders.map((pickOrder) => {
- const status = String(pickOrder.jobOrderStatus || "");
- const statusLower = status.toLowerCase();
- const statusColor =
- statusLower === "completed"
- ? "success"
- : statusLower === "pending" || statusLower === "processing"
- ? "primary"
- : "default";
-
- const finishedCount = pickOrder.finishedPickOLineCount ?? 0;
- const bomDescription = pickOrder.bomDescription
- ? String(pickOrder.bomDescription).trim()
- : "";
- const bomType = pickOrder.bomType
- ? String(pickOrder.bomType).trim()
- : "";
-
- return (
- <Grid
- key={pickOrder.id}
- item
- xs={12}
- sm={6}
- md={4}
- sx={{ display: "flex" }}
- >
- <Card
- sx={{
- width: "100%",
- height: "100%",
- display: "flex",
- flexDirection: "column",
- border: "1px solid",
- borderColor: "divider",
- borderRadius: 2,
- boxShadow: "none",
- }}
- >
- <Box
- sx={{
- p: 2,
- flexGrow: 1,
- display: "flex",
- flexDirection: "column",
- minHeight: 0,
- }}
- >
- <Typography
- variant="body1"
- color="text.primary"
- fontWeight={600}
- title={
- [pickOrder.itemCode, pickOrder.itemName]
- .filter(Boolean)
- .join(" ") || undefined
- }
- sx={{
- display: "-webkit-box",
- WebkitLineClamp: 2,
- WebkitBoxOrient: "vertical",
- overflow: "hidden",
- lineHeight: 1.35,
- }}
- >
- {[pickOrder.itemCode, pickOrder.itemName]
- .filter(Boolean)
- .join(" ") || "-"}
- </Typography>
-
- <Stack
- direction="row"
- alignItems="center"
- spacing={0.75}
- flexWrap="nowrap"
- useFlexGap
- sx={{
- mt: 0.75,
- minWidth: 0,
- overflowX: "auto",
- scrollbarWidth: "none",
- "&::-webkit-scrollbar": { display: "none" },
- }}
- >
- {bomDescription ? (
- <Chip
- size="small"
- label={t(bomDescription)}
- variant="outlined"
- sx={chipSx}
- />
- ) : null}
- {bomType ? (
- <Chip
- size="small"
- label={t(bomType)}
- color="info"
- variant="outlined"
- sx={chipSx}
- />
- ) : null}
- <Chip
- size="small"
- label={t(status)}
- color={statusColor as any}
- sx={chipSx}
- />
- </Stack>
-
- <Stack
- spacing={0.35}
- sx={{ mt: 0.75, color: "text.secondary" }}
- >
- <Typography variant="body2">
- {t("Pick Order")}: {pickOrder.pickOrderCode || "-"}
- </Typography>
- <Typography variant="body2">
- {t("Required Qty")}: {pickOrder.reqQty} ({pickOrder.uomName})
- </Typography>
-
- <Box>
- {selectedFloor === "ALL" ? (
- <>
- {pickOrder.floorPickCounts?.map(
- ({ floor, finishedCount, totalCount }) => (
- <Typography
- key={floor}
- variant="body2"
- color="text.secondary"
- component="span"
- sx={{ mr: 1 }}
- >
- {floor}: {finishedCount}/{totalCount}
- </Typography>
- ),
- )}
- {!!pickOrder.noLotPickCount && (
- <Typography
- key="NO_LOT"
- variant="body2"
- color="text.secondary"
- component="span"
- sx={{ mr: 1 }}
- >
- {t("No Lot")}:{" "}
- {pickOrder.noLotPickCount.finishedCount}/
- {pickOrder.noLotPickCount.totalCount}
- </Typography>
- )}
- </>
- ) : selectedFloor === "NO_LOT" ? (
- !!pickOrder.noLotPickCount && (
- <Typography
- key="NO_LOT"
- variant="body2"
- color="text.secondary"
- component="span"
- sx={{ mr: 1 }}
- >
- {t("No Lot")}:{" "}
- {pickOrder.noLotPickCount.finishedCount}/
- {pickOrder.noLotPickCount.totalCount}
- </Typography>
- )
- ) : (
- pickOrder.floorPickCounts
- ?.filter((c) => c.floor === selectedFloor)
- .map(({ floor, finishedCount, totalCount }) => (
- <Typography
- key={floor}
- variant="body2"
- color="text.secondary"
- component="span"
- sx={{ mr: 1 }}
- >
- {floor}: {finishedCount}/{totalCount}
- </Typography>
- ))
- )}
- </Box>
-
- {typeof pickOrder.suggestedFailCount === "number" &&
- pickOrder.suggestedFailCount > 0 && (
- <Typography variant="body2" color="error">
- {t("Suggested Fail")}: {pickOrder.suggestedFailCount}
- </Typography>
- )}
-
- {statusLower !== "pending" && finishedCount > 0 && (
- <Typography variant="body2" fontWeight={600} color="text.primary">
- {t("Finished lines")}: {finishedCount}
- </Typography>
- )}
- </Stack>
-
- <Stack sx={{ mt: "auto", pt: 1.5 }}>
- <Button
- variant="contained"
- size="small"
- sx={{ alignSelf: "flex-start" }}
- onClick={() => {
- setSelectedPickOrderId(pickOrder.pickOrderId ?? undefined);
- setSelectedJobOrderId(pickOrder.jobOrderId ?? undefined);
- }}
- >
- {t("View Details")}
- </Button>
- </Stack>
-
- <Typography
- variant="caption"
- color="text.secondary"
- sx={{ mt: 1.25 }}
- >
- {pickOrder.jobOrderCode || "-"}
- {" · "}
- {t("Lot No")}: {pickOrder.lotNo || "-"}
- </Typography>
- </Box>
- </Card>
- </Grid>
- );
- })}
- </Grid>
- </Box>
- )}
- </Box>
- );
- };
-
- export default JoPickOrderList;
|