diff --git a/src/app/(main)/jo/page.tsx b/src/app/(main)/jo/page.tsx new file mode 100644 index 0000000..8922424 --- /dev/null +++ b/src/app/(main)/jo/page.tsx @@ -0,0 +1,35 @@ +import JoSearch from "@/components/JoSearch"; +import { I18nProvider, getServerI18n } from "@/i18n"; +import { Stack, Typography } from "@mui/material"; +import { Metadata } from "next"; +import React, { Suspense } from "react"; + +export const metadata: Metadata = { + title: "Job Order" +} + +const jo: React.FC = async () => { + const { t } = await getServerI18n("jo"); + + return ( + <> + + + {t("Job Order")} + + + + }> + + + + + ) +} + +export default jo; \ No newline at end of file diff --git a/src/app/api/jo/actions.ts b/src/app/api/jo/actions.ts index c43fe41..d6bdb44 100644 --- a/src/app/api/jo/actions.ts +++ b/src/app/api/jo/actions.ts @@ -1,8 +1,28 @@ "use server"; -import { serverFetchJson } from "@/app/utils/fetchUtil"; +import { Pageable, serverFetchJson } from "@/app/utils/fetchUtil"; import { Machine, Operator } from "."; import { BASE_API_URL } from "@/config/api"; import { revalidateTag } from "next/cache"; +import { convertObjToURLSearchParams } from "@/app/utils/commonUtil"; + +export interface SearchJoResultRequest extends Pageable { + code: string; + name: string; +} + +export interface SearchJoResultResponse { + records: SearchJoResult[]; + total: number; +} + +export interface SearchJoResult{ + id: number; + code: string; + name: string; + reqQty: number; + uom: string; + status: string; +} export interface IsOperatorExistResponse { id: number | null; @@ -49,3 +69,20 @@ export const isCorrectMachineUsed = async (machineCode: string) => { revalidateTag("po"); return isExist; }; + + +export const fetchJos = async (data?: SearchJoResultRequest) => { + const queryStr = convertObjToURLSearchParams(data) + const response = await serverFetchJson( + `${BASE_API_URL}/jo/getRecordByPage?${queryStr}`, + { + method: "GET", + headers: { "Content-Type": "application/json" }, + next: { + tags: ["jos"] + } + } + ) + + return response +} \ No newline at end of file diff --git a/src/app/utils/commonUtil.ts b/src/app/utils/commonUtil.ts index 195b85e..807659b 100644 --- a/src/app/utils/commonUtil.ts +++ b/src/app/utils/commonUtil.ts @@ -11,7 +11,7 @@ export const downloadFile = (blobData: Uint8Array, filename: string) => { }; export const convertObjToURLSearchParams = ( - data: T | null, + data?: T | null, ): string => { if (isEmpty(data)) { return ""; diff --git a/src/components/Breadcrumb/Breadcrumb.tsx b/src/components/Breadcrumb/Breadcrumb.tsx index e1a09e6..4fc235e 100644 --- a/src/components/Breadcrumb/Breadcrumb.tsx +++ b/src/components/Breadcrumb/Breadcrumb.tsx @@ -25,6 +25,8 @@ const pathToLabelMap: { [path: string]: string } = { "/pickOrder": "Pick Order", "/po": "Purchase Order", "/dashboard": "dashboard", + "/jo": "Job Order", + "/jo/edit": "Edit Job Order", }; const Breadcrumb = () => { diff --git a/src/components/JoSearch/JoSearch.tsx b/src/components/JoSearch/JoSearch.tsx new file mode 100644 index 0000000..727f16a --- /dev/null +++ b/src/components/JoSearch/JoSearch.tsx @@ -0,0 +1,143 @@ +"use client" +import { SearchJoResult, SearchJoResultRequest, fetchJos } from "@/app/api/jo/actions"; +import React, { useCallback, useEffect, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { Criterion } from "../SearchBox"; +import SearchResults, { Column, defaultPagingController } from "../SearchResults/SearchResults"; +import { EditNote } from "@mui/icons-material"; +import { decimalFormatter } from "@/app/utils/formatUtil"; +import { uniqBy, upperFirst } from "lodash"; +import SearchBox from "../SearchBox/SearchBox"; + +interface Props { + defaultInputs: SearchJoResultRequest +} + +type SearchQuery = Partial>; + +type SearchParamNames = keyof SearchQuery; + +const JoSearch: React.FC = ({ defaultInputs }) => { + const { t } = useTranslation("jo"); + const [filteredJos, setFilteredJos] = useState([]); + const [inputs, setInputs] = useState(defaultInputs); + const [pagingController, setPagingController] = useState( + defaultPagingController + ) + const [totalCount, setTotalCount] = useState(0) + + const searchCriteria: Criterion[] = useMemo(() => [ + { label: t("Code"), paramName: "code", type: "text" }, + { label: t("Name"), paramName: "name", type: "text" }, + ], [t]) + + const columns = useMemo[]>( + () => [ + { + name: "id", + label: t("Details"), + onClick: (record) => onDetailClick(record), + buttonIcon: , + }, + { + name: "code", + label: t("Code") + }, + { + name: "name", + label: t("Name"), + }, + { + name: "reqQty", + label: t("Req. Qty"), + align: "right", + headerAlign: "right", + renderCell: (row) => { + return decimalFormatter.format(row.reqQty) + } + }, + { + name: "uom", + label: t("UoM"), + align: "left", + headerAlign: "left", + renderCell: (row) => { + return t(row.uom) + } + }, + { + name: "status", + label: t("Status"), + renderCell: (row) => { + return t(upperFirst(row.status)) + } + } + ], [] + ) + + const refetchData = useCallback(async ( + query: Record | SearchJoResultRequest, + actionType: "reset" | "search" | "paging", + ) => { + const params: SearchJoResultRequest = { + code: query.code, + name: query.name, + pageNum: pagingController.pageNum - 1, + pageSize: pagingController.pageSize, + } + const response = await fetchJos(params) + + if (response) { + setTotalCount(response.total); + switch (actionType) { + case "reset": + case "search": + setFilteredJos(() => response.records); + break; + case "paging": + setFilteredJos((fs) => + uniqBy([...fs, ...response.records], "id"), + ); + break; + } + } + }, [pagingController, setPagingController]) + + useEffect(() => { + refetchData(inputs, "paging"); + }, [pagingController]); + + const onDetailClick = useCallback((record: SearchJoResult) => { + + }, []) + + const onSearch = useCallback((query: Record) => { + setInputs(() => ({ + code: query.code, + name: query.name + })) + refetchData(query, "search"); + }, []) + + const onReset = useCallback(() => { + refetchData(defaultInputs, "paging"); + }, []) + + return <> + + + items={filteredJos} + columns={columns} + setPagingController={setPagingController} + pagingController={pagingController} + totalCount={totalCount} + // isAutoPaging={false} + /> + +} + +export default JoSearch; \ No newline at end of file diff --git a/src/components/JoSearch/JoSearchWrapper.tsx b/src/components/JoSearch/JoSearchWrapper.tsx new file mode 100644 index 0000000..391f476 --- /dev/null +++ b/src/components/JoSearch/JoSearchWrapper.tsx @@ -0,0 +1,21 @@ +import React from "react"; +import GeneralLoading from "../General/GeneralLoading"; +import JoSearch from "./JoSearch"; +import { SearchJoResultRequest } from "@/app/api/jo/actions"; + +interface SubComponents { + Loading: typeof GeneralLoading; +} + +const JoSearchWrapper: React.FC & SubComponents = async () => { + const defaultInputs: SearchJoResultRequest = { + code: "", + name: "", + } + + return +} + +JoSearchWrapper.Loading = GeneralLoading; + +export default JoSearchWrapper; \ No newline at end of file diff --git a/src/components/JoSearch/index.ts b/src/components/JoSearch/index.ts new file mode 100644 index 0000000..9547f6a --- /dev/null +++ b/src/components/JoSearch/index.ts @@ -0,0 +1 @@ +export { default } from "./JoSearchWrapper" \ No newline at end of file diff --git a/src/components/NavigationContent/NavigationContent.tsx b/src/components/NavigationContent/NavigationContent.tsx index 648108e..ce4e59a 100644 --- a/src/components/NavigationContent/NavigationContent.tsx +++ b/src/components/NavigationContent/NavigationContent.tsx @@ -188,6 +188,18 @@ const NavigationContent: React.FC = () => { }, ], }, + { + icon: , + label: "Job Order", + path: "", + children: [ + { + icon: , + label: "Job Order", + path: "/jo", + }, + ], + }, { icon: , label: "Settings", diff --git a/src/components/RoughSchedule/RoughSchedileSearchView.tsx b/src/components/RoughSchedule/RoughSchedileSearchView.tsx index 43b14c8..478571d 100644 --- a/src/components/RoughSchedule/RoughSchedileSearchView.tsx +++ b/src/components/RoughSchedule/RoughSchedileSearchView.tsx @@ -245,7 +245,7 @@ const RSOverview: React.FC = ({ type, defaultInputs }) => { // setFilteredSchedules(items ?? []); // setFilterObj({}); // setTempSelectedValue({}); - refetchData(inputs, "reset"); + refetchData(defaultInputs, "reset"); }, []); return (