From d04e2eeadc8dd6b68855a1ec1504238b3bd856d7 Mon Sep 17 00:00:00 2001 From: "CANCERYS\\kw093" Date: Fri, 23 Jan 2026 19:17:46 +0800 Subject: [PATCH 1/9] update --- .../GoodPickExecutionForm.tsx | 303 +++++++++++------- .../GoodPickExecutiondetail.tsx | 11 +- src/i18n/zh/pickOrder.json | 6 +- 3 files changed, 194 insertions(+), 126 deletions(-) diff --git a/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx b/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx index 9ab4802..03af4de 100644 --- a/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx +++ b/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx @@ -1,4 +1,3 @@ -// FPSMS-frontend/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx "use client"; import { @@ -16,16 +15,18 @@ import { TextField, Typography, } from "@mui/material"; -import { useCallback, useEffect, useState } from "react"; +import { useCallback, useEffect, useState, useRef } from "react"; import { useTranslation } from "react-i18next"; -import { GetPickOrderLineInfo, PickExecutionIssueData } from "@/app/api/pickOrder/actions"; +import { + GetPickOrderLineInfo, + PickExecutionIssueData, +} from "@/app/api/pickOrder/actions"; import { fetchEscalationCombo } from "@/app/api/user/actions"; -import { useRef } from "react"; -import dayjs from 'dayjs'; +import dayjs from "dayjs"; import { INPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; interface LotPickData { - id: number; + id: number; lotId: number; lotNo: string; expiryDate: string; @@ -39,7 +40,12 @@ interface LotPickData { requiredQty: number; actualPickQty: number; lotStatus: string; - lotAvailability: 'available' | 'insufficient_stock' | 'expired' | 'status_unavailable'|'rejected'; + lotAvailability: + | "available" + | "insufficient_stock" + | "expired" + | "status_unavailable" + | "rejected"; stockOutLineId?: number; stockOutLineStatus?: string; stockOutLineQty?: number; @@ -77,12 +83,14 @@ const PickExecutionForm: React.FC = ({ const [formData, setFormData] = useState>({}); const [errors, setErrors] = useState({}); const [loading, setLoading] = useState(false); - const [handlers, setHandlers] = useState>([]); - + const [handlers, setHandlers] = useState>( + [] + ); + const calculateRemainingAvailableQty = useCallback((lot: LotPickData) => { return lot.availableQty || 0; }, []); - + const calculateRequiredQty = useCallback((lot: LotPickData) => { return lot.requiredQty || 0; }, []); @@ -96,7 +104,7 @@ const PickExecutionForm: React.FC = ({ console.error("Error fetching handlers:", error); } }; - + fetchHandlers(); }, []); @@ -136,92 +144,119 @@ const PickExecutionForm: React.FC = ({ requiredQty: selectedLot.requiredQty, actualPickQty: selectedLot.actualPickQty || 0, missQty: 0, - badItemQty: 0, - issueRemark: '', - pickerName: '', + badItemQty: 0, // Bad Item Qty + badPackageQty: 0, // Bad Package Qty (frontend only) + issueRemark: "", + pickerName: "", handledBy: undefined, - reason: '', - badReason: '', + reason: "", + badReason: "", }); initKeyRef.current = key; - }, [open, selectedPickOrderLine?.id, selectedLot?.lotId, pickOrderId, pickOrderCreateDate]); + }, [ + open, + selectedPickOrderLine?.id, + selectedLot?.lotId, + pickOrderId, + pickOrderCreateDate, + ]); - const handleInputChange = useCallback((field: keyof PickExecutionIssueData, value: any) => { - setFormData(prev => ({ ...prev, [field]: value })); - if (errors[field as keyof FormErrors]) { - setErrors(prev => ({ ...prev, [field]: undefined })); - } - }, [errors]); + const handleInputChange = useCallback( + (field: keyof PickExecutionIssueData, value: any) => { + setFormData((prev) => ({ ...prev, [field]: value })); + if (errors[field as keyof FormErrors]) { + setErrors((prev) => ({ ...prev, [field]: undefined })); + } + }, + [errors] + ); // Updated validation logic const validateForm = (): boolean => { const newErrors: FormErrors = {}; - const req = selectedLot?.requiredQty || 0; const ap = Number(formData.actualPickQty) || 0; const miss = Number(formData.missQty) || 0; - const bad = Number(formData.badItemQty) || 0; - const total = ap + miss + bad; + const badItem = Number(formData.badItemQty) || 0; + const badPackage = Number((formData as any).badPackageQty) || 0; + const totalBad = badItem + badPackage; + const total = ap + miss + totalBad; const availableQty = selectedLot?.availableQty || 0; // 1. Check actualPickQty cannot be negative if (ap < 0) { - newErrors.actualPickQty = t('Qty cannot be negative'); + newErrors.actualPickQty = t("Qty cannot be negative"); } - + // 2. Check actualPickQty cannot exceed available quantity if (ap > availableQty) { - newErrors.actualPickQty = t('Actual pick qty cannot exceed available qty'); + newErrors.actualPickQty = t("Actual pick qty cannot exceed available qty"); } - - // 3. Check missQty and badItemQty cannot be negative + + // 3. Check missQty and both bad qtys cannot be negative if (miss < 0) { - newErrors.missQty = t('Invalid qty'); + newErrors.missQty = t("Invalid qty"); } - if (bad < 0) { - newErrors.badItemQty = t('Invalid qty'); + if (badItem < 0 || badPackage < 0) { + newErrors.badItemQty = t("Invalid qty"); } - - // 4. NEW: Total (actualPickQty + missQty + badItemQty) cannot exceed lot available qty + + // 4. Total (actualPickQty + missQty + badItemQty + badPackageQty) cannot exceed lot available qty if (total > availableQty) { - const errorMsg = t('Total qty (actual pick + miss + bad) cannot exceed available qty: {available}', { available: availableQty }); + const errorMsg = t( + "Total qty (actual pick + miss + bad) cannot exceed available qty: {available}", + { available: availableQty } + ); newErrors.actualPickQty = errorMsg; newErrors.missQty = errorMsg; newErrors.badItemQty = errorMsg; } - - // 5. If badItemQty > 0, badReason is required - if (bad > 0 && !formData.badReason) { - newErrors.badReason = t('Bad reason is required when bad item qty > 0'); - newErrors.badItemQty = t('Bad reason is required'); - } - - // 6. At least one field must have a value - if (ap === 0 && miss === 0 && bad === 0) { - newErrors.actualPickQty = t('Enter pick qty or issue qty'); + + // 5. At least one field must have a value + if (ap === 0 && miss === 0 && totalBad === 0) { + newErrors.actualPickQty = t("Enter pick qty or issue qty"); } - + setErrors(newErrors); return Object.keys(newErrors).length === 0; }; const handleSubmit = async () => { if (!validateForm()) { - console.error('Form validation failed:', errors); + console.error("Form validation failed:", errors); return; } - + if (!formData.pickOrderId) { - console.error('Missing pickOrderId'); + console.error("Missing pickOrderId"); return; } + const badItem = Number(formData.badItemQty) || 0; + const badPackage = Number((formData as any).badPackageQty) || 0; + const totalBadQty = badItem + badPackage; + + let badReason: string | undefined; + if (totalBadQty > 0) { + // assumption: only one of them is > 0 + badReason = badPackage > 0 ? "package_problem" : "quantity_problem"; + } + + const submitData: PickExecutionIssueData = { + ...(formData as PickExecutionIssueData), + badItemQty: totalBadQty, + badReason, + }; + setLoading(true); try { - await onSubmit(formData as PickExecutionIssueData); + await onSubmit(submitData); } catch (error: any) { - console.error('Error submitting pick execution issue:', error); - alert(t('Failed to submit issue. Please try again.') + (error.message ? `: ${error.message}` : '')); + console.error("Error submitting pick execution issue:", error); + alert( + t("Failed to submit issue. Please try again.") + + (error.message ? `: ${error.message}` : "") + ); } finally { setLoading(false); } @@ -239,11 +274,13 @@ const PickExecutionForm: React.FC = ({ const remainingAvailableQty = calculateRemainingAvailableQty(selectedLot); const requiredQty = calculateRequiredQty(selectedLot); - + return ( - {t('Pick Execution Issue Form')} + {t("Pick Execution Issue Form") + " - "+selectedPickOrderLine.itemCode+ " "+ selectedPickOrderLine.itemName} +
+ {selectedLot.lotNo}
@@ -251,17 +288,17 @@ const PickExecutionForm: React.FC = ({ - + = ({ - handleInputChange('actualPickQty', e.target.value === '' ? undefined : Math.max(0, Number(e.target.value) || 0))} - error={!!errors.actualPickQty} - helperText={errors.actualPickQty || `${t('Max')}: ${remainingAvailableQty}`} - variant="outlined" - /> - + + handleInputChange( + "actualPickQty", + e.target.value === "" + ? undefined + + : Math.max(0, Number(e.target.value) || 0) + ) + } + error={!!errors.actualPickQty} + helperText={ + errors.actualPickQty || `${t("Max")}: ${remainingAvailableQty}` + } + variant="outlined" + /> + - {t('Reason')} + {t("Reason")} @@ -301,11 +351,22 @@ const PickExecutionForm: React.FC = ({ handleInputChange('missQty', e.target.value === '' ? undefined : Math.max(0, Number(e.target.value) || 0))} + onChange={(e) => + handleInputChange( + "missQty", + e.target.value === "" + ? undefined + : Math.max(0, Number(e.target.value) || 0) + ) + } error={!!errors.missQty} variant="outlined" /> @@ -314,53 +375,61 @@ const PickExecutionForm: React.FC = ({ handleInputChange('badItemQty', e.target.value === '' ? undefined : Math.max(0, Number(e.target.value) || 0))} + onChange={(e) => + handleInputChange( + "badItemQty", + e.target.value === "" + ? undefined + : Math.max(0, Number(e.target.value) || 0) + ) + } error={!!errors.badItemQty} + //helperText={t("Quantity Problem")} variant="outlined" /> - {/* Show bad reason dropdown when badItemQty > 0 */} - {(formData.badItemQty && formData.badItemQty > 0) ? ( - - - {t('Bad Reason')} - - {errors.badReason && ( - - {errors.badReason} - - )} - - - ) : null} - - + + + handleInputChange( + "badPackageQty", + e.target.value === "" + ? undefined + : Math.max(0, Number(e.target.value) || 0) + ) + } + error={!!errors.badItemQty} + //helperText={t("Package Problem")} + variant="outlined" + /> + -
diff --git a/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx b/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx index 1f3a3f7..10acde5 100644 --- a/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx +++ b/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx @@ -937,18 +937,15 @@ console.log(" DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos); setIsConfirmingLot(true); try { const newLotNo = scannedLotData?.lotNo; - if (!newLotNo) { - console.error("No lot number for scanned lot"); - alert(t("Cannot find lot number for scanned lot. Please verify the lot number is correct.")); - setIsConfirmingLot(false); - return; - } + const newStockInLineId = scannedLotData?.stockInLineId; + await confirmLotSubstitution({ pickOrderLineId: selectedLotForQr.pickOrderLineId, stockOutLineId: selectedLotForQr.stockOutLineId, originalSuggestedPickLotId: selectedLotForQr.suggestedPickLotId, - newInventoryLotNo: newLotNo + newInventoryLotNo: "", + newStockInLineId: newStockInLineId }); setQrScanError(false); diff --git a/src/i18n/zh/pickOrder.json b/src/i18n/zh/pickOrder.json index d63e378..05efb57 100644 --- a/src/i18n/zh/pickOrder.json +++ b/src/i18n/zh/pickOrder.json @@ -257,9 +257,11 @@ "Pick Execution Issue Form":"提料問題表單", "This form is for reporting issues only. You must report either missing items or bad items.":"此表單僅用於報告問題。您必須報告缺少的貨品或不良貨品。", "Bad item Qty":"不良貨品數量", - "Missing item Qty":"缺少貨品數量", + "Missing item Qty":"貨品遺失數量", + "Missing Item Qty":"貨品遺失數量", "Bad Item Qty":"不良貨品數量", - "Missing Item Qty":"缺少貨品數量", + "Bad Package Qty":"不良包裝數量", + "Actual Pick Qty":"實際提料數量", "Required Qty":"所需數量", "Issue Remark":"問題描述", From bb5f3d258450f9da013af81d4786322713aac29c Mon Sep 17 00:00:00 2001 From: "CANCERYS\\kw093" Date: Mon, 26 Jan 2026 11:59:19 +0800 Subject: [PATCH 2/9] update do issue form --- src/app/api/pickOrder/actions.ts | 2 ++ .../GoodPickExecutionForm.tsx | 33 ++++++++++--------- .../GoodPickExecutiondetail.tsx | 4 +-- .../ProductionProcessJobOrderDetail.tsx | 2 +- src/i18n/zh/do.json | 2 ++ src/i18n/zh/pickOrder.json | 4 ++- 6 files changed, 28 insertions(+), 19 deletions(-) diff --git a/src/app/api/pickOrder/actions.ts b/src/app/api/pickOrder/actions.ts index 3de7cdc..22c519a 100644 --- a/src/app/api/pickOrder/actions.ts +++ b/src/app/api/pickOrder/actions.ts @@ -207,6 +207,7 @@ export interface PickExecutionIssueData { actualPickQty: number; missQty: number; badItemQty: number; + badPackageQty?: number; issueRemark: string; pickerName: string; handledBy?: number; @@ -996,6 +997,7 @@ export interface LotSubstitutionConfirmRequest { stockOutLineId: number; originalSuggestedPickLotId: number; newInventoryLotNo: string; + newStockInLineId: number; } export const confirmLotSubstitution = async (data: LotSubstitutionConfirmRequest) => { const response = await serverFetchJson( diff --git a/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx b/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx index 03af4de..c5441af 100644 --- a/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx +++ b/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx @@ -278,7 +278,9 @@ const PickExecutionForm: React.FC = ({ return ( - {t("Pick Execution Issue Form") + " - "+selectedPickOrderLine.itemCode+ " "+ selectedPickOrderLine.itemName} + {t("Pick Execution Issue Form") } +
+ {selectedPickOrderLine.itemCode+ " "+ selectedPickOrderLine.itemName}
{selectedLot.lotNo}
@@ -333,20 +335,6 @@ const PickExecutionForm: React.FC = ({ /> - - - {t("Reason")} - - - = ({ /> + + + + {t("Remark")} + + + diff --git a/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx b/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx index 10acde5..0ffac31 100644 --- a/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx +++ b/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx @@ -2695,7 +2695,7 @@ paginatedData.map((lot, index) => { }} title="Report missing or bad items" > - {t("Issue")} + {t("Edit")} ); diff --git a/src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx b/src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx index 99d485a..2b29837 100644 --- a/src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx +++ b/src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx @@ -640,7 +640,7 @@ const handleRelease = useCallback(async ( jobOrderId: number) => { variant="contained" color="primary" onClick={() => handleRelease(jobOrderId)} - disabled={stockCounts.insufficient > 0 || processData?.jobOrderStatus !== "planning"} + //disabled={stockCounts.insufficient > 0 || processData?.jobOrderStatus !== "planning"} > {t("Release")} diff --git a/src/i18n/zh/do.json b/src/i18n/zh/do.json index 4c6584d..4898ac0 100644 --- a/src/i18n/zh/do.json +++ b/src/i18n/zh/do.json @@ -19,6 +19,8 @@ "Loading...": "正在加載...", "Available Trucks": "可用車輛", "No trucks available": "沒有車輛可用", + "Remark": "備註", + "Just Completed": "已完成", "Code": "門店訂單編號", "code": "門店訂單編號", "Create": "新增", diff --git a/src/i18n/zh/pickOrder.json b/src/i18n/zh/pickOrder.json index 05efb57..cb185ac 100644 --- a/src/i18n/zh/pickOrder.json +++ b/src/i18n/zh/pickOrder.json @@ -9,12 +9,14 @@ "Status": "來貨狀態", "N/A": "不適用", "Release Pick Orders": "放單", + "Remark": "備註", "Escalated": "上報狀態", "NotEscalated": "無上報", "Assigned To": "已分配", "Skip": "跳過", "Fetching all matching records...": "正在獲取所有匹配的記錄...", - + "Edit": "改數", + "Just Completed": "已完成", "Do you want to start?": "確定開始嗎?", "Start": "開始", "Pick Order Code(s)": "提料單編號", From b58947b1e5c5a0f5e2a544591535d71c6f8e30ec Mon Sep 17 00:00:00 2001 From: "B.E.N.S.O.N" Date: Mon, 26 Jan 2026 12:39:10 +0800 Subject: [PATCH 3/9] Dashboard: Goods Receipt Status --- src/app/api/dashboard/actions.ts | 18 ++ src/app/api/dashboard/client.ts | 17 ++ .../goodsReceiptStatus/GoodsReceiptStatus.tsx | 206 ++++++++++++------ src/i18n/en/dashboard.json | 19 ++ src/i18n/zh/dashboard.json | 19 ++ 5 files changed, 207 insertions(+), 72 deletions(-) create mode 100644 src/app/api/dashboard/client.ts diff --git a/src/app/api/dashboard/actions.ts b/src/app/api/dashboard/actions.ts index bd0d240..bd71988 100644 --- a/src/app/api/dashboard/actions.ts +++ b/src/app/api/dashboard/actions.ts @@ -190,3 +190,21 @@ export const testing = cache(async (queryParams?: Record) => { ); } }); + +export interface GoodsReceiptStatusRow { + supplierId: number | null; + supplierName: string; + expectedNoOfDelivery: number; + noOfOrdersReceivedAtDock: number; + noOfItemsInspected: number; + noOfItemsWithIqcIssue: number; + noOfItemsCompletedPutAwayAtStore: number; +} + +export const fetchGoodsReceiptStatus = cache(async (date?: string) => { + const url = date + ? `${BASE_API_URL}/dashboard/goods-receipt-status?date=${date}` + : `${BASE_API_URL}/dashboard/goods-receipt-status`; + + return await serverFetchJson(url, { method: "GET" }); +}); diff --git a/src/app/api/dashboard/client.ts b/src/app/api/dashboard/client.ts new file mode 100644 index 0000000..ff0facb --- /dev/null +++ b/src/app/api/dashboard/client.ts @@ -0,0 +1,17 @@ +"use client"; + +import { + fetchGoodsReceiptStatus, + type GoodsReceiptStatusRow, +} from "./actions"; + +export const fetchGoodsReceiptStatusClient = async ( + date?: string, +): Promise => { + return await fetchGoodsReceiptStatus(date); +}; + +export type { GoodsReceiptStatusRow }; + +export default fetchGoodsReceiptStatusClient; + diff --git a/src/components/DashboardPage/goodsReceiptStatus/GoodsReceiptStatus.tsx b/src/components/DashboardPage/goodsReceiptStatus/GoodsReceiptStatus.tsx index 4e725d2..c788078 100644 --- a/src/components/DashboardPage/goodsReceiptStatus/GoodsReceiptStatus.tsx +++ b/src/components/DashboardPage/goodsReceiptStatus/GoodsReceiptStatus.tsx @@ -1,13 +1,9 @@ "use client"; -import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react'; +import React, { useState, useEffect, useCallback, useMemo } from 'react'; import { Box, Typography, - FormControl, - InputLabel, - Select, - MenuItem, Card, CardContent, Stack, @@ -19,88 +15,112 @@ import { TableRow, Paper, CircularProgress, - Chip + Button } from '@mui/material'; import { useTranslation } from 'react-i18next'; import dayjs from 'dayjs'; +import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import { DatePicker } from '@mui/x-date-pickers/DatePicker'; +import { fetchGoodsReceiptStatusClient, type GoodsReceiptStatusRow } from '@/app/api/dashboard/client'; -interface GoodsReceiptStatusItem { - id: string; -} +const REFRESH_MS = 15 * 60 * 1000; const GoodsReceiptStatus: React.FC = () => { const { t } = useTranslation("dashboard"); - const [selectedFilter, setSelectedFilter] = useState(""); - const [data, setData] = useState([]); - const [loading, setLoading] = useState(false); - const [currentTime, setCurrentTime] = useState(null); - const [isClient, setIsClient] = useState(false); - - useEffect(() => { - setIsClient(true); - setCurrentTime(dayjs()); - }, []); + const [selectedDate, setSelectedDate] = useState(dayjs()); + const [data, setData] = useState([]); + const [loading, setLoading] = useState(true); + const [lastUpdated, setLastUpdated] = useState(null); + const [screenCleared, setScreenCleared] = useState(false); const loadData = useCallback(async () => { + if (screenCleared) return; try { - setData([]); + setLoading(true); + const dateParam = selectedDate.format('YYYY-MM-DD'); + const result = await fetchGoodsReceiptStatusClient(dateParam); + setData(result ?? []); + setLastUpdated(dayjs()); } catch (error) { console.error('Error fetching goods receipt status:', error); + setData([]); } finally { setLoading(false); } - }, []); + }, [selectedDate, screenCleared]); useEffect(() => { + if (screenCleared) return; loadData(); const refreshInterval = setInterval(() => { loadData(); - }, 5 * 60 * 1000); + }, REFRESH_MS); return () => clearInterval(refreshInterval); - }, [loadData]); + }, [loadData, screenCleared]); - useEffect(() => { - if (!isClient) return; - - const timeInterval = setInterval(() => { - setCurrentTime(dayjs()); - }, 60 * 1000); - - return () => clearInterval(timeInterval); - }, [isClient]); - const filteredData = useMemo(() => { - if (!selectedFilter) return data; - return data.filter(item => true); - }, [data, selectedFilter]); + const selectedDateLabel = useMemo(() => { + return selectedDate.format('YYYY-MM-DD'); + }, [selectedDate]); + + if (screenCleared) { + return ( + + + + + {t("Screen cleared")} + + + + + + ); + } return ( - {/* Filter */} - - - - {t("Filter")} - - - - - - {t("Auto-refresh every 5 minutes")} | {t("Last updated")}: {isClient && currentTime ? currentTime.format('HH:mm:ss') : '--:--:--'} + {/* Header */} + + + + {t("Date")}: + + + { + if (!value) return; + setSelectedDate(value); + }} + slotProps={{ + textField: { + size: "small", + sx: { minWidth: 160 } + } + }} + /> + + + {t("Allow to select Date to view history.")} + + + + + + + {t("Auto-refresh every 15 minutes")} | {t("Last updated")}: {lastUpdated ? lastUpdated.format('HH:mm:ss') : '--:--:--'} + + {/* Table */} @@ -114,38 +134,80 @@ const GoodsReceiptStatus: React.FC = () => { - {t("Column 1")} - {t("Column 2")} - {t("Column 3")} - {/* TODO: Add table columns when implementing */} + {t("Supplier")} + {t("Expected No. of Delivery")} + {t("No. of Orders Received at Dock")} + {t("No. of Items Inspected")} + {t("No. of Items with IQC Issue")} + {t("No. of Items Completed Put Away at Store")} + + + + + {t("Show Supplier Name")} + + + + + {t("Based on Expected Delivery Date")} + + + + + {t("Upon entry of DN and Lot No. for all items of the order")} + + + + + {t("Upon any IQC decision received")} + + + + + {t("Count any item with IQC defect in any IQC criteria")} + + + + + {t("Upon completion of put away for an material in order. Count no. of items being put away")} + + - {filteredData.length === 0 ? ( + {data.length === 0 ? ( - + - {t("No data available")} + {t("No data available")} ({selectedDateLabel}) ) : ( - filteredData.map((row, index) => ( + data.map((row, index) => ( - {/* TODO: Add table cell content when implementing */} - - + {row.supplierName || '-'} - - - + + {row.expectedNoOfDelivery ?? 0} - - - + + {row.noOfOrdersReceivedAtDock ?? 0} + + + {row.noOfItemsInspected ?? 0} + + + {row.noOfItemsWithIqcIssue ?? 0} + + + {row.noOfItemsCompletedPutAwayAtStore ?? 0} )) diff --git a/src/i18n/en/dashboard.json b/src/i18n/en/dashboard.json index 5a1c9aa..9434bbf 100644 --- a/src/i18n/en/dashboard.json +++ b/src/i18n/en/dashboard.json @@ -79,6 +79,25 @@ "Tomorrow": "Tomorrow", "Day After Tomorrow": "Day After Tomorrow", "Goods Receipt Status": "Goods Receipt Status", + "Date": "Date", + "Time": "Time", + "Allow to select Date to view history.": "Allow to select Date to view history.", + "Auto-refresh every 15 minutes": "Auto-refresh every 15 minutes", + "Exit Screen": "Exit Screen", + "Restore Screen": "Restore Screen", + "Screen cleared": "Screen cleared", + "Supplier": "Supplier", + "Expected No. of Delivery": "Expected No. of Delivery", + "No. of Orders Received at Dock": "No. of Orders Received at Dock", + "No. of Items Inspected": "No. of Items Inspected", + "No. of Items with IQC Issue": "No. of Items with IQC Issue", + "No. of Items Completed Put Away at Store": "No. of Items Completed Put Away at Store", + "Show Supplier Name": "Show Supplier Name", + "Based on Expected Delivery Date": "Based on Expected Delivery Date", + "Upon entry of DN and Lot No. for all items of the order": "Upon entry of DN and Lot No. for all items of the order", + "Upon any IQC decision received": "Upon any IQC decision received", + "Count any item with IQC defect in any IQC criteria": "Count any item with IQC defect in any IQC criteria", + "Upon completion of put away for an material in order. Count no. of items being put away": "Upon completion of put away for an material in order. Count no. of items being put away", "Filter": "Filter", "All": "All", "Column 1": "Column 1", diff --git a/src/i18n/zh/dashboard.json b/src/i18n/zh/dashboard.json index c2cf9d7..2e8253d 100644 --- a/src/i18n/zh/dashboard.json +++ b/src/i18n/zh/dashboard.json @@ -79,6 +79,25 @@ "Tomorrow": "翌日", "Day After Tomorrow": "後日", "Goods Receipt Status": "貨物接收狀態", + "Date": "日期", + "Time": "時間", + "Allow to select Date to view history.": "可選擇日期查看歷史記錄。", + "Auto-refresh every 15 minutes": "每15分鐘自動刷新", + "Exit Screen": "退出畫面", + "Restore Screen": "恢復畫面", + "Screen cleared": "畫面已清除", + "Supplier": "供應商", + "Expected No. of Delivery": "預計送貨數", + "No. of Orders Received at Dock": "碼頭已收訂單數", + "No. of Items Inspected": "已檢驗貨品數", + "No. of Items with IQC Issue": "IQC異常貨品數", + "No. of Items Completed Put Away at Store": "已完成上架貨品數", + "Show Supplier Name": "顯示供應商名稱", + "Based on Expected Delivery Date": "按預計送貨日期統計", + "Upon entry of DN and Lot No. for all items of the order": "當訂單所有貨品已輸入DN及批號時", + "Upon any IQC decision received": "當收到任何IQC判定", + "Count any item with IQC defect in any IQC criteria": "統計任何IQC準則不合格的貨品", + "Upon completion of put away for an material in order. Count no. of items being put away": "當訂單物料完成上架。統計正在上架的貨品數", "Filter": "篩選", "All": "全部", "Column 1": "欄位1", From feb162ae6022a85e879a88b673f2fe864b2f4936 Mon Sep 17 00:00:00 2001 From: "B.E.N.S.O.N" Date: Mon, 26 Jan 2026 12:44:16 +0800 Subject: [PATCH 4/9] Dashboard: Goods Receipt Status Update --- src/i18n/zh/dashboard.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/i18n/zh/dashboard.json b/src/i18n/zh/dashboard.json index 2e8253d..78646d2 100644 --- a/src/i18n/zh/dashboard.json +++ b/src/i18n/zh/dashboard.json @@ -87,8 +87,8 @@ "Restore Screen": "恢復畫面", "Screen cleared": "畫面已清除", "Supplier": "供應商", - "Expected No. of Delivery": "預計送貨數", - "No. of Orders Received at Dock": "碼頭已收訂單數", + "Expected No. of Delivery": "預計送貨單數", + "No. of Orders Received at Dock": "已收訂單數", "No. of Items Inspected": "已檢驗貨品數", "No. of Items with IQC Issue": "IQC異常貨品數", "No. of Items Completed Put Away at Store": "已完成上架貨品數", From 927485e8d3d818dc9a6c1858db2999efc16a754b Mon Sep 17 00:00:00 2001 From: "B.E.N.S.O.N" Date: Mon, 26 Jan 2026 14:31:54 +0800 Subject: [PATCH 5/9] Dashboard Page Update --- .../DashboardPage/DashboardPage.tsx | 109 +++++++++++++----- 1 file changed, 81 insertions(+), 28 deletions(-) diff --git a/src/components/DashboardPage/DashboardPage.tsx b/src/components/DashboardPage/DashboardPage.tsx index 840adcd..c14faf2 100644 --- a/src/components/DashboardPage/DashboardPage.tsx +++ b/src/components/DashboardPage/DashboardPage.tsx @@ -2,23 +2,43 @@ import { useTranslation } from "react-i18next"; import { ThemeProvider } from "@mui/material/styles"; import theme from "../../theme"; -import { TabsProps } from "@mui/material/Tabs"; -import React, { useCallback, useEffect, useState } from "react"; +import React, { useEffect, useState, ReactNode } from "react"; import { useRouter } from "next/navigation"; -import { Card, CardContent, CardHeader, Grid } from "@mui/material"; +import { Card, CardContent, CardHeader, Grid, Tabs, Tab, Box, FormControlLabel, Checkbox } from "@mui/material"; import DashboardProgressChart from "./chart/DashboardProgressChart"; import DashboardLineChart from "./chart/DashboardLineChart"; import PendingInspectionChart from "./chart/PendingInspectionChart"; import PendingStorageChart from "./chart/PendingStorageChart"; import ApplicationCompletionChart from "./chart/ApplicationCompletionChart"; import OrderCompletionChart from "./chart/OrderCompletionChart"; -import DashboardBox from "./Dashboardbox"; -import CollapsibleCard from "../CollapsibleCard"; // import SupervisorQcApproval, { IQCItems } from "./QC/SupervisorQcApproval"; import { EscalationResult } from "@/app/api/escalation"; import EscalationLogTable from "./escalation/EscalationLogTable"; import { TruckScheduleDashboard } from "./truckSchedule"; import { GoodsReceiptStatus } from "./goodsReceiptStatus"; +import { CardFilterContext } from "../CollapsibleCard/CollapsibleCard"; + +interface TabPanelProps { + children?: ReactNode; + index: number; + value: number; +} + +function TabPanel(props: TabPanelProps) { + const { children, value, index, ...other } = props; + + return ( + + ); +} type Props = { // iqc: IQCItems[] | undefined escalationLogs: EscalationResult[] @@ -32,6 +52,8 @@ const DashboardPage: React.FC = ({ const router = useRouter(); const [escLog, setEscLog] = useState([]); + const [currentTab, setCurrentTab] = useState(0); + const [showCompletedLogs, setShowCompletedLogs] = useState(false); const getPendingLog = () => { return escLog.filter(esc => esc.status == "pending"); @@ -41,35 +63,66 @@ const DashboardPage: React.FC = ({ setEscLog(escalationLogs); }, [escalationLogs]) + const handleTabChange = (event: React.SyntheticEvent, newValue: number) => { + setCurrentTab(newValue); + }; + + const handleFilterChange = (checked: boolean) => { + setShowCompletedLogs(checked); + }; + return ( - + + + + + + + 0 ? getPendingLog().length : t("No")})`} + id="dashboard-tab-2" + aria-controls="dashboard-tabpanel-2" + /> + + - + + + + + + + + {} + }}> + + handleFilterChange(e.target.checked)} + /> + } + label={t("show completed logs")} + /> + + + + - - - - - - - - - - - 0 ? getPendingLog().length : t("No")})`} - showFilter={true} - filterText={t("show completed logs")} - - > - - - - + {/* Hidden: Progress chart - not in use currently */} {/* From 5473ff820d8354376db73bb09a79f6afbf5966b5 Mon Sep 17 00:00:00 2001 From: "CANCERYS\\kw093" Date: Mon, 26 Jan 2026 16:15:51 +0800 Subject: [PATCH 6/9] update bar --- .../FinishedGoodSearch/FinishedGoodSearch.tsx | 3 +- .../GoodPickExecutionForm.tsx | 2 +- .../GoodPickExecutiondetail.tsx | 74 +++++++++++++++++-- 3 files changed, 72 insertions(+), 7 deletions(-) diff --git a/src/components/FinishedGoodSearch/FinishedGoodSearch.tsx b/src/components/FinishedGoodSearch/FinishedGoodSearch.tsx index 4637e41..ef47f79 100644 --- a/src/components/FinishedGoodSearch/FinishedGoodSearch.tsx +++ b/src/components/FinishedGoodSearch/FinishedGoodSearch.tsx @@ -655,7 +655,7 @@ const handleAssignByLane = useCallback(async ( > {t("Print All Draft")} ({releasedOrderCount}) - +{/* + */} diff --git a/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx b/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx index c5441af..5ff744d 100644 --- a/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx +++ b/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx @@ -419,7 +419,7 @@ const PickExecutionForm: React.FC = ({ onChange={(e) => handleInputChange("reason", e.target.value)} label={t("Remark")} > - {t("Select Reason")} + {t("Select Remark")} {t("Edit")} {t("Just Complete")} diff --git a/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx b/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx index 0ffac31..4c64435 100644 --- a/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx +++ b/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx @@ -19,6 +19,7 @@ import { TablePagination, Modal, Chip, + LinearProgress, } from "@mui/material"; import dayjs from 'dayjs'; import TestQrCodeProvider from '../QrCodeScannerProvider/TestQrCodeProvider'; @@ -78,7 +79,33 @@ interface Props { onSwitchToRecordTab?: () => void; onRefreshReleasedOrderCount?: () => void; } - +const LinearProgressWithLabel: React.FC<{ completed: number; total: number }> = ({ completed, total }) => { + const { t } = useTranslation(["pickOrder", "do"]); + const progress = total > 0 ? (completed / total) * 100 : 0; + + return ( + + + + + + + + {t("Progress")}: {completed}/{total} + + + + + + ); +}; // QR Code Modal Component (from LotTable) const QrCodeModal: React.FC<{ open: boolean; @@ -477,7 +504,7 @@ const [pickOrderSwitching, setPickOrderSwitching] = useState(false); const [paginationController, setPaginationController] = useState({ pageNum: 0, - pageSize: 10, + pageSize: -1, }); const [usernameList, setUsernameList] = useState([]); @@ -515,7 +542,23 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false); console.log(`QR Code clicked for pick order ID: ${pickOrderId}`); // TODO: Implement QR code functionality }; + const progress = useMemo(() => { + if (combinedLotData.length === 0) { + return { completed: 0, total: 0 }; + } + + const nonPendingCount = combinedLotData.filter(lot => { + const status = lot.stockOutLineStatus?.toLowerCase(); + return status !== 'pending'; + }).length; + + return { + completed: nonPendingCount, + total: combinedLotData.length + }; + }, [combinedLotData]); + const handleLotMismatch = useCallback((expectedLot: any, scannedLot: any) => { console.log("Lot mismatch detected:", { expectedLot, scannedLot }); setExpectedLotData(expectedLot); @@ -1808,13 +1851,16 @@ useEffect(() => { const newPageSize = parseInt(event.target.value, 10); setPaginationController({ pageNum: 0, - pageSize: newPageSize, + pageSize: newPageSize === -1 ? -1 : newPageSize, }); }, []); // Pagination data with sorting by routerIndex // Remove the sorting logic and just do pagination const paginatedData = useMemo(() => { + if (paginationController.pageSize === -1) { + return combinedLotData; // Show all items + } const startIndex = paginationController.pageNum * paginationController.pageSize; const endIndex = startIndex + paginationController.pageSize; return combinedLotData.slice(startIndex, endIndex); // No sorting needed @@ -2337,6 +2383,24 @@ const handleSubmitAllScanned = useCallback(async () => { > + + + {/* DO Header */} @@ -2540,7 +2604,7 @@ paginatedData.map((lot, index) => { }} > {lot.lotNo || - t('⚠️ No Stock Available')} + t('No Stock Available')} @@ -2726,7 +2790,7 @@ paginatedData.map((lot, index) => { rowsPerPage={paginationController.pageSize} onPageChange={handlePageChange} onRowsPerPageChange={handlePageSizeChange} - rowsPerPageOptions={[10, 25, 50]} + rowsPerPageOptions={[10, 25, 50,-1]} labelRowsPerPage={t("Rows per page")} labelDisplayedRows={({ from, to, count }) => `${from}-${to} of ${count !== -1 ? count : `more than ${to}`}` From f807fcee8273357850193a88037c56b38b006546 Mon Sep 17 00:00:00 2001 From: "CANCERYS\\kw093" Date: Mon, 26 Jan 2026 17:03:41 +0800 Subject: [PATCH 7/9] update --- src/components/FinishedGoodSearch/GoodPickExecution.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/FinishedGoodSearch/GoodPickExecution.tsx b/src/components/FinishedGoodSearch/GoodPickExecution.tsx index 73ebd76..3c1e14e 100644 --- a/src/components/FinishedGoodSearch/GoodPickExecution.tsx +++ b/src/components/FinishedGoodSearch/GoodPickExecution.tsx @@ -22,7 +22,7 @@ import { useCallback, useEffect, useState, useRef, useMemo } from "react"; import { useTranslation } from "react-i18next"; import { useRouter } from "next/navigation"; import { - fetchALLPickOrderLineLotDetails, + fetchAllPickOrderLotsHierarchical, updateStockOutLineStatus, createStockOutLine, recordPickExecutionIssue, @@ -427,13 +427,13 @@ const fetchFgPickOrdersData = useCallback(async () => { } // Use the non-auto-assign endpoint - this only fetches existing data - const allLotDetails = await fetchALLPickOrderLineLotDetails(userIdToUse); + const allLotDetails = await fetchAllPickOrderLotsHierarchical(userIdToUse); console.log(" All combined lot details:", allLotDetails); setCombinedLotData(allLotDetails); setOriginalCombinedData(allLotDetails); // 计算完成状态并发送事件 - const allCompleted = allLotDetails.length > 0 && allLotDetails.every(lot => + const allCompleted = allLotDetails.length > 0 && allLotDetails.every((lot: any) => lot.processingStatus === 'completed' ); From 9e9c8d073cb11ebaf97f6a5c8b7ae283ff47a8a1 Mon Sep 17 00:00:00 2001 From: "CANCERYS\\kw093" Date: Mon, 26 Jan 2026 17:12:42 +0800 Subject: [PATCH 8/9] update --- .../FinishedGoodSearch/GoodPickExecution.tsx | 76 ++++++++++++++++--- 1 file changed, 67 insertions(+), 9 deletions(-) diff --git a/src/components/FinishedGoodSearch/GoodPickExecution.tsx b/src/components/FinishedGoodSearch/GoodPickExecution.tsx index 3c1e14e..7d8147b 100644 --- a/src/components/FinishedGoodSearch/GoodPickExecution.tsx +++ b/src/components/FinishedGoodSearch/GoodPickExecution.tsx @@ -426,14 +426,69 @@ const fetchFgPickOrdersData = useCallback(async () => { return; } - // Use the non-auto-assign endpoint - this only fetches existing data - const allLotDetails = await fetchAllPickOrderLotsHierarchical(userIdToUse); + // ✅ Fix: fetchAllPickOrderLotsHierarchical returns hierarchical data, not a flat array + const hierarchicalData = await fetchAllPickOrderLotsHierarchical(userIdToUse); + console.log(" Hierarchical data:", hierarchicalData); + + // ✅ Fix: Ensure we always set an array + // If hierarchicalData is not in the expected format, default to empty array + let allLotDetails: any[] = []; + + if (hierarchicalData && Array.isArray(hierarchicalData)) { + // If it's already an array, use it directly + allLotDetails = hierarchicalData; + } else if (hierarchicalData?.pickOrders && Array.isArray(hierarchicalData.pickOrders)) { + // Process hierarchical data into flat array (similar to GoodPickExecutiondetail.tsx) + const mergedPickOrder = hierarchicalData.pickOrders[0]; + if (mergedPickOrder?.pickOrderLines) { + mergedPickOrder.pickOrderLines.forEach((line: any) => { + if (line.lots && line.lots.length > 0) { + line.lots.forEach((lot: any) => { + allLotDetails.push({ + pickOrderConsoCode: mergedPickOrder.consoCode, + pickOrderTargetDate: mergedPickOrder.targetDate, + pickOrderStatus: mergedPickOrder.status, + pickOrderId: line.pickOrderId || mergedPickOrder.pickOrderIds?.[0] || 0, + pickOrderCode: mergedPickOrder.pickOrderCodes?.[0] || "", + pickOrderLineId: line.id, + pickOrderLineRequiredQty: line.requiredQty, + pickOrderLineStatus: line.status, + itemId: line.item?.id, + itemCode: line.item?.code, + itemName: line.item?.name, + uomDesc: line.item?.uomDesc, + uomShortDesc: line.item?.uomShortDesc, + lotId: lot.id, + lotNo: lot.lotNo, + expiryDate: lot.expiryDate, + location: lot.location, + stockUnit: lot.stockUnit, + availableQty: lot.availableQty, + requiredQty: lot.requiredQty, + actualPickQty: lot.actualPickQty, + lotStatus: lot.lotStatus, + lotAvailability: lot.lotAvailability, + processingStatus: lot.processingStatus, + stockOutLineId: lot.stockOutLineId, + stockOutLineStatus: lot.stockOutLineStatus, + stockOutLineQty: lot.stockOutLineQty, + routerId: lot.router?.id, + routerIndex: lot.router?.index, + routerRoute: lot.router?.route, + routerArea: lot.router?.area, + }); + }); + } + }); + } + } + console.log(" All combined lot details:", allLotDetails); setCombinedLotData(allLotDetails); setOriginalCombinedData(allLotDetails); - // 计算完成状态并发送事件 - const allCompleted = allLotDetails.length > 0 && allLotDetails.every((lot: any) => + // ✅ Fix: Add safety check - ensure allLotDetails is an array before using .every() + const allCompleted = Array.isArray(allLotDetails) && allLotDetails.length > 0 && allLotDetails.every((lot: any) => lot.processingStatus === 'completed' ); @@ -462,6 +517,7 @@ const fetchFgPickOrdersData = useCallback(async () => { } }, [currentUserId, combinedLotData]); + // Only fetch existing data when session is ready, no auto-assignment useEffect(() => { if (session && currentUserId && !initializationRef.current) { @@ -1038,10 +1094,15 @@ const fetchFgPickOrdersData = useCallback(async () => { }); }, []); - // Pagination data with sorting by routerIndex const paginatedData = useMemo(() => { + // ✅ Fix: Add safety check to ensure combinedLotData is an array + if (!Array.isArray(combinedLotData)) { + console.warn("⚠️ combinedLotData is not an array:", combinedLotData); + return []; + } + // Sort by routerIndex first, then by other criteria - const sortedData = [...combinedLotData].sort((a, b) => { + const sortedData = [...combinedLotData].sort((a: any, b: any) => { const aIndex = a.routerIndex || 0; const bIndex = b.routerIndex || 0; @@ -1063,9 +1124,6 @@ const fetchFgPickOrdersData = useCallback(async () => { const endIndex = startIndex + paginationController.pageSize; return sortedData.slice(startIndex, endIndex); }, [combinedLotData, paginationController]); - -// ... existing code ... - return ( {/* 修复:改进条件渲染逻辑 */} From 29bdcf6c1a8cfd8e7c17c79cf2ab7dcaa6d6312b Mon Sep 17 00:00:00 2001 From: "CANCERYS\\kw093" Date: Mon, 26 Jan 2026 17:36:13 +0800 Subject: [PATCH 9/9] update do pick confirm --- .../FinishedGoodFloorLanePanel.tsx | 169 +++++++++++------- src/i18n/zh/do.json | 7 + src/i18n/zh/pickOrder.json | 7 + 3 files changed, 122 insertions(+), 61 deletions(-) diff --git a/src/components/FinishedGoodSearch/FinishedGoodFloorLanePanel.tsx b/src/components/FinishedGoodSearch/FinishedGoodFloorLanePanel.tsx index 5f73626..3f14e5f 100644 --- a/src/components/FinishedGoodSearch/FinishedGoodFloorLanePanel.tsx +++ b/src/components/FinishedGoodSearch/FinishedGoodFloorLanePanel.tsx @@ -57,72 +57,119 @@ const FinishedGoodFloorLanePanel: React.FC = ({ onPickOrderAssigned, onSw loadSummaries(); }, [loadSummaries]); - const handleAssignByLane = useCallback(async ( - storeId: string, - truckDepartureTime: string, - truckLanceCode: string, - requiredDate: string + const handleAssignByLane = useCallback(async ( + storeId: string, + truckDepartureTime: string, + truckLanceCode: string, + requiredDate: string - ) => { - if (!currentUserId) { - console.error("Missing user id in session"); - return; - } - let dateParam: string | undefined; - if (requiredDate === "today") { - dateParam = dayjs().format('YYYY-MM-DD'); - } else if (requiredDate === "tomorrow") { - dateParam = dayjs().add(1, 'day').format('YYYY-MM-DD'); - } else if (requiredDate === "dayAfterTomorrow") { - dateParam = dayjs().add(2, 'day').format('YYYY-MM-DD'); - } - setIsAssigning(true); - try { - const res = await assignByLane(currentUserId, storeId, truckLanceCode, truckDepartureTime, dateParam); - - if (res.code === "SUCCESS") { - console.log(" Successfully assigned pick order from lane", truckLanceCode); - window.dispatchEvent(new CustomEvent('pickOrderAssigned')); - loadSummaries(); // 刷新按钮状态 - onPickOrderAssigned?.(); - onSwitchToDetailTab?.(); - } 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); + ) => { + if (!currentUserId) { + console.error("Missing user id in session"); + return; + } + let dateParam: string | undefined; + if (requiredDate === "today") { + dateParam = dayjs().format('YYYY-MM-DD'); + } else if (requiredDate === "tomorrow") { + dateParam = dayjs().add(1, 'day').format('YYYY-MM-DD'); + } else if (requiredDate === "dayAfterTomorrow") { + dateParam = dayjs().add(2, 'day').format('YYYY-MM-DD'); + } + setIsAssigning(true); + try { + const res = await assignByLane(currentUserId, storeId, truckLanceCode, truckDepartureTime, dateParam); + + if (res.code === "SUCCESS") { + console.log(" Successfully assigned pick order from lane", truckLanceCode); + window.dispatchEvent(new CustomEvent('pickOrderAssigned')); + loadSummaries(); // 刷新按钮状态 + onPickOrderAssigned?.(); + onSwitchToDetailTab?.(); + } else if (res.code === "USER_BUSY") { Swal.fire({ - icon: "error", - title: t("Error"), - text: t("Error occurred during assignment."), + 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" }); - } finally { - setIsAssigning(false); + 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); } - }, [currentUserId, t, selectedDate, onPickOrderAssigned, onSwitchToDetailTab, loadSummaries]); + } 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, selectedDate, onPickOrderAssigned, onSwitchToDetailTab, loadSummaries]); - const getDateLabel = (offset: number) => { - return dayjs().add(offset, 'day').format('YYYY-MM-DD'); - }; +const handleLaneButtonClick = useCallback(async ( + storeId: string, + truckDepartureTime: string, + truckLanceCode: string, + requiredDate: string, + unassigned: number, + total: number +) => { + // Format the date for display + let dateDisplay: string; + if (requiredDate === "today") { + dateDisplay = dayjs().format('YYYY-MM-DD'); + } else if (requiredDate === "tomorrow") { + dateDisplay = dayjs().add(1, 'day').format('YYYY-MM-DD'); + } else if (requiredDate === "dayAfterTomorrow") { + dateDisplay = dayjs().add(2, 'day').format('YYYY-MM-DD'); + } else { + dateDisplay = requiredDate; + } + + // Show confirmation dialog + const result = await Swal.fire({ + title: t("Confirm Assignment"), + html: ` +
+

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

+

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

+

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

+

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

+

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

+
+ `, + icon: "question", + showCancelButton: true, + confirmButtonText: t("Confirm"), + cancelButtonText: t("Cancel"), + confirmButtonColor: "#8dba00", + cancelButtonColor: "#F04438", + reverseButtons: true + }); + + // Only proceed if user confirmed + if (result.isConfirmed) { + await handleAssignByLane(storeId, truckDepartureTime, truckLanceCode, requiredDate); + } +}, [handleAssignByLane, t]); + +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[]) => { @@ -296,7 +343,7 @@ const FinishedGoodFloorLanePanel: React.FC = ({ onPickOrderAssigned, onSw variant="outlined" size="medium" disabled={item.lane.unassigned === 0 || isAssigning} - onClick={() => handleAssignByLane("2/F", item.truckDepartureTime, item.lane.truckLanceCode, selectedDate)} + onClick={() => handleLaneButtonClick("2/F", item.truckDepartureTime, item.lane.truckLanceCode, selectedDate, item.lane.unassigned, item.lane.total)} sx={{ flex: 1, fontSize: '1.1rem', @@ -396,7 +443,7 @@ const FinishedGoodFloorLanePanel: React.FC = ({ onPickOrderAssigned, onSw variant="outlined" size="medium" disabled={item.lane.unassigned === 0 || isAssigning} - onClick={() => handleAssignByLane("4/F", item.truckDepartureTime, item.lane.truckLanceCode, selectedDate)} + onClick={() => handleLaneButtonClick("4/F", item.truckDepartureTime, item.lane.truckLanceCode, selectedDate, item.lane.unassigned, item.lane.total)} sx={{ flex: 1, fontSize: '1.1rem', diff --git a/src/i18n/zh/do.json b/src/i18n/zh/do.json index 4898ac0..2bdf7d6 100644 --- a/src/i18n/zh/do.json +++ b/src/i18n/zh/do.json @@ -11,11 +11,18 @@ "Status": "來貨狀態", "Order Date From": "訂單日期", "Delivery Order Code": "送貨訂單編號", + "Select Remark": "選擇備註", + "Confirm Assignment": "確認分配", + "Required Date": "所需日期", + "Store": "位置", + "Available Orders": "可用訂單", + "Just Complete": "已完成", "Order Date To": "訂單日期至", "Warning: Some delivery orders do not have matching trucks for the target date.": "警告:部分送貨訂單於目標日期沒有可匹配的車輛。", "Truck Availability Warning": "車輛可用性警告", "Problem DO(s): ": "問題送貨訂單", "Fetching all matching records...": "正在獲取所有匹配的記錄...", + "Progress": "進度", "Loading...": "正在加載...", "Available Trucks": "可用車輛", "No trucks available": "沒有車輛可用", diff --git a/src/i18n/zh/pickOrder.json b/src/i18n/zh/pickOrder.json index cb185ac..bfb2f21 100644 --- a/src/i18n/zh/pickOrder.json +++ b/src/i18n/zh/pickOrder.json @@ -13,7 +13,14 @@ "Escalated": "上報狀態", "NotEscalated": "無上報", "Assigned To": "已分配", + "Progress": "進度", + "Select Remark": "選擇備註", + "Just Complete": "已完成", "Skip": "跳過", + "Confirm Assignment": "確認分配", + "Required Date": "所需日期", + "Store": "位置", + "Available Orders": "可用訂單", "Fetching all matching records...": "正在獲取所有匹配的記錄...", "Edit": "改數", "Just Completed": "已完成",