|
|
@@ -57,6 +57,21 @@ interface ItemCodeWithName { |
|
|
name: string; |
|
|
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 = { |
|
|
const FIELD_ERROR_SX = { |
|
|
'& .MuiOutlinedInput-root.Mui-error': { |
|
|
'& .MuiOutlinedInput-root.Mui-error': { |
|
|
'& .MuiOutlinedInput-notchedOutline': { |
|
|
'& .MuiOutlinedInput-notchedOutline': { |
|
|
@@ -97,6 +112,7 @@ export default function ReportPage() { |
|
|
session?.abilities?.includes(AUTH.ADMIN) ?? false; |
|
|
session?.abilities?.includes(AUTH.ADMIN) ?? false; |
|
|
|
|
|
|
|
|
const [selectedReportId, setSelectedReportId] = useState<string>(''); |
|
|
const [selectedReportId, setSelectedReportId] = useState<string>(''); |
|
|
|
|
|
const [ledgerEarliest, setLedgerEarliest] = useState<{ reportId: string; earliest: string } | null>(null); |
|
|
const [criteria, setCriteria] = useState<Record<string, string>>({}); |
|
|
const [criteria, setCriteria] = useState<Record<string, string>>({}); |
|
|
const criteriaRef = useRef<Record<string, string>>({}); |
|
|
const criteriaRef = useRef<Record<string, string>>({}); |
|
|
const pendingItemCodeInputRef = useRef<Record<string, string>>({}); |
|
|
const pendingItemCodeInputRef = useRef<Record<string, string>>({}); |
|
|
@@ -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 |
|
|
// Load initial options when report is selected |
|
|
useEffect(() => { |
|
|
useEffect(() => { |
|
|
if (currentReport) { |
|
|
if (currentReport) { |
|
|
@@ -661,6 +703,50 @@ export default function ReportPage() { |
|
|
field.name === 'status' && |
|
|
field.name === 'status' && |
|
|
rep012MultiRound; |
|
|
rep012MultiRound; |
|
|
|
|
|
|
|
|
|
|
|
if (field.type === 'month') { |
|
|
|
|
|
const earliestForReport = |
|
|
|
|
|
ledgerEarliest?.reportId === currentReport.id ? ledgerEarliest.earliest : ''; |
|
|
|
|
|
const monthOptions = earliestForReport |
|
|
|
|
|
? ledgerMonthOptions(isZh, earliestForReport) |
|
|
|
|
|
: []; |
|
|
|
|
|
return ( |
|
|
|
|
|
<Grid item {...gridSize} key={fieldKey}> |
|
|
|
|
|
<TextField |
|
|
|
|
|
select |
|
|
|
|
|
fullWidth |
|
|
|
|
|
required={field.required} |
|
|
|
|
|
error={Boolean(fieldErrors[field.name])} |
|
|
|
|
|
helperText={fieldErrors[field.name] || undefined} |
|
|
|
|
|
label={translatedLabel} |
|
|
|
|
|
value={currentValue} |
|
|
|
|
|
disabled={disabledByCheckedCheckbox || disabledRep012Status || !!field.disabled} |
|
|
|
|
|
onChange={(e) => handleFieldChange(field.name, e.target.value)} |
|
|
|
|
|
sx={{ |
|
|
|
|
|
...FIELD_ERROR_SX, |
|
|
|
|
|
...(currentReport.id === 'rep-005' ? { |
|
|
|
|
|
'& .MuiOutlinedInput-root': { |
|
|
|
|
|
minHeight: '64px', |
|
|
|
|
|
fontSize: '1rem' |
|
|
|
|
|
}, |
|
|
|
|
|
'& .MuiInputLabel-root': { |
|
|
|
|
|
fontSize: '1rem' |
|
|
|
|
|
} |
|
|
|
|
|
} : {}), |
|
|
|
|
|
}} |
|
|
|
|
|
> |
|
|
|
|
|
<MenuItem value="" disabled> |
|
|
|
|
|
{isZh ? '請選擇月份' : 'Select month'} |
|
|
|
|
|
</MenuItem> |
|
|
|
|
|
{monthOptions.map((opt) => ( |
|
|
|
|
|
<MenuItem key={opt.value} value={opt.value}> |
|
|
|
|
|
{opt.label} |
|
|
|
|
|
</MenuItem> |
|
|
|
|
|
))} |
|
|
|
|
|
</TextField> |
|
|
|
|
|
</Grid> |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
if (field.type === 'date') { |
|
|
if (field.type === 'date') { |
|
|
const parsed = currentValue ? dayjs(currentValue) : null; |
|
|
const parsed = currentValue ? dayjs(currentValue) : null; |
|
|
const dateError = fieldErrors[field.name]; |
|
|
const dateError = fieldErrors[field.name]; |
|
|
|