| @@ -1,24 +0,0 @@ | |||||
| import { SearchParams } from "@/app/utils/fetchUtil"; | |||||
| import { TypeEnum } from "@/app/utils/typeEnum"; | |||||
| import CreateProductMaterial from "@/components/CreateItem"; | |||||
| import { I18nProvider, getServerI18n } from "@/i18n"; | |||||
| import { Typography } from "@mui/material"; | |||||
| import { isString } from "lodash"; | |||||
| type Props = {} & SearchParams; | |||||
| const byProductSetting: React.FC<Props> = async ({ searchParams }) => { | |||||
| const type = TypeEnum.BYPRODUCT; | |||||
| const { t } = await getServerI18n(type); | |||||
| console.log(searchParams); | |||||
| return ( | |||||
| <> | |||||
| {/* <Typography variant="h4">{t("Create Material")}</Typography> */} | |||||
| <I18nProvider namespaces={[type.toString()]}> | |||||
| <CreateProductMaterial type={type} /> | |||||
| </I18nProvider> | |||||
| </> | |||||
| ); | |||||
| }; | |||||
| export default byProductSetting; | |||||
| @@ -1,24 +0,0 @@ | |||||
| import { SearchParams } from "@/app/utils/fetchUtil"; | |||||
| import { TypeEnum } from "@/app/utils/typeEnum"; | |||||
| import CreateProductMaterial from "@/components/CreateItem"; | |||||
| import { I18nProvider, getServerI18n } from "@/i18n"; | |||||
| import { Typography } from "@mui/material"; | |||||
| import { isString } from "lodash"; | |||||
| type Props = {} & SearchParams; | |||||
| const byProductSetting: React.FC<Props> = async ({ searchParams }) => { | |||||
| const type = TypeEnum.BYPRODUCT; | |||||
| const { t } = await getServerI18n(type); | |||||
| console.log(searchParams); | |||||
| return ( | |||||
| <> | |||||
| {/* <Typography variant="h4">{t("Create Material")}</Typography> */} | |||||
| <I18nProvider namespaces={[type]}> | |||||
| <CreateProductMaterial type={type} /> | |||||
| </I18nProvider> | |||||
| </> | |||||
| ); | |||||
| }; | |||||
| export default byProductSetting; | |||||
| @@ -1,48 +0,0 @@ | |||||
| import { TypeEnum } from "@/app/utils/typeEnum"; | |||||
| import MaterialSearch from "@/components/ItemsSearch"; | |||||
| 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 ( | |||||
| <> | |||||
| <Stack | |||||
| direction="row" | |||||
| justifyContent="space-between" | |||||
| flexWrap="wrap" | |||||
| rowGap={2} | |||||
| > | |||||
| <Typography variant="h4" marginInlineEnd={2}> | |||||
| {t("ByProduct")} | |||||
| </Typography> | |||||
| <Button | |||||
| variant="contained" | |||||
| startIcon={<Add />} | |||||
| LinkComponent={Link} | |||||
| href="byProduct/create" | |||||
| > | |||||
| {t("Create by-Product")} | |||||
| </Button> | |||||
| </Stack> | |||||
| <Suspense fallback={<MaterialSearch.Loading />}> | |||||
| <MaterialSearch type={byProduct} /> | |||||
| </Suspense> | |||||
| </> | |||||
| ); | |||||
| }; | |||||
| export default materialSetting; | |||||
| @@ -1,6 +1,6 @@ | |||||
| import { SearchParams } from "@/app/utils/fetchUtil"; | import { SearchParams } from "@/app/utils/fetchUtil"; | ||||
| import { TypeEnum } from "@/app/utils/typeEnum"; | import { TypeEnum } from "@/app/utils/typeEnum"; | ||||
| import CreateProductMaterial from "@/components/CreateItem"; | |||||
| import CreateItem from "@/components/CreateItem"; | |||||
| import { I18nProvider, getServerI18n } from "@/i18n"; | import { I18nProvider, getServerI18n } from "@/i18n"; | ||||
| import { Typography } from "@mui/material"; | import { Typography } from "@mui/material"; | ||||
| import isString from "lodash/isString"; | import isString from "lodash/isString"; | ||||
| @@ -8,13 +8,13 @@ import isString from "lodash/isString"; | |||||
| type Props = {} & SearchParams; | type Props = {} & SearchParams; | ||||
| const materialSetting: React.FC<Props> = async ({ searchParams }) => { | const materialSetting: React.FC<Props> = async ({ searchParams }) => { | ||||
| const type = TypeEnum.PRODUCT; | |||||
| const { t } = await getServerI18n(type); | |||||
| // const type = TypeEnum.PRODUCT; | |||||
| const { t } = await getServerI18n("items"); | |||||
| return ( | return ( | ||||
| <> | <> | ||||
| {/* <Typography variant="h4">{t("Create Material")}</Typography> */} | {/* <Typography variant="h4">{t("Create Material")}</Typography> */} | ||||
| <I18nProvider namespaces={[type]}> | |||||
| <CreateProductMaterial type={type}/> | |||||
| <I18nProvider namespaces={["items"]}> | |||||
| <CreateItem /> | |||||
| </I18nProvider> | </I18nProvider> | ||||
| </> | </> | ||||
| ); | ); | ||||
| @@ -9,7 +9,7 @@ import { notFound } from "next/navigation"; | |||||
| type Props = {} & SearchParams; | type Props = {} & SearchParams; | ||||
| const productSetting: React.FC<Props> = async ({ searchParams }) => { | const productSetting: React.FC<Props> = async ({ searchParams }) => { | ||||
| const type = TypeEnum.PRODUCT; | |||||
| const type = "items"; | |||||
| const { t } = await getServerI18n(type); | const { t } = await getServerI18n(type); | ||||
| const id = isString(searchParams["id"]) | const id = isString(searchParams["id"]) | ||||
| ? parseInt(searchParams["id"]) | ? parseInt(searchParams["id"]) | ||||
| @@ -21,7 +21,7 @@ const productSetting: React.FC<Props> = async ({ searchParams }) => { | |||||
| <> | <> | ||||
| {/* <Typography variant="h4">{t("Create Material")}</Typography> */} | {/* <Typography variant="h4">{t("Create Material")}</Typography> */} | ||||
| <I18nProvider namespaces={[type]}> | <I18nProvider namespaces={[type]}> | ||||
| <CreateProductMaterial type={type} id={id} /> | |||||
| <CreateProductMaterial id={id} /> | |||||
| </I18nProvider> | </I18nProvider> | ||||
| </> | </> | ||||
| ); | ); | ||||
| @@ -1,5 +1,5 @@ | |||||
| import { TypeEnum } from "@/app/utils/typeEnum"; | import { TypeEnum } from "@/app/utils/typeEnum"; | ||||
| import MaterialSearch from "@/components/ItemsSearch"; | |||||
| import ItemsSearch from "@/components/ItemsSearch"; | |||||
| import { getServerI18n } from "@/i18n"; | import { getServerI18n } from "@/i18n"; | ||||
| import Add from "@mui/icons-material/Add"; | import Add from "@mui/icons-material/Add"; | ||||
| import Button from "@mui/material/Button"; | import Button from "@mui/material/Button"; | ||||
| @@ -29,17 +29,17 @@ const productSetting: React.FC = async () => { | |||||
| <Typography variant="h4" marginInlineEnd={2}> | <Typography variant="h4" marginInlineEnd={2}> | ||||
| {t("Product")} | {t("Product")} | ||||
| </Typography> | </Typography> | ||||
| <Button | |||||
| {/* <Button | |||||
| variant="contained" | variant="contained" | ||||
| startIcon={<Add />} | startIcon={<Add />} | ||||
| LinkComponent={Link} | LinkComponent={Link} | ||||
| href="product/create" | href="product/create" | ||||
| > | > | ||||
| {t("Create product")} | {t("Create product")} | ||||
| </Button> | |||||
| </Button> */} | |||||
| </Stack> | </Stack> | ||||
| <Suspense fallback={<MaterialSearch.Loading />}> | |||||
| <MaterialSearch type={project} /> | |||||
| <Suspense fallback={<ItemsSearch.Loading />}> | |||||
| <ItemsSearch /> | |||||
| </Suspense> | </Suspense> | ||||
| </> | </> | ||||
| ); | ); | ||||
| @@ -1,24 +0,0 @@ | |||||
| import { SearchParams } from "@/app/utils/fetchUtil"; | |||||
| import { TypeEnum } from "@/app/utils/typeEnum"; | |||||
| import CreateProductMaterial from "@/components/CreateItem"; | |||||
| import { I18nProvider, getServerI18n } from "@/i18n"; | |||||
| import { Typography } from "@mui/material"; | |||||
| import { isString } from "lodash"; | |||||
| type Props = {} & SearchParams; | |||||
| const materialSetting: React.FC<Props> = async ({ searchParams }) => { | |||||
| const type = TypeEnum.MATERIAL; | |||||
| const { t } = await getServerI18n(type); | |||||
| console.log(searchParams); | |||||
| return ( | |||||
| <> | |||||
| {/* <Typography variant="h4">{t("Create Material")}</Typography> */} | |||||
| <I18nProvider namespaces={[type]}> | |||||
| <CreateProductMaterial type={type} /> | |||||
| </I18nProvider> | |||||
| </> | |||||
| ); | |||||
| }; | |||||
| export default materialSetting; | |||||
| @@ -1,31 +0,0 @@ | |||||
| import { SearchParams } from "@/app/utils/fetchUtil"; | |||||
| import { TypeEnum } from "@/app/utils/typeEnum"; | |||||
| import CreateProductMaterial from "@/components/CreateItem"; | |||||
| 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<Props> = 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 ( | |||||
| <> | |||||
| {/* <Typography variant="h4">{t("Create Material")}</Typography> */} | |||||
| <I18nProvider namespaces={[type.toString()]}> | |||||
| <CreateProductMaterial type={type} id={id} /> | |||||
| </I18nProvider> | |||||
| </> | |||||
| ); | |||||
| }; | |||||
| export default materialSetting; | |||||
| @@ -1,48 +0,0 @@ | |||||
| import { TypeEnum } from "@/app/utils/typeEnum"; | |||||
| import MaterialSearch from "@/components/ItemsSearch"; | |||||
| 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: "Material", | |||||
| }; | |||||
| const materialSetting: React.FC = async () => { | |||||
| const material = TypeEnum.MATERIAL | |||||
| const { t } = await getServerI18n("material"); | |||||
| // preloadClaims(); | |||||
| return ( | |||||
| <> | |||||
| <Stack | |||||
| direction="row" | |||||
| justifyContent="space-between" | |||||
| flexWrap="wrap" | |||||
| rowGap={2} | |||||
| > | |||||
| <Typography variant="h4" marginInlineEnd={2}> | |||||
| {t("Material")} | |||||
| </Typography> | |||||
| <Button | |||||
| variant="contained" | |||||
| startIcon={<Add />} | |||||
| LinkComponent={Link} | |||||
| href="material/create" | |||||
| > | |||||
| {t("Create material")} | |||||
| </Button> | |||||
| </Stack> | |||||
| <Suspense fallback={<MaterialSearch.Loading />}> | |||||
| <MaterialSearch type={material} /> | |||||
| </Suspense> | |||||
| </> | |||||
| ); | |||||
| }; | |||||
| export default materialSetting; | |||||
| @@ -2,20 +2,23 @@ | |||||
| import { ServerFetchError, serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil"; | import { ServerFetchError, serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil"; | ||||
| import { revalidateTag } from "next/cache"; | import { revalidateTag } from "next/cache"; | ||||
| import { BASE_API_URL } from "@/config/api"; | import { BASE_API_URL } from "@/config/api"; | ||||
| import { HTMLInputTypeAttribute } from "react"; | |||||
| import { CreateItemResponse } from "../../utils"; | import { CreateItemResponse } from "../../utils"; | ||||
| import { ItemQc } from "."; | |||||
| import { QcChecksInputs } from "../qcCheck/actions"; | |||||
| // export type TypeInputs = { | |||||
| // id: number; | |||||
| // name: string | |||||
| // } | |||||
| // export type UomInputs = { | |||||
| // uom: string | |||||
| // } | |||||
| // export type WeightUnitInputs = { | |||||
| // weightUnit: string | |||||
| // conversion: number | |||||
| // } | |||||
| export type TypeInputs = { | |||||
| id: number; | |||||
| name: string | |||||
| } | |||||
| export type UomInputs = { | |||||
| uom: string | |||||
| } | |||||
| export type WeightUnitInputs = { | |||||
| weightUnit: string | |||||
| conversion: number | |||||
| } | |||||
| export type CreateItemInputs = { | export type CreateItemInputs = { | ||||
| id?: string | number | id?: string | number | ||||
| code: string; | code: string; | ||||
| @@ -24,14 +27,10 @@ export type CreateItemInputs = { | |||||
| remarks?: string | undefined; | remarks?: string | undefined; | ||||
| shelfLife?: Number | undefined; | shelfLife?: Number | undefined; | ||||
| countryOfOrigin?: string | undefined; | countryOfOrigin?: string | undefined; | ||||
| minHumid?: number | undefined; | |||||
| maxHumid?: number | undefined; | |||||
| minTemp?: number | undefined; | |||||
| maxTemp?: number | undefined; | |||||
| sampleRate?: number | undefined; | |||||
| passingRate?: number | undefined; | |||||
| netWeight: number; | |||||
| typeId: number; | |||||
| maxQty: number; | |||||
| type: string; | |||||
| qcChecks: QcChecksInputs[] | |||||
| qcChecks_active: number[] | |||||
| } | } | ||||
| export const saveItem = async (data: CreateItemInputs) => { | export const saveItem = async (data: CreateItemInputs) => { | ||||
| @@ -43,4 +42,4 @@ export const saveItem = async (data: CreateItemInputs) => { | |||||
| }); | }); | ||||
| revalidateTag("items"); | revalidateTag("items"); | ||||
| return item | return item | ||||
| }; | |||||
| }; | |||||
| @@ -2,7 +2,18 @@ import { cache } from "react"; | |||||
| import "server-only"; | import "server-only"; | ||||
| import { serverFetchJson } from "@/app/utils/fetchUtil"; | import { serverFetchJson } from "@/app/utils/fetchUtil"; | ||||
| import { BASE_API_URL } from "@/config/api"; | import { BASE_API_URL } from "@/config/api"; | ||||
| import { TypeInputs, UomInputs, WeightUnitInputs } from "./actions"; | |||||
| // import { TypeInputs, UomInputs, WeightUnitInputs } from "./actions"; | |||||
| export type ItemQc = { | |||||
| id: number; // id = qc_check # | |||||
| name: string; | |||||
| code: string; | |||||
| description: string | undefined; | |||||
| instruction: string | undefined; | |||||
| lowerLimit: number | undefined; | |||||
| upperLimit: number | undefined; | |||||
| isActive: boolean | undefined; | |||||
| } | |||||
| export type ItemsResult = { | export type ItemsResult = { | ||||
| id: string | number | id: string | number | ||||
| @@ -12,20 +23,15 @@ export type ItemsResult = { | |||||
| remarks: string | undefined; | remarks: string | undefined; | ||||
| shelfLife: Number | undefined; | shelfLife: Number | undefined; | ||||
| countryOfOrigin: string | 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; | |||||
| maxQty: number | undefined; | |||||
| type: string; | |||||
| qcChecks: ItemQc[] | |||||
| action?: any | action?: any | ||||
| } | } | ||||
| export type Result = { | |||||
| item: ItemsResult | |||||
| qcChecks: ItemQc[] | |||||
| } | |||||
| export const fetchAllItems = cache(async () => { | export const fetchAllItems = cache(async () => { | ||||
| return serverFetchJson<ItemsResult[]>(`${BASE_API_URL}/items`, { | return serverFetchJson<ItemsResult[]>(`${BASE_API_URL}/items`, { | ||||
| next: { tags: ["items"] }, | next: { tags: ["items"] }, | ||||
| @@ -34,7 +40,7 @@ export const fetchAllItems = cache(async () => { | |||||
| export const fetchItem = cache(async (id: number) => { | export const fetchItem = cache(async (id: number) => { | ||||
| return serverFetchJson<ItemsResult>(`${BASE_API_URL}/items/details/${id}`, { | |||||
| return serverFetchJson<Result>(`${BASE_API_URL}/items/details/${id}`, { | |||||
| next: { tags: ["items"] }, | next: { tags: ["items"] }, | ||||
| }); | }); | ||||
| }); | }); | ||||
| @@ -0,0 +1,22 @@ | |||||
| "use server"; | |||||
| import { ServerFetchError, serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil"; | |||||
| import { revalidateTag } from "next/cache"; | |||||
| import { BASE_API_URL } from "@/config/api"; | |||||
| import { CreateItemResponse } from "../../utils"; | |||||
| import { ItemQc } from "../item"; | |||||
| export type QcChecksInputs = { | |||||
| } & Partial<ItemQc> | |||||
| export const saveItemQcChecks = async (data: QcChecksInputs[]) => { | |||||
| // try { | |||||
| const res = await serverFetchJson<CreateItemResponse<QcChecksInputs>>(`${BASE_API_URL}/qcCheck/new`, { | |||||
| method: "POST", | |||||
| body: JSON.stringify(data), | |||||
| headers: { "Content-Type": "application/json" }, | |||||
| }); | |||||
| revalidateTag("items"); | |||||
| return res | |||||
| }; | |||||
| @@ -56,6 +56,7 @@ type FetchParams = Parameters<typeof fetch>; | |||||
| export async function serverFetchJson<T>(...args: FetchParams) { | export async function serverFetchJson<T>(...args: FetchParams) { | ||||
| const response = await serverFetch(...args); | const response = await serverFetch(...args); | ||||
| console.log(response.status) | |||||
| if (response.ok) { | if (response.ok) { | ||||
| if (response.status === 204) { | if (response.status === 204) { | ||||
| return response.status as T | return response.status as T | ||||
| @@ -1,294 +0,0 @@ | |||||
| "use client"; | |||||
| import { CreateItemInputs } from "@/app/api/settings/item/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, 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 CreateItemInputs]?: string; | |||||
| } | |||||
| | undefined; | |||||
| const ByProductDetails: React.FC<Props> = ({}) => { | |||||
| const { | |||||
| t, | |||||
| i18n: { language }, | |||||
| } = useTranslation(); | |||||
| const { | |||||
| register, | |||||
| formState: { errors, defaultValues, touchedFields }, | |||||
| watch, | |||||
| control, | |||||
| setValue, | |||||
| getValues, | |||||
| reset, | |||||
| resetField, | |||||
| setError, | |||||
| clearErrors, | |||||
| } = useFormContext<CreateItemInputs>(); | |||||
| const typeColumns = useMemo<GridColDef[]>( | |||||
| () => [ | |||||
| { | |||||
| field: "type", | |||||
| headerName: "type", | |||||
| flex: 1, | |||||
| editable: true, | |||||
| }, | |||||
| ], | |||||
| [] | |||||
| ); | |||||
| const weightUnitColumns = useMemo<GridColDef[]>( | |||||
| () => [ | |||||
| { | |||||
| 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<GridColDef[]>( | |||||
| () => [ | |||||
| { | |||||
| field: "uom", | |||||
| headerName: "uom", | |||||
| flex: 1, | |||||
| editable: true, | |||||
| }, | |||||
| ], | |||||
| [] | |||||
| ); | |||||
| const validationTest = useCallback( | |||||
| ( | |||||
| newRow: GridRowModel<TableRow<Partial<CreateItemInputs>, EntryError>> | |||||
| ): EntryError => { | |||||
| const error: EntryError = {}; | |||||
| console.log(newRow); | |||||
| return Object.keys(error).length > 0 ? error : undefined; | |||||
| }, | |||||
| [] | |||||
| ); | |||||
| const validateUom = useCallback( | |||||
| ( | |||||
| newRow: GridRowModel<TableRow<Partial<CreateItemInputs>, EntryError>> | |||||
| ): EntryError => { | |||||
| const error: EntryError = {}; | |||||
| console.log(newRow); | |||||
| // if (!newRow.uom) { | |||||
| // error.uom = "Uom cannot be empty"; | |||||
| // } | |||||
| return Object.keys(error).length > 0 ? error : undefined; | |||||
| }, | |||||
| [] | |||||
| ); | |||||
| useEffect(() => { | |||||
| console.log(errors) | |||||
| }, [errors]) | |||||
| return ( | |||||
| <Card sx={{ display: "block" }}> | |||||
| <CardContent component={Stack} spacing={4}> | |||||
| <Box> | |||||
| <Typography variant="overline" display="block" marginBlockEnd={1}> | |||||
| {t("Product Details")} | |||||
| </Typography> | |||||
| <Grid container spacing={2} columns={{ xs: 6, sm: 12 }}> | |||||
| <Grid item xs={6}> | |||||
| <TextField | |||||
| label={t("Name")} | |||||
| fullWidth | |||||
| {...register("name", { | |||||
| required: "name required!", | |||||
| })} | |||||
| error={Boolean(errors.name)} | |||||
| helperText={errors.name?.message} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={6}> | |||||
| <TextField | |||||
| label={t("Code")} | |||||
| fullWidth | |||||
| {...register("code", { | |||||
| required: "code required!", | |||||
| })} | |||||
| error={Boolean(errors.code)} | |||||
| helperText={errors.code?.message} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={6}> | |||||
| <TextField | |||||
| label={t("description")} | |||||
| fullWidth | |||||
| {...register("description")} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={6}> | |||||
| <TextField | |||||
| label={t("shelfLife")} | |||||
| type="number" | |||||
| fullWidth | |||||
| {...register("shelfLife", { | |||||
| valueAsNumber: true, | |||||
| required: "shelfLife required!", | |||||
| })} | |||||
| error={Boolean(errors.shelfLife)} | |||||
| helperText={errors.shelfLife?.message} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={6}> | |||||
| <TextField | |||||
| label={t("countryOfOrigin")} | |||||
| fullWidth | |||||
| {...register("countryOfOrigin", { | |||||
| required: "countryOfOrigin required!", | |||||
| })} | |||||
| error={Boolean(errors.countryOfOrigin)} | |||||
| helperText={errors.countryOfOrigin?.message} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={6}> | |||||
| <TextField | |||||
| label={t("minHumid")} | |||||
| type="number" | |||||
| fullWidth | |||||
| inputProps={NumberInputProps} | |||||
| {...register("minHumid", { | |||||
| valueAsNumber: true, | |||||
| required: "minHumid required!", | |||||
| })} | |||||
| error={Boolean(errors.minHumid)} | |||||
| helperText={errors.minHumid?.message} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={6}> | |||||
| <TextField | |||||
| label={t("maxHumid")} | |||||
| type="number" | |||||
| fullWidth | |||||
| inputProps={NumberInputProps} | |||||
| {...register("maxHumid", { | |||||
| valueAsNumber: true, | |||||
| })} | |||||
| error={Boolean(errors.maxHumid)} | |||||
| helperText={errors.maxHumid?.message} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={6}> | |||||
| <TextField | |||||
| label={t("minTemp")} | |||||
| type="number" | |||||
| fullWidth | |||||
| inputProps={NumberInputProps} | |||||
| {...register("minTemp", { | |||||
| valueAsNumber: true, | |||||
| required: "minTemp required!", | |||||
| })} | |||||
| error={Boolean(errors.minTemp)} | |||||
| helperText={errors.minTemp?.message} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={6}> | |||||
| <TextField | |||||
| label={t("maxTemp")} | |||||
| type="number" | |||||
| fullWidth | |||||
| inputProps={NumberInputProps} | |||||
| {...register("maxTemp", { | |||||
| valueAsNumber: true, | |||||
| required: "maxTemp required!", | |||||
| })} | |||||
| error={Boolean(errors.maxTemp)} | |||||
| helperText={errors.maxTemp?.message} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={6}> | |||||
| <TextField | |||||
| label={t("sampleRate")} | |||||
| type="number" | |||||
| fullWidth | |||||
| inputProps={NumberInputProps} | |||||
| {...register("sampleRate", { | |||||
| valueAsNumber: true, | |||||
| required: "sampleRate required!", | |||||
| })} | |||||
| error={Boolean(errors.sampleRate)} | |||||
| helperText={errors.sampleRate?.message} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={6}> | |||||
| <TextField | |||||
| label={t("passingRate")} | |||||
| type="number" | |||||
| fullWidth | |||||
| inputProps={NumberInputProps} | |||||
| {...register("passingRate", { | |||||
| valueAsNumber: true, | |||||
| required: "passingRate required!", | |||||
| })} | |||||
| error={Boolean(errors.passingRate)} // change backend for null or not null | |||||
| helperText={errors.passingRate?.message} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={6} /> | |||||
| <Grid item xs={6}> | |||||
| <TextField | |||||
| label={t("remarks")} | |||||
| fullWidth | |||||
| {...register("remarks", { | |||||
| // required: "remarks required!", | |||||
| })} | |||||
| error={Boolean(errors.remarks)} | |||||
| helperText={errors.remarks?.message} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={6}> | |||||
| <TextField | |||||
| label={t("netWeight")} | |||||
| type="number" | |||||
| fullWidth | |||||
| inputProps={NumberInputProps} | |||||
| {...register("netWeight", { | |||||
| valueAsNumber: true, | |||||
| required: "netWeight required!", | |||||
| })} | |||||
| error={Boolean(errors.netWeight)} | |||||
| helperText={errors.netWeight?.message} | |||||
| /> | |||||
| </Grid> | |||||
| </Grid> | |||||
| </Box> | |||||
| </CardContent> | |||||
| </Card> | |||||
| ); | |||||
| }; | |||||
| export default ByProductDetails; | |||||
| @@ -14,140 +14,126 @@ import { | |||||
| useForm, | useForm, | ||||
| } from "react-hook-form"; | } from "react-hook-form"; | ||||
| import { deleteDialog } from "../Swal/CustomAlerts"; | import { deleteDialog } from "../Swal/CustomAlerts"; | ||||
| import { Box, Button, Grid, Stack, Typography } from "@mui/material"; | |||||
| import { Box, Button, Grid, Stack, Tab, Tabs, TabsProps, Typography } from "@mui/material"; | |||||
| import { Check, Close, EditNote } from "@mui/icons-material"; | import { Check, Close, EditNote } from "@mui/icons-material"; | ||||
| import { TypeEnum } from "@/app/utils/typeEnum"; | import { TypeEnum } from "@/app/utils/typeEnum"; | ||||
| import MaterialDetails from "./MaterialDetails"; | |||||
| import ProductDetails from "./ProductDetails"; | import ProductDetails from "./ProductDetails"; | ||||
| import { CreateItemResponse } from "@/app/api/utils"; | import { CreateItemResponse } from "@/app/api/utils"; | ||||
| import ByProductDetails from "./ByProductDetails"; | |||||
| import QcDetails from "./QcDetails"; | |||||
| import { ItemQc } from "@/app/api/settings/item"; | |||||
| import { saveItemQcChecks } from "@/app/api/settings/qcCheck/actions"; | |||||
| import { useGridApiRef } from "@mui/x-data-grid"; | |||||
| type Props = { | type Props = { | ||||
| isEditMode: boolean; | isEditMode: boolean; | ||||
| type: TypeEnum; | |||||
| // type: TypeEnum; | |||||
| defaultValues: Partial<CreateItemInputs> | undefined; | defaultValues: Partial<CreateItemInputs> | undefined; | ||||
| qcChecks: ItemQc[] | |||||
| }; | }; | ||||
| const CreateItem: React.FC<Props> = ({ | const CreateItem: React.FC<Props> = ({ | ||||
| isEditMode, | isEditMode, | ||||
| type, | |||||
| // type, | |||||
| defaultValues, | defaultValues, | ||||
| qcChecks | |||||
| }) => { | }) => { | ||||
| console.log(type) | |||||
| // console.log(type) | |||||
| const apiRef = useGridApiRef(); | |||||
| const params = useSearchParams() | |||||
| console.log(params.get("id")) | |||||
| const [serverError, setServerError] = useState(""); | const [serverError, setServerError] = useState(""); | ||||
| const [tabIndex, setTabIndex] = useState(0); | const [tabIndex, setTabIndex] = useState(0); | ||||
| const { t } = useTranslation(); | const { t } = useTranslation(); | ||||
| const router = useRouter(); | const router = useRouter(); | ||||
| const [typeId, title, mode, redirPath] = useMemo(() => { | |||||
| var typeId = TypeEnum.CONSUMABLE_ID | |||||
| const title = "Product / Material" | |||||
| const [mode, redirPath] = useMemo(() => { | |||||
| // var typeId = TypeEnum.CONSUMABLE_ID | |||||
| var title = ""; | var title = ""; | ||||
| var mode = ""; | var mode = ""; | ||||
| var redirPath = ""; | var redirPath = ""; | ||||
| if (type === TypeEnum.MATERIAL) { | |||||
| typeId = TypeEnum.MATERIAL_ID | |||||
| title = "Material"; | |||||
| redirPath = "/settings/material"; | |||||
| } | |||||
| if (type === TypeEnum.PRODUCT) { | |||||
| typeId = TypeEnum.PRODUCT_ID | |||||
| // if (type === TypeEnum.MATERIAL) { | |||||
| // typeId = TypeEnum.MATERIAL_ID | |||||
| // title = "Material"; | |||||
| // redirPath = "/settings/material"; | |||||
| // } | |||||
| // if (type === TypeEnum.PRODUCT) { | |||||
| // typeId = TypeEnum.PRODUCT_ID | |||||
| title = "Product"; | title = "Product"; | ||||
| redirPath = "/settings/product"; | |||||
| } | |||||
| if (type === TypeEnum.BYPRODUCT) { | |||||
| typeId = TypeEnum.BYPRODUCT_ID | |||||
| title = "By-Product"; | |||||
| redirPath = "/settings/byProduct"; | |||||
| } | |||||
| redirPath = "/settings/items"; | |||||
| // } | |||||
| // if (type === TypeEnum.BYPRODUCT) { | |||||
| // typeId = TypeEnum.BYPRODUCT_ID | |||||
| // title = "By-Product"; | |||||
| // redirPath = "/settings/byProduct"; | |||||
| // } | |||||
| if (isEditMode) { | if (isEditMode) { | ||||
| mode = "Edit"; | mode = "Edit"; | ||||
| } else { | } else { | ||||
| mode = "Create"; | mode = "Create"; | ||||
| } | } | ||||
| return [typeId, title, mode, redirPath]; | |||||
| }, [type, isEditMode]); | |||||
| console.log(typeId) | |||||
| return [mode, redirPath]; | |||||
| }, [isEditMode]); | |||||
| // console.log(typeId) | |||||
| const formProps = useForm<CreateItemInputs>({ | const formProps = useForm<CreateItemInputs>({ | ||||
| defaultValues: defaultValues ? defaultValues : { | defaultValues: defaultValues ? defaultValues : { | ||||
| typeId: typeId | |||||
| }, | }, | ||||
| }); | }); | ||||
| const errors = formProps.formState.errors; | const errors = formProps.formState.errors; | ||||
| const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>( | |||||
| (_e, newValue) => { | |||||
| setTabIndex(newValue); | |||||
| }, | |||||
| [], | |||||
| ); | |||||
| const handleCancel = () => { | const handleCancel = () => { | ||||
| router.replace(`/settings/${type}`); | |||||
| router.replace(`/settings/product`); | |||||
| }; | }; | ||||
| const onSubmit = useCallback<SubmitHandler<CreateItemInputs>>( | |||||
| const onSubmit = useCallback<SubmitHandler<CreateItemInputs & {}>>( | |||||
| async (data, event) => { | async (data, event) => { | ||||
| let hasErrors = false; | let hasErrors = false; | ||||
| console.log("dasasdasd") | |||||
| console.log(errors) | |||||
| // console.log(apiRef.current.getCellValue(2, "lowerLimit")) | |||||
| // apiRef.current. | |||||
| try { | 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 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 < 0 || 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 < 0 || data.sampleRate > 100)) { | |||||
| const message = "sampleRate should not be greater than 100%"; | |||||
| formProps.setError("sampleRate", { | |||||
| message: message, | |||||
| type: "required", | |||||
| }); | |||||
| } | |||||
| if (hasErrors) { | if (hasErrors) { | ||||
| setServerError(t("An error has occurred. Please try again later.")); | setServerError(t("An error has occurred. Please try again later.")); | ||||
| return false; | return false; | ||||
| } | } | ||||
| console.log("data posted"); | console.log("data posted"); | ||||
| console.log(data); | console.log(data); | ||||
| const qcCheck = data.qcChecks.length > 0 ? data.qcChecks.filter((q) => data.qcChecks_active.includes(q.id!!)).map((qc) => { | |||||
| return { | |||||
| qcItemId: qc.id, | |||||
| instruction: qc.instruction, | |||||
| lowerLimit: qc.lowerLimit, | |||||
| upperLimit: qc.upperLimit, | |||||
| itemId: parseInt(params.get("id")!.toString()) | |||||
| } | |||||
| }) : [] | |||||
| const test = data.qcChecks.filter((q) => data.qcChecks_active.includes(q.id!!)) | |||||
| // TODO: | |||||
| // 1. check field ( directly modify col def / check here ) | |||||
| // 2. set error change tab index | |||||
| console.log(test) | |||||
| console.log(qcCheck) | |||||
| // return | // return | ||||
| // do api | // do api | ||||
| var response = await saveItem(data); | |||||
| if (response) { | |||||
| if (!Boolean(response.id)) { | |||||
| formProps.setError(response.errorPosition!! as keyof CreateItemInputs, { | |||||
| message: response.message!!, | |||||
| console.log("asdad") | |||||
| var responseI = await saveItem(data); | |||||
| console.log("asdad") | |||||
| var responseQ = await saveItemQcChecks(qcCheck) | |||||
| if (responseI && responseQ) { | |||||
| if (!Boolean(responseI.id)) { | |||||
| formProps.setError(responseI.errorPosition!! as keyof CreateItemInputs, { | |||||
| message: responseI.message!!, | |||||
| type: "required", | type: "required", | ||||
| }); | }); | ||||
| } else if (Boolean(response.id)) { | |||||
| } else if (!Boolean(responseQ.id)) { | |||||
| } else if (Boolean(responseI.id) && Boolean(responseQ.id)) { | |||||
| router.replace(redirPath); | router.replace(redirPath); | ||||
| } | } | ||||
| } | } | ||||
| @@ -157,7 +143,7 @@ const CreateItem: React.FC<Props> = ({ | |||||
| console.log(e); | console.log(e); | ||||
| } | } | ||||
| }, | }, | ||||
| [router, t] | |||||
| [apiRef, router, t] | |||||
| ); | ); | ||||
| // multiple tabs | // multiple tabs | ||||
| @@ -174,19 +160,24 @@ const CreateItem: React.FC<Props> = ({ | |||||
| component="form" | component="form" | ||||
| onSubmit={formProps.handleSubmit(onSubmit, onSubmitError)} | onSubmit={formProps.handleSubmit(onSubmit, onSubmitError)} | ||||
| > | > | ||||
| <Grid> | |||||
| <Typography mb={2} variant="h4"> | |||||
| {t(`${mode} ${title}`)} | |||||
| </Typography> | |||||
| </Grid> | |||||
| <Grid> | |||||
| <Typography mb={2} variant="h4"> | |||||
| {t(`${mode} ${title}`)} | |||||
| </Typography> | |||||
| </Grid> | |||||
| <Tabs value={tabIndex} onChange={handleTabChange} variant="scrollable"> | |||||
| <Tab label={t("Product / Material Details")} iconPosition="end"/> | |||||
| <Tab label={t("Qc items")} iconPosition="end" /> | |||||
| </Tabs> | |||||
| {serverError && ( | {serverError && ( | ||||
| <Typography variant="body2" color="error" alignSelf="flex-end"> | <Typography variant="body2" color="error" alignSelf="flex-end"> | ||||
| {serverError} | {serverError} | ||||
| </Typography> | </Typography> | ||||
| )} | )} | ||||
| {type === TypeEnum.MATERIAL && <MaterialDetails />} | |||||
| {type === TypeEnum.PRODUCT && <ProductDetails />} | |||||
| {type === TypeEnum.BYPRODUCT && <ByProductDetails />} | |||||
| {tabIndex === 0 && <ProductDetails />} | |||||
| {tabIndex === 1 && <QcDetails apiRef={apiRef} />} | |||||
| {/* {type === TypeEnum.MATERIAL && <MaterialDetails />} */} | |||||
| {/* {type === TypeEnum.BYPRODUCT && <ByProductDetails />} */} | |||||
| <Stack direction="row" justifyContent="flex-end" gap={1}> | <Stack direction="row" justifyContent="flex-end" gap={1}> | ||||
| <Button | <Button | ||||
| name="submit" | name="submit" | ||||
| @@ -4,47 +4,48 @@ import CreateItemLoading from "./CreateItemLoading"; | |||||
| import { CreateItemInputs } from "@/app/api/settings/item/actions"; | import { CreateItemInputs } from "@/app/api/settings/item/actions"; | ||||
| import { notFound } from "next/navigation"; | import { notFound } from "next/navigation"; | ||||
| import { fetchItem } from "@/app/api/settings/item"; | import { fetchItem } from "@/app/api/settings/item"; | ||||
| import { fetchQcItems } from "@/app/api/settings/qcItem"; | |||||
| interface SubComponents { | interface SubComponents { | ||||
| Loading: typeof CreateItemLoading; | Loading: typeof CreateItemLoading; | ||||
| } | } | ||||
| type Props = { | type Props = { | ||||
| id?: number | id?: number | ||||
| type: TypeEnum; | |||||
| // type: TypeEnum; | |||||
| }; | }; | ||||
| const CreateItemWrapper: React.FC<Props> & | const CreateItemWrapper: React.FC<Props> & | ||||
| SubComponents = async ({ type, id }) => { | |||||
| SubComponents = async ({ id }) => { | |||||
| var result | var result | ||||
| var defaultValues: Partial<CreateItemInputs> | undefined | |||||
| console.log(type) | |||||
| var defaultValues: Partial<CreateItemInputs> | undefined | |||||
| // console.log(type) | |||||
| var qcChecks | |||||
| if (id) { | if (id) { | ||||
| result = await fetchItem(id); | result = await fetchItem(id); | ||||
| console.log(result) | |||||
| const item = result.item | |||||
| qcChecks = result.qcChecks | |||||
| const activeRows = qcChecks.filter(it => it.isActive).map(i => i.id) | |||||
| console.log(qcChecks) | |||||
| defaultValues = { | defaultValues = { | ||||
| typeId: result?.type.id, | |||||
| id: result?.id, | |||||
| code: result?.code, | |||||
| name: result?.name, | |||||
| description: result?.description, | |||||
| 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 | |||||
| type: item?.type, | |||||
| id: item?.id, | |||||
| code: item?.code, | |||||
| name: item?.name, | |||||
| description: item?.description, | |||||
| remarks: item?.remarks, | |||||
| shelfLife: item?.shelfLife, | |||||
| countryOfOrigin: item?.countryOfOrigin, | |||||
| maxQty: item?.maxQty, | |||||
| qcChecks: qcChecks, | |||||
| qcChecks_active: activeRows | |||||
| }; | }; | ||||
| } | } | ||||
| return ( | return ( | ||||
| <CreateItem | <CreateItem | ||||
| isEditMode={Boolean(id)} | isEditMode={Boolean(id)} | ||||
| type={type} | |||||
| defaultValues={defaultValues} | defaultValues={defaultValues} | ||||
| qcChecks={qcChecks || []} | |||||
| /> | /> | ||||
| ); | ); | ||||
| }; | }; | ||||
| @@ -1,325 +0,0 @@ | |||||
| "use client"; | |||||
| import { CreateItemInputs } from "@/app/api/settings/item/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 CreateItemInputs]?: string; | |||||
| } | |||||
| | undefined; | |||||
| const MaterialDetails: React.FC<Props> = ({}) => { | |||||
| const { | |||||
| t, | |||||
| i18n: { language }, | |||||
| } = useTranslation(); | |||||
| const { | |||||
| register, | |||||
| formState: { errors, defaultValues, touchedFields }, | |||||
| watch, | |||||
| control, | |||||
| setValue, | |||||
| getValues, | |||||
| reset, | |||||
| resetField, | |||||
| setError, | |||||
| clearErrors, | |||||
| } = useFormContext<CreateItemInputs>(); | |||||
| // textfield error msg locale problem | |||||
| console.log(defaultValues) | |||||
| const typeColumns = useMemo<GridColDef[]>( | |||||
| () => [ | |||||
| { | |||||
| field: "type", | |||||
| headerName: "type", | |||||
| flex: 1, | |||||
| editable: true, | |||||
| }, | |||||
| ], | |||||
| [] | |||||
| ); | |||||
| const weightUnitColumns = useMemo<GridColDef[]>( | |||||
| () => [ | |||||
| { | |||||
| 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<GridColDef[]>( | |||||
| () => [ | |||||
| { | |||||
| field: "uom", | |||||
| headerName: "uom", | |||||
| flex: 1, | |||||
| editable: true, | |||||
| }, | |||||
| ], | |||||
| [] | |||||
| ); | |||||
| const validationTest = useCallback( | |||||
| ( | |||||
| newRow: GridRowModel<TableRow<Partial<CreateItemInputs>, EntryError>> | |||||
| ): EntryError => { | |||||
| const error: EntryError = {}; | |||||
| console.log(newRow); | |||||
| return Object.keys(error).length > 0 ? error : undefined; | |||||
| }, | |||||
| [] | |||||
| ); | |||||
| return ( | |||||
| <Card sx={{ display: "block" }}> | |||||
| <CardContent component={Stack} spacing={4}> | |||||
| <Box> | |||||
| <Stack direction="row" justifyContent="space-between" gap={1}> | |||||
| <Typography variant="overline" display="block" marginBlockEnd={1}> | |||||
| {t("Material Details")} | |||||
| </Typography> | |||||
| <FormControlLabel | |||||
| control={ | |||||
| <Controller | |||||
| name={"typeId"} | |||||
| control={control} | |||||
| render={({ field: props }) => { | |||||
| console.log(props) | |||||
| return ( | |||||
| <Checkbox | |||||
| {...props} | |||||
| checked={props.value === TypeEnum.CONSUMABLE_ID} | |||||
| onChange={(e) => { | |||||
| const newValue = e.target.checked ? TypeEnum.CONSUMABLE_ID : TypeEnum.MATERIAL_ID; | |||||
| props.onChange(newValue); | |||||
| }} | |||||
| /> | |||||
| )}} | |||||
| /> | |||||
| } | |||||
| label={ | |||||
| <Typography | |||||
| variant="overline" | |||||
| display="block" | |||||
| marginBlockEnd={1} | |||||
| > | |||||
| {t("Consumables")} | |||||
| </Typography> | |||||
| } | |||||
| /> | |||||
| </Stack> | |||||
| <Grid container spacing={2} columns={{ xs: 6, sm: 12 }}> | |||||
| <Grid item xs={6}> | |||||
| <TextField | |||||
| label={t("Name")} | |||||
| fullWidth | |||||
| {...register("name", { | |||||
| required: "name required!", | |||||
| })} | |||||
| error={Boolean(errors.name)} | |||||
| helperText={errors.name?.message} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={6}> | |||||
| <TextField | |||||
| label={t("Code")} | |||||
| fullWidth | |||||
| {...register("code", { | |||||
| required: "code required!", | |||||
| })} | |||||
| error={Boolean(errors.code)} | |||||
| helperText={errors.code?.message} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={6}> | |||||
| <TextField | |||||
| label={t("description")} | |||||
| fullWidth | |||||
| {...register("description")} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={6}> | |||||
| <TextField | |||||
| label={t("shelfLife")} | |||||
| type="number" | |||||
| fullWidth | |||||
| {...register("shelfLife", { | |||||
| valueAsNumber: true, | |||||
| required: "shelfLife required!", | |||||
| })} | |||||
| error={Boolean(errors.shelfLife)} | |||||
| helperText={errors.shelfLife?.message} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={6}> | |||||
| <TextField | |||||
| label={t("countryOfOrigin")} | |||||
| fullWidth | |||||
| {...register("countryOfOrigin", { | |||||
| required: "countryOfOrigin required!", | |||||
| })} | |||||
| error={Boolean(errors.countryOfOrigin)} | |||||
| helperText={errors.countryOfOrigin?.message} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={6}> | |||||
| <TextField | |||||
| label={t("minHumid")} | |||||
| type="number" | |||||
| fullWidth | |||||
| inputProps={NumberInputProps} | |||||
| {...register("minHumid", { | |||||
| valueAsNumber: true, | |||||
| // min: 0, | |||||
| // max: watch("maxHumid") || 100, | |||||
| required: "minHumid required!", | |||||
| })} | |||||
| error={Boolean(errors.minHumid)} | |||||
| helperText={errors.minHumid?.message} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={6}> | |||||
| <TextField | |||||
| label={t("maxHumid")} | |||||
| type="number" | |||||
| fullWidth | |||||
| inputProps={NumberInputProps} | |||||
| {...register("maxHumid", { | |||||
| valueAsNumber: true, | |||||
| })} | |||||
| error={Boolean(errors.maxHumid)} | |||||
| helperText={errors.maxHumid?.message} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={6}> | |||||
| <TextField | |||||
| label={t("minTemp")} | |||||
| type="number" | |||||
| fullWidth | |||||
| inputProps={NumberInputProps} | |||||
| {...register("minTemp", { | |||||
| valueAsNumber: true, | |||||
| required: "minTemp required!", | |||||
| })} | |||||
| error={Boolean(errors.minTemp)} | |||||
| helperText={errors.minTemp?.message} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={6}> | |||||
| <TextField | |||||
| label={t("maxTemp")} | |||||
| type="number" | |||||
| fullWidth | |||||
| inputProps={NumberInputProps} | |||||
| {...register("maxTemp", { | |||||
| valueAsNumber: true, | |||||
| // min: watch("minTemp"), | |||||
| required: "maxTemp required!", | |||||
| })} | |||||
| error={Boolean(errors.maxTemp)} | |||||
| helperText={errors.maxTemp?.message} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={6}> | |||||
| <TextField | |||||
| label={t("sampleRate")} | |||||
| type="number" | |||||
| fullWidth | |||||
| inputProps={NumberInputProps} | |||||
| {...register("sampleRate", { | |||||
| valueAsNumber: true, | |||||
| min: 0, | |||||
| max: 100, | |||||
| required: "sampleRate required!", | |||||
| })} | |||||
| error={Boolean(errors.sampleRate)} | |||||
| helperText={errors.sampleRate?.message} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={6}> | |||||
| <TextField | |||||
| label={t("passingRate")} | |||||
| type="number" | |||||
| fullWidth | |||||
| inputProps={NumberInputProps} | |||||
| {...register("passingRate", { | |||||
| valueAsNumber: true, | |||||
| min: 0, | |||||
| max: 100, | |||||
| required: "passingRate required!", | |||||
| })} | |||||
| error={Boolean(errors.passingRate)} // change backend for null or not null | |||||
| helperText={errors.passingRate?.message} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={6} /> | |||||
| <Grid item xs={6}> | |||||
| <TextField | |||||
| label={t("remarks")} | |||||
| fullWidth | |||||
| {...register("remarks", { | |||||
| // required: "remarks required!", | |||||
| })} | |||||
| error={Boolean(errors.remarks)} | |||||
| helperText={errors.remarks?.message} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={6}> | |||||
| <TextField | |||||
| label={t("netWeight")} | |||||
| type="number" | |||||
| fullWidth | |||||
| inputProps={NumberInputProps} | |||||
| {...register("netWeight", { | |||||
| valueAsNumber: true, | |||||
| min: 0, | |||||
| required: "netWeight required!", | |||||
| })} | |||||
| error={Boolean(errors.netWeight)} | |||||
| helperText={errors.netWeight?.message} | |||||
| /> | |||||
| </Grid> | |||||
| </Grid> | |||||
| </Box> | |||||
| </CardContent> | |||||
| </Card> | |||||
| ); | |||||
| }; | |||||
| export default MaterialDetails; | |||||
| @@ -19,19 +19,11 @@ import { NumberInputProps } from "./NumberInputProps"; | |||||
| import { CreateItemInputs } from "@/app/api/settings/item/actions"; | import { CreateItemInputs } from "@/app/api/settings/item/actions"; | ||||
| type Props = { | type Props = { | ||||
| // isEditMode: boolean; | |||||
| // type: TypeEnum; | |||||
| // isEditMode: boolean; | |||||
| // type: TypeEnum; | |||||
| }; | }; | ||||
| export type EntryError = | |||||
| | { | |||||
| [field in keyof CreateItemInputs]?: string; | |||||
| } | |||||
| | undefined; | |||||
| const ProductDetails: React.FC<Props> = ({ | |||||
| }) => { | |||||
| const ProductDetails: React.FC<Props> = ({}) => { | |||||
| const { | const { | ||||
| t, | t, | ||||
| i18n: { language }, | i18n: { language }, | ||||
| @@ -49,57 +41,57 @@ const ProductDetails: React.FC<Props> = ({ | |||||
| setError, | setError, | ||||
| clearErrors, | clearErrors, | ||||
| } = useFormContext<CreateItemInputs>(); | } = useFormContext<CreateItemInputs>(); | ||||
| const typeColumns = useMemo<GridColDef[]>( | |||||
| () => [ | |||||
| { | |||||
| field: "type", | |||||
| headerName: "type", | |||||
| flex: 1, | |||||
| editable: true, | |||||
| }, | |||||
| ], | |||||
| [] | |||||
| ); | |||||
| const weightUnitColumns = useMemo<GridColDef[]>( | |||||
| () => [ | |||||
| { | |||||
| 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<GridColDef[]>( | |||||
| () => [ | |||||
| { | |||||
| field: "uom", | |||||
| headerName: "uom", | |||||
| flex: 1, | |||||
| editable: true, | |||||
| }, | |||||
| ], | |||||
| [] | |||||
| ); | |||||
| // const typeColumns = useMemo<GridColDef[]>( | |||||
| // () => [ | |||||
| // { | |||||
| // field: "type", | |||||
| // headerName: "type", | |||||
| // flex: 1, | |||||
| // editable: true, | |||||
| // }, | |||||
| // ], | |||||
| // [] | |||||
| // ); | |||||
| // const weightUnitColumns = useMemo<GridColDef[]>( | |||||
| // () => [ | |||||
| // { | |||||
| // 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<GridColDef[]>( | |||||
| // () => [ | |||||
| // { | |||||
| // field: "uom", | |||||
| // headerName: "uom", | |||||
| // flex: 1, | |||||
| // editable: true, | |||||
| // }, | |||||
| // ], | |||||
| // [] | |||||
| // ); | |||||
| const validationTest = useCallback( | |||||
| ( | |||||
| newRow: GridRowModel<TableRow<Partial<CreateItemInputs>, EntryError>> | |||||
| ): EntryError => { | |||||
| const error: EntryError = {}; | |||||
| console.log(newRow); | |||||
| return Object.keys(error).length > 0 ? error : undefined; | |||||
| }, | |||||
| [] | |||||
| ); | |||||
| // const validationTest = useCallback( | |||||
| // ( | |||||
| // newRow: GridRowModel<TableRow<Partial<CreateItemInputs>, EntryError>> | |||||
| // ): EntryError => { | |||||
| // const error: EntryError = {}; | |||||
| // console.log(newRow); | |||||
| // return Object.keys(error).length > 0 ? error : undefined; | |||||
| // }, | |||||
| // [] | |||||
| // ); | |||||
| return ( | return ( | ||||
| <Card sx={{ display: "block" }}> | <Card sx={{ display: "block" }}> | ||||
| @@ -131,6 +123,17 @@ const ProductDetails: React.FC<Props> = ({ | |||||
| helperText={errors.code?.message} | helperText={errors.code?.message} | ||||
| /> | /> | ||||
| </Grid> | </Grid> | ||||
| <Grid item xs={6}> | |||||
| <TextField | |||||
| label={t("Type")} | |||||
| fullWidth | |||||
| {...register("type", { | |||||
| required: "type required!", | |||||
| })} | |||||
| error={Boolean(errors.type)} | |||||
| helperText={errors.type?.message} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={6}> | <Grid item xs={6}> | ||||
| <TextField | <TextField | ||||
| label={t("description")} | label={t("description")} | ||||
| @@ -162,93 +165,6 @@ const ProductDetails: React.FC<Props> = ({ | |||||
| helperText={errors.countryOfOrigin?.message} | helperText={errors.countryOfOrigin?.message} | ||||
| /> | /> | ||||
| </Grid> | </Grid> | ||||
| <Grid item xs={6}> | |||||
| <TextField | |||||
| label={t("minHumid")} | |||||
| type="number" | |||||
| fullWidth | |||||
| inputProps={NumberInputProps} | |||||
| {...register("minHumid", { | |||||
| valueAsNumber: true, | |||||
| // min: 0, | |||||
| // max: watch("maxHumid") || 100, | |||||
| required: "minHumid required!", | |||||
| })} | |||||
| error={Boolean(errors.minHumid)} | |||||
| helperText={errors.minHumid?.message} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={6}> | |||||
| <TextField | |||||
| label={t("maxHumid")} | |||||
| type="number" | |||||
| fullWidth | |||||
| inputProps={NumberInputProps} | |||||
| {...register("maxHumid", { | |||||
| valueAsNumber: true, | |||||
| })} | |||||
| error={Boolean(errors.maxHumid)} | |||||
| helperText={errors.maxHumid?.message} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={6}> | |||||
| <TextField | |||||
| label={t("minTemp")} | |||||
| type="number" | |||||
| fullWidth | |||||
| inputProps={NumberInputProps} | |||||
| {...register("minTemp", { | |||||
| valueAsNumber: true, | |||||
| required: "minTemp required!", | |||||
| })} | |||||
| error={Boolean(errors.minTemp)} | |||||
| helperText={errors.minTemp?.message} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={6}> | |||||
| <TextField | |||||
| label={t("maxTemp")} | |||||
| type="number" | |||||
| fullWidth | |||||
| inputProps={NumberInputProps} | |||||
| {...register("maxTemp", { | |||||
| valueAsNumber: true, | |||||
| // min: watch("minTemp"), | |||||
| required: "maxTemp required!", | |||||
| })} | |||||
| error={Boolean(errors.maxTemp)} | |||||
| helperText={errors.maxTemp?.message} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={6}> | |||||
| <TextField | |||||
| label={t("sampleRate")} | |||||
| type="number" | |||||
| fullWidth | |||||
| inputProps={NumberInputProps} | |||||
| {...register("sampleRate", { | |||||
| valueAsNumber: true, | |||||
| required: "sampleRate required!", | |||||
| })} | |||||
| error={Boolean(errors.sampleRate)} | |||||
| helperText={errors.sampleRate?.message} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={6}> | |||||
| <TextField | |||||
| label={t("passingRate")} | |||||
| type="number" | |||||
| fullWidth | |||||
| inputProps={NumberInputProps} | |||||
| {...register("passingRate", { | |||||
| valueAsNumber: true, | |||||
| required: "passingRate required!", | |||||
| })} | |||||
| error={Boolean(errors.passingRate)} // change backend for null or not null | |||||
| helperText={errors.passingRate?.message} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={6} /> | |||||
| <Grid item xs={6}> | <Grid item xs={6}> | ||||
| <TextField | <TextField | ||||
| label={t("remarks")} | label={t("remarks")} | ||||
| @@ -262,17 +178,17 @@ const ProductDetails: React.FC<Props> = ({ | |||||
| </Grid> | </Grid> | ||||
| <Grid item xs={6}> | <Grid item xs={6}> | ||||
| <TextField | <TextField | ||||
| label={t("netWeight")} | |||||
| label={t("maxQty")} | |||||
| type="number" | type="number" | ||||
| fullWidth | fullWidth | ||||
| inputProps={NumberInputProps} | inputProps={NumberInputProps} | ||||
| {...register("netWeight", { | |||||
| {...register("maxQty", { | |||||
| valueAsNumber: true, | valueAsNumber: true, | ||||
| min: 0, | min: 0, | ||||
| required: "netWeight required!", | |||||
| required: "maxQty required!", | |||||
| })} | })} | ||||
| error={Boolean(errors.netWeight)} | |||||
| helperText={errors.netWeight?.message} | |||||
| error={Boolean(errors.maxQty)} | |||||
| helperText={errors.maxQty?.message} | |||||
| /> | /> | ||||
| </Grid> | </Grid> | ||||
| {/* <Grid item xs={6}> | {/* <Grid item xs={6}> | ||||
| @@ -296,7 +212,7 @@ const ProductDetails: React.FC<Props> = ({ | |||||
| validateRow={validationTest} | validateRow={validationTest} | ||||
| /> | /> | ||||
| </Grid>*/} | </Grid>*/} | ||||
| </Grid> | |||||
| </Grid> | |||||
| </Box> | </Box> | ||||
| </CardContent> | </CardContent> | ||||
| </Card> | </Card> | ||||
| @@ -0,0 +1,163 @@ | |||||
| "use client"; | |||||
| import { CreateItemInputs } from "@/app/api/settings/item/actions"; | |||||
| import { | |||||
| GridColDef, | |||||
| GridRowModel, | |||||
| GridRenderEditCellParams, | |||||
| GridEditInputCell, | |||||
| GridRowSelectionModel, | |||||
| useGridApiRef, | |||||
| } from "@mui/x-data-grid"; | |||||
| import { MutableRefObject, useCallback, useMemo } from "react"; | |||||
| import { useFormContext } from "react-hook-form"; | |||||
| import { useTranslation } from "react-i18next"; | |||||
| import InputDataGrid, { TableRow } from "../InputDataGrid/InputDataGrid"; | |||||
| import { Box, Grid, Tooltip } from "@mui/material"; | |||||
| import { ItemQc } from "@/app/api/settings/item"; | |||||
| import { QcChecksInputs } from "@/app/api/settings/qcCheck/actions"; | |||||
| import { GridApiCommunity } from "@mui/x-data-grid/internals"; | |||||
| import { RiceBowl } from "@mui/icons-material"; | |||||
| type Props = { | |||||
| apiRef: MutableRefObject<GridApiCommunity> | |||||
| }; | |||||
| type EntryError = | |||||
| | { | |||||
| [field in keyof QcChecksInputs]?: string; | |||||
| } | |||||
| | undefined; | |||||
| type QcRow = TableRow<Partial<QcChecksInputs>, EntryError> | |||||
| const QcDetails: React.FC<Props> = ({ apiRef }) => { | |||||
| const { | |||||
| t, | |||||
| i18n: { language }, | |||||
| } = useTranslation(); | |||||
| const { | |||||
| register, | |||||
| formState: { errors, defaultValues, touchedFields }, | |||||
| watch, | |||||
| control, | |||||
| setValue, | |||||
| getValues, | |||||
| reset, | |||||
| resetField, | |||||
| setError, | |||||
| clearErrors, | |||||
| } = useFormContext<CreateItemInputs>(); | |||||
| // const apiRef = useGridApiRef(); | |||||
| const qcColumns = useMemo<GridColDef[]>( | |||||
| () => [ | |||||
| { | |||||
| field: "name", | |||||
| headerName: "name", | |||||
| flex: 1, | |||||
| // editable: true, | |||||
| }, | |||||
| { | |||||
| field: "code", | |||||
| headerName: "code", | |||||
| flex: 1, | |||||
| // editable: true, | |||||
| }, | |||||
| { | |||||
| field: "description", | |||||
| headerName: "Description", | |||||
| flex: 1, | |||||
| // editable: true, | |||||
| }, | |||||
| { | |||||
| field: "instruction", | |||||
| headerName: "Instruction", | |||||
| flex: 1, | |||||
| editable: true, | |||||
| }, | |||||
| { | |||||
| field: "lowerLimit", | |||||
| headerName: "lowerLimit", | |||||
| flex: 1, | |||||
| editable: true, | |||||
| type: "number", | |||||
| renderEditCell(params: GridRenderEditCellParams<QcRow>) { | |||||
| const errorMessage = params.row._error?.[params.field as keyof QcChecksInputs]; | |||||
| const content = ( | |||||
| <GridEditInputCell | |||||
| {...params} | |||||
| // inputProps={{ min: 0 }} | |||||
| /> | |||||
| ); | |||||
| return errorMessage ? ( | |||||
| <Tooltip title={t(errorMessage)}> | |||||
| <Box width="100%">{content}</Box> | |||||
| </Tooltip> | |||||
| ) : ( | |||||
| content | |||||
| ); | |||||
| }, | |||||
| }, | |||||
| { | |||||
| field: "upperLimit", | |||||
| headerName: "upperLimit", | |||||
| flex: 1, | |||||
| editable: true, | |||||
| type: "number", | |||||
| renderEditCell(params: GridRenderEditCellParams<QcRow>) { | |||||
| const errorMessage = params.row._error?.[params.field as keyof QcChecksInputs]; | |||||
| const content = ( | |||||
| <GridEditInputCell | |||||
| {...params} | |||||
| // inputProps={{ min: 0 }} | |||||
| /> | |||||
| ); | |||||
| return errorMessage ? ( | |||||
| <Tooltip title={t(errorMessage)}> | |||||
| <Box width="100%">{content}</Box> | |||||
| </Tooltip> | |||||
| ) : ( | |||||
| content | |||||
| ); | |||||
| }, | |||||
| }, | |||||
| ], | |||||
| [] | |||||
| ); | |||||
| const validationTest = useCallback( | |||||
| ( | |||||
| newRow: GridRowModel<TableRow<Partial<QcChecksInputs>, EntryError>>, | |||||
| // rowModel: GridRowSelectionModel | |||||
| ): EntryError => { | |||||
| const error: EntryError = {}; | |||||
| console.log(newRow); | |||||
| if (!newRow.lowerLimit) { | |||||
| error["lowerLimit"] = "lower limit cannot be null" | |||||
| } | |||||
| // if (rowModel.find(r => r == newRow.id)) { | |||||
| // } | |||||
| if (newRow.lowerLimit && newRow.upperLimit && newRow.lowerLimit > newRow.upperLimit) { | |||||
| error["lowerLimit"] = "lower limit should not be greater than upper limit" | |||||
| error["upperLimit"] = "lower limit should not be greater than upper limit" | |||||
| } | |||||
| return Object.keys(error).length > 0 ? error : undefined; | |||||
| }, | |||||
| [] | |||||
| ); | |||||
| return ( | |||||
| <Grid container> | |||||
| <Grid item xs={12}> | |||||
| <InputDataGrid<CreateItemInputs, QcChecksInputs, EntryError> | |||||
| apiRef={apiRef} | |||||
| checkboxSelection={true} | |||||
| _formKey={"qcChecks"} | |||||
| columns={qcColumns} | |||||
| validateRow={validationTest} | |||||
| /> | |||||
| </Grid> | |||||
| </Grid> | |||||
| ); | |||||
| }; | |||||
| export default QcDetails; | |||||
| @@ -1,6 +1,7 @@ | |||||
| "use client" | "use client" | ||||
| import { | import { | ||||
| Dispatch, | Dispatch, | ||||
| MutableRefObject, | |||||
| SetStateAction, | SetStateAction, | ||||
| useCallback, | useCallback, | ||||
| useEffect, | useEffect, | ||||
| @@ -20,6 +21,7 @@ import { | |||||
| GridRowModel, | GridRowModel, | ||||
| GridRowModes, | GridRowModes, | ||||
| GridRowModesModel, | GridRowModesModel, | ||||
| GridRowSelectionModel, | |||||
| GridToolbarContainer, | GridToolbarContainer, | ||||
| GridValidRowModel, | GridValidRowModel, | ||||
| useGridApiRef, | useGridApiRef, | ||||
| @@ -31,6 +33,7 @@ import CancelIcon from "@mui/icons-material/Cancel"; | |||||
| import { Add } from "@mui/icons-material"; | import { Add } from "@mui/icons-material"; | ||||
| import { Box, Button, Typography } from "@mui/material"; | import { Box, Button, Typography } from "@mui/material"; | ||||
| import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
| import { GridApiCommunity } from "@mui/x-data-grid/internals"; | |||||
| interface ResultWithId { | interface ResultWithId { | ||||
| id: string | number; | id: string | number; | ||||
| @@ -38,19 +41,44 @@ interface ResultWithId { | |||||
| // export type InputGridProps = { | // export type InputGridProps = { | ||||
| // [key: string]: any | // [key: string]: any | ||||
| // } | // } | ||||
| export type TableRow<T, E> = Partial< | |||||
| T & { | |||||
| interface DefaultResult<E> { | |||||
| _isNew: boolean; | |||||
| _error: E; | |||||
| } | |||||
| interface SelectionResult<E> { | |||||
| active: boolean; | |||||
| _isNew: boolean; | |||||
| _error: E; | |||||
| } | |||||
| type Result<E> = DefaultResult<E> | SelectionResult<E> | |||||
| export type TableRow<V, E> = Partial< | |||||
| V & { | |||||
| isActive: boolean | undefined; | |||||
| _isNew: boolean; | _isNew: boolean; | ||||
| _error: E; | _error: E; | ||||
| } & ResultWithId | } & ResultWithId | ||||
| >; | >; | ||||
| export type InputDataGridProps<T, E> = { | |||||
| export interface InputDataGridProps<T, V, E> { | |||||
| // needAdd: boolean | undefined; | |||||
| apiRef: MutableRefObject<GridApiCommunity> | |||||
| checkboxSelection: false | undefined; | |||||
| _formKey: keyof T; | _formKey: keyof T; | ||||
| columns: GridColDef[]; | columns: GridColDef[]; | ||||
| validateRow: (newRow: GridRowModel<TableRow<T, E>>) => E; | |||||
| validateRow: (newRow: GridRowModel<TableRow<V, E>>) => E; | |||||
| }; | }; | ||||
| export interface SelectionInputDataGridProps<T, V, E> { // thinking how do | |||||
| apiRef: MutableRefObject<GridApiCommunity> | |||||
| checkboxSelection: true; | |||||
| _formKey: keyof T; | |||||
| columns: GridColDef[]; | |||||
| validateRow: (newRow: GridRowModel<TableRow<V, E>>) => E; | |||||
| } | |||||
| export type Props<T, V, E> = InputDataGridProps<T, V, E> | SelectionInputDataGridProps<T, V, E> | |||||
| export class ProcessRowUpdateError<T, E> extends Error { | export class ProcessRowUpdateError<T, E> extends Error { | ||||
| public readonly row: T; | public readonly row: T; | ||||
| public readonly errors: E | undefined; | public readonly errors: E | undefined; | ||||
| @@ -62,12 +90,16 @@ export class ProcessRowUpdateError<T, E> extends Error { | |||||
| Object.setPrototypeOf(this, ProcessRowUpdateError.prototype); | Object.setPrototypeOf(this, ProcessRowUpdateError.prototype); | ||||
| } | } | ||||
| } | } | ||||
| function InputDataGrid<T, E>({ | |||||
| // T == CreatexxxInputs | |||||
| // V == target field input inside CreatexxxInputs, e.g. qcChecks: ItemQc[], V = ItemQc | |||||
| // E == error | |||||
| function InputDataGrid<T, V, E>({ | |||||
| apiRef, | |||||
| checkboxSelection, | |||||
| _formKey, | _formKey, | ||||
| columns, | columns, | ||||
| validateRow, | validateRow, | ||||
| }: InputDataGridProps<T, E>) { | |||||
| }: Props<T, V, E>) { | |||||
| const { | const { | ||||
| t, | t, | ||||
| // i18n: { language }, | // i18n: { language }, | ||||
| @@ -76,17 +108,24 @@ function InputDataGrid<T, E>({ | |||||
| const { setValue, getValues } = useFormContext(); | const { setValue, getValues } = useFormContext(); | ||||
| const [rowModesModel, setRowModesModel] = | const [rowModesModel, setRowModesModel] = | ||||
| useState<GridRowModesModel>({}); | useState<GridRowModesModel>({}); | ||||
| const apiRef = useGridApiRef(); | |||||
| const getRowId = useCallback<GridRowIdGetter<TableRow<T, E>>>( | |||||
| // const apiRef = useGridApiRef(); | |||||
| const getRowId = useCallback<GridRowIdGetter<TableRow<V, E>>>( | |||||
| (row) => row.id!! as number, | (row) => row.id!! as number, | ||||
| [] | [] | ||||
| ); | ); | ||||
| const list: TableRow<T, E>[] = getValues(formKey); | |||||
| const [rows, setRows] = useState<TableRow<T, E>[]>(() => { | |||||
| const list: TableRow<T, E>[] = getValues(formKey); | |||||
| const list: TableRow<V, E>[] = getValues(formKey); | |||||
| const [rows, setRows] = useState<TableRow<V, E>[]>(() => { | |||||
| const list: TableRow<V, E>[] = getValues(formKey); | |||||
| return list && list.length > 0 ? list : []; | return list && list.length > 0 ? list : []; | ||||
| }); | }); | ||||
| const originalRows = list && list.length > 0 ? list : []; | const originalRows = list && list.length > 0 ? list : []; | ||||
| // const originalRowModel = originalRows.filter((li) => li.isActive).map(i => i.id) as GridRowSelectionModel | |||||
| const [rowSelectionModel, setRowSelectionModel] = useState<GridRowSelectionModel>(() => { | |||||
| // const rowModel = list.filter((li) => li.isActive).map(i => i.id) as GridRowSelectionModel | |||||
| const rowModel: GridRowSelectionModel = getValues(`${formKey}_active`) as GridRowSelectionModel | |||||
| console.log(rowModel) | |||||
| return rowModel | |||||
| }); | |||||
| const handleSave = useCallback( | const handleSave = useCallback( | ||||
| (id: GridRowId) => () => { | (id: GridRowId) => () => { | ||||
| @@ -109,8 +148,8 @@ function InputDataGrid<T, E>({ | |||||
| const processRowUpdate = useCallback( | const processRowUpdate = useCallback( | ||||
| ( | ( | ||||
| newRow: GridRowModel<TableRow<T, E>>, | |||||
| originalRow: GridRowModel<TableRow<T, E>> | |||||
| newRow: GridRowModel<TableRow<V, E>>, | |||||
| originalRow: GridRowModel<TableRow<V, E>> | |||||
| ) => { | ) => { | ||||
| ///////////////// | ///////////////// | ||||
| // validation here | // validation here | ||||
| @@ -127,7 +166,7 @@ function InputDataGrid<T, E>({ | |||||
| const { _isNew, _error, ...updatedRow } = newRow; | const { _isNew, _error, ...updatedRow } = newRow; | ||||
| const rowToSave = { | const rowToSave = { | ||||
| ...updatedRow, | ...updatedRow, | ||||
| } as TableRow<T, E>; /// test | |||||
| } as TableRow<V, E>; /// test | |||||
| console.log(rowToSave) | console.log(rowToSave) | ||||
| setRows((rw) => | setRows((rw) => | ||||
| rw.map((r) => (getRowId(r) === getRowId(originalRow) ? rowToSave : r)) | rw.map((r) => (getRowId(r) === getRowId(originalRow) ? rowToSave : r)) | ||||
| @@ -138,7 +177,7 @@ function InputDataGrid<T, E>({ | |||||
| ); | ); | ||||
| const addRow = useCallback(() => { | const addRow = useCallback(() => { | ||||
| const newEntry = { id: Date.now(), _isNew: true } as TableRow<T, E>; | |||||
| const newEntry = { id: Date.now(), _isNew: true } as TableRow<V, E>; | |||||
| setRows((prev) => [...prev, newEntry]); | setRows((prev) => [...prev, newEntry]); | ||||
| setRowModesModel((model) => ({ | setRowModesModel((model) => ({ | ||||
| ...model, | ...model, | ||||
| @@ -225,7 +264,7 @@ function InputDataGrid<T, E>({ | |||||
| }, | }, | ||||
| }, | }, | ||||
| ], | ], | ||||
| [columns,rowModesModel, handleSave, handleCancel, handleDelete] | |||||
| [columns, rowModesModel, handleSave, handleCancel, handleDelete] | |||||
| ); | ); | ||||
| // sync useForm | // sync useForm | ||||
| useEffect(() => { | useEffect(() => { | ||||
| @@ -266,9 +305,20 @@ function InputDataGrid<T, E>({ | |||||
| <StyledDataGrid | <StyledDataGrid | ||||
| // {...props} | // {...props} | ||||
| // getRowId={getRowId as GridRowIdGetter<GridValidRowModel>} | // getRowId={getRowId as GridRowIdGetter<GridValidRowModel>} | ||||
| // checkbox selection | |||||
| checkboxSelection={checkboxSelection} | |||||
| disableRowSelectionOnClick={checkboxSelection} | |||||
| onRowSelectionModelChange={(newRowSelectionModel) => { | |||||
| if (checkboxSelection) { | |||||
| setRowSelectionModel(newRowSelectionModel); | |||||
| setValue("qcChecks_active", newRowSelectionModel) | |||||
| } | |||||
| }} | |||||
| rowSelectionModel={rowSelectionModel} | |||||
| apiRef={apiRef} | apiRef={apiRef} | ||||
| rows={rows} | rows={rows} | ||||
| columns={_columns} | |||||
| columns={!checkboxSelection ? _columns : columns} | |||||
| editMode="row" | editMode="row" | ||||
| autoHeight | autoHeight | ||||
| sx={{ | sx={{ | ||||
| @@ -295,13 +345,13 @@ function InputDataGrid<T, E>({ | |||||
| } | } | ||||
| return classname; | return classname; | ||||
| }} | }} | ||||
| slots={{ | |||||
| slots={!checkboxSelection ? { | |||||
| footer: FooterToolbar, | footer: FooterToolbar, | ||||
| noRowsOverlay: NoRowsOverlay, | noRowsOverlay: NoRowsOverlay, | ||||
| }} | |||||
| slotProps={{ | |||||
| } : undefined} | |||||
| slotProps={!checkboxSelection ? { | |||||
| footer: { child: footer }, | footer: { child: footer }, | ||||
| }} | |||||
| } : undefined} | |||||
| /> | /> | ||||
| ) | ) | ||||
| } | } | ||||
| @@ -12,28 +12,21 @@ import { TypeEnum } from "@/app/utils/typeEnum"; | |||||
| type Props = { | type Props = { | ||||
| items: ItemsResult[]; | items: ItemsResult[]; | ||||
| type: TypeEnum; | |||||
| }; | }; | ||||
| type SearchQuery = Partial<Omit<ItemsResult, "id">>; | type SearchQuery = Partial<Omit<ItemsResult, "id">>; | ||||
| type SearchParamNames = keyof SearchQuery; | type SearchParamNames = keyof SearchQuery; | ||||
| const ItemsSearch: React.FC<Props> = ({ items, type }) => { | |||||
| const ItemsSearch: React.FC<Props> = ({ items }) => { | |||||
| const [filteredItems, setFilteredItems] = useState<ItemsResult[]>(items); | const [filteredItems, setFilteredItems] = useState<ItemsResult[]>(items); | ||||
| const { t } = useTranslation(type.toString()); | |||||
| const { t } = useTranslation("items"); | |||||
| const router = useRouter(); | const router = useRouter(); | ||||
| const searchCriteria: Criterion<SearchParamNames>[] = useMemo( | const searchCriteria: Criterion<SearchParamNames>[] = useMemo( | ||||
| () => { | () => { | ||||
| var searchCriteria: Criterion<SearchParamNames>[] = [] | |||||
| if (type === TypeEnum.MATERIAL) { | |||||
| searchCriteria = [ | |||||
| { label: t("Code"), paramName: "code", type: "text" }, | |||||
| { label: t("Name"), paramName: "name", type: "text" }, | |||||
| ] | |||||
| } | |||||
| if (type === TypeEnum.MATERIAL) { | |||||
| } | |||||
| var searchCriteria: Criterion<SearchParamNames>[] = [ | |||||
| { label: t("Code"), paramName: "code", type: "text" }, | |||||
| { label: t("Name"), paramName: "name", type: "text" }, | |||||
| ] | |||||
| return searchCriteria | return searchCriteria | ||||
| }, | }, | ||||
| [t, items] | [t, items] | ||||
| @@ -41,9 +34,9 @@ const ItemsSearch: React.FC<Props> = ({ items, type }) => { | |||||
| const onDetailClick = useCallback( | const onDetailClick = useCallback( | ||||
| (item: ItemsResult) => { | (item: ItemsResult) => { | ||||
| router.push(`/settings/${type}/edit?id=${item.id}`); | |||||
| router.push(`/settings/items/edit?id=${item.id}`); | |||||
| }, | }, | ||||
| [type, router] | |||||
| [router] | |||||
| ); | ); | ||||
| const onDeleteClick = useCallback( | const onDeleteClick = useCallback( | ||||
| @@ -88,22 +81,10 @@ const ItemsSearch: React.FC<Props> = ({ items, type }) => { | |||||
| onSearch={(query) => { | onSearch={(query) => { | ||||
| setFilteredItems( | setFilteredItems( | ||||
| items.filter((pm) => { | items.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()) | |||||
| ); | |||||
| } else if (type === TypeEnum.BYPRODUCT) { | |||||
| return ( | |||||
| pm.code.toLowerCase().includes(query.code.toLowerCase()) && | |||||
| pm.name.toLowerCase().includes(query.name.toLowerCase()) | |||||
| ); | |||||
| } | |||||
| return ( | |||||
| pm.code.toLowerCase().includes(query.code.toLowerCase()) && | |||||
| pm.name.toLowerCase().includes(query.name.toLowerCase()) | |||||
| ); | |||||
| }) | }) | ||||
| ); | ); | ||||
| }} | }} | ||||
| @@ -10,15 +10,15 @@ interface SubComponents { | |||||
| } | } | ||||
| type Props = { | type Props = { | ||||
| type: TypeEnum; | |||||
| // type: TypeEnum; | |||||
| }; | }; | ||||
| const ItemsSearchWrapper: React.FC<Props> & SubComponents = async ({ | const ItemsSearchWrapper: React.FC<Props> & SubComponents = async ({ | ||||
| type, | |||||
| // type, | |||||
| }) => { | }) => { | ||||
| console.log(type) | |||||
| // console.log(type) | |||||
| var result = await fetchAllItems() | var result = await fetchAllItems() | ||||
| return <ItemsSearch items={result} type={type} />; | |||||
| return <ItemsSearch items={result} />; | |||||
| }; | }; | ||||
| ItemsSearchWrapper.Loading = ItemsSearchLoading; | ItemsSearchWrapper.Loading = ItemsSearchLoading; | ||||
| @@ -13,7 +13,7 @@ const Logo: React.FC<Props> = ({ width, height }) => { | |||||
| > | > | ||||
| <g | <g | ||||
| id="svgGroup" | id="svgGroup" | ||||
| stroke-linecap="round" | |||||
| strokeLinecap="round" | |||||
| fill-rule="evenodd" | fill-rule="evenodd" | ||||
| font-size="9pt" | font-size="9pt" | ||||
| stroke="#000" | stroke="#000" | ||||
| @@ -181,20 +181,20 @@ const NavigationContent: React.FC = () => { | |||||
| label: "User Group", | label: "User Group", | ||||
| path: "/settings/user", | path: "/settings/user", | ||||
| }, | }, | ||||
| // { | |||||
| // icon: <RequestQuote />, | |||||
| // label: "Material", | |||||
| // path: "/settings/material", | |||||
| // }, | |||||
| // { | |||||
| // icon: <RequestQuote />, | |||||
| // label: "By-product", | |||||
| // path: "/settings/byProduct", | |||||
| // }, | |||||
| { | { | ||||
| icon: <RequestQuote />, | icon: <RequestQuote />, | ||||
| label: "Material", | |||||
| path: "/settings/material", | |||||
| }, | |||||
| { | |||||
| icon: <RequestQuote />, | |||||
| label: "By-product", | |||||
| path: "/settings/byProduct", | |||||
| }, | |||||
| { | |||||
| icon: <RequestQuote />, | |||||
| label: "Product", | |||||
| path: "/settings/product", | |||||
| label: "Items", | |||||
| path: "/settings/items", | |||||
| }, | }, | ||||
| { | { | ||||
| icon: <RequestQuote />, | icon: <RequestQuote />, | ||||