| @@ -0,0 +1,48 @@ | |||||
| import { TypeEnum } from "@/app/utils/typeEnum"; | |||||
| import ItemsSearch from "@/components/ItemsSearch"; | |||||
| import { getServerI18n } from "@/i18n"; | |||||
| import Add from "@mui/icons-material/Add"; | |||||
| import Button from "@mui/material/Button"; | |||||
| import Stack from "@mui/material/Stack"; | |||||
| import Typography from "@mui/material/Typography"; | |||||
| import { Metadata } from "next"; | |||||
| import Link from "next/link"; | |||||
| import { Suspense } from "react"; | |||||
| export const metadata: Metadata = { | |||||
| title: "Detail Scheduling", | |||||
| }; | |||||
| const detailScheduling: React.FC = async () => { | |||||
| const project = TypeEnum.PRODUCT | |||||
| const { t } = await getServerI18n(project); | |||||
| // preloadClaims(); | |||||
| return ( | |||||
| <> | |||||
| <Stack | |||||
| direction="row" | |||||
| justifyContent="space-between" | |||||
| flexWrap="wrap" | |||||
| rowGap={2} | |||||
| > | |||||
| <Typography variant="h4" marginInlineEnd={2}> | |||||
| {t("Detail Scheduling")} | |||||
| </Typography> | |||||
| {/* <Button | |||||
| variant="contained" | |||||
| startIcon={<Add />} | |||||
| LinkComponent={Link} | |||||
| href="product/create" | |||||
| > | |||||
| {t("Create product")} | |||||
| </Button> */} | |||||
| </Stack> | |||||
| <Suspense fallback={<ItemsSearch.Loading />}> | |||||
| <ItemsSearch /> | |||||
| </Suspense> | |||||
| </> | |||||
| ); | |||||
| }; | |||||
| export default detailScheduling; | |||||
| @@ -0,0 +1,11 @@ | |||||
| import { Metadata } from "next"; | |||||
| export const metadata: Metadata = { | |||||
| title: "Scheduling", | |||||
| }; | |||||
| const Scheduling: React.FC = async () => { | |||||
| return null; | |||||
| }; | |||||
| export default Scheduling; | |||||
| @@ -0,0 +1,48 @@ | |||||
| import { TypeEnum } from "@/app/utils/typeEnum"; | |||||
| import RoughSchedule from "@/components/RoughSchedule"; | |||||
| import { getServerI18n } from "@/i18n"; | |||||
| import Add from "@mui/icons-material/Add"; | |||||
| import Button from "@mui/material/Button"; | |||||
| import Stack from "@mui/material/Stack"; | |||||
| import Typography from "@mui/material/Typography"; | |||||
| import { Metadata } from "next"; | |||||
| import Link from "next/link"; | |||||
| import { Suspense } from "react"; | |||||
| export const metadata: Metadata = { | |||||
| title: "Rough Scheduling", | |||||
| }; | |||||
| const roughScheduling: React.FC = async () => { | |||||
| const project = TypeEnum.PRODUCT | |||||
| const { t } = await getServerI18n(project); | |||||
| // preloadClaims(); | |||||
| return ( | |||||
| <> | |||||
| <Stack | |||||
| direction="row" | |||||
| justifyContent="space-between" | |||||
| flexWrap="wrap" | |||||
| rowGap={2} | |||||
| > | |||||
| <Typography variant="h4" marginInlineEnd={2}> | |||||
| {t("Rough Scheduling")} | |||||
| </Typography> | |||||
| {/* <Button | |||||
| variant="contained" | |||||
| startIcon={<Add />} | |||||
| LinkComponent={Link} | |||||
| href="product/create" | |||||
| > | |||||
| {t("Create product")} | |||||
| </Button> */} | |||||
| </Stack> | |||||
| <Suspense fallback={<RoughSchedule.Loading />}> | |||||
| <RoughSchedule /> | |||||
| </Suspense> | |||||
| </> | |||||
| ); | |||||
| }; | |||||
| export default roughScheduling; | |||||
| @@ -14,6 +14,8 @@ const pathToLabelMap: { [path: string]: string } = { | |||||
| "/tasks/create": "Create Task Template", | "/tasks/create": "Create Task Template", | ||||
| "/settings/qcItem": "Qc Item", | "/settings/qcItem": "Qc Item", | ||||
| "/settings/rss": "Rough Schedule Setting", | "/settings/rss": "Rough Schedule Setting", | ||||
| "/scheduling/rough": "Rough Scheduling", | |||||
| "/scheduling/detail": "Detail Scheduling", | |||||
| }; | }; | ||||
| const Breadcrumb = () => { | const Breadcrumb = () => { | ||||
| @@ -166,6 +166,23 @@ const NavigationContent: React.FC = () => { | |||||
| }, | }, | ||||
| ], | ], | ||||
| }, | }, | ||||
| { | |||||
| icon: <RequestQuote />, | |||||
| label: "Scheduling", | |||||
| path: "", | |||||
| children: [ | |||||
| { | |||||
| icon: <RequestQuote />, | |||||
| label: "Rough Scheduling", | |||||
| path: "/scheduling/rough", | |||||
| }, | |||||
| { | |||||
| icon: <RequestQuote />, | |||||
| label: "Detail Scheduling", | |||||
| path: "/scheduling/detail", | |||||
| }, | |||||
| ], | |||||
| }, | |||||
| { | { | ||||
| icon: <RequestQuote />, | icon: <RequestQuote />, | ||||
| label: "Settings", | label: "Settings", | ||||
| @@ -0,0 +1,194 @@ | |||||
| "use client"; | |||||
| import React, {useCallback, useEffect, useMemo, useState} from "react"; | |||||
| import SearchBox, { Criterion } from "../SearchBox"; | |||||
| import { ItemsResult} from "@/app/api/settings/item"; | |||||
| import SearchResults, { Column } from "../SearchResults"; | |||||
| import { EditNote } from "@mui/icons-material"; | |||||
| import { useRouter, useSearchParams } from "next/navigation"; | |||||
| import { GridDeleteIcon } from "@mui/x-data-grid"; | |||||
| import { TypeEnum } from "@/app/utils/typeEnum"; | |||||
| import axios from "axios"; | |||||
| import {BASE_API_URL, NEXT_PUBLIC_API_URL} from "@/config/api"; | |||||
| import { useTranslation } from "react-i18next"; | |||||
| import axiosInstance from "@/app/(main)/axios/axiosInstance"; | |||||
| import Qs from 'qs'; | |||||
| import EditableSearchResults from "@/components/SearchResults/EditableSearchResults"; // Make sure to import Qs | |||||
| type RecordStructure ={ | |||||
| id: number, | |||||
| schedulePeriod: string, | |||||
| scheduleAt: string | |||||
| }; | |||||
| type Props = { | |||||
| records: RecordStructure[]; | |||||
| }; | |||||
| type SearchQuery = Partial<Omit<ItemsResult, "id">>; | |||||
| type SearchParamNames = keyof SearchQuery; | |||||
| const RSOverview: React.FC<Props> = ({ records }) => { | |||||
| const [filteredItems, setFilteredItems] = useState<Object[]>(records ?? []); | |||||
| const { t } = useTranslation("items"); | |||||
| const router = useRouter(); | |||||
| const [filterObj, setFilterObj] = useState({}); | |||||
| const [tempSelectedValue, setTempSelectedValue] = useState({}); | |||||
| const [pagingController, setPagingController] = useState({ | |||||
| pageNum: 1, | |||||
| pageSize: 10, | |||||
| totalCount: 0, | |||||
| }) | |||||
| const [mode, redirPath] = useMemo(() => { | |||||
| // var typeId = TypeEnum.CONSUMABLE_ID | |||||
| var title = ""; | |||||
| var mode = "Search"; | |||||
| var redirPath = ""; | |||||
| title = "Product"; | |||||
| redirPath = "/scheduling/rough"; | |||||
| return [mode, redirPath]; | |||||
| }, []); | |||||
| const searchCriteria: Criterion<SearchParamNames>[] = useMemo( | |||||
| () => { | |||||
| var searchCriteria: Criterion<SearchParamNames>[] = [ | |||||
| { label: t("Schedule Period"), paramName: "schedulePeriod", type: "dateRange" }, | |||||
| { label: t("Scheduled At"), paramName: "scheduleAt", type: "date" }, | |||||
| { label: t("Product Count"), paramName: "productCount", type: "input" }, | |||||
| ] | |||||
| return searchCriteria | |||||
| }, | |||||
| [t, records] | |||||
| ); | |||||
| // const onDetailClick = useCallback( | |||||
| // (item: ItemsResult) => { | |||||
| // router.push(`/settings/items/edit?id=${item.id}`); | |||||
| // }, | |||||
| // [router] | |||||
| // ); | |||||
| const onDeleteClick = useCallback( | |||||
| (item: ItemsResult) => {}, | |||||
| [router] | |||||
| ); | |||||
| const columns = useMemo<Column<ItemsResult>[]>( | |||||
| () => [ | |||||
| { | |||||
| name: "id", | |||||
| label: t("Details"), | |||||
| onClick: ()=>{}, | |||||
| buttonIcon: <EditNote />, | |||||
| }, | |||||
| { | |||||
| name: "scheduledPeriod", | |||||
| label: "Rough Schedule Period", | |||||
| }, | |||||
| { | |||||
| name: "scheduledAt", | |||||
| label: t("Scheduled At"), | |||||
| }, | |||||
| { | |||||
| name: "productCount", | |||||
| label: t("Product Count(s)"), | |||||
| }, | |||||
| // { | |||||
| // name: "action", | |||||
| // label: t(""), | |||||
| // buttonIcon: <GridDeleteIcon />, | |||||
| // onClick: onDeleteClick, | |||||
| // }, | |||||
| ], | |||||
| [filteredItems] | |||||
| ); | |||||
| useEffect(() => { | |||||
| refetchData(filterObj); | |||||
| }, [filterObj, pagingController.pageNum, pagingController.pageSize]); | |||||
| const refetchData = async (filterObj: SearchQuery) => { | |||||
| const authHeader = axiosInstance.defaults.headers['Authorization']; | |||||
| if (!authHeader) { | |||||
| return; // Exit the function if the token is not set | |||||
| } | |||||
| const params ={ | |||||
| pageNum: pagingController.pageNum, | |||||
| pageSize: pagingController.pageSize, | |||||
| ...filterObj, | |||||
| ...tempSelectedValue, | |||||
| } | |||||
| try { | |||||
| const response = await axiosInstance.get<ItemsResult[]>(`${NEXT_PUBLIC_API_URL}/items/getRecordByPage`, { | |||||
| params, | |||||
| paramsSerializer: (params) => { | |||||
| return Qs.stringify(params, { arrayFormat: 'repeat' }); | |||||
| }, | |||||
| }); | |||||
| //setFilteredItems(response.data.records); | |||||
| setFilteredItems([ | |||||
| { | |||||
| id: 1, | |||||
| scheduledPeriod: "2025-05-11 to 2025-05-17", | |||||
| scheduledAt: "2025-05-07", | |||||
| productCount: 13, | |||||
| }, | |||||
| { | |||||
| id: 2, | |||||
| scheduledPeriod: "2025-05-18 to 2025-05-24", | |||||
| scheduledAt: "2025-05-14", | |||||
| productCount: 15, | |||||
| }, | |||||
| { | |||||
| id: 3, | |||||
| scheduledPeriod: "2025-05-25 to 2025-05-31", | |||||
| scheduledAt: "2025-05-21", | |||||
| productCount: 13, | |||||
| }, | |||||
| ]) | |||||
| setPagingController({ | |||||
| ...pagingController, | |||||
| totalCount: response.data.total | |||||
| }) | |||||
| return response; // Return the data from the response | |||||
| } catch (error) { | |||||
| console.error('Error fetching items:', error); | |||||
| throw error; // Rethrow the error for further handling | |||||
| } | |||||
| }; | |||||
| const onReset = useCallback(() => { | |||||
| //setFilteredItems(items ?? []); | |||||
| setFilterObj({}); | |||||
| setTempSelectedValue({}); | |||||
| refetchData(); | |||||
| }, [records]); | |||||
| return ( | |||||
| <> | |||||
| <SearchBox | |||||
| criteria={searchCriteria} | |||||
| onSearch={(query) => { | |||||
| setFilterObj({ | |||||
| ...query | |||||
| }) | |||||
| }} | |||||
| onReset={onReset} | |||||
| /> | |||||
| <SearchResults<ItemsResult> | |||||
| items={filteredItems} | |||||
| columns={columns} | |||||
| setPagingController={setPagingController} | |||||
| pagingController={pagingController} | |||||
| isAutoPaging={false} | |||||
| /> | |||||
| </> | |||||
| ); | |||||
| }; | |||||
| export default RSOverview; | |||||
| @@ -0,0 +1,40 @@ | |||||
| import Card from "@mui/material/Card"; | |||||
| import CardContent from "@mui/material/CardContent"; | |||||
| import Skeleton from "@mui/material/Skeleton"; | |||||
| import Stack from "@mui/material/Stack"; | |||||
| import React from "react"; | |||||
| // Can make this nicer | |||||
| export const RoughScheduleLoading: React.FC = () => { | |||||
| return ( | |||||
| <> | |||||
| <Card> | |||||
| <CardContent> | |||||
| <Stack spacing={2}> | |||||
| <Skeleton variant="rounded" height={60} /> | |||||
| <Skeleton variant="rounded" height={60} /> | |||||
| <Skeleton variant="rounded" height={60} /> | |||||
| <Skeleton | |||||
| variant="rounded" | |||||
| height={50} | |||||
| width={100} | |||||
| sx={{ alignSelf: "flex-end" }} | |||||
| /> | |||||
| </Stack> | |||||
| </CardContent> | |||||
| </Card> | |||||
| <Card> | |||||
| <CardContent> | |||||
| <Stack spacing={2}> | |||||
| <Skeleton variant="rounded" height={40} /> | |||||
| <Skeleton variant="rounded" height={40} /> | |||||
| <Skeleton variant="rounded" height={40} /> | |||||
| <Skeleton variant="rounded" height={40} /> | |||||
| </Stack> | |||||
| </CardContent> | |||||
| </Card> | |||||
| </> | |||||
| ); | |||||
| }; | |||||
| export default RoughScheduleLoading; | |||||
| @@ -0,0 +1,19 @@ | |||||
| import { fetchAllItems, } from "@/app/api/settings/item"; | |||||
| import {RoughScheduleLoading} from "./RoughScheduleLoading"; | |||||
| import RSOverview from "./RoughSchedileSearchView"; | |||||
| interface SubComponents { | |||||
| Loading: typeof RoughScheduleLoading; | |||||
| } | |||||
| const RoughScheduleWrapper: ({}: {}) => Promise<JSX.Element> = async ({ | |||||
| // type, | |||||
| }) => { | |||||
| // console.log(type) | |||||
| var result = await fetchAllItems() | |||||
| return <RSOverview records={result} />; | |||||
| }; | |||||
| RoughScheduleWrapper.Loading = RoughScheduleLoading; | |||||
| export default RoughScheduleWrapper; | |||||
| @@ -0,0 +1 @@ | |||||
| export { default } from "./RoughScheduleWrapper"; | |||||
| @@ -196,6 +196,22 @@ function SearchBox<T extends string>({ | |||||
| </Box> | </Box> | ||||
| </LocalizationProvider> | </LocalizationProvider> | ||||
| )} | )} | ||||
| {c.type === "date" && ( | |||||
| <LocalizationProvider | |||||
| dateAdapter={AdapterDayjs} | |||||
| // TODO: Should maybe use a custom adapterLocale here to support YYYY-MM-DD | |||||
| adapterLocale="zh-hk" | |||||
| > | |||||
| <Box display="flex"> | |||||
| <FormControl fullWidth> | |||||
| <DatePicker | |||||
| label={c.label} | |||||
| onChange={makeDateChangeHandler(c.paramName)} | |||||
| /> | |||||
| </FormControl> | |||||
| </Box> | |||||
| </LocalizationProvider> | |||||
| )} | |||||
| </Grid> | </Grid> | ||||
| ); | ); | ||||
| })} | })} | ||||