From 589bc2b773cc1a1c2668a82decfda4dd9854026c Mon Sep 17 00:00:00 2001 From: "MSI\\derek" Date: Wed, 12 Mar 2025 14:26:49 +0800 Subject: [PATCH] byProduct frontend only --- .../(main)/settings/byProduct/create/page.tsx | 24 ++ .../(main)/settings/byProduct/edit/page.tsx | 24 ++ src/app/(main)/settings/byProduct/page.tsx | 48 +++ src/app/api/settings/byProduct/actions.ts | 39 +++ src/app/api/settings/byProduct/index.ts | 40 +++ src/app/api/settings/material/actions.ts | 10 +- src/app/api/settings/product/actions.ts | 7 +- src/app/api/settings/product/index.ts | 12 +- src/app/api/utils/index.ts | 7 + src/app/utils/typeEnum.ts | 1 + .../ByProductDetails.tsx | 302 ++++++++++++++++++ .../CreateProductMaterial.tsx | 31 +- .../CreateProductMaterialWrapper.tsx | 86 +++-- .../CreateProductMaterial/ProductDetails.tsx | 4 - .../NavigationContent/NavigationContent.tsx | 4 +- .../ProductMaterialSearch.tsx | 5 + .../ProductMaterialSearchWrapper.tsx | 17 +- 17 files changed, 579 insertions(+), 82 deletions(-) create mode 100644 src/app/(main)/settings/byProduct/create/page.tsx create mode 100644 src/app/(main)/settings/byProduct/edit/page.tsx create mode 100644 src/app/(main)/settings/byProduct/page.tsx create mode 100644 src/app/api/settings/byProduct/actions.ts create mode 100644 src/app/api/settings/byProduct/index.ts create mode 100644 src/app/api/utils/index.ts create mode 100644 src/components/CreateProductMaterial/ByProductDetails.tsx diff --git a/src/app/(main)/settings/byProduct/create/page.tsx b/src/app/(main)/settings/byProduct/create/page.tsx new file mode 100644 index 0000000..13106bd --- /dev/null +++ b/src/app/(main)/settings/byProduct/create/page.tsx @@ -0,0 +1,24 @@ +import { SearchParams } from "@/app/utils/fetchUtil"; +import { TypeEnum } from "@/app/utils/typeEnum"; +import CreateProductMaterial from "@/components/CreateProductMaterial"; +import { I18nProvider, getServerI18n } from "@/i18n"; +import { Typography } from "@mui/material"; +import { isString } from "lodash"; + +type Props = {} & SearchParams; + +const byProductSetting: React.FC = async ({ searchParams }) => { + const type = TypeEnum.BYPRODUCT; + const { t } = await getServerI18n(type); + console.log(searchParams); + + return ( + <> + {/* {t("Create Material")} */} + + + + + ); +}; +export default byProductSetting; diff --git a/src/app/(main)/settings/byProduct/edit/page.tsx b/src/app/(main)/settings/byProduct/edit/page.tsx new file mode 100644 index 0000000..13106bd --- /dev/null +++ b/src/app/(main)/settings/byProduct/edit/page.tsx @@ -0,0 +1,24 @@ +import { SearchParams } from "@/app/utils/fetchUtil"; +import { TypeEnum } from "@/app/utils/typeEnum"; +import CreateProductMaterial from "@/components/CreateProductMaterial"; +import { I18nProvider, getServerI18n } from "@/i18n"; +import { Typography } from "@mui/material"; +import { isString } from "lodash"; + +type Props = {} & SearchParams; + +const byProductSetting: React.FC = async ({ searchParams }) => { + const type = TypeEnum.BYPRODUCT; + const { t } = await getServerI18n(type); + console.log(searchParams); + + return ( + <> + {/* {t("Create Material")} */} + + + + + ); +}; +export default byProductSetting; diff --git a/src/app/(main)/settings/byProduct/page.tsx b/src/app/(main)/settings/byProduct/page.tsx new file mode 100644 index 0000000..3dd4601 --- /dev/null +++ b/src/app/(main)/settings/byProduct/page.tsx @@ -0,0 +1,48 @@ +import { TypeEnum } from "@/app/utils/typeEnum"; +import MaterialSearch from "@/components/ProductMaterialSearch"; +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: "ByProduct", +}; + +const materialSetting: React.FC = async () => { + const byProduct = TypeEnum.BYPRODUCT + const { t } = await getServerI18n(byProduct); + // preloadClaims(); + + return ( + <> + + + {t("ByProduct")} + + + + }> + + + + ); +}; + +export default materialSetting; diff --git a/src/app/api/settings/byProduct/actions.ts b/src/app/api/settings/byProduct/actions.ts new file mode 100644 index 0000000..a658b84 --- /dev/null +++ b/src/app/api/settings/byProduct/actions.ts @@ -0,0 +1,39 @@ +"use server"; +import { ServerFetchError, serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil"; +import { revalidateTag } from "next/cache"; +import { BASE_API_URL } from "@/config/api"; +import { TypeInputs, UomInputs, WeightUnitInputs } from "../material/actions"; +import { CreateItemResponse } from "../../utils"; + +export type CreateByProductInputs = { + id?: string | number + code: string; + name: string; + // same goes for other props + // change backend for null or not null + description?: string | undefined; + remarks?: string | undefined; + shelfLife?: Number | undefined; + countryOfOrigin?: string | undefined; + minHumid?: number | undefined; + maxHumid?: number | undefined; + minTemp?: number | undefined; + maxTemp?: number | undefined; + sampleRate?: number | undefined; + passingRate?: number | undefined; + netWeight?: number | undefined; + type?: TypeInputs[]; + uom?: UomInputs[]; + weightUnit?: WeightUnitInputs[]; +} + +export const saveByProduct = async (data: CreateByProductInputs) => { + // try { + const byProduct = await serverFetchJson>(`${BASE_API_URL}/byProduct/new`, { + method: "POST", + body: JSON.stringify(data), + headers: { "Content-Type": "application/json" }, + }); + revalidateTag("byProduct"); + return byProduct + }; \ No newline at end of file diff --git a/src/app/api/settings/byProduct/index.ts b/src/app/api/settings/byProduct/index.ts new file mode 100644 index 0000000..fe90bb3 --- /dev/null +++ b/src/app/api/settings/byProduct/index.ts @@ -0,0 +1,40 @@ +import { cache } from "react"; +import "server-only"; +import { serverFetchJson } from "@/app/utils/fetchUtil"; +import { BASE_API_URL } from "@/config/api"; +import { TypeInputs, UomInputs, WeightUnitInputs } from "../material/actions"; + +export type ByProductResult = { + id: string | number + code: string; + name: string; + description: string | undefined; + remarks: string | undefined; + shelfLife: Number | undefined; + countryOfOrigin: string | undefined; + minHumid: number | undefined; + maxHumid: number | undefined; + minTemp: number | undefined; + maxTemp: number | undefined; + sampleRate: number | undefined; + passingRate: number | undefined; + netWeight: number | undefined; + uom: UomInputs[]; + weightUnit: WeightUnitInputs[]; + type: TypeInputs[]; + action?: any; +} + + +export const fetchAllByProduct = cache(async () => { + return serverFetchJson(`${BASE_API_URL}/byProduct`, { + next: { tags: ["byProduct"] }, + }); + }); + + +export const fetchByProduct = cache(async (id: number) => { + return serverFetchJson(`${BASE_API_URL}/byProduct/details/${id}`, { + next: { tags: ["byProduct"] }, + }); +}); \ No newline at end of file diff --git a/src/app/api/settings/material/actions.ts b/src/app/api/settings/material/actions.ts index 1dc587b..a38ec62 100644 --- a/src/app/api/settings/material/actions.ts +++ b/src/app/api/settings/material/actions.ts @@ -3,6 +3,7 @@ import { ServerFetchError, serverFetchJson, serverFetchWithNoContent } from "@/a import { revalidateTag } from "next/cache"; import { BASE_API_URL } from "@/config/api"; import { HTMLInputTypeAttribute } from "react"; +import { CreateItemResponse } from "../../utils"; export type TypeInputs = { type: string @@ -36,17 +37,10 @@ export type CreateMaterialInputs = { uom?: UomInputs[]; weightUnit?: WeightUnitInputs[]; } -export interface CreateProductMaterialResponse { - id: number | null; - name: string; - code: string; - message: string | null; - errorPosition: keyof CreateMaterialInputs; -} export const saveMaterial = async (data: CreateMaterialInputs) => { // try { - const material = await serverFetchJson(`${BASE_API_URL}/material/new`, { + const material = await serverFetchJson>(`${BASE_API_URL}/material/new`, { method: "POST", body: JSON.stringify(data), headers: { "Content-Type": "application/json" }, diff --git a/src/app/api/settings/product/actions.ts b/src/app/api/settings/product/actions.ts index 08ebdff..b73c04d 100644 --- a/src/app/api/settings/product/actions.ts +++ b/src/app/api/settings/product/actions.ts @@ -2,7 +2,8 @@ import { ServerFetchError, serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil"; import { revalidateTag } from "next/cache"; import { BASE_API_URL } from "@/config/api"; -import { CreateProductMaterialResponse, TypeInputs, UomInputs, WeightUnitInputs } from "../material/actions"; +import { TypeInputs, UomInputs, WeightUnitInputs } from "../material/actions"; +import { CreateItemResponse } from "../../utils"; export type CreateProductInputs = { id?: string | number @@ -26,11 +27,11 @@ export type CreateProductInputs = { export const saveProduct = async (data: CreateProductInputs) => { // try { - const material = await serverFetchJson(`${BASE_API_URL}/product/new`, { + const product = await serverFetchJson>(`${BASE_API_URL}/product/new`, { method: "POST", body: JSON.stringify(data), headers: { "Content-Type": "application/json" }, }); revalidateTag("product"); - return material + return product }; \ No newline at end of file diff --git a/src/app/api/settings/product/index.ts b/src/app/api/settings/product/index.ts index 1843f28..67675b8 100644 --- a/src/app/api/settings/product/index.ts +++ b/src/app/api/settings/product/index.ts @@ -2,14 +2,13 @@ import { cache } from "react"; import "server-only"; import { serverFetchJson } from "@/app/utils/fetchUtil"; import { BASE_API_URL } from "@/config/api"; +import { TypeInputs, UomInputs, WeightUnitInputs } from "../material/actions"; export type ProductResult = { id: string | number code: string; name: string; - isConsumables: boolean; description: string | undefined; - type: string | undefined; remarks: string | undefined; shelfLife: Number | undefined; countryOfOrigin: string | undefined; @@ -20,20 +19,21 @@ export type ProductResult = { sampleRate: number | undefined; passingRate: number | undefined; netWeight: number | undefined; - uom: string[] | any[]; - weightUnit: string[] | any[]; + uom: UomInputs[]; + weightUnit: WeightUnitInputs[]; + type: TypeInputs[]; action?: any; } -export const fetchAllMaterials = cache(async () => { +export const fetchAllProduct = cache(async () => { return serverFetchJson(`${BASE_API_URL}/product`, { next: { tags: ["product"] }, }); }); -export const fetchMaterial = cache(async (id: number) => { +export const fetchProduct = cache(async (id: number) => { return serverFetchJson(`${BASE_API_URL}/product/details/${id}`, { next: { tags: ["product"] }, }); diff --git a/src/app/api/utils/index.ts b/src/app/api/utils/index.ts new file mode 100644 index 0000000..2134176 --- /dev/null +++ b/src/app/api/utils/index.ts @@ -0,0 +1,7 @@ +export interface CreateItemResponse { + id: number | null; + name: string; + code: string; + message: string | null; + errorPosition: string | keyof T; +} \ No newline at end of file diff --git a/src/app/utils/typeEnum.ts b/src/app/utils/typeEnum.ts index 624d9d8..de84b22 100644 --- a/src/app/utils/typeEnum.ts +++ b/src/app/utils/typeEnum.ts @@ -1,4 +1,5 @@ export enum TypeEnum { PRODUCT = "product", MATERIAL = "material", + BYPRODUCT = "byProduct", } \ No newline at end of file diff --git a/src/components/CreateProductMaterial/ByProductDetails.tsx b/src/components/CreateProductMaterial/ByProductDetails.tsx new file mode 100644 index 0000000..95d3fbb --- /dev/null +++ b/src/components/CreateProductMaterial/ByProductDetails.tsx @@ -0,0 +1,302 @@ +"use client"; +import { CreateMaterialInputs } from "@/app/api/settings/material/actions"; +import { + Box, + Card, + CardContent, + Grid, + Stack, + TextField, + Typography, +} from "@mui/material"; +import { useFormContext } from "react-hook-form"; +import { useTranslation } from "react-i18next"; +import InputDataGrid from "../InputDataGrid"; +import { useCallback, useMemo, useState } from "react"; +import { GridColDef, GridRowModel } from "@mui/x-data-grid"; +import { InputDataGridProps, TableRow } from "../InputDataGrid/InputDataGrid"; +import { TypeEnum } from "@/app/utils/typeEnum"; +import { CreateByProductInputs } from "@/app/api/settings/byProduct/actions"; +import { NumberInputProps } from "./NumberInputProps"; + +type Props = { +// isEditMode: boolean; +// type: TypeEnum; +}; + +export type EntryError = + | { + [field in keyof CreateByProductInputs]?: string; + } + | undefined; + +const ByProductDetails: React.FC = ({ + + }) => { + const { + t, + i18n: { language }, + } = useTranslation(); + + const { + register, + formState: { errors, defaultValues, touchedFields }, + watch, + control, + setValue, + getValues, + reset, + resetField, + setError, + clearErrors, + } = useFormContext(); + const typeColumns = useMemo( + () => [ + { + field: "type", + headerName: "type", + flex: 1, + editable: true, + }, + ], + [] + ); + const weightUnitColumns = useMemo( + () => [ + { + field: "weightUnit", + headerName: "Weight Unit", + flex: 1, + editable: true, + }, + { + field: "conversion", + headerName: "conversion", // show base unit + flex: 1, + type: "number", + editable: true, + }, + ], + [] + ); + const uomColumns = useMemo( + () => [ + { + field: "uom", + headerName: "uom", + flex: 1, + editable: true, + }, + ], + [] + ); + + const validationTest = useCallback( + ( + newRow: GridRowModel, EntryError>> + ): EntryError => { + const error: EntryError = {}; + console.log(newRow); + return Object.keys(error).length > 0 ? error : undefined; + }, + [] + ); + + return ( + + + + + {t("Product Details")} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + _formKey={"type"} + columns={typeColumns} + validateRow={validationTest} + /> + + + + _formKey={"uom"} + columns={uomColumns} + validateRow={validationTest} + /> + + + + _formKey={"weightUnit"} + columns={weightUnitColumns} + validateRow={validationTest} + /> + + + + + + ); +}; +export default ByProductDetails; diff --git a/src/components/CreateProductMaterial/CreateProductMaterial.tsx b/src/components/CreateProductMaterial/CreateProductMaterial.tsx index 30e022d..8e6315e 100644 --- a/src/components/CreateProductMaterial/CreateProductMaterial.tsx +++ b/src/components/CreateProductMaterial/CreateProductMaterial.tsx @@ -5,7 +5,6 @@ import { useRouter, useSearchParams } from "next/navigation"; import { useTranslation } from "react-i18next"; import { CreateMaterialInputs, - CreateProductMaterialResponse, saveMaterial, } from "@/app/api/settings/material/actions"; import { @@ -25,6 +24,9 @@ import { import { CreateInputsFields } from "./CreateProductMaterialWrapper"; import MaterialDetails from "./MaterialDetails"; import ProductDetails from "./ProductDetails"; +import { CreateItemResponse } from "@/app/api/utils"; +import { CreateByProductInputs, saveByProduct } from "@/app/api/settings/byProduct/actions"; +import ByProductDetails from "./ByProductDetails"; type Props = { isEditMode: boolean; @@ -53,6 +55,10 @@ const CreateProductMaterial: React.FC = ({ title = "Product"; redirPath = "/product"; } + if (type === TypeEnum.BYPRODUCT) { + title = "By-Product"; + redirPath = "/byProduct"; + } if (isEditMode) { mode = "Edit"; } else { @@ -99,8 +105,16 @@ const CreateProductMaterial: React.FC = ({ }); hasErrors = true; } + // checking temp input + if (data.netWeight && data.netWeight < 0) { + const message = "netWeight should not be greater than 0"; + formProps.setError("netWeight", { + message: message, + type: "required", + }); + } // checking passing rate - if (data.passingRate && data.passingRate > 100) { + if (data.passingRate && (data.passingRate < 0 || data.passingRate > 100)) { const message = "passingRate should not be greater than 100%"; formProps.setError("passingRate", { message: message, @@ -108,7 +122,7 @@ const CreateProductMaterial: React.FC = ({ }); } // checking sampling rate - if (data.sampleRate && data.sampleRate > 100) { + if (data.sampleRate && (data.sampleRate < 0 || data.sampleRate > 100)) { const message = "sampleRate should not be greater than 100%"; formProps.setError("sampleRate", { message: message, @@ -122,15 +136,21 @@ const CreateProductMaterial: React.FC = ({ console.log("data posted"); console.log(data); // do api - var response: CreateProductMaterialResponse | undefined; + var response; if (type === TypeEnum.MATERIAL) { response = await saveMaterial(data as CreateMaterialInputs); } else if (type === TypeEnum.PRODUCT) { response = await saveProduct(data as CreateProductInputs); + } else if (type === TypeEnum.BYPRODUCT) { + // response = await saveByProduct(data as CreateByProductInputs); + + } else { + // Handle unexpected type + throw new Error("Invalid type provided"); } if (response) { if (!Boolean(response.id)) { - formProps.setError(response.errorPosition!!, { + formProps.setError(response.errorPosition!! as keyof CreateInputsFields, { message: response.message!!, type: "required", }); @@ -176,6 +196,7 @@ const CreateProductMaterial: React.FC = ({ )} {type === TypeEnum.MATERIAL && } {type === TypeEnum.PRODUCT && } + {type === TypeEnum.BYPRODUCT && }