| @@ -171,7 +171,7 @@ function CategoryColumn({ | |||
| ); | |||
| } | |||
| /** FP-MTMS Version Checklist | Functions Ref. No. 69 | v1.0.0 | 2026-08-31 */ | |||
| /** FP-MTMS Version Checklist | Functions Ref. No. 69 | v1.0.1 | 2026-08-31 */ | |||
| export default function ReportSelectionDashboard({ | |||
| selectedReportId, | |||
| onSelectReport, | |||
| @@ -30,6 +30,11 @@ import { NEXT_PUBLIC_API_URL } from '@/config/api'; | |||
| import { clientAuthFetch } from '@/app/utils/clientAuthFetch'; | |||
| import SemiFGProductionAnalysisReport from './SemiFGProductionAnalysisReport'; | |||
| import ReportSelectionDashboard from './ReportSelectionDashboard'; | |||
| import { DatePicker, LocalizationProvider } from '@mui/x-date-pickers'; | |||
| import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; | |||
| import dayjs from 'dayjs'; | |||
| import 'dayjs/locale/zh-hk'; | |||
| import { OUTPUT_DATE_FORMAT } from '@/app/utils/formatUtil'; | |||
| import { useReportLabels } from './reportI18n'; | |||
| import { | |||
| fetchSemiFGItemCodes, | |||
| @@ -51,10 +56,12 @@ interface ItemCodeWithName { | |||
| /** FP-MTMS Version Checklist | Functions Ref. No. 39 | v1.0.0 | 2026-08-03 */ | |||
| /** FP-MTMS Version Checklist | Functions Ref. No. 60 | v1.0.1 | 2026-08-11 */ | |||
| /** FP-MTMS Version Checklist | Functions Ref. No. 69 | v1.0.0 | 2026-08-31 */ | |||
| /** FP-MTMS Version Checklist | Functions Ref. No. 69 | v1.0.1 | 2026-08-31 */ | |||
| export default function ReportPage() { | |||
| const { data: session } = useSession() as { data: SessionWithTokens | null }; | |||
| const { t, reportTitle, fieldLabel, optionLabel } = useReportLabels(); | |||
| const { t, i18n, reportTitle, fieldLabel, optionLabel } = useReportLabels(); | |||
| const isZh = (i18n.language || 'zh').startsWith('zh'); | |||
| const dateDisplayFormat = isZh ? 'DD/MM/YYYY' : 'DD/MM/YYYY'; | |||
| const includeGrnFinancialColumns = | |||
| session?.abilities?.includes(AUTH.ADMIN) ?? false; | |||
| @@ -506,6 +513,19 @@ export default function ReportPage() { | |||
| </Typography> | |||
| <Divider sx={{ mb: 3 }} /> | |||
| <LocalizationProvider | |||
| dateAdapter={AdapterDayjs} | |||
| adapterLocale={isZh ? 'zh-hk' : 'en'} | |||
| localeText={ | |||
| isZh | |||
| ? { | |||
| fieldDayPlaceholder: () => '日', | |||
| fieldMonthPlaceholder: () => '月', | |||
| fieldYearPlaceholder: () => '年', | |||
| } | |||
| : undefined | |||
| } | |||
| > | |||
| <Grid container spacing={3}> | |||
| {currentReport.fields.map((field) => { | |||
| const translatedLabel = fieldLabel(currentReport.id, field); | |||
| @@ -524,11 +544,6 @@ export default function ReportPage() { | |||
| // Use larger grid size for 成品/半成品生產分析報告 | |||
| const gridSize = currentReport.id === 'rep-005' ? { xs: 12, sm: 12, md: 6 } : { xs: 12, sm: 6 }; | |||
| const todayLocal = (() => { | |||
| const d = new Date(); | |||
| return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`; | |||
| })(); | |||
| const disabledByCheckedCheckbox = currentReport.fields.some((f) => { | |||
| if (f.type !== 'checkbox' || criteria[f.name] !== 'true') return false; | |||
| return f.disablesFieldsWhenChecked?.includes(field.name) ?? false; | |||
| @@ -538,6 +553,41 @@ export default function ReportPage() { | |||
| field.name === 'status' && | |||
| rep012MultiRound; | |||
| if (field.type === 'date') { | |||
| const parsed = currentValue ? dayjs(currentValue) : null; | |||
| return ( | |||
| <Grid item {...gridSize} key={field.name}> | |||
| <DatePicker | |||
| label={translatedLabel} | |||
| format={dateDisplayFormat} | |||
| value={parsed?.isValid() ? parsed : null} | |||
| minDate={field.minDate === 'today' ? dayjs().startOf('day') : undefined} | |||
| disabled={disabledByCheckedCheckbox || disabledRep012Status || !!field.disabled} | |||
| onChange={(date) => { | |||
| handleFieldChange( | |||
| field.name, | |||
| date?.isValid() ? date.format(OUTPUT_DATE_FORMAT) : '', | |||
| ); | |||
| }} | |||
| slotProps={{ | |||
| textField: { | |||
| fullWidth: true, | |||
| sx: currentReport.id === 'rep-005' ? { | |||
| '& .MuiOutlinedInput-root': { | |||
| minHeight: '64px', | |||
| fontSize: '1rem' | |||
| }, | |||
| '& .MuiInputLabel-root': { | |||
| fontSize: '1rem' | |||
| } | |||
| } : {}, | |||
| }, | |||
| }} | |||
| /> | |||
| </Grid> | |||
| ); | |||
| } | |||
| if (field.type === 'checkbox') { | |||
| return ( | |||
| <Grid item {...gridSize} key={field.name}> | |||
| @@ -653,12 +703,6 @@ export default function ReportPage() { | |||
| type={field.type} | |||
| placeholder={field.placeholder} | |||
| disabled={disabledByCheckedCheckbox || disabledRep012Status || !!field.disabled} | |||
| InputLabelProps={field.type === 'date' ? { shrink: true } : {}} | |||
| inputProps={ | |||
| field.type === 'date' && field.minDate === 'today' | |||
| ? { min: todayLocal } | |||
| : undefined | |||
| } | |||
| sx={currentReport.id === 'rep-005' ? { | |||
| '& .MuiOutlinedInput-root': { | |||
| minHeight: '64px', | |||
| @@ -737,6 +781,7 @@ export default function ReportPage() { | |||
| ); | |||
| })} | |||
| </Grid> | |||
| </LocalizationProvider> | |||
| <Box sx={{ mt: 4, display: 'flex', gap: 2, justifyContent: 'flex-end' }}> | |||
| {currentReport.id === 'rep-005' ? ( | |||
| @@ -9,7 +9,7 @@ export interface ReportCategoryConfig { | |||
| reportIds: string[]; | |||
| } | |||
| /** FP-MTMS Version Checklist | Functions Ref. No. 69 | v1.0.0 | 2026-08-31 */ | |||
| /** FP-MTMS Version Checklist | Functions Ref. No. 69 | v1.0.1 | 2026-08-31 */ | |||
| export const REPORT_CATEGORIES: ReportCategoryConfig[] = [ | |||
| { | |||
| id: "inventory", | |||
| @@ -4,8 +4,9 @@ import { useTranslation } from "react-i18next"; | |||
| import type { TFunction } from "i18next"; | |||
| import type { ReportDefinition, ReportField } from "@/config/reportConfig"; | |||
| /** FP-MTMS Version Checklist | Functions Ref. No. 69 | v1.0.1 | 2026-08-31 */ | |||
| export function useReportLabels() { | |||
| const { t } = useTranslation("report"); | |||
| const { t, i18n } = useTranslation("report"); | |||
| const reportTitle = (report: Pick<ReportDefinition, "id" | "title">) => | |||
| t(`reports.${report.id}.title`, { defaultValue: report.title }); | |||
| @@ -34,7 +35,7 @@ export function useReportLabels() { | |||
| const categoryTitle = (id: string, fallback: string) => | |||
| t(`categories.${id}`, { defaultValue: fallback }); | |||
| return { t, reportTitle, fieldLabel, optionLabel, categoryTitle }; | |||
| return { t, i18n, reportTitle, fieldLabel, optionLabel, categoryTitle }; | |||
| } | |||
| export function reportExcelT( | |||
| @@ -34,7 +34,7 @@ export interface ReportDefinition { | |||
| fields: ReportField[]; | |||
| } | |||
| /** FP-MTMS Version Checklist | Functions Ref. No. 69 | v1.0.0 | 2026-08-31 */ | |||
| /** FP-MTMS Version Checklist | Functions Ref. No. 69 | v1.0.1 | 2026-08-31 */ | |||
| export const REPORTS: ReportDefinition[] = [ | |||
| //{ | |||
| // id: "rep-001", | |||
| @@ -45,14 +45,14 @@ | |||
| "rep-004": { | |||
| "title": "入倉追蹤報告", | |||
| "fields": { | |||
| "lastInDateStart": "入倉日期:由 Last In Date Start", | |||
| "lastInDateEnd": "入倉日期:至 Last In Date End", | |||
| "itemCode": "貨品編號 Item Code", | |||
| "storeId": "樓層 Store ID", | |||
| "warehouse": "倉庫 Warehouse", | |||
| "area": "區域 Area", | |||
| "slot": "儲位 Slot", | |||
| "lotNo": "批號 Lot No", | |||
| "lastInDateStart": "入倉日期:由", | |||
| "lastInDateEnd": "入倉日期:至", | |||
| "itemCode": "貨品編號", | |||
| "storeId": "樓層", | |||
| "warehouse": "倉庫", | |||
| "area": "區域", | |||
| "slot": "儲位", | |||
| "lotNo": "批號", | |||
| "poPrefix": "PP/PF 分類" | |||
| }, | |||
| "options": { | |||
| @@ -69,10 +69,10 @@ | |||
| "rep-008": { | |||
| "title": "成品出倉報告", | |||
| "fields": { | |||
| "lastOutDateStart": "出貨日期:由 Last Out Date Start", | |||
| "lastOutDateEnd": "出貨日期:至 Last Out Date End", | |||
| "year": "年份 Year", | |||
| "itemCode": "貨品編號 Item Code" | |||
| "lastOutDateStart": "出貨日期:由", | |||
| "lastOutDateEnd": "出貨日期:至", | |||
| "year": "年份", | |||
| "itemCode": "貨品編號" | |||
| } | |||
| }, | |||
| "rep-012": { | |||
| @@ -109,42 +109,42 @@ | |||
| "rep-011": { | |||
| "title": "庫存明細報告", | |||
| "fields": { | |||
| "lastInDateStart": "庫存日期:由 Last In Date Start", | |||
| "lastInDateEnd": "庫存日期:至 Last In Date End", | |||
| "itemCode": "貨品編號 Item Code" | |||
| "lastInDateStart": "庫存日期:由", | |||
| "lastInDateEnd": "庫存日期:至", | |||
| "itemCode": "貨品編號" | |||
| } | |||
| }, | |||
| "rep-007": { | |||
| "title": "庫存結餘報告", | |||
| "fields": { | |||
| "stockDate": "庫存日期: Stock Date", | |||
| "itemCode": "貨品編號 Item Code" | |||
| "stockDate": "庫存日期", | |||
| "itemCode": "貨品編號" | |||
| } | |||
| }, | |||
| "rep-014": { | |||
| "title": "PO入倉記錄報告", | |||
| "fields": { | |||
| "receiptDateStart": "收貨日期:由 Receipt Date Start", | |||
| "receiptDateEnd": "收貨日期:至 Receipt Date End", | |||
| "itemCode": "貨品編號 Item Code" | |||
| "receiptDateStart": "收貨日期:由", | |||
| "receiptDateEnd": "收貨日期:至", | |||
| "itemCode": "貨品編號" | |||
| } | |||
| }, | |||
| "rep-009": { | |||
| "title": "成品出倉追蹤報告", | |||
| "fields": { | |||
| "lastOutDateStart": "出貨日期:由 Last Out Date Start", | |||
| "lastOutDateEnd": "出貨日期:至 Last Out Date End", | |||
| "itemCode": "貨品編號 Item Code", | |||
| "handler": "提料員 Handler" | |||
| "lastOutDateStart": "出貨日期:由", | |||
| "lastOutDateEnd": "出貨日期:至", | |||
| "itemCode": "貨品編號", | |||
| "handler": "提料員" | |||
| } | |||
| }, | |||
| "rep-010": { | |||
| "title": "庫存品質檢測報告", | |||
| "fields": { | |||
| "lastInDateStart": "QC 檢測日期:由 QC Date Start", | |||
| "lastInDateEnd": "QC 檢測日期:至 QC Date End", | |||
| "lastInDateStart": "QC 檢測日期:由", | |||
| "lastInDateEnd": "QC 檢測日期:至", | |||
| "qcType": "QC 類型", | |||
| "itemCode": "貨品編號 Item Code", | |||
| "itemCode": "貨品編號", | |||
| "qcItemScope": "QC 項目範圍" | |||
| }, | |||
| "options": { | |||
| @@ -162,71 +162,71 @@ | |||
| "rep-013": { | |||
| "title": "貨品出倉追蹤報告", | |||
| "fields": { | |||
| "lastOutDateStart": "出倉日期:由 Last Out Date Start", | |||
| "lastOutDateEnd": "出倉日期:至 Last Out Date End", | |||
| "itemCode": "貨品編號 Item Code", | |||
| "handler": "提料人 Handler" | |||
| "lastOutDateStart": "出倉日期:由", | |||
| "lastOutDateEnd": "出倉日期:至", | |||
| "itemCode": "貨品編號", | |||
| "handler": "提料人" | |||
| } | |||
| }, | |||
| "rep-006": { | |||
| "title": "庫存材料消耗趨勢報告", | |||
| "fields": { | |||
| "lastOutDateStart": "材料消耗日期:由 Last Out Date Start", | |||
| "lastOutDateEnd": "材料消耗日期:至 Last Out Date End", | |||
| "year": "年份 Year", | |||
| "stockCategory": "類別 Category", | |||
| "itemCode": "貨品編號 Item Code" | |||
| "lastOutDateStart": "材料消耗日期:由", | |||
| "lastOutDateEnd": "材料消耗日期:至", | |||
| "year": "年份", | |||
| "stockCategory": "類別", | |||
| "itemCode": "貨品編號" | |||
| } | |||
| }, | |||
| "rep-005": { | |||
| "title": "成品/半成品生產分析報告", | |||
| "fields": { | |||
| "lastOutDateStart": "完成生產日期:由 Last Out Date Start", | |||
| "lastOutDateEnd": "完成生產日期:至 Last Out Date End", | |||
| "year": "年份 Year", | |||
| "stockCategory": "類別 Category", | |||
| "itemCode": "貨品編號 Item Code" | |||
| "lastOutDateStart": "完成生產日期:由", | |||
| "lastOutDateEnd": "完成生產日期:至", | |||
| "year": "年份", | |||
| "stockCategory": "類別", | |||
| "itemCode": "貨品編號" | |||
| } | |||
| }, | |||
| "rep-015": { | |||
| "title": "M18 BOM Shop 同步記錄", | |||
| "fields": { | |||
| "syncDateStart": "同步日期:由 Sync Date Start", | |||
| "syncDateEnd": "同步日期:至 Sync Date End", | |||
| "finishedItemCode": "成品貨號 Finished Item Code", | |||
| "syncStatus": "同步狀態 Sync Status" | |||
| "syncDateStart": "同步日期:由", | |||
| "syncDateEnd": "同步日期:至", | |||
| "finishedItemCode": "成品貨號", | |||
| "syncStatus": "同步狀態" | |||
| }, | |||
| "options": { | |||
| "syncStatus": { | |||
| "all": "全部 All", | |||
| "success": "成功 Success", | |||
| "failed": "失敗 Failed" | |||
| "all": "全部", | |||
| "success": "成功", | |||
| "failed": "失敗" | |||
| } | |||
| } | |||
| }, | |||
| "rep-016": { | |||
| "title": "成品出倉揀貨合規報告", | |||
| "fields": { | |||
| "dateStart": "日期 Date", | |||
| "handler": "提料人 Handler", | |||
| "dateStart": "日期", | |||
| "handler": "提料人", | |||
| "ticketNo": "提票號碼", | |||
| "itemCode": "貨品編號 Item Code", | |||
| "itemCode": "貨品編號", | |||
| "storeId": "樓層" | |||
| } | |||
| }, | |||
| "rep-017": { | |||
| "title": "店鋪訂單補貨記錄", | |||
| "fields": { | |||
| "shopOrderDateStart": "店鋪訂單日期:由 Shop Order Date Start", | |||
| "shopOrderDateEnd": "店鋪訂單日期:至 Shop Order Date End", | |||
| "shopCode": "店鋪編號 Shop Code" | |||
| "shopOrderDateStart": "店鋪訂單日期:由", | |||
| "shopOrderDateEnd": "店鋪訂單日期:至", | |||
| "shopCode": "店鋪編號" | |||
| } | |||
| }, | |||
| "rep-018": { | |||
| "title": "送貨訂單與倉存單位不符報告", | |||
| "fields": { | |||
| "deliveryDate": "預計送貨日期 Estimated Arrival Date", | |||
| "storeId": "送貨訂單樓層 Floor" | |||
| "deliveryDate": "預計送貨日期", | |||
| "storeId": "送貨訂單樓層" | |||
| }, | |||
| "options": { | |||
| "storeId": { | |||
| @@ -237,14 +237,14 @@ | |||
| "rep-021": { | |||
| "title": "庫存批次現況報告", | |||
| "fields": { | |||
| "itemCode": "貨品編號 Item Code", | |||
| "storeId": "樓層 Store ID", | |||
| "warehouse": "倉庫 Warehouse", | |||
| "area": "區域 Area", | |||
| "slot": "儲位 Slot", | |||
| "stockTakeSectionDescription": "盤點區域說明 Stock Take Section", | |||
| "lotNo": "批號 Lot No", | |||
| "lotOrigin": "來源 Lot Origin" | |||
| "itemCode": "貨品編號", | |||
| "storeId": "樓層", | |||
| "warehouse": "倉庫", | |||
| "area": "區域", | |||
| "slot": "儲位", | |||
| "stockTakeSectionDescription": "盤點區域說明", | |||
| "lotNo": "批號", | |||
| "lotOrigin": "來源" | |||
| }, | |||
| "options": { | |||
| "storeId": { | |||