diff --git a/src/components/StockIn/CalculateExpiryDateModal.tsx b/src/components/StockIn/CalculateExpiryDateModal.tsx index 8d3ab5f..bcda7c2 100644 --- a/src/components/StockIn/CalculateExpiryDateModal.tsx +++ b/src/components/StockIn/CalculateExpiryDateModal.tsx @@ -1,7 +1,7 @@ import { OUTPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; import { Check, SwapHoriz, Add } from "@mui/icons-material"; -import { Box, Button, Card, Grid, Modal, Stack, TextField, Typography } from "@mui/material"; +import { Box, Button, Card, FormHelperText, Modal, Stack, Typography } from "@mui/material"; import { DatePicker, DateTimePicker, LocalizationProvider } from "@mui/x-date-pickers"; import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; import dayjs, { Dayjs } from "dayjs"; @@ -24,6 +24,78 @@ type EntryError = shelfLife: string, } +const INPUT_HEIGHT = "5rem"; +const ICON_COL_WIDTH = 48; + +const iconColumnSx = { + width: ICON_COL_WIDTH, + minWidth: ICON_COL_WIDTH, + height: INPUT_HEIGHT, + display: "flex", + alignItems: "center", + justifyContent: "center", + flexShrink: 0, +}; + +const columnSx = { + flex: { sm: "1 1 22%" }, + minWidth: { sm: 180 }, + width: { xs: "100%", sm: "auto" }, +}; + +const shelfColumnSx = { + flex: { sm: "1 1 28%" }, + minWidth: { sm: 200 }, + width: { xs: "100%", sm: "auto" }, +}; + +const rowSx = { + display: "flex", + flexDirection: { xs: "column", sm: "row" }, + gap: { xs: 2, sm: 2 }, + overflowX: { sm: "auto" }, +}; + +const defaultModalFieldSx = { + width: "100%", + "& .MuiInputBase-root": { + height: INPUT_HEIGHT, + }, + "& .MuiInputBase-input": { + height: "100%", + boxSizing: "border-box", + padding: "0.75rem 2.75rem 0.75rem 0.75rem", + fontSize: { xs: 22, sm: 26 }, + "&::placeholder": { + fontSize: { xs: 20, sm: 24 }, + opacity: 0.55, + }, + }, + "& .MuiInputLabel-root": { + display: "none", + }, +}; + +const hiddenPickerLabelProps = { + shrink: true, + sx: { display: "none" }, +}; + +const FieldLabel: React.FC<{ children: React.ReactNode; sx?: object }> = ({ children, sx }) => ( + + {children} + +); + const CalculateExpiryDateModal: React.FC = ({ open, onClose, @@ -31,6 +103,14 @@ const CalculateExpiryDateModal: React.FC = ({ textfieldSx, }) => { const { t, i18n: { language }, } = useTranslation("purchaseOrder"); + const fieldSx = { + ...(textfieldSx ?? defaultModalFieldSx), + "& .MuiInputLabel-root": { display: "none" }, + "& .MuiInputBase-input::placeholder": { + fontSize: { xs: 20, sm: 24 }, + opacity: 0.55, + }, + }; const [productionDate, setProductionDate] = useState(); const [shelfLife, setShelfLife] = useState(); @@ -158,24 +238,27 @@ const CalculateExpiryDateModal: React.FC = ({ onClose={onModalClose} > = ({ dateAdapter={AdapterDayjs} adapterLocale={`${language}-hk`} > - - + + {t("Fill in Expiry Date")} - - - + + {/* Label row */} + + + {t("productionDate")} + + + + + + + + + + + + {t("expiryDate")} + + + + {/* Input row — icons align with fields only */} + + { @@ -218,51 +320,38 @@ const CalculateExpiryDateModal: React.FC = ({ }} slotProps={{ textField: { - error: errors.productionDate.length > 0, - helperText: errors.productionDate, + fullWidth: true, + InputLabelProps: hiddenPickerLabelProps, + error: errors.productionDate.length > 0, }, }} /> - - - - - + + + + + { const val = value == 0 ? undefined : value; setShelfLife(val); calculateDates(val, "shelfLife"); }} /> - - - - - - + + + + + { @@ -275,15 +364,48 @@ const CalculateExpiryDateModal: React.FC = ({ }} slotProps={{ textField: { - error: errors.expiryDate.length > 0, - helperText: errors.expiryDate, + fullWidth: true, + InputLabelProps: hiddenPickerLabelProps, + error: errors.expiryDate.length > 0, }, }} - disabled={true} + disabled /> - - - + + + + {/* Helper / error row */} + + + {errors.productionDate && ( + {errors.productionDate} + )} + + + + + {t("shelfLife")}:{" "} + + {shelfLife ?? 0} 日 + + + + + + {errors.expiryDate && ( + {errors.expiryDate} + )} + + + = ({ diff --git a/src/components/StockIn/ShelfLifeInput.tsx b/src/components/StockIn/ShelfLifeInput.tsx index 3cef1d8..978ba99 100644 --- a/src/components/StockIn/ShelfLifeInput.tsx +++ b/src/components/StockIn/ShelfLifeInput.tsx @@ -1,7 +1,7 @@ 'use client'; import { useState, useEffect, useMemo } from 'react'; -import { Box, TextField, FormHelperText, styled } from '@mui/material'; +import { Box, TextField, FormHelperText, Typography, styled } from '@mui/material'; import { useTranslation } from "react-i18next"; interface ShelfLifeInputProps { @@ -9,16 +9,39 @@ interface ShelfLifeInputProps { onChange?: (value: number) => void; label?: string; sx?: any; - showHelperText?: boolean; // Option to show/hide the helper text + showHelperText?: boolean; + /** When true, shows "保質期: X 日" under the 年 field only */ + helperUnderYear?: boolean; + /** Labels above fields instead of on the outline (avoids clipping) */ + externalLabels?: boolean; + /** Only render Y/M/D inputs (labels & helper rendered by parent) */ + inputsOnly?: boolean; } +const UnitLabel: React.FC<{ children: React.ReactNode }> = ({ children }) => ( + + {children} + +); + const ShelfLifeContainer = styled(Box)(({ theme }) => ({ display: 'flex', - gap: theme.spacing(1), + gap: theme.spacing(2), alignItems: 'flex-start', width: '100%', '& .MuiTextField-root': { flex: 1, + minWidth: 88, '& input': { textAlign: 'center', }, @@ -62,7 +85,16 @@ const formatDuration = (years: number, months: number, days: number) => { return parts.length > 0 ? parts.join(' ') : '0 日'; }; -const ShelfLifeInput: React.FC = ({ value = 0, onChange = () => {}, label = 'Shelf Life', sx, showHelperText = true }) => { +const ShelfLifeInput: React.FC = ({ + value = 0, + onChange = () => {}, + label = 'Shelf Life', + sx, + showHelperText = true, + helperUnderYear = false, + externalLabels = false, + inputsOnly = false, +}) => { const { t } = useTranslation("purchaseOrder"); const { years, months, days } = daysToDuration(value); @@ -101,54 +133,103 @@ const ShelfLifeInput: React.FC = ({ value = 0, onChange = ( } }; + const shelfLifeSummary = showHelperText && ( + + {label}:{' '} + + {totalDays} 日 + + + ); + + const hiddenLabelProps = { shrink: true, sx: { display: 'none' } }; + + const yearField = ( + + ); + + const monthField = ( + + ); + + const dayField = ( + + ); + + const inputsRow = ( + + + {!inputsOnly && externalLabels && } + {yearField} + {!inputsOnly && helperUnderYear && shelfLifeSummary} + + + {!inputsOnly && externalLabels && } + {monthField} + + + {!inputsOnly && externalLabels && } + {dayField} + + + ); + + if (inputsOnly) { + return {inputsRow}; + } + return ( - - - - - - - {showHelperText && ( - - {label}: - {/* {formatDuration(duration.years, duration.months, duration.days)} */} - {totalDays} 日 - - - )} + + {inputsRow} + {showHelperText && !helperUnderYear && shelfLifeSummary} ); };