| @@ -131,19 +131,26 @@ export default function ReportPage() { | |||||
| setDynamicOptions({}); | setDynamicOptions({}); | ||||
| }, [selectedReportId]); | }, [selectedReportId]); | ||||
| const handlePrint = async () => { | |||||
| if (!currentReport) return; | |||||
| const validateRequiredFields = () => { | |||||
| if (!currentReport) return true; | |||||
| // 1. Mandatory Field Validation | |||||
| // Mandatory Field Validation | |||||
| const missingFields = currentReport.fields | const missingFields = currentReport.fields | ||||
| .filter(field => field.required && !criteria[field.name]) | .filter(field => field.required && !criteria[field.name]) | ||||
| .map(field => field.label); | .map(field => field.label); | ||||
| if (missingFields.length > 0) { | if (missingFields.length > 0) { | ||||
| alert(`缺少必填條件:\n- ${missingFields.join('\n- ')}`); | alert(`缺少必填條件:\n- ${missingFields.join('\n- ')}`); | ||||
| return; | |||||
| return false; | |||||
| } | } | ||||
| return true; | |||||
| }; | |||||
| const handlePrint = async () => { | |||||
| if (!currentReport) return; | |||||
| if (!validateRequiredFields()) return; | |||||
| // For rep-005, the print logic is handled by SemiFGProductionAnalysisReport component | // For rep-005, the print logic is handled by SemiFGProductionAnalysisReport component | ||||
| if (currentReport.id === 'rep-005') return; | if (currentReport.id === 'rep-005') return; | ||||
| @@ -156,12 +163,51 @@ export default function ReportPage() { | |||||
| await executePrint(); | await executePrint(); | ||||
| }; | }; | ||||
| const handleExcelPrint = async () => { | |||||
| if (!currentReport) return; | |||||
| if (!validateRequiredFields()) return; | |||||
| await executeExcelReport(); | |||||
| }; | |||||
| const executeExcelReport = async () => { | const executeExcelReport = async () => { | ||||
| if (!currentReport) return; | if (!currentReport) return; | ||||
| setLoading(true); | setLoading(true); | ||||
| try { | try { | ||||
| if (currentReport.id === 'rep-014') { | if (currentReport.id === 'rep-014') { | ||||
| await generateGrnReportExcel(criteria, currentReport.title); | await generateGrnReportExcel(criteria, currentReport.title); | ||||
| } else { | |||||
| // Backend returns actual .xlsx bytes for this Excel endpoint. | |||||
| const queryParams = new URLSearchParams(criteria).toString(); | |||||
| const excelUrl = `${currentReport.apiEndpoint}-excel?${queryParams}`; | |||||
| const response = await clientAuthFetch(excelUrl, { | |||||
| method: 'GET', | |||||
| headers: { Accept: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }, | |||||
| }); | |||||
| if (response.status === 401 || response.status === 403) return; | |||||
| if (!response.ok) { | |||||
| const errorText = await response.text(); | |||||
| console.error("Response error:", errorText); | |||||
| throw new Error(`HTTP error! status: ${response.status}, message: ${errorText}`); | |||||
| } | |||||
| 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 = `${currentReport.title}.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); | |||||
| } | } | ||||
| setShowConfirmDialog(false); | setShowConfirmDialog(false); | ||||
| } catch (error) { | } catch (error) { | ||||
| @@ -438,7 +484,7 @@ export default function ReportPage() { | |||||
| })} | })} | ||||
| </Grid> | </Grid> | ||||
| <Box sx={{ mt: 4, display: 'flex', justifyContent: 'flex-end' }}> | |||||
| <Box sx={{ mt: 4, display: 'flex', gap: 2, justifyContent: 'flex-end' }}> | |||||
| {currentReport.id === 'rep-005' ? ( | {currentReport.id === 'rep-005' ? ( | ||||
| <SemiFGProductionAnalysisReport | <SemiFGProductionAnalysisReport | ||||
| criteria={criteria} | criteria={criteria} | ||||
| @@ -447,6 +493,29 @@ export default function ReportPage() { | |||||
| setLoading={setLoading} | setLoading={setLoading} | ||||
| reportTitle={currentReport.title} | reportTitle={currentReport.title} | ||||
| /> | /> | ||||
| ) : currentReport.id === 'rep-006' ? ( | |||||
| <> | |||||
| <Button | |||||
| variant="contained" | |||||
| size="large" | |||||
| startIcon={<DownloadIcon />} | |||||
| onClick={handlePrint} | |||||
| disabled={loading} | |||||
| sx={{ px: 4 }} | |||||
| > | |||||
| {loading ? "生成 PDF..." : "下載報告 (PDF)"} | |||||
| </Button> | |||||
| <Button | |||||
| variant="outlined" | |||||
| size="large" | |||||
| startIcon={<DownloadIcon />} | |||||
| onClick={handleExcelPrint} | |||||
| disabled={loading} | |||||
| sx={{ px: 4 }} | |||||
| > | |||||
| {loading ? "生成 Excel..." : "下載報告 (Excel)"} | |||||
| </Button> | |||||
| </> | |||||
| ) : currentReport.responseType === 'excel' ? ( | ) : currentReport.responseType === 'excel' ? ( | ||||
| <Button | <Button | ||||
| variant="contained" | variant="contained" | ||||