@@ -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> | ||||
); | ); | ||||
})} | })} | ||||