@@ -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 { TypeEnum } from "@/app/utils/typeEnum"; | |||
import CreateProductMaterial from "@/components/CreateItem"; | |||
import CreateItem from "@/components/CreateItem"; | |||
import { I18nProvider, getServerI18n } from "@/i18n"; | |||
import { Typography } from "@mui/material"; | |||
import isString from "lodash/isString"; | |||
@@ -8,13 +8,13 @@ import isString from "lodash/isString"; | |||
type Props = {} & 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 ( | |||
<> | |||
{/* <Typography variant="h4">{t("Create Material")}</Typography> */} | |||
<I18nProvider namespaces={[type]}> | |||
<CreateProductMaterial type={type}/> | |||
<I18nProvider namespaces={["items"]}> | |||
<CreateItem /> | |||
</I18nProvider> | |||
</> | |||
); |
@@ -9,7 +9,7 @@ import { notFound } from "next/navigation"; | |||
type Props = {} & SearchParams; | |||
const productSetting: React.FC<Props> = async ({ searchParams }) => { | |||
const type = TypeEnum.PRODUCT; | |||
const type = "items"; | |||
const { t } = await getServerI18n(type); | |||
const id = isString(searchParams["id"]) | |||
? parseInt(searchParams["id"]) | |||
@@ -21,7 +21,7 @@ const productSetting: React.FC<Props> = async ({ searchParams }) => { | |||
<> | |||
{/* <Typography variant="h4">{t("Create Material")}</Typography> */} | |||
<I18nProvider namespaces={[type]}> | |||
<CreateProductMaterial type={type} id={id} /> | |||
<CreateProductMaterial id={id} /> | |||
</I18nProvider> | |||
</> | |||
); |
@@ -1,5 +1,5 @@ | |||
import { TypeEnum } from "@/app/utils/typeEnum"; | |||
import MaterialSearch from "@/components/ItemsSearch"; | |||
import ItemsSearch from "@/components/ItemsSearch"; | |||
import { getServerI18n } from "@/i18n"; | |||
import Add from "@mui/icons-material/Add"; | |||
import Button from "@mui/material/Button"; | |||
@@ -29,17 +29,17 @@ const productSetting: React.FC = async () => { | |||
<Typography variant="h4" marginInlineEnd={2}> | |||
{t("Product")} | |||
</Typography> | |||
<Button | |||
{/* <Button | |||
variant="contained" | |||
startIcon={<Add />} | |||
LinkComponent={Link} | |||
href="product/create" | |||
> | |||
{t("Create product")} | |||
</Button> | |||
</Button> */} | |||
</Stack> | |||
<Suspense fallback={<MaterialSearch.Loading />}> | |||
<MaterialSearch type={project} /> | |||
<Suspense fallback={<ItemsSearch.Loading />}> | |||
<ItemsSearch /> | |||
</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 { revalidateTag } from "next/cache"; | |||
import { BASE_API_URL } from "@/config/api"; | |||
import { HTMLInputTypeAttribute } from "react"; | |||
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 = { | |||
id?: string | number | |||
code: string; | |||
@@ -24,14 +27,10 @@ export type CreateItemInputs = { | |||
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; | |||
typeId: number; | |||
maxQty: number; | |||
type: string; | |||
qcChecks: QcChecksInputs[] | |||
qcChecks_active: number[] | |||
} | |||
export const saveItem = async (data: CreateItemInputs) => { | |||
@@ -43,4 +42,4 @@ export const saveItem = async (data: CreateItemInputs) => { | |||
}); | |||
revalidateTag("items"); | |||
return item | |||
}; | |||
}; |
@@ -2,7 +2,18 @@ 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"; | |||
// 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 = { | |||
id: string | number | |||
@@ -12,20 +23,15 @@ export type ItemsResult = { | |||
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; | |||
maxQty: number | undefined; | |||
type: string; | |||
qcChecks: ItemQc[] | |||
action?: any | |||
} | |||
export type Result = { | |||
item: ItemsResult | |||
qcChecks: ItemQc[] | |||
} | |||
export const fetchAllItems = cache(async () => { | |||
return serverFetchJson<ItemsResult[]>(`${BASE_API_URL}/items`, { | |||
next: { tags: ["items"] }, | |||
@@ -34,7 +40,7 @@ export const fetchAllItems = cache(async () => { | |||
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"] }, | |||
}); | |||
}); |
@@ -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) { | |||
const response = await serverFetch(...args); | |||
console.log(response.status) | |||
if (response.ok) { | |||
if (response.status === 204) { | |||
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, | |||
} from "react-hook-form"; | |||
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 { TypeEnum } from "@/app/utils/typeEnum"; | |||
import MaterialDetails from "./MaterialDetails"; | |||
import ProductDetails from "./ProductDetails"; | |||
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 = { | |||
isEditMode: boolean; | |||
type: TypeEnum; | |||
// type: TypeEnum; | |||
defaultValues: Partial<CreateItemInputs> | undefined; | |||
qcChecks: ItemQc[] | |||
}; | |||
const CreateItem: React.FC<Props> = ({ | |||
isEditMode, | |||
type, | |||
// type, | |||
defaultValues, | |||
qcChecks | |||
}) => { | |||
console.log(type) | |||
// console.log(type) | |||
const apiRef = useGridApiRef(); | |||
const params = useSearchParams() | |||
console.log(params.get("id")) | |||
const [serverError, setServerError] = useState(""); | |||
const [tabIndex, setTabIndex] = useState(0); | |||
const { t } = useTranslation(); | |||
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 mode = ""; | |||
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"; | |||
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) { | |||
mode = "Edit"; | |||
} else { | |||
mode = "Create"; | |||
} | |||
return [typeId, title, mode, redirPath]; | |||
}, [type, isEditMode]); | |||
console.log(typeId) | |||
return [mode, redirPath]; | |||
}, [isEditMode]); | |||
// console.log(typeId) | |||
const formProps = useForm<CreateItemInputs>({ | |||
defaultValues: defaultValues ? defaultValues : { | |||
typeId: typeId | |||
}, | |||
}); | |||
const errors = formProps.formState.errors; | |||
const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>( | |||
(_e, newValue) => { | |||
setTabIndex(newValue); | |||
}, | |||
[], | |||
); | |||
const handleCancel = () => { | |||
router.replace(`/settings/${type}`); | |||
router.replace(`/settings/product`); | |||
}; | |||
const onSubmit = useCallback<SubmitHandler<CreateItemInputs>>( | |||
const onSubmit = useCallback<SubmitHandler<CreateItemInputs & {}>>( | |||
async (data, event) => { | |||
let hasErrors = false; | |||
console.log("dasasdasd") | |||
console.log(errors) | |||
// console.log(apiRef.current.getCellValue(2, "lowerLimit")) | |||
// apiRef.current. | |||
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) { | |||
setServerError(t("An error has occurred. Please try again later.")); | |||
return false; | |||
} | |||
console.log("data posted"); | |||
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 | |||
// 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", | |||
}); | |||
} else if (Boolean(response.id)) { | |||
} else if (!Boolean(responseQ.id)) { | |||
} else if (Boolean(responseI.id) && Boolean(responseQ.id)) { | |||
router.replace(redirPath); | |||
} | |||
} | |||
@@ -157,7 +143,7 @@ const CreateItem: React.FC<Props> = ({ | |||
console.log(e); | |||
} | |||
}, | |||
[router, t] | |||
[apiRef, router, t] | |||
); | |||
// multiple tabs | |||
@@ -174,19 +160,24 @@ const CreateItem: React.FC<Props> = ({ | |||
component="form" | |||
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 && ( | |||
<Typography variant="body2" color="error" alignSelf="flex-end"> | |||
{serverError} | |||
</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}> | |||
<Button | |||
name="submit" | |||
@@ -4,47 +4,48 @@ import CreateItemLoading from "./CreateItemLoading"; | |||
import { CreateItemInputs } from "@/app/api/settings/item/actions"; | |||
import { notFound } from "next/navigation"; | |||
import { fetchItem } from "@/app/api/settings/item"; | |||
import { fetchQcItems } from "@/app/api/settings/qcItem"; | |||
interface SubComponents { | |||
Loading: typeof CreateItemLoading; | |||
} | |||
type Props = { | |||
id?: number | |||
type: TypeEnum; | |||
// type: TypeEnum; | |||
}; | |||
const CreateItemWrapper: React.FC<Props> & | |||
SubComponents = async ({ type, id }) => { | |||
SubComponents = async ({ id }) => { | |||
var result | |||
var defaultValues: Partial<CreateItemInputs> | undefined | |||
console.log(type) | |||
var defaultValues: Partial<CreateItemInputs> | undefined | |||
// console.log(type) | |||
var qcChecks | |||
if (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 = { | |||
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 ( | |||
<CreateItem | |||
isEditMode={Boolean(id)} | |||
type={type} | |||
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"; | |||
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 { | |||
t, | |||
i18n: { language }, | |||
@@ -49,57 +41,57 @@ const ProductDetails: React.FC<Props> = ({ | |||
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 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 ( | |||
<Card sx={{ display: "block" }}> | |||
@@ -131,6 +123,17 @@ const ProductDetails: React.FC<Props> = ({ | |||
helperText={errors.code?.message} | |||
/> | |||
</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}> | |||
<TextField | |||
label={t("description")} | |||
@@ -162,93 +165,6 @@ const ProductDetails: React.FC<Props> = ({ | |||
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, | |||
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")} | |||
@@ -262,17 +178,17 @@ const ProductDetails: React.FC<Props> = ({ | |||
</Grid> | |||
<Grid item xs={6}> | |||
<TextField | |||
label={t("netWeight")} | |||
label={t("maxQty")} | |||
type="number" | |||
fullWidth | |||
inputProps={NumberInputProps} | |||
{...register("netWeight", { | |||
{...register("maxQty", { | |||
valueAsNumber: true, | |||
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 item xs={6}> | |||
@@ -296,7 +212,7 @@ const ProductDetails: React.FC<Props> = ({ | |||
validateRow={validationTest} | |||
/> | |||
</Grid>*/} | |||
</Grid> | |||
</Grid> | |||
</Box> | |||
</CardContent> | |||
</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" | |||
import { | |||
Dispatch, | |||
MutableRefObject, | |||
SetStateAction, | |||
useCallback, | |||
useEffect, | |||
@@ -20,6 +21,7 @@ import { | |||
GridRowModel, | |||
GridRowModes, | |||
GridRowModesModel, | |||
GridRowSelectionModel, | |||
GridToolbarContainer, | |||
GridValidRowModel, | |||
useGridApiRef, | |||
@@ -31,6 +33,7 @@ import CancelIcon from "@mui/icons-material/Cancel"; | |||
import { Add } from "@mui/icons-material"; | |||
import { Box, Button, Typography } from "@mui/material"; | |||
import { useTranslation } from "react-i18next"; | |||
import { GridApiCommunity } from "@mui/x-data-grid/internals"; | |||
interface ResultWithId { | |||
id: string | number; | |||
@@ -38,19 +41,44 @@ interface ResultWithId { | |||
// export type InputGridProps = { | |||
// [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; | |||
_error: E; | |||
} & ResultWithId | |||
>; | |||
export type InputDataGridProps<T, E> = { | |||
export interface InputDataGridProps<T, V, E> { | |||
// needAdd: boolean | undefined; | |||
apiRef: MutableRefObject<GridApiCommunity> | |||
checkboxSelection: false | undefined; | |||
_formKey: keyof T; | |||
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 { | |||
public readonly row: T; | |||
public readonly errors: E | undefined; | |||
@@ -62,12 +90,16 @@ export class ProcessRowUpdateError<T, E> extends Error { | |||
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, | |||
columns, | |||
validateRow, | |||
}: InputDataGridProps<T, E>) { | |||
}: Props<T, V, E>) { | |||
const { | |||
t, | |||
// i18n: { language }, | |||
@@ -76,17 +108,24 @@ function InputDataGrid<T, E>({ | |||
const { setValue, getValues } = useFormContext(); | |||
const [rowModesModel, setRowModesModel] = | |||
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, | |||
[] | |||
); | |||
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 : []; | |||
}); | |||
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( | |||
(id: GridRowId) => () => { | |||
@@ -109,8 +148,8 @@ function InputDataGrid<T, E>({ | |||
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 | |||
@@ -127,7 +166,7 @@ function InputDataGrid<T, E>({ | |||
const { _isNew, _error, ...updatedRow } = newRow; | |||
const rowToSave = { | |||
...updatedRow, | |||
} as TableRow<T, E>; /// test | |||
} as TableRow<V, E>; /// test | |||
console.log(rowToSave) | |||
setRows((rw) => | |||
rw.map((r) => (getRowId(r) === getRowId(originalRow) ? rowToSave : r)) | |||
@@ -138,7 +177,7 @@ function InputDataGrid<T, E>({ | |||
); | |||
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]); | |||
setRowModesModel((model) => ({ | |||
...model, | |||
@@ -225,7 +264,7 @@ function InputDataGrid<T, E>({ | |||
}, | |||
}, | |||
], | |||
[columns,rowModesModel, handleSave, handleCancel, handleDelete] | |||
[columns, rowModesModel, handleSave, handleCancel, handleDelete] | |||
); | |||
// sync useForm | |||
useEffect(() => { | |||
@@ -266,9 +305,20 @@ function InputDataGrid<T, E>({ | |||
<StyledDataGrid | |||
// {...props} | |||
// 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} | |||
rows={rows} | |||
columns={_columns} | |||
columns={!checkboxSelection ? _columns : columns} | |||
editMode="row" | |||
autoHeight | |||
sx={{ | |||
@@ -295,13 +345,13 @@ function InputDataGrid<T, E>({ | |||
} | |||
return classname; | |||
}} | |||
slots={{ | |||
slots={!checkboxSelection ? { | |||
footer: FooterToolbar, | |||
noRowsOverlay: NoRowsOverlay, | |||
}} | |||
slotProps={{ | |||
} : undefined} | |||
slotProps={!checkboxSelection ? { | |||
footer: { child: footer }, | |||
}} | |||
} : undefined} | |||
/> | |||
) | |||
} | |||
@@ -12,28 +12,21 @@ import { TypeEnum } from "@/app/utils/typeEnum"; | |||
type Props = { | |||
items: ItemsResult[]; | |||
type: TypeEnum; | |||
}; | |||
type SearchQuery = Partial<Omit<ItemsResult, "id">>; | |||
type SearchParamNames = keyof SearchQuery; | |||
const ItemsSearch: React.FC<Props> = ({ items, type }) => { | |||
const ItemsSearch: React.FC<Props> = ({ items }) => { | |||
const [filteredItems, setFilteredItems] = useState<ItemsResult[]>(items); | |||
const { t } = useTranslation(type.toString()); | |||
const { t } = useTranslation("items"); | |||
const router = useRouter(); | |||
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 | |||
}, | |||
[t, items] | |||
@@ -41,9 +34,9 @@ const ItemsSearch: React.FC<Props> = ({ items, type }) => { | |||
const onDetailClick = useCallback( | |||
(item: ItemsResult) => { | |||
router.push(`/settings/${type}/edit?id=${item.id}`); | |||
router.push(`/settings/items/edit?id=${item.id}`); | |||
}, | |||
[type, router] | |||
[router] | |||
); | |||
const onDeleteClick = useCallback( | |||
@@ -88,22 +81,10 @@ const ItemsSearch: React.FC<Props> = ({ items, type }) => { | |||
onSearch={(query) => { | |||
setFilteredItems( | |||
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: TypeEnum; | |||
// type: TypeEnum; | |||
}; | |||
const ItemsSearchWrapper: React.FC<Props> & SubComponents = async ({ | |||
type, | |||
// type, | |||
}) => { | |||
console.log(type) | |||
// console.log(type) | |||
var result = await fetchAllItems() | |||
return <ItemsSearch items={result} type={type} />; | |||
return <ItemsSearch items={result} />; | |||
}; | |||
ItemsSearchWrapper.Loading = ItemsSearchLoading; | |||
@@ -13,7 +13,7 @@ const Logo: React.FC<Props> = ({ width, height }) => { | |||
> | |||
<g | |||
id="svgGroup" | |||
stroke-linecap="round" | |||
strokeLinecap="round" | |||
fill-rule="evenodd" | |||
font-size="9pt" | |||
stroke="#000" | |||
@@ -181,20 +181,20 @@ const NavigationContent: React.FC = () => { | |||
label: "User Group", | |||
path: "/settings/user", | |||
}, | |||
// { | |||
// icon: <RequestQuote />, | |||
// label: "Material", | |||
// path: "/settings/material", | |||
// }, | |||
// { | |||
// icon: <RequestQuote />, | |||
// label: "By-product", | |||
// path: "/settings/byProduct", | |||
// }, | |||
{ | |||
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 />, | |||