diff --git a/src/app/api/pickOrder/actions.ts b/src/app/api/pickOrder/actions.ts index 2dcde47..ed22191 100644 --- a/src/app/api/pickOrder/actions.ts +++ b/src/app/api/pickOrder/actions.ts @@ -457,6 +457,7 @@ export interface LaneRow { export interface LaneBtn { truckLanceCode: string; + loadingSequence?: number | null; unassigned: number; total: number; } @@ -636,6 +637,7 @@ export async function assignByLane( storeId: string, truckLanceCode: string, truckDepartureTime?: string, + loadingSequence?: number | null, requiredDate?: string ): Promise { const response = await serverFetchJson( @@ -650,6 +652,7 @@ export async function assignByLane( storeId, truckLanceCode, truckDepartureTime, + loadingSequence, requiredDate, }), } diff --git a/src/components/FinishedGoodSearch/FinishedGoodFloorLanePanel.tsx b/src/components/FinishedGoodSearch/FinishedGoodFloorLanePanel.tsx index a91178e..995c74b 100644 --- a/src/components/FinishedGoodSearch/FinishedGoodFloorLanePanel.tsx +++ b/src/components/FinishedGoodSearch/FinishedGoodFloorLanePanel.tsx @@ -1,11 +1,11 @@ "use client"; import { Box, Button, Grid, Stack, Typography, Select, MenuItem, FormControl, InputLabel ,Tooltip} from "@mui/material"; -import { useCallback, useEffect, useState, useRef } from "react"; +import { useCallback, useEffect, useMemo, useState, useRef } from "react"; import { useTranslation } from "react-i18next"; import { useSession } from "next-auth/react"; import { SessionWithTokens } from "@/config/authConfig"; -import { fetchStoreLaneSummary,fetchReleasedDoPickOrdersForSelection,fetchReleasedDoPickOrderCountByStore, assignByLane, type StoreLaneSummary } from "@/app/api/pickOrder/actions"; +import { fetchStoreLaneSummary,fetchReleasedDoPickOrdersForSelection,fetchReleasedDoPickOrderCountByStore, assignByLane, type StoreLaneSummary, type LaneRow, type LaneBtn } from "@/app/api/pickOrder/actions"; import Swal from "sweetalert2"; import dayjs from "dayjs"; import ReleasedDoPickOrderSelectModal from "./ReleasedDoPickOrderSelectModal"; @@ -15,6 +15,9 @@ interface Props { onSwitchToDetailTab?: () => void; } +type LaneSlot4F = { truckDepartureTime: string; lane: LaneBtn }; +type TruckGroup4F = { truckLanceCode: string; slots: (LaneSlot4F & { sequenceIndex: number })[] }; + const FinishedGoodFloorLanePanel: React.FC = ({ onPickOrderAssigned, onSwitchToDetailTab }) => { const { t } = useTranslation("pickOrder"); const { data: session } = useSession() as { data: SessionWithTokens | null }; @@ -41,6 +44,7 @@ const [defaultDateScope, setDefaultDateScope] = useState<"today" | "before">("to const [selectedDate, setSelectedDate] = useState("today"); const [releaseType, setReleaseType] = useState("batch"); + const [ticketFloor, setTicketFloor] = useState<"2/F" | "4/F">("2/F"); const startFullTimer = () => { if (typeof window === "undefined") return; const key = "__FG_FLOOR_FULL_TIMER_STARTED__" as const; @@ -166,6 +170,7 @@ const [defaultDateScope, setDefaultDateScope] = useState<"today" | "before">("to storeId: string, truckDepartureTime: string, truckLanceCode: string, + loadingSequence: number | null | undefined, requiredDate: string ) => { @@ -183,7 +188,7 @@ const [defaultDateScope, setDefaultDateScope] = useState<"today" | "before">("to } setIsAssigning(true); try { - const res = await assignByLane(currentUserId, storeId, truckLanceCode, truckDepartureTime, dateParam); + const res = await assignByLane(currentUserId, storeId, truckLanceCode, truckDepartureTime, loadingSequence ?? null, dateParam); if (res.code === "SUCCESS") { console.log(" Successfully assigned pick order from lane", truckLanceCode); @@ -229,6 +234,7 @@ const handleLaneButtonClick = useCallback(async ( storeId: string, truckDepartureTime: string, truckLanceCode: string, + loadingSequence: number | null | undefined, requiredDate: string, unassigned: number, total: number @@ -252,6 +258,7 @@ const handleLaneButtonClick = useCallback(async (

${t("Store")}: ${storeId}

${t("Lane Code")}: ${truckLanceCode}

+ ${loadingSequence != null ? `

${t("Loading Sequence")}: ${loadingSequence}

` : ``}

${t("Departure Time")}: ${truckDepartureTime}

${t("Required Date")}: ${dateDisplay}

${t("Available Orders")}: ${unassigned}/${total}

@@ -268,7 +275,7 @@ const handleLaneButtonClick = useCallback(async ( // Only proceed if user confirmed if (result.isConfirmed) { - await handleAssignByLane(storeId, truckDepartureTime, truckLanceCode, requiredDate); + await handleAssignByLane(storeId, truckDepartureTime, truckLanceCode, loadingSequence, requiredDate); } }, [handleAssignByLane, t]); @@ -290,6 +297,31 @@ const getDateLabel = (offset: number) => { return flattened; }; + /** 4/F:依車線匯總,同車多筆依 API 出現順序為裝載序(出發時間相同時仍可分序)。 */ + const truckGroups4F = useMemo((): TruckGroup4F[] => { + const rows = summary4F?.rows as LaneRow[] | undefined; + if (!rows?.length) return []; + const map = new Map(); + for (const row of rows) { + for (const lane of row.lanes) { + const code = lane.truckLanceCode; + const list = map.get(code); + const slot: LaneSlot4F = { truckDepartureTime: row.truckDepartureTime, lane }; + if (list) list.push(slot); + else map.set(code, [slot]); + } + } + return Array.from(map.entries()) + .sort(([a], [b]) => a.localeCompare(b)) + .map(([truckLanceCode, slots]) => ({ + truckLanceCode, + slots: slots + .slice() + .sort((a, b) => (a.lane.loadingSequence ?? 999) - (b.lane.loadingSequence ?? 999)) + .map((s: LaneSlot4F, i: number) => ({ ...s, sequenceIndex: i + 1 })), + })); + }, [summary4F?.rows]); + return ( {/* Date Selector Dropdown and Legend */} @@ -348,6 +380,21 @@ const getDateLabel = (offset: number) => { + + + {t("Floor ticket")} + + + { {t("EDT - Lane Code (Unassigned/Total)")} - - - - - - - - {/* Grid containing both floors */} {/* 2/F 楼层面板 */} + {ticketFloor === "2/F" && ( {/* Floor Label */} @@ -416,55 +456,71 @@ const getDateLabel = (offset: number) => { ) : ( - {flattenRows(summary2F.rows).map((item, idx) => ( - + {summary2F.rows.map((row) => ( + - {/* Time on the left */} - - {item.truckDepartureTime} + {row.truckDepartureTime} - - {/* Single Button on the right */} - + {row.lanes.map((lane) => ( + + ))} + ))} @@ -473,8 +529,8 @@ const getDateLabel = (offset: number) => { - - {/* 4/F 楼层面板 */} +)} +{ticketFloor === "4/F" && ( {/* Floor Label */} @@ -501,7 +557,7 @@ const getDateLabel = (offset: number) => { > {isLoadingSummary ? ( {t("Loading...")} - ) : !summary4F?.rows || summary4F.rows.length === 0 ? ( + ) : truckGroups4F.length === 0 ? ( { ) : ( - {flattenRows(summary4F.rows).map((item, idx) => ( - + {truckGroups4F.map(({ truckLanceCode, slots }) => ( + - {/* Time on the left */} - - {item.truckDepartureTime} + {truckLanceCode} - - {/* Single Button on the right */} - + {slots.map((slot) => ( + + ))} + ))} @@ -573,7 +643,7 @@ const getDateLabel = (offset: number) => { - +)} {/* 4/F Today default lane*/} @@ -632,7 +702,9 @@ const getDateLabel = (offset: number) => { {t("Released orders not yet completed - click lane to select and assign")} + + {ticketFloor === "2/F" && ( @@ -699,8 +771,8 @@ const getDateLabel = (offset: number) => { - - {/* 4/F 未完成已放單 - 與上方相同 UI */} + )} + {ticketFloor === "4/F" && ( @@ -767,7 +839,10 @@ const getDateLabel = (offset: number) => { + )} + + {t("Truck X")}