>({});
+ const [exportFormat, setExportFormat] = useState<'pdf' | 'excel'>('pdf');
// Fetch item codes with category when stockCategory changes
useEffect(() => {
@@ -62,16 +64,17 @@ export default function SemiFGProductionAnalysisReport({
}
}, [criteria.stockCategory]);
- const handlePrintClick = async () => {
+ const handleExportClick = async (format: 'pdf' | 'excel') => {
+ setExportFormat(format);
// Validate required fields
if (requiredFieldLabels.length > 0) {
alert(`缺少必填條件:\n- ${requiredFieldLabels.join('\n- ')}`);
return;
}
- // If no itemCode is selected, print directly without confirmation
+ // If no itemCode is selected, export directly without confirmation
if (!criteria.itemCode) {
- await executePrint();
+ await executeExport(format);
return;
}
@@ -90,10 +93,14 @@ export default function SemiFGProductionAnalysisReport({
setShowConfirmDialog(true);
};
- const executePrint = async () => {
+ const executeExport = async (format: 'pdf' | 'excel' = exportFormat) => {
setLoading(true);
try {
- await generateSemiFGProductionAnalysisReport(criteria, reportTitle);
+ if (format === 'excel') {
+ await generateSemiFGProductionAnalysisReportExcel(criteria, reportTitle);
+ } else {
+ await generateSemiFGProductionAnalysisReport(criteria, reportTitle);
+ }
setShowConfirmDialog(false);
} catch (error) {
console.error('Failed to generate report:', error);
@@ -105,16 +112,27 @@ export default function SemiFGProductionAnalysisReport({
return (
<>
- }
- onClick={handlePrintClick}
- disabled={loading}
- sx={{ px: 4 }}
- >
- {loading ? '生成報告...' : '列印報告'}
-
+
+ }
+ onClick={() => handleExportClick('pdf')}
+ disabled={loading}
+ sx={{ px: 4 }}
+ >
+ {loading ? '生成 PDF...' : '下載報告 (PDF)'}
+
+
+
{/* Confirmation Dialog for 成品/半成品生產分析報告 */}
diff --git a/src/app/(main)/report/page.tsx b/src/app/(main)/report/page.tsx
index 2c21c9e..df24b3b 100644
--- a/src/app/(main)/report/page.tsx
+++ b/src/app/(main)/report/page.tsx
@@ -14,7 +14,7 @@ import {
Chip,
Autocomplete
} from '@mui/material';
-import PrintIcon from '@mui/icons-material/Print';
+import DownloadIcon from '@mui/icons-material/Download';
import { REPORTS, ReportDefinition } from '@/config/reportConfig';
import { NEXT_PUBLIC_API_URL } from '@/config/api';
import { clientAuthFetch } from '@/app/utils/clientAuthFetch';
@@ -451,23 +451,23 @@ export default function ReportPage() {
}
+ startIcon={}
onClick={handlePrint}
disabled={loading}
sx={{ px: 4 }}
>
- {loading ? "生成報告..." : "匯出 Excel"}
+ {loading ? "生成 Excel..." : "下載報告 (Excel)"}
) : (
}
+ startIcon={}
onClick={handlePrint}
disabled={loading}
sx={{ px: 4 }}
>
- {loading ? "生成報告..." : "列印報告"}
+ {loading ? "生成報告..." : "下載報告 (PDF)"}
)}
diff --git a/src/app/(main)/report/semiFGProductionAnalysisApi.ts b/src/app/(main)/report/semiFGProductionAnalysisApi.ts
index ba7a1d7..b4e8b69 100644
--- a/src/app/(main)/report/semiFGProductionAnalysisApi.ts
+++ b/src/app/(main)/report/semiFGProductionAnalysisApi.ts
@@ -77,7 +77,7 @@ export const generateSemiFGProductionAnalysisReport = async (
const response = await clientAuthFetch(url, {
method: 'GET',
- headers: { 'Accept': 'application/pdf' },
+ headers: { Accept: 'application/pdf' },
});
if (response.status === 401 || response.status === 403) throw new Error("Unauthorized");
@@ -100,3 +100,42 @@ export const generateSemiFGProductionAnalysisReport = async (
link.remove();
window.URL.revokeObjectURL(downloadUrl);
};
+
+/**
+ * Generate and download the SemiFG Production Analysis Report as Excel
+ * @param criteria - Report criteria parameters
+ * @param reportTitle - Title of the report for filename
+ * @returns Promise that resolves when download is complete
+ */
+export const generateSemiFGProductionAnalysisReportExcel = async (
+ criteria: Record,
+ reportTitle: string = '成品/半成品生產分析報告'
+): Promise => {
+ const queryParams = new URLSearchParams(criteria).toString();
+ const url = `${NEXT_PUBLIC_API_URL}/report/print-semi-fg-production-analysis-excel?${queryParams}`;
+
+ const response = await clientAuthFetch(url, {
+ method: 'GET',
+ headers: { Accept: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' },
+ });
+
+ if (response.status === 401 || response.status === 403) throw new Error('Unauthorized');
+ if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
+
+ const blob = await response.blob();
+ const downloadUrl = window.URL.createObjectURL(blob);
+ const link = document.createElement('a');
+ link.href = downloadUrl;
+
+ const contentDisposition = response.headers.get('Content-Disposition');
+ let fileName = `${reportTitle}.xlsx`;
+ if (contentDisposition?.includes('filename=')) {
+ fileName = contentDisposition.split('filename=')[1].split(';')[0].replace(/"/g, '');
+ }
+
+ link.setAttribute('download', fileName);
+ document.body.appendChild(link);
+ link.click();
+ link.remove();
+ window.URL.revokeObjectURL(downloadUrl);
+};
diff --git a/src/components/NavigationContent/NavigationContent.tsx b/src/components/NavigationContent/NavigationContent.tsx
index a497e46..37b79b9 100644
--- a/src/components/NavigationContent/NavigationContent.tsx
+++ b/src/components/NavigationContent/NavigationContent.tsx
@@ -194,18 +194,20 @@ const NavigationContent: React.FC = () => {
requiredAbility: [AUTH.TESTING, AUTH.ADMIN],
isHidden: false,
children: [
- {
- icon: ,
- label: "庫存與倉儲",
- path: "/chart/warehouse",
- requiredAbility: [AUTH.TESTING, AUTH.ADMIN],
- },
+
{
icon: ,
label: "採購",
path: "/chart/purchase",
requiredAbility: [AUTH.TESTING, AUTH.ADMIN],
},
+
+ {
+ icon: ,
+ label: "工單",
+ path: "/chart/joborder",
+ requiredAbility: [AUTH.TESTING, AUTH.ADMIN],
+ },
{
icon: ,
label: "發貨與配送",
@@ -213,9 +215,9 @@ const NavigationContent: React.FC = () => {
requiredAbility: [AUTH.TESTING, AUTH.ADMIN],
},
{
- icon: ,
- label: "工單",
- path: "/chart/joborder",
+ icon: ,
+ label: "庫存與倉儲",
+ path: "/chart/warehouse",
requiredAbility: [AUTH.TESTING, AUTH.ADMIN],
},
{
diff --git a/src/config/reportConfig.ts b/src/config/reportConfig.ts
index 182e7a9..37c8847 100644
--- a/src/config/reportConfig.ts
+++ b/src/config/reportConfig.ts
@@ -122,6 +122,18 @@ export const REPORTS: ReportDefinition[] = [
{ label: "物料編號 Item Code", name: "itemCode", type: "text", required: false},
]
},
+
+ {
+ id: "rep-014",
+ title: "PO入倉記錄報告",
+ apiEndpoint: `${NEXT_PUBLIC_API_URL}/report/grn-report`,
+ responseType: "excel",
+ fields: [
+ { label: "收貨日期:由 Receipt Date Start", name: "receiptDateStart", type: "date", required: false },
+ { label: "收貨日期:至 Receipt Date End", name: "receiptDateEnd", type: "date", required: false },
+ { label: "物料編號 Item Code", name: "itemCode", type: "text", required: false },
+ ],
+ },
{ id: "rep-009",
title: "成品出倉追蹤報告",
@@ -190,17 +202,7 @@ export const REPORTS: ReportDefinition[] = [
]
},
- {
- id: "rep-014",
- title: "PO 入倉記錄",
- apiEndpoint: `${NEXT_PUBLIC_API_URL}/report/grn-report`,
- responseType: "excel",
- fields: [
- { label: "收貨日期:由 Receipt Date Start", name: "receiptDateStart", type: "date", required: false },
- { label: "收貨日期:至 Receipt Date End", name: "receiptDateEnd", type: "date", required: false },
- { label: "物料編號 Item Code", name: "itemCode", type: "text", required: false },
- ],
- },
+
{
id: "rep-005",
title: "成品/半成品生產分析報告",