@@ -0,0 +1,33 @@ | |||||
import DoSearch from "@/components/DoSearch"; | |||||
import { getServerI18n } from "@/i18n" | |||||
import { Stack, Typography } from "@mui/material"; | |||||
import { Metadata } from "next"; | |||||
import { Suspense } from "react"; | |||||
export const metadata: Metadata = { | |||||
title: "Delivery Order" | |||||
} | |||||
const DeliveryOrder: React.FC = async () => { | |||||
const { t } = await getServerI18n("do"); | |||||
return ( | |||||
<> | |||||
<Stack | |||||
direction="row" | |||||
justifyContent={"space-between"} | |||||
flexWrap={"wrap"} | |||||
rowGap={2} | |||||
> | |||||
<Typography variant="h4" marginInlineEnd={2}> | |||||
{t("Delivery Order")} | |||||
</Typography> | |||||
</Stack> | |||||
<Suspense fallback={<DoSearch.Loading />}> | |||||
<DoSearch /> | |||||
</Suspense> | |||||
</> | |||||
) | |||||
} | |||||
export default DeliveryOrder; |
@@ -0,0 +1,21 @@ | |||||
import { serverFetchJson } from "@/app/utils/fetchUtil"; | |||||
import { BASE_API_URL } from "@/config/api"; | |||||
import { cache } from "react"; | |||||
export interface DoResult { | |||||
id: number, | |||||
code: string, | |||||
orderDate: string, | |||||
status: string, | |||||
shopName: string, | |||||
} | |||||
export const preloadDo = () => { | |||||
fetchDoList(); | |||||
} | |||||
export const fetchDoList = cache(async () => { | |||||
return serverFetchJson<DoResult[]>(`${BASE_API_URL}/do/list`, { | |||||
next: { tags: ["doList"] } | |||||
}) | |||||
}) |
@@ -20,6 +20,7 @@ const pathToLabelMap: { [path: string]: string } = { | |||||
"/scheduling/detail/edit": "FG Production Schedule", | "/scheduling/detail/edit": "FG Production Schedule", | ||||
"/inventory": "Inventory", | "/inventory": "Inventory", | ||||
"/settings/importTesting": "Import Testing", | "/settings/importTesting": "Import Testing", | ||||
"/do": "Delivery Order", | |||||
}; | }; | ||||
const Breadcrumb = () => { | const Breadcrumb = () => { | ||||
@@ -0,0 +1 @@ | |||||
export default from "./DoSaveWrapper" |
@@ -0,0 +1,115 @@ | |||||
"use client"; | |||||
import { DoResult } from "@/app/api/do" | |||||
import { useRouter } from "next/navigation"; | |||||
import { useCallback, useMemo, useState } from "react"; | |||||
import { useTranslation } from "react-i18next"; | |||||
import { Criterion } from "../SearchBox"; | |||||
import { isEmpty, sortBy, uniqBy, upperFirst } from "lodash"; | |||||
import { Column } from "../SearchResults"; | |||||
import { arrayToDateString, arrayToDayjs } from "@/app/utils/formatUtil"; | |||||
import SearchBox from "../SearchBox/SearchBox"; | |||||
import SearchResults from "../SearchResults/SearchResults"; | |||||
import { EditNote } from "@mui/icons-material"; | |||||
type Props = { | |||||
dos: DoResult[] | |||||
} | |||||
type SearchQuery = Partial<Omit<DoResult, "id">>; | |||||
type SearchParamNames = keyof SearchQuery; | |||||
const DoSearch: React.FC<Props> = ({ | |||||
dos | |||||
}) => { | |||||
const [filteredDos, setFilteredDos] = useState<DoResult[]>(dos); | |||||
const { t } = useTranslation("do"); | |||||
const router = useRouter(); | |||||
const searchCriteria: Criterion<SearchParamNames>[] = useMemo(() => [ | |||||
{ label: t("Code"), paramName: "code", type: "text" }, | |||||
{ label: t("Order Date From"), label2: t("Order Date To"), paramName: "orderDate", type: "dateRange" }, | |||||
{ label: t("Shop Name"), paramName: "shopName", type: "text" }, | |||||
{ | |||||
label: t("Status"), paramName: "status", type: "autocomplete", options: sortBy( | |||||
uniqBy(dos.map((_do) => ({ value: _do.status, label: t(upperFirst(_do.status)) })), "value"), | |||||
"label") | |||||
}, | |||||
], [t, dos]) | |||||
const onReset = useCallback(() => { | |||||
setFilteredDos(dos) | |||||
}, [dos]) | |||||
const onDetailClick = useCallback( | |||||
(doResult: DoResult) => { | |||||
router.push(`/do/edit?id=${doResult.id}`); | |||||
}, | |||||
[router] | |||||
); | |||||
const columns = useMemo<Column<DoResult>[]>(() => [ | |||||
{ | |||||
name: "id", | |||||
label: t("Details"), | |||||
onClick: onDetailClick, | |||||
buttonIcon: <EditNote />, | |||||
}, | |||||
{ | |||||
name: "code", | |||||
label: t("Code") | |||||
}, | |||||
{ | |||||
name: "shopName", | |||||
label: t("Shop Name") | |||||
}, | |||||
{ | |||||
name: "orderDate", | |||||
label: t("Order Date"), | |||||
renderCell: (params) => { | |||||
return params.orderDate ? arrayToDateString(params.orderDate) : "N/A" | |||||
}, | |||||
}, | |||||
{ | |||||
name: "status", | |||||
label: t("Status"), | |||||
renderCell: (params) => { | |||||
return t(upperFirst(params.status)) | |||||
}, | |||||
}, | |||||
], [t]) | |||||
return ( | |||||
<> | |||||
<SearchBox | |||||
criteria={searchCriteria} | |||||
onSearch={(query) => { | |||||
setFilteredDos( | |||||
dos.filter( | |||||
(_do) => { | |||||
const doOrderDateStr = arrayToDayjs(_do.orderDate) | |||||
return _do.code.toLowerCase().includes(query.code.toLowerCase()) | |||||
&& _do.shopName.toLowerCase().includes(query.shopName.toLowerCase()) | |||||
&& (query.status == "All" || _do.status.toLowerCase().includes(query.status.toLowerCase())) | |||||
&& (isEmpty(query.orderDate) || doOrderDateStr.isSame(query.orderDate) || doOrderDateStr.isAfter(query.orderDate)) | |||||
&& (isEmpty(query.orderDateTo) || doOrderDateStr.isSame(query.orderDateTo) || doOrderDateStr.isBefore(query.orderDateTo)) | |||||
} | |||||
) | |||||
) | |||||
}} | |||||
onReset={onReset} | |||||
/> | |||||
<SearchResults<DoResult> items={filteredDos} columns={columns} pagingController={{ | |||||
pageNum: 0, | |||||
pageSize: 0, | |||||
totalCount: 0, | |||||
}} /> | |||||
</> | |||||
) | |||||
} | |||||
export default DoSearch; |
@@ -0,0 +1,22 @@ | |||||
import React from "react"; | |||||
import GeneralLoading from "../General/GeneralLoading" | |||||
import { fetchDoList } from "@/app/api/do"; | |||||
import DoSearch from "./DoSearch"; | |||||
interface SubComponents { | |||||
Loading: typeof GeneralLoading; | |||||
} | |||||
const DoSearchWrapper: React.FC & SubComponents = async () => { | |||||
const [ | |||||
dos | |||||
] = await Promise.all([ | |||||
fetchDoList() | |||||
]); | |||||
return <DoSearch dos={dos}/> | |||||
} | |||||
DoSearchWrapper.Loading = GeneralLoading; | |||||
export default DoSearchWrapper; |
@@ -0,0 +1 @@ | |||||
export { default } from "./DoSearchWrapper" |
@@ -128,7 +128,7 @@ const NavigationContent: React.FC = () => { | |||||
{ | { | ||||
icon: <RequestQuote />, | icon: <RequestQuote />, | ||||
label: "Delivery Order", | label: "Delivery Order", | ||||
path: "", | |||||
path: "/do", | |||||
}, | }, | ||||
], | ], | ||||
}, | }, | ||||
@@ -210,7 +210,7 @@ function SearchBox<T extends string>({ | |||||
{c.type === "autocomplete" && ( | {c.type === "autocomplete" && ( | ||||
<Autocomplete | <Autocomplete | ||||
groupBy={ | groupBy={ | ||||
c.options.every((option) => option.group) | |||||
c.options.filter((option) => option.group !== "All").length > 0 && c.options.every((option) => option.group) | |||||
? (option) => (option.group && option.group.trim() !== '' ? option.group : 'Ungrouped') | ? (option) => (option.group && option.group.trim() !== '' ? option.group : 'Ungrouped') | ||||
: undefined | : undefined | ||||
} | } | ||||