diff --git a/src/components/DoSearch/DoReplenishmentTab.tsx b/src/components/DoSearch/DoReplenishmentTab.tsx index 97976b4..17e014a 100644 --- a/src/components/DoSearch/DoReplenishmentTab.tsx +++ b/src/components/DoSearch/DoReplenishmentTab.tsx @@ -151,6 +151,66 @@ function lineUomDisplay(line?: DoDetailLine | null): string { return (line.shortUom ?? line.uomCode ?? line.uom ?? "").trim(); } +type DraftDoGroup = { + sourceDoId: number; + sourceDoCode: string; + rows: ReplenishmentDraftRow[]; +}; + +type DraftShopGroup = { + shopKey: string; + shopCode: string; + shopName?: string; + dos: DraftDoGroup[]; +}; + +function draftShopGroupKey(row: ReplenishmentDraftRow): string { + return row.shopCode?.trim() || row.shopName?.trim() || "—"; +} + +function groupDraftRowsByShopAndDo(rows: ReplenishmentDraftRow[]): DraftShopGroup[] { + const shopMap = new Map(); + + for (const row of rows) { + const shopKey = draftShopGroupKey(row); + let shopGroup = shopMap.get(shopKey); + if (!shopGroup) { + shopGroup = { + shopKey, + shopCode: row.shopCode?.trim() || "", + shopName: row.shopName, + dos: [], + }; + shopMap.set(shopKey, shopGroup); + } + + let doGroup = shopGroup.dos.find((group) => group.sourceDoId === row.sourceDoId); + if (!doGroup) { + doGroup = { + sourceDoId: row.sourceDoId, + sourceDoCode: row.sourceDoCode, + rows: [], + }; + shopGroup.dos.push(doGroup); + } + doGroup.rows.push(row); + } + + return Array.from(shopMap.values()) + .sort((a, b) => a.shopKey.localeCompare(b.shopKey, undefined, { numeric: true })) + .map((shopGroup) => ({ + ...shopGroup, + dos: shopGroup.dos + .sort((a, b) => + a.sourceDoCode.localeCompare(b.sourceDoCode, undefined, { numeric: true }), + ) + .map((doGroup) => ({ + ...doGroup, + rows: [...doGroup.rows], + })), + })); +} + const DoReplenishmentTab: React.FC = () => { const { t } = useTranslation("do"); const inFlightRef = useRef(false); @@ -256,7 +316,6 @@ const DoReplenishmentTab: React.FC = () => { status: detail.status, lines: detail.deliveryOrderLines ?? [], }); - setDraftRows([]); setSelectedLine(null); setReplenishQtyInput(""); } catch { @@ -334,6 +393,10 @@ const DoReplenishmentTab: React.FC = () => { setDraftRows((prev) => prev.filter((r) => r.rowId !== rowId)); }, []); + const handleClearDraftRows = useCallback(() => { + setDraftRows([]); + }, []); + const handleSubmit = useCallback(async () => { if (inFlightRef.current) return; if (draftRows.length === 0) { @@ -435,6 +498,11 @@ const DoReplenishmentTab: React.FC = () => { ); const selectedLineUom = lineUomDisplay(selectedLine); + const sourceTruckLaneDisplay = sourceDo + ? sourceDo.truckLaneCode?.trim() + ? sourceDo.truckLaneCode + : t("Truck X") + : ""; const datePickerSlotProps = useMemo( () => ({ @@ -450,33 +518,256 @@ const DoReplenishmentTab: React.FC = () => { [t], ); + const groupedDraftRows = useMemo( + () => groupDraftRowsByShopAndDo(draftRows), + [draftRows], + ); + + const currentDoDraftRows = useMemo( + () => + sourceDo + ? draftRows.filter((row) => row.sourceDoId === sourceDo.doId) + : [], + [draftRows, sourceDo], + ); + + const draftPreviewPanel = ( + (theme.palette.mode === "dark" ? "grey.900" : "common.white"), + }} + > + + + {t("Draft List")} + {draftRows.length > 0 ? ` (${draftRows.length})` : ""} + + + {t("Replenishment preview hint")} + + + + {draftRows.length === 0 ? ( + `1px dashed ${theme.palette.divider}`, + borderRadius: 2, + px: 2, + }} + > + + {t("Replenishment preview empty")} + + + ) : ( + *": { flexShrink: 0 }, + }} + > + {groupedDraftRows.map((shopGroup) => { + const shopDisplay = + shopGroup.shopCode || shopGroup.shopName?.trim() || shopGroup.shopKey; + return ( + + theme.palette.mode === "dark" ? "grey.800" : "grey.50", + }} + > + + {t("Shop Code")} + + + + {shopDisplay} + + + + + {shopGroup.dos.map((doGroup) => ( + ({ + border: `1px solid ${theme.palette.divider}`, + borderRadius: 1.5, + p: 1.25, + bgcolor: + theme.palette.mode === "dark" ? "grey.900" : "common.white", + })} + > + + {t("Delivery Order Code")} + + + + {doGroup.sourceDoCode} + + + + + {doGroup.rows.map((row) => { + const qtyLabel = row.shortUom + ? `${row.replenishQty} ${row.shortUom}` + : String(row.replenishQty); + return ( + ({ + position: "relative", + pr: 4, + py: 0.75, + px: 1, + borderRadius: 1, + bgcolor: + theme.palette.mode === "dark" + ? "grey.800" + : "grey.50", + })} + > + handleRemoveDraftRow(row.rowId)} + aria-label={t("Delete")} + sx={{ position: "absolute", top: 2, right: 2 }} + > + + + + + + {row.itemNo} + + + {row.itemName} + + + + + + {t("Replenish Qty")}:{" "} + + {qtyLabel} + + + ); + })} + + + ))} + + + ); + })} + + )} + + {draftRows.length > 0 && ( + + + + + )} + + ); + return ( - (theme.palette.mode === "dark" ? "grey.900" : "grey.50"), + display: "grid", + gridTemplateColumns: { xs: "1fr", lg: "minmax(0, 1fr) minmax(340px, 420px)" }, + gap: 2, + alignItems: "stretch", }} > - - setTrackingDialogOpen(true)} - aria-label={t("Replenishment Tracking")} - sx={{ - position: "absolute", - top: 8, - right: 8, - zIndex: 1, - color: "text.secondary", - }} - > - - - - + (theme.palette.mode === "dark" ? "grey.900" : "grey.50"), + }} + > + + setTrackingDialogOpen(true)} + aria-label={t("Replenishment Tracking")} + sx={{ + position: "absolute", + top: 8, + right: 8, + zIndex: 1, + color: "text.secondary", + }} + > + + + + { {t("Replenishment item code")} - {t("Item Name")} - + {t("Item Name")} + {t("Original Shipment Qty")} - + {t("Replenish Qty")} - + {t("uom")} - + {t("Truck Lance Code")} - + {t("Action")} - {draftRows.map((row) => ( - + {currentDoDraftRows.map((row) => ( + ({ + bgcolor: + theme.palette.mode === "dark" + ? "action.selected" + : "action.hover", + })} + > {row.itemNo} {row.itemName} {row.originalQty} {row.replenishQty} {row.shortUom || "—"} - - {row.truckLaneCode?.trim() || sourceDo.truckLaneCode?.trim() || t("Truck X")} + + + + {row.truckLaneCode?.trim() || + sourceDo.truckLaneCode?.trim() || + t("Truck X")} + + @@ -724,16 +1063,29 @@ const DoReplenishmentTab: React.FC = () => { sx={{ whiteSpace: "nowrap" }} /> - - + + + theme.spacing(5), + lineHeight: (theme) => theme.spacing(5), + }} + > + {sourceTruckLaneDisplay} + + @@ -758,22 +1110,17 @@ const DoReplenishmentTab: React.FC = () => { - - {draftRows.length > 0 && ( - - - - )} )} - - + + + + + {draftPreviewPanel} + + + + {draftPreviewPanel}