diff --git a/src/app/(main)/report/page.tsx b/src/app/(main)/report/page.tsx index 0bd8e61b..06d4006d 100644 --- a/src/app/(main)/report/page.tsx +++ b/src/app/(main)/report/page.tsx @@ -57,6 +57,21 @@ interface ItemCodeWithName { name: string; } +/** Months from the earliest ledger month through the current month. Value stays YYYY-MM-01. */ +function ledgerMonthOptions(isZh: boolean, earliest: string) { + const start = dayjs().startOf('month'); + let end = dayjs(earliest).startOf('month'); + if (!end.isValid() || end.isAfter(start)) end = start; + const count = start.diff(end, 'month') + 1; + return Array.from({ length: count }, (_, i) => { + const d = start.subtract(i, 'month'); + return { + value: d.format(OUTPUT_DATE_FORMAT), + label: isZh ? `${d.year()}年${d.month() + 1}月` : d.format('MMMM YYYY'), + }; + }); +} + const FIELD_ERROR_SX = { '& .MuiOutlinedInput-root.Mui-error': { '& .MuiOutlinedInput-notchedOutline': { @@ -97,6 +112,7 @@ export default function ReportPage() { session?.abilities?.includes(AUTH.ADMIN) ?? false; const [selectedReportId, setSelectedReportId] = useState(''); + const [ledgerEarliest, setLedgerEarliest] = useState<{ reportId: string; earliest: string } | null>(null); const [criteria, setCriteria] = useState>({}); const criteriaRef = useRef>({}); const pendingItemCodeInputRef = useRef>({}); @@ -269,6 +285,32 @@ export default function ReportPage() { } }; + // Earliest stock_ledger month for the period-start list. Newest month is always the current month. + useEffect(() => { + const endpoint = currentReport?.fields.find( + (field) => field.type === 'month' && field.earliestMonthEndpoint, + )?.earliestMonthEndpoint; + if (!currentReport || !endpoint) return; + const reportId = currentReport.id; + let cancelled = false; + (async () => { + try { + const response = await clientAuthFetch(endpoint); + if (!response.ok) return; + const data = await response.json(); + const earliest = typeof data?.earliest === 'string' ? data.earliest : ''; + if (!cancelled && earliest) { + setLedgerEarliest({ reportId, earliest }); + } + } catch (error) { + console.error('Failed to load ledger months', error); + } + })(); + return () => { + cancelled = true; + }; + }, [currentReport]); + // Load initial options when report is selected useEffect(() => { if (currentReport) { @@ -661,6 +703,50 @@ export default function ReportPage() { field.name === 'status' && rep012MultiRound; + if (field.type === 'month') { + const earliestForReport = + ledgerEarliest?.reportId === currentReport.id ? ledgerEarliest.earliest : ''; + const monthOptions = earliestForReport + ? ledgerMonthOptions(isZh, earliestForReport) + : []; + return ( + + handleFieldChange(field.name, e.target.value)} + sx={{ + ...FIELD_ERROR_SX, + ...(currentReport.id === 'rep-005' ? { + '& .MuiOutlinedInput-root': { + minHeight: '64px', + fontSize: '1rem' + }, + '& .MuiInputLabel-root': { + fontSize: '1rem' + } + } : {}), + }} + > + + {isZh ? '請選擇月份' : 'Select month'} + + {monthOptions.map((opt) => ( + + {opt.label} + + ))} + + + ); + } + if (field.type === 'date') { const parsed = currentValue ? dayjs(currentValue) : null; const dateError = fieldErrors[field.name]; diff --git a/src/config/reportConfig.ts b/src/config/reportConfig.ts index b01954f4..483e61cf 100644 --- a/src/config/reportConfig.ts +++ b/src/config/reportConfig.ts @@ -1,5 +1,5 @@ /** FP-MTMS Version Checklist | Functions Ref. No. 39 | v1.0.0 | 2026-08-03 */ -export type FieldType = 'date' | 'text' | 'select' | 'number' | 'checkbox'; +export type FieldType = 'date' | 'month' | 'text' | 'select' | 'number' | 'checkbox'; import { NEXT_PUBLIC_API_URL } from "@/config/api"; @@ -29,6 +29,8 @@ export interface ReportField { multiline?: boolean; /** Rows for multiline text areas. Default 4. */ minRows?: number; + /** Month fields: GET returning { earliest: "YYYY-MM-DD" }. List runs from that month through the current month. */ + earliestMonthEndpoint?: string; } export type ReportResponseType = 'pdf' | 'excel'; @@ -322,7 +324,13 @@ export const REPORTS: ReportDefinition[] = [ apiEndpoint: `${NEXT_PUBLIC_API_URL}/report/print-stock-lot-ledger`, responseType: "excel", fields: [ - { label: "期間起 Period From(永遠該月1日)", name: "lastInDateStart", type: "date", required: true }, + { + label: "期間起 Period From", + name: "lastInDateStart", + type: "month", + required: true, + earliestMonthEndpoint: `${NEXT_PUBLIC_API_URL}/report/stock-lot-ledger-earliest`, + }, { label: "期間迄 Period To", name: "lastInDateEnd", type: "date", required: true }, asyncItemCodeField(), ], diff --git a/src/i18n/en/report.json b/src/i18n/en/report.json index ea517d8e..b8ad8a2c 100644 --- a/src/i18n/en/report.json +++ b/src/i18n/en/report.json @@ -139,7 +139,7 @@ "rep-020": { "title": "Stock Lot Ledger Report", "fields": { - "lastInDateStart": "Period From (always 1st of month)", + "lastInDateStart": "Period From", "lastInDateEnd": "Period To", "itemCode": "Item Code" } diff --git a/src/i18n/zh/report.json b/src/i18n/zh/report.json index 1c349ee5..c74e58e8 100644 --- a/src/i18n/zh/report.json +++ b/src/i18n/zh/report.json @@ -139,7 +139,7 @@ "rep-020": { "title": "庫存流水帳報告", "fields": { - "lastInDateStart": "期間起(永遠該月1日)", + "lastInDateStart": "期間起", "lastInDateEnd": "期間迄", "itemCode": "貨品編號" }