diff --git a/src/app/(main)/settings/material/create/page.tsx b/src/app/(main)/settings/material/create/page.tsx index 505af93..6f9c8fc 100644 --- a/src/app/(main)/settings/material/create/page.tsx +++ b/src/app/(main)/settings/material/create/page.tsx @@ -1,19 +1,22 @@ import { SearchParams } from "@/app/utils/fetchUtil"; -import CreateMaterial from "@/components/CreateMaterial"; +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 materialSetting: React.FC = async ({ searchParams }) => { - const { t } = await getServerI18n("materials"); + const type = TypeEnum.MATERIAL; + const { t } = await getServerI18n(type); console.log(searchParams); return ( <> {/* {t("Create Material")} */} - - + + ); diff --git a/src/app/(main)/settings/material/edit/page.tsx b/src/app/(main)/settings/material/edit/page.tsx new file mode 100644 index 0000000..41b9bfa --- /dev/null +++ b/src/app/(main)/settings/material/edit/page.tsx @@ -0,0 +1,31 @@ +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"; +import { notFound } from "next/navigation"; + +type Props = {} & SearchParams; + +const materialSetting: React.FC = async ({ searchParams }) => { + const type = TypeEnum.MATERIAL; + const { t } = await getServerI18n(type); + console.log(searchParams); + const id = isString(searchParams["id"]) + ? parseInt(searchParams["id"]) + : undefined; + console.log(id) + if (!id) { + notFound(); + } + return ( + <> + {/* {t("Create Material")} */} + + + + + ); +}; +export default materialSetting; diff --git a/src/app/(main)/settings/material/page.tsx b/src/app/(main)/settings/material/page.tsx index 8be6005..a74ea18 100644 --- a/src/app/(main)/settings/material/page.tsx +++ b/src/app/(main)/settings/material/page.tsx @@ -1,4 +1,5 @@ -import MaterialSearch from "@/components/MaterialSearch"; +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"; @@ -13,8 +14,9 @@ export const metadata: Metadata = { }; const materialSetting: React.FC = async () => { - const { t } = await getServerI18n("material"); -// preloadClaims(); + const material = TypeEnum.MATERIAL + const { t } = await getServerI18n(material); + // preloadClaims(); return ( <> @@ -37,7 +39,7 @@ const materialSetting: React.FC = async () => { }> - + ); diff --git a/src/app/(main)/settings/product/create/page.tsx b/src/app/(main)/settings/product/create/page.tsx new file mode 100644 index 0000000..5081efe --- /dev/null +++ b/src/app/(main)/settings/product/create/page.tsx @@ -0,0 +1,22 @@ +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/isString"; + +type Props = {} & SearchParams; + +const materialSetting: React.FC = async ({ searchParams }) => { + const type = TypeEnum.PRODUCT; + const { t } = await getServerI18n(type); + return ( + <> + {/* {t("Create Material")} */} + + + + + ); +}; +export default materialSetting; diff --git a/src/app/(main)/settings/product/edit/page.tsx b/src/app/(main)/settings/product/edit/page.tsx new file mode 100644 index 0000000..e112a41 --- /dev/null +++ b/src/app/(main)/settings/product/edit/page.tsx @@ -0,0 +1,29 @@ +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/isString"; +import { notFound } from "next/navigation"; + +type Props = {} & SearchParams; + +const productSetting: React.FC = async ({ searchParams }) => { + const type = TypeEnum.PRODUCT; + const { t } = await getServerI18n(type); + const id = isString(searchParams["id"]) + ? parseInt(searchParams["id"]) + : undefined; + if (!id) { + notFound(); + } + return ( + <> + {/* {t("Create Material")} */} + + + + + ); +}; +export default productSetting; diff --git a/src/app/(main)/settings/product/page.tsx b/src/app/(main)/settings/product/page.tsx new file mode 100644 index 0000000..6290413 --- /dev/null +++ b/src/app/(main)/settings/product/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: "Product", +}; + +const productSetting: React.FC = async () => { + const project = TypeEnum.PRODUCT + const { t } = await getServerI18n(project); + // preloadClaims(); + + return ( + <> + + + {t("Product")} + + + + }> + + + + ); +}; + +export default productSetting; diff --git a/src/app/api/settings/material/actions.ts b/src/app/api/settings/material/actions.ts index 14a2676..1dc587b 100644 --- a/src/app/api/settings/material/actions.ts +++ b/src/app/api/settings/material/actions.ts @@ -2,14 +2,26 @@ import { ServerFetchError, serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil"; import { revalidateTag } from "next/cache"; import { BASE_API_URL } from "@/config/api"; +import { HTMLInputTypeAttribute } from "react"; +export type TypeInputs = { + type: string +} +export type UomInputs = { + uom: string +} +export type WeightUnitInputs = { + weightUnit: string + conversion: number +} export type CreateMaterialInputs = { id?: string | number code: string; name: string; isConsumables: boolean; + // same goes for other props + // change backend for null or not null description?: string | undefined; - type?: string | undefined; remarks?: string | undefined; shelfLife?: Number | undefined; countryOfOrigin?: string | undefined; @@ -20,17 +32,25 @@ export type CreateMaterialInputs = { sampleRate?: number | undefined; passingRate?: number | undefined; netWeight?: number | undefined; - uom?: any; - weightUnit?: any; + type?: TypeInputs[]; + 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 materials = await serverFetchJson(`${BASE_API_URL}/materials/save`, { + const material = await serverFetchJson(`${BASE_API_URL}/material/new`, { method: "POST", body: JSON.stringify(data), headers: { "Content-Type": "application/json" }, }); - revalidateTag("materials"); - return materials + revalidateTag("material"); + return material }; diff --git a/src/app/api/settings/material/index.ts b/src/app/api/settings/material/index.ts index b568198..5f452d1 100644 --- a/src/app/api/settings/material/index.ts +++ b/src/app/api/settings/material/index.ts @@ -2,18 +2,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 "./actions"; export type MaterialResult = { - id: number; + id: string | number code: string; name: string; - description: string; - action: any | undefined; + isConsumables: boolean; + 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 fetchMaterials = cache(async () => { - return serverFetchJson(`${BASE_API_URL}/materials`, { - next: { tags: ["materials"] }, +export const fetchAllMaterials = cache(async () => { + return serverFetchJson(`${BASE_API_URL}/material`, { + next: { tags: ["material"] }, }); - }); \ No newline at end of file + }); + + +export const fetchMaterial = cache(async (id: number) => { + return serverFetchJson(`${BASE_API_URL}/material/details/${id}`, { + next: { tags: ["material"] }, + }); +}); \ No newline at end of file diff --git a/src/app/api/settings/product/actions.ts b/src/app/api/settings/product/actions.ts new file mode 100644 index 0000000..23e9499 --- /dev/null +++ b/src/app/api/settings/product/actions.ts @@ -0,0 +1,37 @@ +"use server"; +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"; + +export type CreateProductInputs = { + id?: string | number + code: string; + name: string; + isConsumables: boolean; + 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 saveProduct = async (data: CreateProductInputs) => { + // try { + const material = await serverFetchJson(`${BASE_API_URL}/product/new`, { + method: "POST", + body: JSON.stringify(data), + headers: { "Content-Type": "application/json" }, + }); + revalidateTag("product"); + return material + }; \ No newline at end of file diff --git a/src/app/api/settings/product/index.ts b/src/app/api/settings/product/index.ts new file mode 100644 index 0000000..1843f28 --- /dev/null +++ b/src/app/api/settings/product/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"; + +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; + minHumid: number | undefined; + maxHumid: number | undefined; + minTemp: number | undefined; + maxTemp: number | undefined; + sampleRate: number | undefined; + passingRate: number | undefined; + netWeight: number | undefined; + uom: string[] | any[]; + weightUnit: string[] | any[]; + action?: any; +} + + +export const fetchAllMaterials = cache(async () => { + return serverFetchJson(`${BASE_API_URL}/product`, { + next: { tags: ["product"] }, + }); + }); + + +export const fetchMaterial = cache(async (id: number) => { + return serverFetchJson(`${BASE_API_URL}/product/details/${id}`, { + next: { tags: ["product"] }, + }); +}); \ No newline at end of file diff --git a/src/app/utils/typeEnum.ts b/src/app/utils/typeEnum.ts new file mode 100644 index 0000000..624d9d8 --- /dev/null +++ b/src/app/utils/typeEnum.ts @@ -0,0 +1,4 @@ +export enum TypeEnum { + PRODUCT = "product", + MATERIAL = "material", + } \ No newline at end of file diff --git a/src/components/CreateMaterial/CreateMaterial.tsx b/src/components/CreateMaterial/CreateMaterial.tsx deleted file mode 100644 index 8cc501a..0000000 --- a/src/components/CreateMaterial/CreateMaterial.tsx +++ /dev/null @@ -1,88 +0,0 @@ -"use client"; - -import { useCallback, useState } from "react"; -import { useRouter, useSearchParams } from "next/navigation"; -import { useTranslation } from "react-i18next"; -import { CreateMaterialInputs } from "@/app/api/settings/material/actions"; -import { - FormProvider, - SubmitErrorHandler, - SubmitHandler, - useForm, -} from "react-hook-form"; -import { deleteDialog } from "../Swal/CustomAlerts"; -import { Box, Button, Grid, Stack, Typography } from "@mui/material"; -import MaterialDetails from "./MaterialDetails"; -import { Check, Close, EditNote } from "@mui/icons-material"; - -type Props = { - isEditMode: boolean; -}; - -const CreateStaff: React.FC = ({ isEditMode }) => { - const [serverError, setServerError] = useState(""); - const [tabIndex, setTabIndex] = useState(0); - const { t } = useTranslation(); - const router = useRouter(); - - const formProps = useForm({ - defaultValues: {}, - }); - - const handleCancel = () => { - router.replace("/materials"); - }; - const onSubmit = useCallback>( - async (data, event) => { - try { - console.log(data) - } catch (e) { - - } - }, - [router, t] - ); - const onSubmitError = useCallback>( - (errors) => {}, - [] - ); - const errors = formProps.formState.errors; - - return ( - <> - - - - - {isEditMode ? t("Edit Material") : t("Create Material")} - - - - - - - - - - - ); -}; -export default CreateStaff; diff --git a/src/components/CreateMaterial/CreateMaterialWrapper.tsx b/src/components/CreateMaterial/CreateMaterialWrapper.tsx deleted file mode 100644 index 8dd3896..0000000 --- a/src/components/CreateMaterial/CreateMaterialWrapper.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import CreateStaff from "./CreateMaterial"; -import CreateMaterialLoading from "./CreateMaterialLoading"; -import { cookies } from 'next/headers' -interface SubComponents { - Loading: typeof CreateMaterialLoading; - } - -type CreateMaterialProps = { - isEditMode?: false; -}; - -type Props = CreateMaterialProps - -const CreateMaterialWrapper: React.FC & SubComponents = async (props) => { - const cookieStore = await cookies() - // console.log("==================cookieStore==================") - // console.log(cookieStore) - - return -} -CreateMaterialWrapper.Loading = CreateMaterialLoading; - -export default CreateMaterialWrapper \ No newline at end of file diff --git a/src/components/CreateMaterial/index.ts b/src/components/CreateMaterial/index.ts deleted file mode 100644 index efb2111..0000000 --- a/src/components/CreateMaterial/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./CreateMaterialWrapper"; diff --git a/src/components/CreateProductMaterial/CreateProductMaterial.tsx b/src/components/CreateProductMaterial/CreateProductMaterial.tsx new file mode 100644 index 0000000..0c14584 --- /dev/null +++ b/src/components/CreateProductMaterial/CreateProductMaterial.tsx @@ -0,0 +1,202 @@ +"use client"; + +import { useCallback, useEffect, useMemo, useState } from "react"; +import { useRouter, useSearchParams } from "next/navigation"; +import { useTranslation } from "react-i18next"; +import { + CreateMaterialInputs, + CreateProductMaterialResponse, + saveMaterial, +} from "@/app/api/settings/material/actions"; +import { + FormProvider, + SubmitErrorHandler, + SubmitHandler, + useForm, +} from "react-hook-form"; +import { deleteDialog } from "../Swal/CustomAlerts"; +import { Box, Button, Grid, Stack, Typography } from "@mui/material"; +import { Check, Close, EditNote } from "@mui/icons-material"; +import { TypeEnum } from "@/app/utils/typeEnum"; +import { + CreateProductInputs, + saveProduct, +} from "@/app/api/settings/product/actions"; +import { CreateInputsFields } from "./CreateProductMaterialWrapper"; +import MaterialDetails from "./MaterialDetails"; +import ProductDetails from "./ProductDetails"; + +type Props = { + isEditMode: boolean; + type: TypeEnum; + defaultValues: Partial | undefined; +}; + +const CreateProductMaterial: React.FC = ({ + isEditMode, + type, + defaultValues, +}) => { + const [serverError, setServerError] = useState(""); + const [tabIndex, setTabIndex] = useState(0); + const { t } = useTranslation(); + const router = useRouter(); + const [title, mode, redirPath] = useMemo(() => { + var title = ""; + var mode = ""; + var redirPath = ""; + if (type === TypeEnum.MATERIAL) { + title = "Material"; + redirPath = "/material"; + } + if (type === TypeEnum.PRODUCT) { + title = "Project"; + redirPath = "/project"; + } + if (isEditMode) { + mode = "Edit"; + } else { + mode = "Create"; + } + return [title, mode, redirPath]; + }, [type, isEditMode]); + + const formProps = useForm({ + defaultValues: defaultValues ? defaultValues : {}, + }); + const errors = formProps.formState.errors; + + const handleCancel = () => { + router.replace(`/settings/${type}`); + }; + const onSubmit = useCallback>( + async (data, event) => { + let hasErrors = false; + try { + // checking humid input + if (data.maxHumid && data.minHumid!! > data.maxHumid!!) { + const message = "minHumid should not be greater than maxHumid"; + formProps.setError("minHumid", { + message: message, + type: "required", + }); + formProps.setError("maxHumid", { + message: message, + type: "required", + }); + hasErrors = true; + } + // checking temp input + if (data.maxTemp && data.minTemp!! > data.maxTemp!!) { + const message = "minTemp should not be greater than maxTemp"; + formProps.setError("minTemp", { + message: message, + type: "required", + }); + formProps.setError("maxTemp", { + message: message, + type: "required", + }); + hasErrors = true; + } + // checking passing rate + if (data.passingRate && data.passingRate > 100) { + const message = "passingRate should not be greater than 100%"; + formProps.setError("passingRate", { + message: message, + type: "required", + }); + } + // checking sampling rate + if (data.sampleRate && data.sampleRate > 100) { + const message = "sampleRate should not be greater than 100%"; + formProps.setError("sampleRate", { + message: message, + type: "required", + }); + } + if (hasErrors) { + setServerError(t("An error has occurred. Please try again later.")); + return false; + } + console.log("data posted"); + console.log(data); + // do api + var response: CreateProductMaterialResponse | undefined; + if (type === TypeEnum.MATERIAL) { + response = await saveMaterial(data as CreateMaterialInputs); + } else if (type === TypeEnum.PRODUCT) { + response = await saveProduct(data as CreateProductInputs); + } + if (response) { + if (!Boolean(response.id)) { + formProps.setError(response.errorPosition!!, { + message: response.message!!, + type: "required", + }); + throw response; + } else if (Boolean(response.id)) { + router.replace(redirPath); + } + } else { + throw "no response"; + } + } catch (e) { + // backend error + setServerError(t("An error has occurred. Please try again later.")); + console.log(e); + } + }, + [router, t] + ); + + // multiple tabs + const onSubmitError = useCallback>( + (errors) => {}, + [] + ); + + return ( + <> + + + + + {t(`${mode} ${title}`)} + + + {serverError && ( + + {serverError} + + )} + {type === TypeEnum.MATERIAL && } + {type === TypeEnum.PRODUCT && } + + + + + + + + ); +}; +export default CreateProductMaterial; diff --git a/src/components/CreateMaterial/CreateMaterialLoading.tsx b/src/components/CreateProductMaterial/CreateProductMaterialLoading.tsx similarity index 90% rename from src/components/CreateMaterial/CreateMaterialLoading.tsx rename to src/components/CreateProductMaterial/CreateProductMaterialLoading.tsx index 159a97d..e954b54 100644 --- a/src/components/CreateMaterial/CreateMaterialLoading.tsx +++ b/src/components/CreateProductMaterial/CreateProductMaterialLoading.tsx @@ -5,7 +5,7 @@ import Stack from "@mui/material/Stack"; import React from "react"; // Can make this nicer -export const CreateMaterialLoading: React.FC = () => { +export const CreateProductMaterialLoading: React.FC = () => { return ( <> @@ -37,4 +37,4 @@ export const CreateMaterialLoading: React.FC = () => { ); }; -export default CreateMaterialLoading; +export default CreateProductMaterialLoading; diff --git a/src/components/CreateProductMaterial/CreateProductMaterialWrapper.tsx b/src/components/CreateProductMaterial/CreateProductMaterialWrapper.tsx new file mode 100644 index 0000000..57258ea --- /dev/null +++ b/src/components/CreateProductMaterial/CreateProductMaterialWrapper.tsx @@ -0,0 +1,77 @@ +import { TypeEnum } from "@/app/utils/typeEnum"; +import CreateProductMaterial from "./CreateProductMaterial"; +import CreateMaterialLoading from "./CreateProductMaterialLoading"; +import { CreateMaterialInputs } from "@/app/api/settings/material/actions"; +import { CreateProductInputs } from "@/app/api/settings/product/actions"; +import { fetchMaterial, MaterialResult } from "@/app/api/settings/material"; +interface SubComponents { + Loading: typeof CreateMaterialLoading; +} + +type CreateMaterialProps = { +// isEditMode: false; + type: TypeEnum; +}; +type EditMaterialProps = { +// isEditMode: true; + type: TypeEnum; +}; +type CreateProductProps = { +// isEditMode: false; + type: TypeEnum; +}; +type EditProductProps = { +// isEditMode: true; + type: TypeEnum; +}; + +type Props = + | CreateMaterialProps + | EditMaterialProps + | CreateProductProps + | EditProductProps; + +export type CreateInputsFields = CreateMaterialInputs | CreateProductInputs; + +const CreateProductMaterialWrapper: React.FC & + SubComponents = async ({ type, id }) => { + var defaultValues: Partial = {}; + if (id && type === TypeEnum.MATERIAL) { + const result = await fetchMaterial(id); + console.log(result) + defaultValues = { + id: result.id, + code: result.name, + name: result.name, + isConsumables: result.isConsumables, + description: result.description, + type: result.type, + remarks: result.remarks, + shelfLife: result.shelfLife, + countryOfOrigin: result.countryOfOrigin, + minHumid: result.minHumid, + maxHumid: result.maxHumid, + minTemp: result.minTemp, + maxTemp: result.maxTemp, + sampleRate: result.sampleRate, + passingRate: result.passingRate, + netWeight: result.netWeight, + uom: result.uom, + weightUnit: result.weightUnit, + }; + } + if (id && type === TypeEnum.PRODUCT) { + defaultValues = {}; + } + + return ( + + ); +}; +CreateProductMaterialWrapper.Loading = CreateMaterialLoading; + +export default CreateProductMaterialWrapper; diff --git a/src/components/CreateProductMaterial/MaterialDetails.tsx b/src/components/CreateProductMaterial/MaterialDetails.tsx new file mode 100644 index 0000000..1478cc0 --- /dev/null +++ b/src/components/CreateProductMaterial/MaterialDetails.tsx @@ -0,0 +1,340 @@ +"use client"; +import { CreateMaterialInputs } from "@/app/api/settings/material/actions"; +import { + Box, + Card, + CardContent, + Checkbox, + FilledInputProps, + FormControlLabel, + FormGroup, + Grid, + InputBaseComponentProps, + OutlinedInputProps, + Stack, + TextField, + Typography, +} from "@mui/material"; +import { Controller, useFormContext } from "react-hook-form"; +import { useTranslation } from "react-i18next"; +import InputDataGrid from "../InputDataGrid"; +import { useCallback, useEffect, 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 { NumberInputProps } from "./NumberInputProps"; + +type Props = { + // isEditMode: boolean; + // type: TypeEnum; +}; + +export type EntryError = + | { + [field in keyof CreateMaterialInputs]?: string; + } + | undefined; + +const MaterialDetails: React.FC = ({}) => { + const { + t, + i18n: { language }, + } = useTranslation(); + + const { + register, + formState: { errors, defaultValues, touchedFields }, + watch, + control, + setValue, + getValues, + reset, + resetField, + setError, + clearErrors, + } = useFormContext(); + // textfield error msg locale problem + 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("Material Details")} + + ( + props.onChange(e.target.checked)} + /> + )} + /> + } + label={ + + {t("Consumables")} + + } + /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + _formKey={"type"} + columns={typeColumns} + validateRow={validationTest} + /> + + + + _formKey={"uom"} + columns={uomColumns} + validateRow={validationTest} + /> + + + + _formKey={"weightUnit"} + columns={weightUnitColumns} + validateRow={validationTest} + /> + + + + + + ); +}; +export default MaterialDetails; diff --git a/src/components/CreateProductMaterial/NumberInputProps.ts b/src/components/CreateProductMaterial/NumberInputProps.ts new file mode 100644 index 0000000..eb8e9ea --- /dev/null +++ b/src/components/CreateProductMaterial/NumberInputProps.ts @@ -0,0 +1,5 @@ +import { InputBaseComponentProps } from "@mui/material"; + +export var NumberInputProps: InputBaseComponentProps = { + step: 0.01, + }; \ No newline at end of file diff --git a/src/components/CreateMaterial/MaterialDetails.tsx b/src/components/CreateProductMaterial/ProductDetails.tsx similarity index 57% rename from src/components/CreateMaterial/MaterialDetails.tsx rename to src/components/CreateProductMaterial/ProductDetails.tsx index db029c3..8a5ef49 100644 --- a/src/components/CreateMaterial/MaterialDetails.tsx +++ b/src/components/CreateProductMaterial/ProductDetails.tsx @@ -14,20 +14,25 @@ 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 { InputDataGridProps, TableRow } from "../InputDataGrid/InputDataGrid"; +import { TypeEnum } from "@/app/utils/typeEnum"; +import { CreateProductInputs } from "@/app/api/settings/product/actions"; +import { NumberInputProps } from "./NumberInputProps"; + type Props = { - isEditMode: boolean; +// isEditMode: boolean; +// type: TypeEnum; }; + export type EntryError = | { - [field in keyof CreateMaterialInputs]?: string; + [field in keyof CreateProductInputs]?: string; } | undefined; -const MaterialDetails: React.FC = ({ isEditMode }) => { +const ProductDetails: React.FC = ({ + + }) => { const { t, i18n: { language }, @@ -44,59 +49,65 @@ const MaterialDetails: React.FC = ({ isEditMode }) => { resetField, setError, clearErrors, - } = useFormContext(); - const typeColumns = useMemo(() => [ - { - field: "type", - headerName: 'type', - flex: 1, - 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 undefined - }, []); - // const MaterialTypeInputGridProps: InputDataGridProps< - // Partial, - // EntryError - // > = { - // _formKey: "type", - // columns: typeColumns, - // validateRow: validationTest, - // }; - // const MaterialUomInputGridProps: InputDataGridProps< - // Partial, - // EntryError - // > = { - // _formKey: "uom", - // columns: uomColumns, - // validateRow: validationTest, - // }; - // const MaterialTypeInputGrid = useInputDataGrid( - // MaterialTypeInputGridProps - // ); - // const MaterialUomInputGrid = useInputDataGrid( - // MaterialUomInputGridProps - // ); + } = 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("Material Details")} + {t("Product Details")} @@ -107,6 +118,7 @@ const MaterialDetails: React.FC = ({ isEditMode }) => { required: "name required!", })} error={Boolean(errors.name)} + helperText={errors.name?.message} /> @@ -117,26 +129,27 @@ const MaterialDetails: React.FC = ({ isEditMode }) => { required: "code required!", })} error={Boolean(errors.code)} + helperText={errors.code?.message} /> @@ -147,66 +160,97 @@ const MaterialDetails: React.FC = ({ isEditMode }) => { required: "countryOfOrigin required!", })} error={Boolean(errors.countryOfOrigin)} + helperText={errors.countryOfOrigin?.message} /> @@ -215,39 +259,52 @@ const MaterialDetails: React.FC = ({ isEditMode }) => { label={t("remarks")} fullWidth {...register("remarks", { - required: "remarks required!", + // required: "remarks required!", })} error={Boolean(errors.remarks)} + helperText={errors.remarks?.message} /> - + _formKey={"type"} columns={typeColumns} validateRow={validationTest} /> - + _formKey={"uom"} columns={uomColumns} validateRow={validationTest} /> + + + _formKey={"weightUnit"} + columns={weightUnitColumns} + validateRow={validationTest} + /> + ); }; -export default MaterialDetails; +export default ProductDetails; diff --git a/src/components/CreateProductMaterial/index.ts b/src/components/CreateProductMaterial/index.ts new file mode 100644 index 0000000..7d77b3c --- /dev/null +++ b/src/components/CreateProductMaterial/index.ts @@ -0,0 +1 @@ +export { default } from "./CreateProductMaterialWrapper"; diff --git a/src/components/InputDataGrid/InputDataGrid.tsx b/src/components/InputDataGrid/InputDataGrid.tsx index 3c8e1bd..5e1c783 100644 --- a/src/components/InputDataGrid/InputDataGrid.tsx +++ b/src/components/InputDataGrid/InputDataGrid.tsx @@ -107,34 +107,31 @@ function InputDataGrid({ [apiRef, rowModesModel] ); - console.log("asdasdasd") const processRowUpdate = useCallback( ( - newRow: GridValidRowModel, - originalRow: GridValidRowModel, - // GridRowModel>, - // originalRow: GridRowModel> + newRow: GridRowModel>, + originalRow: GridRowModel> ) => { ///////////////// // validation here - // const errors = validateRow(newRow); + const errors = validateRow(newRow); console.log(newRow) - // if (errors) { - // throw new ProcessRowUpdateError( - // originalRow, - // "validation error", - // errors, - // ); - // } + if (errors) { + throw new ProcessRowUpdateError( + originalRow, + "validation error", + errors, + ); + } ///////////////// const { _isNew, _error, ...updatedRow } = newRow; const rowToSave = { ...updatedRow, } as TableRow; /// test console.log(rowToSave) - // setRows((rw) => - // rw.map((r) => (getRowId(r) === getRowId(originalRow) ? rowToSave : r)) - // ); + setRows((rw) => + rw.map((r) => (getRowId(r) === getRowId(originalRow) ? rowToSave : r)) + ); return rowToSave; }, [validateRow, getRowId] @@ -190,7 +187,7 @@ function InputDataGrid({ field: "actions", type: "actions", headerName: "", - width: 100, + flex: 0.5, cellClassName: "actions", getActions: ({ id }: { id: GridRowId }) => { const isInEditMode = rowModesModel[id]?.mode === GridRowModes.Edit; @@ -230,18 +227,10 @@ function InputDataGrid({ ], [columns,rowModesModel, handleSave, handleCancel, handleDelete] ); - - // const processRowUpdate = (newRow: GridRowModel) => { - // const updatedRow = { ...newRow, isNew: false }; - // console.log("asdasdasdsda") - // // setRows(rows.map((row) => (row.id === newRow.id ? updatedRow : row))); - // return updatedRow; - // }; - // sync useForm useEffect(() => { - console.log(formKey) - console.log(rows) + // console.log(formKey) + // console.log(rows) setValue(formKey, rows); }, [formKey, rows]); @@ -294,7 +283,7 @@ function InputDataGrid({ }, }} disableColumnMenu - processRowUpdate={processRowUpdate} + processRowUpdate={processRowUpdate as any} // onRowEditStop={handleRowEditStop} rowModesModel={rowModesModel} onRowModesModelChange={setRowModesModel} diff --git a/src/components/Logo/Logo.tsx b/src/components/Logo/Logo.tsx index 6005307..d10b2bc 100644 --- a/src/components/Logo/Logo.tsx +++ b/src/components/Logo/Logo.tsx @@ -6,37 +6,26 @@ interface Props { const Logo: React.FC = ({ width, height }) => { return ( - + - - - - - - - ); }; diff --git a/src/components/MaterialSearch/MaterialSearch.tsx b/src/components/MaterialSearch/MaterialSearch.tsx deleted file mode 100644 index e5f1770..0000000 --- a/src/components/MaterialSearch/MaterialSearch.tsx +++ /dev/null @@ -1,96 +0,0 @@ -"use client" - -import { useCallback, useMemo, useState } from "react"; -import SearchBox, { Criterion } from "../SearchBox"; -import { MaterialResult } from "@/app/api/settings/material"; -import { useTranslation } from "react-i18next"; -import SearchResults, { Column } from "../SearchResults"; -import { EditNote } from "@mui/icons-material"; -import { useRouter, useSearchParams } from "next/navigation"; -import { GridDeleteIcon } from "@mui/x-data-grid"; - -type Props = { - materials: MaterialResult[] -} -type SearchQuery = Partial>; -type SearchParamNames = keyof SearchQuery; - -const MaterialSearch: React.FC = ({ - materials - }) => { -const [filteredMaterials, setFilteredMaterials] = useState(materials) -const { t } = useTranslation("materials"); -const router = useRouter(); - - const searchCriteria: Criterion[] = useMemo( - () => [ - { label: t("Code"), paramName: "code", type: "text" }, - { label: t("Name"), paramName: "name", type: "text" }, - ], [t, materials]) - - const onMaterialClick = useCallback( - (material: MaterialResult) => { - - }, - [router], - ); - - const onDeleteClick = useCallback( - (material: MaterialResult) => { - - }, - [router], - ); - - const columns = useMemo[]>(() => [ - { - name: "id", - label: t("Details"), - onClick: onMaterialClick, - buttonIcon: , - }, - { - name: "code", - label: t("Code"), - }, - { - name: "name", - label: t("Name"), - }, - { - name: "action", - label: t(""), - buttonIcon: , - onClick: onDeleteClick, - }, - - ], [filteredMaterials]) - - const onReset = useCallback(() => { - setFilteredMaterials(materials); - }, [materials]); - - return ( - <> - { - setFilteredMaterials( - materials.filter( - (mat) => - mat.code.toLowerCase().includes(query.code.toLowerCase()) && - mat.name.toLowerCase().includes(query.name.toLowerCase()) - ), - ); - }} - onReset={onReset} - /> - - items={filteredMaterials} - columns={columns} - /> - -) - } - - export default MaterialSearch \ No newline at end of file diff --git a/src/components/MaterialSearch/MaterialSearchWrapper.tsx b/src/components/MaterialSearch/MaterialSearchWrapper.tsx deleted file mode 100644 index 1be689b..0000000 --- a/src/components/MaterialSearch/MaterialSearchWrapper.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { fetchMaterials, MaterialResult } from "@/app/api/settings/material"; -import MaterialSearch from "./MaterialSearch"; -import MaterialSearchLoading from "./MaterialSearchLoading"; - -interface SubComponents { - Loading: typeof MaterialSearchLoading; - } - -const MaterialSearchWrapper: React.FC & SubComponents = async () => { - const materials = await fetchMaterials(); - console.log(materials) - - return ( - - ) -} - -MaterialSearchWrapper.Loading = MaterialSearchLoading; - -export default MaterialSearchWrapper; \ No newline at end of file diff --git a/src/components/MaterialSearch/index.ts b/src/components/MaterialSearch/index.ts deleted file mode 100644 index b12f72b..0000000 --- a/src/components/MaterialSearch/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./MaterialSearchWrapper"; diff --git a/src/components/NavigationContent/NavigationContent.tsx b/src/components/NavigationContent/NavigationContent.tsx index a648c96..6910c8c 100644 --- a/src/components/NavigationContent/NavigationContent.tsx +++ b/src/components/NavigationContent/NavigationContent.tsx @@ -193,8 +193,8 @@ const NavigationContent: React.FC = () => { }, { icon: , - label: "Finished Goods", - path: "/settings/user", + label: "Product", + path: "/settings/product", }, { icon: , diff --git a/src/components/ProductMaterialSearch/ProductMaterialSearch.tsx b/src/components/ProductMaterialSearch/ProductMaterialSearch.tsx new file mode 100644 index 0000000..cab3799 --- /dev/null +++ b/src/components/ProductMaterialSearch/ProductMaterialSearch.tsx @@ -0,0 +1,114 @@ +"use client"; + +import { useCallback, useMemo, useState } from "react"; +import SearchBox, { Criterion } from "../SearchBox"; +import { MaterialResult } from "@/app/api/settings/material"; +import { useTranslation } from "react-i18next"; +import SearchResults, { Column } from "../SearchResults"; +import { EditNote } from "@mui/icons-material"; +import { useRouter, useSearchParams } from "next/navigation"; +import { GridDeleteIcon } from "@mui/x-data-grid"; +import { TypeEnum } from "@/app/utils/typeEnum"; +import { ProductResult } from "@/app/api/settings/product"; + +export type ListResult = MaterialResult | ProductResult; +type Props = { + productMaterial: ListResult[]; + type: TypeEnum; +}; +type SearchQuery = Partial>; +type SearchParamNames = keyof SearchQuery; + +const MaterialSearch: React.FC = ({ productMaterial, type }) => { + const [filteredItems, setFilteredItems] = useState(productMaterial); + const { t } = useTranslation(type); + const router = useRouter(); + + const searchCriteria: Criterion[] = useMemo( + () => { + var searchCriteria: Criterion[] = [] + if (type === TypeEnum.MATERIAL) { + searchCriteria = [ + { label: t("Code"), paramName: "code", type: "text" }, + { label: t("Name"), paramName: "name", type: "text" }, + ] + } + if (type === TypeEnum.MATERIAL) { + + } + return searchCriteria + }, + [t, productMaterial] + ); + + const onDetailClick = useCallback( + (productMaterial: ListResult) => { + router.push(`/settings/${type}/edit?id=${productMaterial.id}`); + }, + [type, router] + ); + + const onDeleteClick = useCallback( + (productMaterial: ListResult) => {}, + [router] + ); + + const columns = useMemo[]>( + () => [ + { + name: "id", + label: t("Details"), + onClick: onDetailClick, + buttonIcon: , + }, + { + name: "code", + label: t("Code"), + }, + { + name: "name", + label: t("Name"), + }, + { + name: "action", + label: t(""), + buttonIcon: , + onClick: onDeleteClick, + }, + ], + [filteredItems] + ); + + const onReset = useCallback(() => { + setFilteredItems(productMaterial); + }, [productMaterial]); + + return ( + <> + { + setFilteredItems( + productMaterial.filter((pm) => { + if (type === TypeEnum.MATERIAL) { + return ( + pm.code.toLowerCase().includes(query.code.toLowerCase()) && + pm.name.toLowerCase().includes(query.name.toLowerCase()) + ); + } else if (type === TypeEnum.PRODUCT) { + return ( + pm.code.toLowerCase().includes(query.code.toLowerCase()) && + pm.name.toLowerCase().includes(query.name.toLowerCase()) + ); + } + }) + ); + }} + onReset={onReset} + /> + items={filteredItems} columns={columns} /> + + ); +}; + +export default MaterialSearch; diff --git a/src/components/MaterialSearch/MaterialSearchLoading.tsx b/src/components/ProductMaterialSearch/ProductMaterialSearchLoading.tsx similarity index 90% rename from src/components/MaterialSearch/MaterialSearchLoading.tsx rename to src/components/ProductMaterialSearch/ProductMaterialSearchLoading.tsx index 127a557..a591e2a 100644 --- a/src/components/MaterialSearch/MaterialSearchLoading.tsx +++ b/src/components/ProductMaterialSearch/ProductMaterialSearchLoading.tsx @@ -5,7 +5,7 @@ import Stack from "@mui/material/Stack"; import React from "react"; // Can make this nicer -export const MaterialSearchLoading: React.FC = () => { +export const ProductMaterialSearchLoading: React.FC = () => { return ( <> @@ -37,4 +37,4 @@ export const MaterialSearchLoading: React.FC = () => { ); }; -export default MaterialSearchLoading; +export default ProductMaterialSearchLoading; diff --git a/src/components/ProductMaterialSearch/ProductMaterialSearchWrapper.tsx b/src/components/ProductMaterialSearch/ProductMaterialSearchWrapper.tsx new file mode 100644 index 0000000..494e5cb --- /dev/null +++ b/src/components/ProductMaterialSearch/ProductMaterialSearchWrapper.tsx @@ -0,0 +1,30 @@ +import { fetchAllMaterials, MaterialResult } from "@/app/api/settings/material"; +import ProductMaterialSearch, { ListResult } from "./ProductMaterialSearch"; +import MaterialSearchLoading from "./ProductMaterialSearchLoading"; +import { SearchParams } from "@/app/utils/fetchUtil"; +import { TypeEnum } from "@/app/utils/typeEnum"; + +interface SubComponents { + Loading: typeof MaterialSearchLoading; +} + +type Props = { + type: TypeEnum; +}; + +const ProductMaterialSearchWrapper: React.FC & SubComponents = async ({ + type +}) => { + var result: ListResult[] = [] + if (TypeEnum.PRODUCT === type) { + result = [] + } else if (TypeEnum.MATERIAL === type) { + result = await fetchAllMaterials(); + } + + return ; +}; + +ProductMaterialSearchWrapper.Loading = MaterialSearchLoading; + +export default ProductMaterialSearchWrapper; diff --git a/src/components/ProductMaterialSearch/index.ts b/src/components/ProductMaterialSearch/index.ts new file mode 100644 index 0000000..b0faf28 --- /dev/null +++ b/src/components/ProductMaterialSearch/index.ts @@ -0,0 +1 @@ +export { default } from "./ProductMaterialSearchWrapper";