From 9d59e597ef55db24a20062556e57987e68c89f3d Mon Sep 17 00:00:00 2001 From: "kelvin.yau" Date: Wed, 15 Oct 2025 18:51:31 +0800 Subject: [PATCH 1/2] FGPickOrder update --- .../FinishedGoodSearch/FGPickOrderCard.tsx | 9 +- .../FinishedGoodFloorLanePanel.tsx | 350 ++++++++++++++++++ .../FinishedGoodSearch/FinishedGoodSearch.tsx | 5 - 3 files changed, 358 insertions(+), 6 deletions(-) create mode 100644 src/components/FinishedGoodSearch/FinishedGoodFloorLanePanel.tsx diff --git a/src/components/FinishedGoodSearch/FGPickOrderCard.tsx b/src/components/FinishedGoodSearch/FGPickOrderCard.tsx index 1f705ea..a02aef7 100644 --- a/src/components/FinishedGoodSearch/FGPickOrderCard.tsx +++ b/src/components/FinishedGoodSearch/FGPickOrderCard.tsx @@ -6,16 +6,22 @@ import { useTranslation } from "react-i18next"; import QrCodeIcon from '@mui/icons-material/QrCode'; import { OUTPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; import dayjs from "dayjs"; +import FinishedGoodFloorLanePanel from "./FinishedGoodFloorLanePanel"; type Props = { fgOrder: FGPickOrderResponse; onQrCodeClick: (pickOrderId: number) => void; + onPickOrderAssigned?: () => void; }; -const FGPickOrderCard: React.FC = ({ fgOrder, onQrCodeClick }) => { +const FGPickOrderCard: React.FC = ({ fgOrder, onQrCodeClick, onPickOrderAssigned }) => { const { t } = useTranslation("pickOrder"); return ( + <> + + + @@ -102,6 +108,7 @@ const FGPickOrderCard: React.FC = ({ fgOrder, onQrCodeClick }) => { + ); }; diff --git a/src/components/FinishedGoodSearch/FinishedGoodFloorLanePanel.tsx b/src/components/FinishedGoodSearch/FinishedGoodFloorLanePanel.tsx new file mode 100644 index 0000000..b33b279 --- /dev/null +++ b/src/components/FinishedGoodSearch/FinishedGoodFloorLanePanel.tsx @@ -0,0 +1,350 @@ +"use client"; + +import { Box, Button, Grid, Stack, Typography, Select, MenuItem, FormControl, InputLabel } from "@mui/material"; +import { useCallback, useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useSession } from "next-auth/react"; +import { SessionWithTokens } from "@/config/authConfig"; +import { fetchStoreLaneSummary, assignByLane, type StoreLaneSummary } from "@/app/api/pickOrder/actions"; +import Swal from "sweetalert2"; +import dayjs from "dayjs"; + +interface Props { + onPickOrderAssigned?: () => void; +} + +const FinishedGoodFloorLanePanel: React.FC = ({ onPickOrderAssigned }) => { + const { t } = useTranslation("pickOrder"); + const { data: session } = useSession() as { data: SessionWithTokens | null }; + const currentUserId = session?.id ? parseInt(session.id) : undefined; + + const [summary2F, setSummary2F] = useState(null); + const [summary4F, setSummary4F] = useState(null); + const [isLoadingSummary, setIsLoadingSummary] = useState(false); + const [isAssigning, setIsAssigning] = useState(false); + const [selectedDate, setSelectedDate] = useState("today"); + + const loadSummaries = useCallback(async () => { + setIsLoadingSummary(true); + try { + const [s2, s4] = await Promise.all([ + fetchStoreLaneSummary("2/F"), + fetchStoreLaneSummary("4/F") + ]); + setSummary2F(s2); + setSummary4F(s4); + } catch (error) { + console.error("Error loading summaries:", error); + } finally { + setIsLoadingSummary(false); + } + }, []); + + useEffect(() => { + loadSummaries(); + }, [loadSummaries]); + + const handleAssignByLane = useCallback(async ( + storeId: string, + truckDepartureTime: string, + truckLanceCode: string + ) => { + if (!currentUserId) { + console.error("Missing user id in session"); + return; + } + + setIsAssigning(true); + try { + const res = await assignByLane(currentUserId, storeId, truckLanceCode, truckDepartureTime); + + if (res.code === "SUCCESS") { + console.log("✅ Successfully assigned pick order from lane", truckLanceCode); + window.dispatchEvent(new CustomEvent('pickOrderAssigned')); + loadSummaries(); // 刷新按钮状态 + onPickOrderAssigned?.(); + } else if (res.code === "USER_BUSY") { + Swal.fire({ + icon: "warning", + title: t("Warning"), + text: t("You already have a pick order in progess. Please complete it first before taking next pick order."), + confirmButtonText: t("Confirm"), + confirmButtonColor: "#8dba00" + }); + window.dispatchEvent(new CustomEvent('pickOrderAssigned')); + } else if (res.code === "NO_ORDERS") { + Swal.fire({ + icon: "info", + title: t("Info"), + text: t("No available pick order(s) for this lane."), + confirmButtonText: t("Confirm"), + confirmButtonColor: "#8dba00" + }); + } else { + console.log("ℹ️ Assignment result:", res.message); + } + } catch (error) { + console.error("❌ Error assigning by lane:", error); + Swal.fire({ + icon: "error", + title: t("Error"), + text: t("Error occurred during assignment."), + confirmButtonText: t("Confirm"), + confirmButtonColor: "#8dba00" + }); + } finally { + setIsAssigning(false); + } + }, [currentUserId, t, loadSummaries, onPickOrderAssigned]); + + const getDateLabel = (offset: number) => { + return dayjs().add(offset, 'day').format('YYYY-MM-DD'); + }; + + // Flatten rows to create one box per lane + const flattenRows = (rows: any[]) => { + const flattened: any[] = []; + rows.forEach(row => { + row.lanes.forEach((lane: any) => { + flattened.push({ + truckDepartureTime: row.truckDepartureTime, + lane: lane + }); + }); + }); + return flattened; + }; + + return ( + + {/* Date Selector Dropdown */} + + + {t("Select Date")} + + + + + {/* Grid containing both floors */} + + {/* 2/F 楼层面板 */} + + + {/* Floor Label */} + + 2/F + + + {/* Content Box */} + + {isLoadingSummary ? ( + Loading... + ) : !summary2F?.rows || summary2F.rows.length === 0 ? ( + + {t("No entries available")} + + ) : ( + + {flattenRows(summary2F.rows).map((item, idx) => ( + + + {/* Time on the left */} + + {item.truckDepartureTime} + + + {/* Single Button on the right */} + + + + ))} + + )} + + + + + {/* 4/F 楼层面板 */} + + + {/* Floor Label */} + + 4/F + + + {/* Content Box */} + + {isLoadingSummary ? ( + Loading... + ) : !summary4F?.rows || summary4F.rows.length === 0 ? ( + + {t("No entries available")} + + ) : ( + + {flattenRows(summary4F.rows).map((item, idx) => ( + + + {/* Time on the left */} + + {item.truckDepartureTime} + + + {/* Single Button on the right */} + + + + ))} + + )} + + + + + + ); +}; + +export default FinishedGoodFloorLanePanel; \ No newline at end of file diff --git a/src/components/FinishedGoodSearch/FinishedGoodSearch.tsx b/src/components/FinishedGoodSearch/FinishedGoodSearch.tsx index 9f9c4e2..71cdaa5 100644 --- a/src/components/FinishedGoodSearch/FinishedGoodSearch.tsx +++ b/src/components/FinishedGoodSearch/FinishedGoodSearch.tsx @@ -938,11 +938,6 @@ const handleAssignByLane = useCallback(async ( - - - - - {/* Tabs section - ✅ Move the click handler here */} Date: Wed, 15 Oct 2025 18:58:39 +0800 Subject: [PATCH 2/2] quick ui fix --- .../FinishedGoodSearch/FGPickOrderCard.tsx | 457 ++++++++++++++---- 1 file changed, 360 insertions(+), 97 deletions(-) diff --git a/src/components/FinishedGoodSearch/FGPickOrderCard.tsx b/src/components/FinishedGoodSearch/FGPickOrderCard.tsx index a02aef7..3ee7c1a 100644 --- a/src/components/FinishedGoodSearch/FGPickOrderCard.tsx +++ b/src/components/FinishedGoodSearch/FGPickOrderCard.tsx @@ -1,115 +1,378 @@ "use client"; -import { FGPickOrderResponse } from "@/app/api/pickOrder/actions"; -import { Box, Card, CardContent, Grid, Stack, TextField, Button } from "@mui/material"; +import { Box, Button, Grid, Stack, Typography, Select, MenuItem, FormControl, InputLabel, Card, CardContent } from "@mui/material"; +import { useCallback, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import QrCodeIcon from '@mui/icons-material/QrCode'; -import { OUTPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; +import { useSession } from "next-auth/react"; +import { SessionWithTokens } from "@/config/authConfig"; +import { fetchStoreLaneSummary, assignByLane, type StoreLaneSummary } from "@/app/api/pickOrder/actions"; +import Swal from "sweetalert2"; import dayjs from "dayjs"; -import FinishedGoodFloorLanePanel from "./FinishedGoodFloorLanePanel"; -type Props = { - fgOrder: FGPickOrderResponse; - onQrCodeClick: (pickOrderId: number) => void; +interface Props { onPickOrderAssigned?: () => void; -}; +} -const FGPickOrderCard: React.FC = ({ fgOrder, onQrCodeClick, onPickOrderAssigned }) => { +const FinishedGoodFloorLanePanel: React.FC = ({ onPickOrderAssigned }) => { const { t } = useTranslation("pickOrder"); + const { data: session } = useSession() as { data: SessionWithTokens | null }; + const currentUserId = session?.id ? parseInt(session.id) : undefined; - return ( - <> - - - - - - - - - - - - - + const [summary2F, setSummary2F] = useState(null); + const [summary4F, setSummary4F] = useState(null); + const [isLoadingSummary, setIsLoadingSummary] = useState(false); + const [isAssigning, setIsAssigning] = useState(false); + const [selectedDate, setSelectedDate] = useState("today"); - - - + const loadSummaries = useCallback(async () => { + setIsLoadingSummary(true); + try { + const [s2, s4] = await Promise.all([ + fetchStoreLaneSummary("2/F"), + fetchStoreLaneSummary("4/F") + ]); + setSummary2F(s2); + setSummary4F(s4); + } catch (error) { + console.error("Error loading summaries:", error); + } finally { + setIsLoadingSummary(false); + } + }, []); - - - - - - - - - - - - - - - - - - + useEffect(() => { + loadSummaries(); + }, [loadSummaries]); - + const handleAssignByLane = useCallback(async ( + storeId: string, + truckDepartureTime: string, + truckLanceCode: string + ) => { + if (!currentUserId) { + console.error("Missing user id in session"); + return; + } + + setIsAssigning(true); + try { + const res = await assignByLane(currentUserId, storeId, truckLanceCode, truckDepartureTime); + + if (res.code === "SUCCESS") { + console.log("✅ Successfully assigned pick order from lane", truckLanceCode); + window.dispatchEvent(new CustomEvent('pickOrderAssigned')); + loadSummaries(); // 刷新按钮状态 + onPickOrderAssigned?.(); + } else if (res.code === "USER_BUSY") { + Swal.fire({ + icon: "warning", + title: t("Warning"), + text: t("You already have a pick order in progess. Please complete it first before taking next pick order."), + confirmButtonText: t("Confirm"), + confirmButtonColor: "#8dba00" + }); + window.dispatchEvent(new CustomEvent('pickOrderAssigned')); + } else if (res.code === "NO_ORDERS") { + Swal.fire({ + icon: "info", + title: t("Info"), + text: t("No available pick order(s) for this lane."), + confirmButtonText: t("Confirm"), + confirmButtonColor: "#8dba00" + }); + } else { + console.log("ℹ️ Assignment result:", res.message); + } + } catch (error) { + console.error("❌ Error assigning by lane:", error); + Swal.fire({ + icon: "error", + title: t("Error"), + text: t("Error occurred during assignment."), + confirmButtonText: t("Confirm"), + confirmButtonColor: "#8dba00" + }); + } finally { + setIsAssigning(false); + } + }, [currentUserId, t, loadSummaries, onPickOrderAssigned]); + + const getDateLabel = (offset: number) => { + return dayjs().add(offset, 'day').format('YYYY-MM-DD'); + }; + + // Flatten rows to create one box per lane + const flattenRows = (rows: any[]) => { + const flattened: any[] = []; + rows.forEach(row => { + row.lanes.forEach((lane: any) => { + flattened.push({ + truckDepartureTime: row.truckDepartureTime, + lane: lane + }); + }); + }); + return flattened; + }; + + return ( + + + {/* Date Selector Dropdown */} + + + {t("Select Date")} + + + + {/* Grid containing both floors */} + + {/* 2/F 楼层面板 */} + + + {/* Floor Label */} + + + 2/F + + + + {/* Content Box */} + + {isLoadingSummary ? ( + Loading... + ) : !summary2F?.rows || summary2F.rows.length === 0 ? ( + + {t("No entries available")} + + ) : ( + + {flattenRows(summary2F.rows).slice(0, 4).map((item, idx) => ( + + + {/* Time on the left */} + + {item.truckDepartureTime} + + + {/* Single Button on the right */} + + + + ))} + + )} + + + + + {/* 4/F 楼层面板 */} + + + {/* Floor Label */} + + + 4/F + + + + {/* Content Box */} + + {isLoadingSummary ? ( + Loading... + ) : !summary4F?.rows || summary4F.rows.length === 0 ? ( + + {t("No entries available")} + + ) : ( + + {flattenRows(summary4F.rows).slice(0, 4).map((item, idx) => ( + + + {/* Time on the left */} + + {item.truckDepartureTime} + + + {/* Single Button on the right */} + + + + ))} + + )} + + + + - ); }; -export default FGPickOrderCard; \ No newline at end of file +export default FinishedGoodFloorLanePanel; \ No newline at end of file