| @@ -22,7 +22,8 @@ export interface SaveJoResponse { | |||||
| export interface SearchJoResultRequest extends Pageable { | export interface SearchJoResultRequest extends Pageable { | ||||
| code: string; | code: string; | ||||
| itemName?: string; | itemName?: string; | ||||
| planStart?: string; | |||||
| planStartTo?: string; | |||||
| } | } | ||||
| @@ -25,6 +25,7 @@ export interface JobOrder { | |||||
| pickLines?: JoDetailPickLine[]; | pickLines?: JoDetailPickLine[]; | ||||
| status: JoStatus; | status: JoStatus; | ||||
| planStart?: number[]; | planStart?: number[]; | ||||
| planStartTo?: string; | |||||
| planEnd?: number[]; | planEnd?: number[]; | ||||
| type: string; | type: string; | ||||
| // TODO pack below into StockInLineInfo | // TODO pack below into StockInLineInfo | ||||
| @@ -181,6 +181,7 @@ const JoCreateFormModal: React.FC<Props> = ({ | |||||
| render={({ field, fieldState: { error } }) => ( | render={({ field, fieldState: { error } }) => ( | ||||
| <DateTimePicker | <DateTimePicker | ||||
| label={t("Plan Start")} | label={t("Plan Start")} | ||||
| views={['year','month','day','hours', 'minutes', 'seconds']} | |||||
| format={`${OUTPUT_DATE_FORMAT} ${OUTPUT_TIME_FORMAT}`} | format={`${OUTPUT_DATE_FORMAT} ${OUTPUT_TIME_FORMAT}`} | ||||
| onChange={(newValue: Dayjs | null) => { | onChange={(newValue: Dayjs | null) => { | ||||
| handleDateTimePickerChange(newValue, field.onChange) | handleDateTimePickerChange(newValue, field.onChange) | ||||
| @@ -5,7 +5,7 @@ import { useTranslation } from "react-i18next"; | |||||
| import { Criterion } from "../SearchBox"; | import { Criterion } from "../SearchBox"; | ||||
| import SearchResults, { Column, defaultPagingController } from "../SearchResults/SearchResults"; | import SearchResults, { Column, defaultPagingController } from "../SearchResults/SearchResults"; | ||||
| import { EditNote } from "@mui/icons-material"; | import { EditNote } from "@mui/icons-material"; | ||||
| import { arrayToDateString, integerFormatter } from "@/app/utils/formatUtil"; | |||||
| import { arrayToDateString, arrayToDateTimeString, integerFormatter } from "@/app/utils/formatUtil"; | |||||
| import { orderBy, uniqBy, upperFirst } from "lodash"; | import { orderBy, uniqBy, upperFirst } from "lodash"; | ||||
| import SearchBox from "../SearchBox/SearchBox"; | import SearchBox from "../SearchBox/SearchBox"; | ||||
| import { useRouter } from "next/navigation"; | import { useRouter } from "next/navigation"; | ||||
| @@ -31,10 +31,7 @@ interface Props { | |||||
| bomCombo: BomCombo[] | bomCombo: BomCombo[] | ||||
| } | } | ||||
| type SearchQuery = Partial<Omit<JobOrder, "id">> & { | |||||
| planStartFrom?: string; | |||||
| planStartTo?: string; | |||||
| }; | |||||
| type SearchQuery = Partial<Omit<JobOrder, "id">>; | |||||
| type SearchParamNames = keyof SearchQuery; | type SearchParamNames = keyof SearchQuery; | ||||
| @@ -49,6 +46,7 @@ const JoSearch: React.FC<Props> = ({ defaultInputs, bomCombo }) => { | |||||
| const [totalCount, setTotalCount] = useState(0) | const [totalCount, setTotalCount] = useState(0) | ||||
| const [isCreateJoModalOpen, setIsCreateJoModalOpen] = useState(false) | const [isCreateJoModalOpen, setIsCreateJoModalOpen] = useState(false) | ||||
| console.log(inputs) | |||||
| const [inventoryData, setInventoryData] = useState<InventoryResult[]>([]); | const [inventoryData, setInventoryData] = useState<InventoryResult[]>([]); | ||||
| const [detailedJos, setDetailedJos] = useState<Map<number, JobOrder>>(new Map()); | const [detailedJos, setDetailedJos] = useState<Map<number, JobOrder>>(new Map()); | ||||
| @@ -138,7 +136,8 @@ const JoSearch: React.FC<Props> = ({ defaultInputs, bomCombo }) => { | |||||
| const searchCriteria: Criterion<SearchParamNames>[] = useMemo(() => [ | const searchCriteria: Criterion<SearchParamNames>[] = useMemo(() => [ | ||||
| { label: t("Code"), paramName: "code", type: "text" }, | { label: t("Code"), paramName: "code", type: "text" }, | ||||
| { label: t("Item Name"), paramName: "itemName", type: "text" }, | |||||
| { label: t("Item Name"), paramName: "itemName", type: "text" }, | |||||
| { label: t("Plan Start From"), label2: t("Plan Start To"), paramName: "planStart", type: "datetimeRange" }, | |||||
| ], [t]) | ], [t]) | ||||
| const columns = useMemo<Column<JobOrder>[]>( | const columns = useMemo<Column<JobOrder>[]>( | ||||
| @@ -194,7 +193,7 @@ const JoSearch: React.FC<Props> = ({ defaultInputs, bomCombo }) => { | |||||
| align: "left", | align: "left", | ||||
| headerAlign: "left", | headerAlign: "left", | ||||
| renderCell: (row) => { | renderCell: (row) => { | ||||
| return row.planStart ? arrayToDateString(row.planStart, "output") : '-' | |||||
| return row.planStart ? arrayToDateTimeString(row.planStart) : '-' | |||||
| } | } | ||||
| }, | }, | ||||
| { | { | ||||
| @@ -273,6 +272,8 @@ const JoSearch: React.FC<Props> = ({ defaultInputs, bomCombo }) => { | |||||
| const params: SearchJoResultRequest = { | const params: SearchJoResultRequest = { | ||||
| code: query.code, | code: query.code, | ||||
| itemName: query.itemName, | itemName: query.itemName, | ||||
| planStart: query.planStart, | |||||
| planStartTo: query.planStartTo, | |||||
| pageNum: pagingController.pageNum - 1, | pageNum: pagingController.pageNum - 1, | ||||
| pageSize: pagingController.pageSize, | pageSize: pagingController.pageSize, | ||||
| } | } | ||||
| @@ -364,7 +365,9 @@ const JoSearch: React.FC<Props> = ({ defaultInputs, bomCombo }) => { | |||||
| const onSearch = useCallback((query: Record<SearchParamNames, string>) => { | const onSearch = useCallback((query: Record<SearchParamNames, string>) => { | ||||
| setInputs(() => ({ | setInputs(() => ({ | ||||
| code: query.code, | code: query.code, | ||||
| itemName: query.itemName | |||||
| itemName: query.itemName, | |||||
| planStart: query.planStart, | |||||
| planStartTo: query.planStartTo | |||||
| })) | })) | ||||
| refetchData(query, "search"); | refetchData(query, "search"); | ||||
| }, []) | }, []) | ||||
| @@ -11,7 +11,7 @@ interface SubComponents { | |||||
| const JoSearchWrapper: React.FC & SubComponents = async () => { | const JoSearchWrapper: React.FC & SubComponents = async () => { | ||||
| const defaultInputs: SearchJoResultRequest = { | const defaultInputs: SearchJoResultRequest = { | ||||
| code: "", | code: "", | ||||
| name: "", | |||||
| itemName: "", | |||||
| } | } | ||||
| const [ | const [ | ||||
| @@ -15,7 +15,7 @@ import CardActions from "@mui/material/CardActions"; | |||||
| import Button from "@mui/material/Button"; | import Button from "@mui/material/Button"; | ||||
| import RestartAlt from "@mui/icons-material/RestartAlt"; | import RestartAlt from "@mui/icons-material/RestartAlt"; | ||||
| import Search from "@mui/icons-material/Search"; | import Search from "@mui/icons-material/Search"; | ||||
| import dayjs from "dayjs"; | |||||
| import dayjs, { Dayjs } from "dayjs"; | |||||
| import "dayjs/locale/zh-hk"; | import "dayjs/locale/zh-hk"; | ||||
| import { DatePicker } from "@mui/x-date-pickers/DatePicker"; | import { DatePicker } from "@mui/x-date-pickers/DatePicker"; | ||||
| import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider"; | import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider"; | ||||
| @@ -29,6 +29,8 @@ import { | |||||
| } from "@mui/material"; | } from "@mui/material"; | ||||
| import MultiSelect from "@/components/SearchBox/MultiSelect"; | import MultiSelect from "@/components/SearchBox/MultiSelect"; | ||||
| import { intersectionWith } from "lodash"; | import { intersectionWith } from "lodash"; | ||||
| import { INPUT_DATE_FORMAT, INPUT_TIME_FORMAT, OUTPUT_DATE_FORMAT, OUTPUT_TIME_FORMAT, dayjsToDateTimeString } from "@/app/utils/formatUtil"; | |||||
| import { DateTimePicker } from "@mui/x-date-pickers"; | |||||
| interface BaseCriterion<T extends string> { | interface BaseCriterion<T extends string> { | ||||
| label: string; | label: string; | ||||
| @@ -86,6 +88,10 @@ interface DateRangeCriterion<T extends string> extends BaseCriterion<T> { | |||||
| type: "dateRange"; | type: "dateRange"; | ||||
| } | } | ||||
| interface DatetimeRangeCriterion<T extends string> extends BaseCriterion<T> { | |||||
| type: "datetimeRange"; | |||||
| } | |||||
| interface DateCriterion<T extends string> extends BaseCriterion<T> { | interface DateCriterion<T extends string> extends BaseCriterion<T> { | ||||
| type: "date"; | type: "date"; | ||||
| } | } | ||||
| @@ -95,6 +101,7 @@ export type Criterion<T extends string> = | |||||
| | SelectCriterion<T> | | SelectCriterion<T> | ||||
| | SelectWithLabelCriterion<T> | | SelectWithLabelCriterion<T> | ||||
| | DateRangeCriterion<T> | | DateRangeCriterion<T> | ||||
| | DatetimeRangeCriterion<T> | |||||
| | DateCriterion<T> | | DateCriterion<T> | ||||
| | MultiSelectCriterion<T> | | MultiSelectCriterion<T> | ||||
| | AutocompleteCriterion<T>; | | AutocompleteCriterion<T>; | ||||
| @@ -135,7 +142,7 @@ function SearchBox<T extends string>({ | |||||
| : "", | : "", | ||||
| }; | }; | ||||
| if (c.type === "dateRange") { | |||||
| if (c.type === "dateRange" || c.type === "datetimeRange") { | |||||
| tempCriteria = { | tempCriteria = { | ||||
| ...tempCriteria, | ...tempCriteria, | ||||
| [c.paramName]: c.defaultValue ?? "", | [c.paramName]: c.defaultValue ?? "", | ||||
| @@ -216,6 +223,24 @@ function SearchBox<T extends string>({ | |||||
| }; | }; | ||||
| }, []); | }, []); | ||||
| const makeDatetimeChangeHandler = useCallback((paramName: T) => { | |||||
| return (value: Dayjs | null) => { | |||||
| setInputs((i) => ({ | |||||
| ...i, | |||||
| [paramName]: value ? dayjsToDateTimeString(value) : null | |||||
| })); | |||||
| }; | |||||
| }, []); | |||||
| const makeDatetimeToChangeHandler = useCallback((paramName: T) => { | |||||
| return (value: Dayjs | null) => { | |||||
| setInputs((i) => ({ | |||||
| ...i, | |||||
| [paramName + "To"]: value ? dayjsToDateTimeString(value) : null | |||||
| })); | |||||
| }; | |||||
| }, []); | |||||
| const handleReset = () => { | const handleReset = () => { | ||||
| setInputs(defaultInputs); | setInputs(defaultInputs); | ||||
| onReset?.(); | onReset?.(); | ||||
| @@ -397,6 +422,7 @@ function SearchBox<T extends string>({ | |||||
| <Box display="flex"> | <Box display="flex"> | ||||
| <FormControl fullWidth> | <FormControl fullWidth> | ||||
| <DatePicker | <DatePicker | ||||
| format={`${OUTPUT_DATE_FORMAT}`} | |||||
| label={t(c.label)} | label={t(c.label)} | ||||
| onChange={makeDateChangeHandler(c.paramName)} | onChange={makeDateChangeHandler(c.paramName)} | ||||
| value={ | value={ | ||||
| @@ -416,6 +442,7 @@ function SearchBox<T extends string>({ | |||||
| </Box> | </Box> | ||||
| <FormControl fullWidth> | <FormControl fullWidth> | ||||
| <DatePicker | <DatePicker | ||||
| format={`${OUTPUT_DATE_FORMAT}`} | |||||
| label={c.label2 ? t(c.label2) : null} | label={c.label2 ? t(c.label2) : null} | ||||
| onChange={makeDateToChangeHandler(c.paramName)} | onChange={makeDateToChangeHandler(c.paramName)} | ||||
| value={ | value={ | ||||
| @@ -428,6 +455,50 @@ function SearchBox<T extends string>({ | |||||
| </Box> | </Box> | ||||
| </LocalizationProvider> | </LocalizationProvider> | ||||
| )} | )} | ||||
| {c.type === "datetimeRange" && ( | |||||
| <LocalizationProvider | |||||
| dateAdapter={AdapterDayjs} | |||||
| // TODO: Should maybe use a custom adapterLocale here to support YYYY-MM-DD | |||||
| adapterLocale="zh-hk" | |||||
| > | |||||
| <Box display="flex"> | |||||
| <FormControl fullWidth> | |||||
| <DateTimePicker | |||||
| views={['year','month','day','hours', 'minutes', 'seconds']} | |||||
| format={`${OUTPUT_DATE_FORMAT} ${OUTPUT_TIME_FORMAT}`} | |||||
| label={t(c.label)} | |||||
| onChange={makeDatetimeChangeHandler(c.paramName)} | |||||
| value={ | |||||
| dayjs(inputs[c.paramName]).isValid() | |||||
| ? dayjs(inputs[c.paramName]) | |||||
| : null | |||||
| } | |||||
| /> | |||||
| </FormControl> | |||||
| <Box | |||||
| display="flex" | |||||
| alignItems="center" | |||||
| justifyContent="center" | |||||
| marginInline={2} | |||||
| > | |||||
| {"-"} | |||||
| </Box> | |||||
| <FormControl fullWidth> | |||||
| <DateTimePicker | |||||
| views={['year','month','day','hours', 'minutes', 'seconds']} | |||||
| format={`${OUTPUT_DATE_FORMAT} ${OUTPUT_TIME_FORMAT}`} | |||||
| label={c.label2 ? t(c.label2) : null} | |||||
| onChange={makeDatetimeToChangeHandler(c.paramName)} | |||||
| value={ | |||||
| dayjs(inputs[`${c.paramName}To`]).isValid() | |||||
| ? dayjs(inputs[`${c.paramName}To`]) | |||||
| : null | |||||
| } | |||||
| /> | |||||
| </FormControl> | |||||
| </Box> | |||||
| </LocalizationProvider> | |||||
| )} | |||||
| {c.type === "date" && ( | {c.type === "date" && ( | ||||
| <LocalizationProvider | <LocalizationProvider | ||||
| dateAdapter={AdapterDayjs} | dateAdapter={AdapterDayjs} | ||||
| @@ -437,6 +508,7 @@ function SearchBox<T extends string>({ | |||||
| <Box display="flex"> | <Box display="flex"> | ||||
| <FormControl fullWidth> | <FormControl fullWidth> | ||||
| <DatePicker | <DatePicker | ||||
| format={`${OUTPUT_DATE_FORMAT}`} | |||||
| label={t(c.label)} | label={t(c.label)} | ||||
| onChange={makeDateChangeHandler(c.paramName)} | onChange={makeDateChangeHandler(c.paramName)} | ||||
| /> | /> | ||||