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