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 }}
- />
- }
- onClick={handleDownloadGrnPreviewXlsx}
+
+
+
- Download GRN Preview XLSX
-
-
-
- Backend endpoint:{" "}
- /report/grn-preview-m18?receiptDate=YYYY-MM-DD
+ setGrnPreviewReceiptDate(e.target.value)}
+ InputLabelProps={{ shrink: true }}
+ disabled
+ />
+ }
+ disabled
+ >
+ Download GRN Preview XLSX
+
+
+
+
+ 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同步記錄",