diff --git a/src/app/(main)/report/ReportSelectionDashboard.tsx b/src/app/(main)/report/ReportSelectionDashboard.tsx index b5eb3802..3f165b8c 100644 --- a/src/app/(main)/report/ReportSelectionDashboard.tsx +++ b/src/app/(main)/report/ReportSelectionDashboard.tsx @@ -17,7 +17,7 @@ import OutboundOutlinedIcon from "@mui/icons-material/OutboundOutlined"; import BarChartOutlinedIcon from "@mui/icons-material/BarChartOutlined"; import PieChartOutlineOutlinedIcon from "@mui/icons-material/PieChartOutlineOutlined"; import type { SvgIconComponent } from "@mui/icons-material"; -import { REPORTS } from "@/config/reportConfig"; +import { DEMO_UNAVAILABLE_REPORT_IDS, REPORTS } from "@/config/reportConfig"; import { REPORT_CATEGORIES, type ReportCategoryConfig } from "./reportCategories"; import { useReportLabels } from "./reportI18n"; @@ -52,12 +52,16 @@ function ReportCard({ title, accent, selected, + unavailable, + unavailableLabel, onClick, }: { reportId: string; title: string; accent: string; selected: boolean; + unavailable: boolean; + unavailableLabel: string; onClick: () => void; }) { const Icon = REPORT_ICON_MAP[reportId] ?? Inventory2OutlinedIcon; @@ -66,7 +70,9 @@ function ReportCard({ - - {title} - + + + {title} + + {unavailable && ( + + {unavailableLabel} + + )} + ); } @@ -118,7 +136,7 @@ function CategoryColumn({ selectedReportId: string; onSelectReport: (reportId: string) => void; }) { - const { reportTitle, categoryTitle } = useReportLabels(); + const { t, reportTitle, categoryTitle } = useReportLabels(); const reports = category.reportIds .map((id) => reportById[id]) .filter(Boolean); @@ -161,6 +179,8 @@ function CategoryColumn({ title={reportTitle(report)} accent={category.accent} selected={selectedReportId === report.id} + unavailable={DEMO_UNAVAILABLE_REPORT_IDS.has(report.id)} + unavailableLabel={t("unavailableInDemo")} onClick={() => onSelectReport(report.id)} /> diff --git a/src/app/(main)/report/bomShopSyncReportApi.ts b/src/app/(main)/report/bomShopSyncReportApi.ts index d3eee465..043e67b7 100644 --- a/src/app/(main)/report/bomShopSyncReportApi.ts +++ b/src/app/(main)/report/bomShopSyncReportApi.ts @@ -205,7 +205,7 @@ function toMaterialExcelRow( export async function generateBomShopSyncReportExcel( criteria: Record, - reportTitle: string = "M18 BOM Shop 同步記錄", + reportTitle: string = "BOM Shop 同步記錄", t?: TFunction, ): Promise { const L = bomSyncLabels(t); diff --git a/src/app/(main)/report/grnReportApi.ts b/src/app/(main)/report/grnReportApi.ts index 2e2f001f..772aa271 100644 --- a/src/app/(main)/report/grnReportApi.ts +++ b/src/app/(main)/report/grnReportApi.ts @@ -33,12 +33,6 @@ export interface GrnReportRow { lineAmount?: number; /** PO currency code (currency.code) */ currencyCode?: string; - /** M18 AN document code from m18_goods_receipt_note_log.grn_code */ - grnCode?: string; - /** M18 record id (m18_record_id) */ - grnId?: number | string; - /** From purchase_order.m18CreatedUId; e.g. "2569 (legato)" */ - poM18CreatorDisplay?: string; [key: string]: unknown; } @@ -54,7 +48,6 @@ export interface ListedPoAmounts { poCode?: string; currencyCode?: string; totalAmount?: number; - grnCodes?: string; }[]; } @@ -148,13 +141,9 @@ function grnLabels(t?: TFunction) { unitPrice: tx(t, "excel.grn.unitPrice", "單價"), currency: tx(t, "excel.grn.currency", "貨幣"), amount: tx(t, "excel.grn.amount", "金額"), - grnCode: tx(t, "excel.grn.grnCode", "M18 入倉單號"), - grnId: tx(t, "excel.grn.grnId", "M18 記錄編號"), - poCreator: tx(t, "excel.grn.poCreator", "PO建立者(M18)"), note: tx(t, "excel.grn.note", "備註"), category: tx(t, "excel.grn.category", "類別"), totalAmount: tx(t, "excel.grn.totalAmount", "金額"), - grnCodes: tx(t, "excel.grn.grnCodes", "M18 入倉單號"), noCompletedPo: tx(t, "excel.grn.noCompletedPo", "(篩選範圍內無已完成之 PO 行)"), categoryCurrencyTotal: tx(t, "excel.grn.categoryCurrencyTotal", "貨幣小計"), categoryPo: tx(t, "excel.grn.categoryPo", "訂單"), @@ -187,9 +176,6 @@ function toExcelRow( base[L.currency] = r.currencyCode ?? ""; base[L.amount] = moneyCellValue(r.lineAmount); } - base[L.grnCode] = r.grnCode ?? ""; - base[L.grnId] = r.grnId ?? ""; - base[L.poCreator] = r.poM18CreatorDisplay ?? ""; return base; } @@ -213,7 +199,6 @@ function buildListedPoAmountSheetRows( [L.poNo]: "", [L.currency]: c.currencyCode ?? "", [L.totalAmount]: moneyCellValue(c.totalAmount), - [L.grnCodes]: "", }); } for (const p of listed.byPurchaseOrder) { @@ -223,7 +208,6 @@ function buildListedPoAmountSheetRows( [L.poNo]: p.poCode ?? "", [L.currency]: p.currencyCode ?? "", [L.totalAmount]: moneyCellValue(p.totalAmount), - [L.grnCodes]: p.grnCodes ?? "", }); } return out; diff --git a/src/app/(main)/report/page.tsx b/src/app/(main)/report/page.tsx index c559f7bf..165392fc 100644 --- a/src/app/(main)/report/page.tsx +++ b/src/app/(main)/report/page.tsx @@ -25,7 +25,7 @@ import { } from '@mui/material'; import DownloadIcon from '@mui/icons-material/Download'; import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'; -import { REPORTS } from '@/config/reportConfig'; +import { DEMO_UNAVAILABLE_REPORT_IDS, REPORTS } from '@/config/reportConfig'; import { mergePastedItemCodes, buildItemCodePasteRequestBody, parseItemCodeTokens } from './parseItemCodeTokens'; import { NEXT_PUBLIC_API_URL } from '@/config/api'; import { clientAuthFetch } from '@/app/utils/clientAuthFetch'; @@ -152,6 +152,7 @@ export default function ReportPage() { [selectedReportId]); const handleSelectReport = (reportId: string) => { + if (DEMO_UNAVAILABLE_REPORT_IDS.has(reportId)) return; if (reportId === selectedReportId) return; setSelectedReportId(reportId); setFieldErrors({}); @@ -452,6 +453,7 @@ export default function ReportPage() { const executeExcelReport = async () => { if (!currentReport) return; + if (DEMO_UNAVAILABLE_REPORT_IDS.has(currentReport.id)) return; if (excelInFlightRef.current) return; excelInFlightRef.current = true; setLoading(true); diff --git a/src/app/(main)/report/reportCategories.ts b/src/app/(main)/report/reportCategories.ts index aee5b7cf..2b2f7aa7 100644 --- a/src/app/(main)/report/reportCategories.ts +++ b/src/app/(main)/report/reportCategories.ts @@ -33,6 +33,6 @@ export const REPORT_CATEGORIES: ReportCategoryConfig[] = [ headerBg: "#f5d4a8", bodyBg: "#fdf6ec", accent: "#e65100", - reportIds: ["rep-006", "rep-005"], + reportIds: ["rep-006", "rep-005", "rep-015"], }, ]; diff --git a/src/app/(main)/testing/page.tsx b/src/app/(main)/testing/page.tsx index 06e9260b..9ee34ced 100644 --- a/src/app/(main)/testing/page.tsx +++ b/src/app/(main)/testing/page.tsx @@ -37,8 +37,6 @@ import { type LaserBag2AutoSendReport, type LaserLastReceiveSuccess, } from "@/app/api/laserPrint/actions"; -import * as XLSX from "xlsx"; - interface TabPanelProps { children?: React.ReactNode; index: number; @@ -175,49 +173,6 @@ export default function TestingPage() { }; }, [tabValue]); - const handleDownloadGrnPreviewXlsx = async () => { - try { - const response = await clientAuthFetch( - `${NEXT_PUBLIC_API_URL}/report/grn-preview-m18?receiptDate=${encodeURIComponent( - grnPreviewReceiptDate, - )}`, - { method: "GET" }, - ); - if (response.status === 401 || response.status === 403) return; - if (!response.ok) throw new Error(`Download failed: ${response.status}`); - - const data = await response.json(); - const rows = Array.isArray(data?.rows) ? data.rows : []; - - const ws = XLSX.utils.json_to_sheet(rows); - const wb = XLSX.utils.book_new(); - XLSX.utils.book_append_sheet(wb, ws, "GRN Preview"); - - const xlsxArrayBuffer = XLSX.write(wb, { - bookType: "xlsx", - type: "array", - }); - const blob = new Blob([xlsxArrayBuffer], { - type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", - }); - - const url = window.URL.createObjectURL(blob); - const link = document.createElement("a"); - link.href = url; - link.setAttribute( - "download", - `grn-preview-m18-${grnPreviewReceiptDate}.xlsx`, - ); - document.body.appendChild(link); - link.click(); - link.remove(); - window.URL.revokeObjectURL(url); - } catch (e) { - console.error("GRN Preview XLSX Download Error:", e); - alert("GRN Preview XLSX download failed. Check console/network."); - } - }; - const downloadBlob = (blob: Blob, filename: string) => { const url = window.URL.createObjectURL(blob); const link = document.createElement("a"); @@ -524,33 +479,35 @@ export default function TestingPage() { -
- - setGrnPreviewReceiptDate(e.target.value)} - InputLabelProps={{ shrink: true }} - /> - - - - Backend endpoint:{" "} - /report/grn-preview-m18?receiptDate=YYYY-MM-DD + setGrnPreviewReceiptDate(e.target.value)} + InputLabelProps={{ shrink: true }} + disabled + /> + + + + + Unavailable in demo mode
diff --git a/src/config/reportConfig.ts b/src/config/reportConfig.ts index cb3c1e5c..06f01e0c 100644 --- a/src/config/reportConfig.ts +++ b/src/config/reportConfig.ts @@ -33,6 +33,9 @@ export interface ReportField { export type ReportResponseType = 'pdf' | 'excel'; +/** Live-system reports. The demo page shows them greyed out. */ +export const DEMO_UNAVAILABLE_REPORT_IDS = new Set(["rep-015"]); + export interface ReportDefinition { id: string; title: string; @@ -459,7 +462,7 @@ export const REPORTS: ReportDefinition[] = [ }, { id: "rep-015", - title: "M18 BOM Shop 同步記錄", + title: "BOM Shop 同步記錄", apiEndpoint: `${NEXT_PUBLIC_API_URL}/report/bom-shop-sync-history`, responseType: "excel", fields: [ diff --git a/src/i18n/en/report.json b/src/i18n/en/report.json index d5b942ad..d58c6754 100644 --- a/src/i18n/en/report.json +++ b/src/i18n/en/report.json @@ -15,6 +15,7 @@ "noDataFoundTitle": "No Data Found / 查無資料", "noDataFoundHint": "No inventory records match your search. Please try adjusting the filters.", "ok": "OK", + "unavailableInDemo": "Unavailable in demo mode", "missingRequired": "Missing required fields:\n- {{fields}}", "requiredField": "This is a required field", "dateNotBeforeToday": "Date cannot be before today:\n- {{fields}}", @@ -231,7 +232,7 @@ } }, "rep-015": { - "title": "M18 BOM Shop Sync History", + "title": "BOM Shop Sync History", "fields": { "syncDateStart": "Sync Date Start", "syncDateEnd": "Sync Date End", @@ -332,13 +333,9 @@ "unitPrice": "Unit Price", "currency": "Currency", "amount": "Amount", - "grnCode": "GRN Code / M18 Receipt No.", - "grnId": "GRN Id / M18 Record Id", - "poCreator": "PO creator (M18)", "note": "Note", "category": "Category", - "totalAmount": "Total Amount", - "grnCodes": "GRN Code(s) / M18 Receipt No." + "totalAmount": "Total Amount" }, "bomSync": { "sheetSync": "BOM Sync Log", diff --git a/src/i18n/zh/report.json b/src/i18n/zh/report.json index 301f63a9..d0b8fa4c 100644 --- a/src/i18n/zh/report.json +++ b/src/i18n/zh/report.json @@ -15,6 +15,7 @@ "noDataFoundTitle": "查無資料", "noDataFoundHint": "找不到符合您搜尋條件的庫存資料,請嘗試調整篩選條件。", "ok": "確定", + "unavailableInDemo": "示範模式無法使用", "missingRequired": "缺少必填條件:\n- {{fields}}", "requiredField": "此為必填欄位", "dateNotBeforeToday": "日期不可早於今天:\n- {{fields}}", @@ -231,7 +232,7 @@ } }, "rep-015": { - "title": "M18 BOM Shop 同步記錄", + "title": "BOM Shop 同步記錄", "fields": { "syncDateStart": "同步日期:由", "syncDateEnd": "同步日期:至", @@ -332,13 +333,9 @@ "unitPrice": "單價", "currency": "貨幣", "amount": "金額", - "grnCode": "M18 入倉單號", - "grnId": "M18 記錄編號", - "poCreator": "PO建立者(M18)", "note": "備註", "category": "類別", - "totalAmount": "金額", - "grnCodes": "M18 入倉單號" + "totalAmount": "金額" }, "bomSync": { "sheetSync": "BOM同步記錄",