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 (