Ver código fonte

[Job Order] Search Job Order By Page

master
cyril.tsui 1 mês atrás
pai
commit
4d8c5afc6e
9 arquivos alterados com 254 adições e 3 exclusões
  1. +35
    -0
      src/app/(main)/jo/page.tsx
  2. +38
    -1
      src/app/api/jo/actions.ts
  3. +1
    -1
      src/app/utils/commonUtil.ts
  4. +2
    -0
      src/components/Breadcrumb/Breadcrumb.tsx
  5. +143
    -0
      src/components/JoSearch/JoSearch.tsx
  6. +21
    -0
      src/components/JoSearch/JoSearchWrapper.tsx
  7. +1
    -0
      src/components/JoSearch/index.ts
  8. +12
    -0
      src/components/NavigationContent/NavigationContent.tsx
  9. +1
    -1
      src/components/RoughSchedule/RoughSchedileSearchView.tsx

+ 35
- 0
src/app/(main)/jo/page.tsx Ver arquivo

@@ -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 (
<>
<Stack
direction="row"
justifyContent="space-between"
flexWrap="wrap"
rowGap={2}
>
<Typography variant="h4" marginInlineEnd={2}>
{t("Job Order")}
</Typography>
</Stack>
<I18nProvider namespaces={["jo", "common"]}>
<Suspense fallback={<JoSearch.Loading />}>
<JoSearch />
</Suspense>
</I18nProvider>
</>
)
}

export default jo;

+ 38
- 1
src/app/api/jo/actions.ts Ver arquivo

@@ -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<T> {
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<SearchJoResultResponse>(
`${BASE_API_URL}/jo/getRecordByPage?${queryStr}`,
{
method: "GET",
headers: { "Content-Type": "application/json" },
next: {
tags: ["jos"]
}
}
)

return response
}

+ 1
- 1
src/app/utils/commonUtil.ts Ver arquivo

@@ -11,7 +11,7 @@ export const downloadFile = (blobData: Uint8Array, filename: string) => {
};

export const convertObjToURLSearchParams = <T extends Object>(
data: T | null,
data?: T | null,
): string => {
if (isEmpty(data)) {
return "";


+ 2
- 0
src/components/Breadcrumb/Breadcrumb.tsx Ver arquivo

@@ -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 = () => {


+ 143
- 0
src/components/JoSearch/JoSearch.tsx Ver arquivo

@@ -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<Omit<SearchJoResult, "id">>;

type SearchParamNames = keyof SearchQuery;

const JoSearch: React.FC<Props> = ({ defaultInputs }) => {
const { t } = useTranslation("jo");
const [filteredJos, setFilteredJos] = useState<SearchJoResult[]>([]);
const [inputs, setInputs] = useState(defaultInputs);
const [pagingController, setPagingController] = useState(
defaultPagingController
)
const [totalCount, setTotalCount] = useState(0)

const searchCriteria: Criterion<SearchParamNames>[] = useMemo(() => [
{ label: t("Code"), paramName: "code", type: "text" },
{ label: t("Name"), paramName: "name", type: "text" },
], [t])

const columns = useMemo<Column<SearchJoResult>[]>(
() => [
{
name: "id",
label: t("Details"),
onClick: (record) => onDetailClick(record),
buttonIcon: <EditNote />,
},
{
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<SearchParamNames, string> | 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<SearchParamNames, string>) => {
setInputs(() => ({
code: query.code,
name: query.name
}))
refetchData(query, "search");
}, [])

const onReset = useCallback(() => {
refetchData(defaultInputs, "paging");
}, [])

return <>
<SearchBox
criteria={searchCriteria}
onSearch={onSearch}
onReset={onReset}
/>
<SearchResults<SearchJoResult>
items={filteredJos}
columns={columns}
setPagingController={setPagingController}
pagingController={pagingController}
totalCount={totalCount}
// isAutoPaging={false}
/>
</>
}

export default JoSearch;

+ 21
- 0
src/components/JoSearch/JoSearchWrapper.tsx Ver arquivo

@@ -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 <JoSearch defaultInputs={defaultInputs}/>
}

JoSearchWrapper.Loading = GeneralLoading;

export default JoSearchWrapper;

+ 1
- 0
src/components/JoSearch/index.ts Ver arquivo

@@ -0,0 +1 @@
export { default } from "./JoSearchWrapper"

+ 12
- 0
src/components/NavigationContent/NavigationContent.tsx Ver arquivo

@@ -188,6 +188,18 @@ const NavigationContent: React.FC = () => {
},
],
},
{
icon: <RequestQuote />,
label: "Job Order",
path: "",
children: [
{
icon: <RequestQuote />,
label: "Job Order",
path: "/jo",
},
],
},
{
icon: <RequestQuote />,
label: "Settings",


+ 1
- 1
src/components/RoughSchedule/RoughSchedileSearchView.tsx Ver arquivo

@@ -245,7 +245,7 @@ const RSOverview: React.FC<Props> = ({ type, defaultInputs }) => {
// setFilteredSchedules(items ?? []);
// setFilterObj({});
// setTempSelectedValue({});
refetchData(inputs, "reset");
refetchData(defaultInputs, "reset");
}, []);

return (


Carregando…
Cancelar
Salvar