@@ -1,19 +1,22 @@ | |||
import { SearchParams } from "@/app/utils/fetchUtil"; | |||
import CreateMaterial from "@/components/CreateMaterial"; | |||
import { TypeEnum } from "@/app/utils/typeEnum"; | |||
import CreateProductMaterial from "@/components/CreateProductMaterial"; | |||
import { I18nProvider, getServerI18n } from "@/i18n"; | |||
import { Typography } from "@mui/material"; | |||
import { isString } from "lodash"; | |||
type Props = {} & SearchParams; | |||
const materialSetting: React.FC<Props> = async ({ searchParams }) => { | |||
const { t } = await getServerI18n("materials"); | |||
const type = TypeEnum.MATERIAL; | |||
const { t } = await getServerI18n(type); | |||
console.log(searchParams); | |||
return ( | |||
<> | |||
{/* <Typography variant="h4">{t("Create Material")}</Typography> */} | |||
<I18nProvider namespaces={["materials"]}> | |||
<CreateMaterial isEditMode={false} /> | |||
<I18nProvider namespaces={[type]}> | |||
<CreateProductMaterial type={type} /> | |||
</I18nProvider> | |||
</> | |||
); | |||
@@ -0,0 +1,31 @@ | |||
import { SearchParams } from "@/app/utils/fetchUtil"; | |||
import { TypeEnum } from "@/app/utils/typeEnum"; | |||
import CreateProductMaterial from "@/components/CreateProductMaterial"; | |||
import { I18nProvider, getServerI18n } from "@/i18n"; | |||
import { Typography } from "@mui/material"; | |||
import { isString } from "lodash"; | |||
import { notFound } from "next/navigation"; | |||
type Props = {} & SearchParams; | |||
const materialSetting: React.FC<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]}> | |||
<CreateProductMaterial type={type} id={id} /> | |||
</I18nProvider> | |||
</> | |||
); | |||
}; | |||
export default materialSetting; |
@@ -1,4 +1,5 @@ | |||
import MaterialSearch from "@/components/MaterialSearch"; | |||
import { TypeEnum } from "@/app/utils/typeEnum"; | |||
import MaterialSearch from "@/components/ProductMaterialSearch"; | |||
import { getServerI18n } from "@/i18n"; | |||
import Add from "@mui/icons-material/Add"; | |||
import Button from "@mui/material/Button"; | |||
@@ -13,8 +14,9 @@ export const metadata: Metadata = { | |||
}; | |||
const materialSetting: React.FC = async () => { | |||
const { t } = await getServerI18n("material"); | |||
// preloadClaims(); | |||
const material = TypeEnum.MATERIAL | |||
const { t } = await getServerI18n(material); | |||
// preloadClaims(); | |||
return ( | |||
<> | |||
@@ -37,7 +39,7 @@ const materialSetting: React.FC = async () => { | |||
</Button> | |||
</Stack> | |||
<Suspense fallback={<MaterialSearch.Loading />}> | |||
<MaterialSearch /> | |||
<MaterialSearch type={material} /> | |||
</Suspense> | |||
</> | |||
); | |||
@@ -0,0 +1,22 @@ | |||
import { SearchParams } from "@/app/utils/fetchUtil"; | |||
import { TypeEnum } from "@/app/utils/typeEnum"; | |||
import CreateProductMaterial from "@/components/CreateProductMaterial"; | |||
import { I18nProvider, getServerI18n } from "@/i18n"; | |||
import { Typography } from "@mui/material"; | |||
import isString from "lodash/isString"; | |||
type Props = {} & SearchParams; | |||
const materialSetting: React.FC<Props> = async ({ searchParams }) => { | |||
const type = TypeEnum.PRODUCT; | |||
const { t } = await getServerI18n(type); | |||
return ( | |||
<> | |||
{/* <Typography variant="h4">{t("Create Material")}</Typography> */} | |||
<I18nProvider namespaces={[type]}> | |||
<CreateProductMaterial type={type}/> | |||
</I18nProvider> | |||
</> | |||
); | |||
}; | |||
export default materialSetting; |
@@ -0,0 +1,29 @@ | |||
import { SearchParams } from "@/app/utils/fetchUtil"; | |||
import { TypeEnum } from "@/app/utils/typeEnum"; | |||
import CreateProductMaterial from "@/components/CreateProductMaterial"; | |||
import { I18nProvider, getServerI18n } from "@/i18n"; | |||
import { Typography } from "@mui/material"; | |||
import isString from "lodash/isString"; | |||
import { notFound } from "next/navigation"; | |||
type Props = {} & SearchParams; | |||
const productSetting: React.FC<Props> = async ({ searchParams }) => { | |||
const type = TypeEnum.PRODUCT; | |||
const { t } = await getServerI18n(type); | |||
const id = isString(searchParams["id"]) | |||
? parseInt(searchParams["id"]) | |||
: undefined; | |||
if (!id) { | |||
notFound(); | |||
} | |||
return ( | |||
<> | |||
{/* <Typography variant="h4">{t("Create Material")}</Typography> */} | |||
<I18nProvider namespaces={[type]}> | |||
<CreateProductMaterial type={type} id={id} /> | |||
</I18nProvider> | |||
</> | |||
); | |||
}; | |||
export default productSetting; |
@@ -0,0 +1,48 @@ | |||
import { TypeEnum } from "@/app/utils/typeEnum"; | |||
import MaterialSearch from "@/components/ProductMaterialSearch"; | |||
import { getServerI18n } from "@/i18n"; | |||
import Add from "@mui/icons-material/Add"; | |||
import Button from "@mui/material/Button"; | |||
import Stack from "@mui/material/Stack"; | |||
import Typography from "@mui/material/Typography"; | |||
import { Metadata } from "next"; | |||
import Link from "next/link"; | |||
import { Suspense } from "react"; | |||
export const metadata: Metadata = { | |||
title: "Product", | |||
}; | |||
const productSetting: React.FC = async () => { | |||
const project = TypeEnum.PRODUCT | |||
const { t } = await getServerI18n(project); | |||
// preloadClaims(); | |||
return ( | |||
<> | |||
<Stack | |||
direction="row" | |||
justifyContent="space-between" | |||
flexWrap="wrap" | |||
rowGap={2} | |||
> | |||
<Typography variant="h4" marginInlineEnd={2}> | |||
{t("Product")} | |||
</Typography> | |||
<Button | |||
variant="contained" | |||
startIcon={<Add />} | |||
LinkComponent={Link} | |||
href="product/create" | |||
> | |||
{t("Create product")} | |||
</Button> | |||
</Stack> | |||
<Suspense fallback={<MaterialSearch.Loading />}> | |||
<MaterialSearch type={project} /> | |||
</Suspense> | |||
</> | |||
); | |||
}; | |||
export default productSetting; |
@@ -2,14 +2,26 @@ | |||
import { ServerFetchError, serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil"; | |||
import { revalidateTag } from "next/cache"; | |||
import { BASE_API_URL } from "@/config/api"; | |||
import { HTMLInputTypeAttribute } from "react"; | |||
export type TypeInputs = { | |||
type: string | |||
} | |||
export type UomInputs = { | |||
uom: string | |||
} | |||
export type WeightUnitInputs = { | |||
weightUnit: string | |||
conversion: number | |||
} | |||
export type CreateMaterialInputs = { | |||
id?: string | number | |||
code: string; | |||
name: string; | |||
isConsumables: boolean; | |||
// same goes for other props | |||
// change backend for null or not null | |||
description?: string | undefined; | |||
type?: string | undefined; | |||
remarks?: string | undefined; | |||
shelfLife?: Number | undefined; | |||
countryOfOrigin?: string | undefined; | |||
@@ -20,17 +32,25 @@ export type CreateMaterialInputs = { | |||
sampleRate?: number | undefined; | |||
passingRate?: number | undefined; | |||
netWeight?: number | undefined; | |||
uom?: any; | |||
weightUnit?: any; | |||
type?: TypeInputs[]; | |||
uom?: UomInputs[]; | |||
weightUnit?: WeightUnitInputs[]; | |||
} | |||
export interface CreateProductMaterialResponse { | |||
id: number | null; | |||
name: string; | |||
code: string; | |||
message: string | null; | |||
errorPosition: keyof CreateMaterialInputs; | |||
} | |||
export const saveMaterial = async (data: CreateMaterialInputs) => { | |||
// try { | |||
const materials = await serverFetchJson(`${BASE_API_URL}/materials/save`, { | |||
const material = await serverFetchJson<CreateProductMaterialResponse>(`${BASE_API_URL}/material/new`, { | |||
method: "POST", | |||
body: JSON.stringify(data), | |||
headers: { "Content-Type": "application/json" }, | |||
}); | |||
revalidateTag("materials"); | |||
return materials | |||
revalidateTag("material"); | |||
return material | |||
}; |
@@ -2,18 +2,40 @@ import { cache } from "react"; | |||
import "server-only"; | |||
import { serverFetchJson } from "@/app/utils/fetchUtil"; | |||
import { BASE_API_URL } from "@/config/api"; | |||
import { TypeInputs, UomInputs, WeightUnitInputs } from "./actions"; | |||
export type MaterialResult = { | |||
id: number; | |||
id: string | number | |||
code: string; | |||
name: string; | |||
description: string; | |||
action: any | undefined; | |||
isConsumables: boolean; | |||
description: string | undefined; | |||
remarks: string | undefined; | |||
shelfLife: Number | undefined; | |||
countryOfOrigin: string | undefined; | |||
minHumid: number | undefined; | |||
maxHumid: number | undefined; | |||
minTemp: number | undefined; | |||
maxTemp: number | undefined; | |||
sampleRate: number | undefined; | |||
passingRate: number | undefined; | |||
netWeight: number | undefined; | |||
uom: UomInputs[]; | |||
weightUnit: WeightUnitInputs[]; | |||
type: TypeInputs[]; | |||
action?: any | |||
} | |||
export const fetchMaterials = cache(async () => { | |||
return serverFetchJson<MaterialResult[]>(`${BASE_API_URL}/materials`, { | |||
next: { tags: ["materials"] }, | |||
export const fetchAllMaterials = cache(async () => { | |||
return serverFetchJson<MaterialResult[]>(`${BASE_API_URL}/material`, { | |||
next: { tags: ["material"] }, | |||
}); | |||
}); | |||
}); | |||
export const fetchMaterial = cache(async (id: number) => { | |||
return serverFetchJson<MaterialResult>(`${BASE_API_URL}/material/details/${id}`, { | |||
next: { tags: ["material"] }, | |||
}); | |||
}); |
@@ -0,0 +1,37 @@ | |||
"use server"; | |||
import { ServerFetchError, serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil"; | |||
import { revalidateTag } from "next/cache"; | |||
import { BASE_API_URL } from "@/config/api"; | |||
import { CreateProductMaterialResponse, TypeInputs, UomInputs, WeightUnitInputs } from "../material/actions"; | |||
export type CreateProductInputs = { | |||
id?: string | number | |||
code: string; | |||
name: string; | |||
isConsumables: boolean; | |||
description?: string | undefined; | |||
remarks?: string | undefined; | |||
shelfLife?: number | undefined; | |||
countryOfOrigin?: string | undefined; | |||
minHumid?: number | undefined; | |||
maxHumid?: number | undefined; | |||
minTemp?: number | undefined; | |||
maxTemp?: number | undefined; | |||
sampleRate?: number | undefined; | |||
passingRate?: number | undefined; | |||
netWeight?: number | undefined; | |||
type?: TypeInputs[]; | |||
uom?: UomInputs[]; | |||
weightUnit?: WeightUnitInputs[]; | |||
} | |||
export const saveProduct = async (data: CreateProductInputs) => { | |||
// try { | |||
const material = await serverFetchJson<CreateProductMaterialResponse>(`${BASE_API_URL}/product/new`, { | |||
method: "POST", | |||
body: JSON.stringify(data), | |||
headers: { "Content-Type": "application/json" }, | |||
}); | |||
revalidateTag("product"); | |||
return material | |||
}; |
@@ -0,0 +1,40 @@ | |||
import { cache } from "react"; | |||
import "server-only"; | |||
import { serverFetchJson } from "@/app/utils/fetchUtil"; | |||
import { BASE_API_URL } from "@/config/api"; | |||
export type ProductResult = { | |||
id: string | number | |||
code: string; | |||
name: string; | |||
isConsumables: boolean; | |||
description: string | undefined; | |||
type: string | undefined; | |||
remarks: string | undefined; | |||
shelfLife: Number | undefined; | |||
countryOfOrigin: string | undefined; | |||
minHumid: number | undefined; | |||
maxHumid: number | undefined; | |||
minTemp: number | undefined; | |||
maxTemp: number | undefined; | |||
sampleRate: number | undefined; | |||
passingRate: number | undefined; | |||
netWeight: number | undefined; | |||
uom: string[] | any[]; | |||
weightUnit: string[] | any[]; | |||
action?: any; | |||
} | |||
export const fetchAllMaterials = cache(async () => { | |||
return serverFetchJson<ProductResult[]>(`${BASE_API_URL}/product`, { | |||
next: { tags: ["product"] }, | |||
}); | |||
}); | |||
export const fetchMaterial = cache(async (id: number) => { | |||
return serverFetchJson<ProductResult>(`${BASE_API_URL}/product/details/${id}`, { | |||
next: { tags: ["product"] }, | |||
}); | |||
}); |
@@ -0,0 +1,4 @@ | |||
export enum TypeEnum { | |||
PRODUCT = "product", | |||
MATERIAL = "material", | |||
} |
@@ -1,88 +0,0 @@ | |||
"use client"; | |||
import { useCallback, useState } from "react"; | |||
import { useRouter, useSearchParams } from "next/navigation"; | |||
import { useTranslation } from "react-i18next"; | |||
import { CreateMaterialInputs } from "@/app/api/settings/material/actions"; | |||
import { | |||
FormProvider, | |||
SubmitErrorHandler, | |||
SubmitHandler, | |||
useForm, | |||
} from "react-hook-form"; | |||
import { deleteDialog } from "../Swal/CustomAlerts"; | |||
import { Box, Button, Grid, Stack, Typography } from "@mui/material"; | |||
import MaterialDetails from "./MaterialDetails"; | |||
import { Check, Close, EditNote } from "@mui/icons-material"; | |||
type Props = { | |||
isEditMode: boolean; | |||
}; | |||
const CreateStaff: React.FC<Props> = ({ isEditMode }) => { | |||
const [serverError, setServerError] = useState(""); | |||
const [tabIndex, setTabIndex] = useState(0); | |||
const { t } = useTranslation(); | |||
const router = useRouter(); | |||
const formProps = useForm<CreateMaterialInputs>({ | |||
defaultValues: {}, | |||
}); | |||
const handleCancel = () => { | |||
router.replace("/materials"); | |||
}; | |||
const onSubmit = useCallback<SubmitHandler<CreateMaterialInputs>>( | |||
async (data, event) => { | |||
try { | |||
console.log(data) | |||
} catch (e) { | |||
} | |||
}, | |||
[router, t] | |||
); | |||
const onSubmitError = useCallback<SubmitErrorHandler<CreateMaterialInputs>>( | |||
(errors) => {}, | |||
[] | |||
); | |||
const errors = formProps.formState.errors; | |||
return ( | |||
<> | |||
<FormProvider {...formProps}> | |||
<Stack | |||
spacing={2} | |||
component="form" | |||
onSubmit={formProps.handleSubmit(onSubmit, onSubmitError)} | |||
> | |||
<Grid> | |||
<Typography mb={2} variant="h4"> | |||
{isEditMode ? t("Edit Material") : t("Create Material")} | |||
</Typography> | |||
</Grid> | |||
<MaterialDetails isEditMode={isEditMode} /> | |||
<Stack direction="row" justifyContent="flex-end" gap={1}> | |||
<Button | |||
name="submit" | |||
variant="contained" | |||
startIcon={<Check />} | |||
type="submit" | |||
// disabled={submitDisabled} | |||
> | |||
{isEditMode ? t("Save") : t("Confirm")} | |||
</Button> | |||
<Button | |||
variant="outlined" | |||
startIcon={<Close />} | |||
onClick={handleCancel} | |||
> | |||
{t("Cancel")} | |||
</Button> | |||
</Stack> | |||
</Stack> | |||
</FormProvider> | |||
</> | |||
); | |||
}; | |||
export default CreateStaff; |
@@ -1,23 +0,0 @@ | |||
import CreateStaff from "./CreateMaterial"; | |||
import CreateMaterialLoading from "./CreateMaterialLoading"; | |||
import { cookies } from 'next/headers' | |||
interface SubComponents { | |||
Loading: typeof CreateMaterialLoading; | |||
} | |||
type CreateMaterialProps = { | |||
isEditMode?: false; | |||
}; | |||
type Props = CreateMaterialProps | |||
const CreateMaterialWrapper: React.FC<Props> & SubComponents = async (props) => { | |||
const cookieStore = await cookies() | |||
// console.log("==================cookieStore==================") | |||
// console.log(cookieStore) | |||
return <CreateStaff isEditMode={Boolean(props.isEditMode)}/> | |||
} | |||
CreateMaterialWrapper.Loading = CreateMaterialLoading; | |||
export default CreateMaterialWrapper |
@@ -1 +0,0 @@ | |||
export { default } from "./CreateMaterialWrapper"; |
@@ -0,0 +1,202 @@ | |||
"use client"; | |||
import { useCallback, useEffect, useMemo, useState } from "react"; | |||
import { useRouter, useSearchParams } from "next/navigation"; | |||
import { useTranslation } from "react-i18next"; | |||
import { | |||
CreateMaterialInputs, | |||
CreateProductMaterialResponse, | |||
saveMaterial, | |||
} from "@/app/api/settings/material/actions"; | |||
import { | |||
FormProvider, | |||
SubmitErrorHandler, | |||
SubmitHandler, | |||
useForm, | |||
} from "react-hook-form"; | |||
import { deleteDialog } from "../Swal/CustomAlerts"; | |||
import { Box, Button, Grid, Stack, Typography } from "@mui/material"; | |||
import { Check, Close, EditNote } from "@mui/icons-material"; | |||
import { TypeEnum } from "@/app/utils/typeEnum"; | |||
import { | |||
CreateProductInputs, | |||
saveProduct, | |||
} from "@/app/api/settings/product/actions"; | |||
import { CreateInputsFields } from "./CreateProductMaterialWrapper"; | |||
import MaterialDetails from "./MaterialDetails"; | |||
import ProductDetails from "./ProductDetails"; | |||
type Props = { | |||
isEditMode: boolean; | |||
type: TypeEnum; | |||
defaultValues: Partial<CreateInputsFields> | undefined; | |||
}; | |||
const CreateProductMaterial: React.FC<Props> = ({ | |||
isEditMode, | |||
type, | |||
defaultValues, | |||
}) => { | |||
const [serverError, setServerError] = useState(""); | |||
const [tabIndex, setTabIndex] = useState(0); | |||
const { t } = useTranslation(); | |||
const router = useRouter(); | |||
const [title, mode, redirPath] = useMemo(() => { | |||
var title = ""; | |||
var mode = ""; | |||
var redirPath = ""; | |||
if (type === TypeEnum.MATERIAL) { | |||
title = "Material"; | |||
redirPath = "/material"; | |||
} | |||
if (type === TypeEnum.PRODUCT) { | |||
title = "Project"; | |||
redirPath = "/project"; | |||
} | |||
if (isEditMode) { | |||
mode = "Edit"; | |||
} else { | |||
mode = "Create"; | |||
} | |||
return [title, mode, redirPath]; | |||
}, [type, isEditMode]); | |||
const formProps = useForm<CreateInputsFields>({ | |||
defaultValues: defaultValues ? defaultValues : {}, | |||
}); | |||
const errors = formProps.formState.errors; | |||
const handleCancel = () => { | |||
router.replace(`/settings/${type}`); | |||
}; | |||
const onSubmit = useCallback<SubmitHandler<CreateInputsFields>>( | |||
async (data, event) => { | |||
let hasErrors = false; | |||
try { | |||
// checking humid input | |||
if (data.maxHumid && data.minHumid!! > data.maxHumid!!) { | |||
const message = "minHumid should not be greater than maxHumid"; | |||
formProps.setError("minHumid", { | |||
message: message, | |||
type: "required", | |||
}); | |||
formProps.setError("maxHumid", { | |||
message: message, | |||
type: "required", | |||
}); | |||
hasErrors = true; | |||
} | |||
// checking temp input | |||
if (data.maxTemp && data.minTemp!! > data.maxTemp!!) { | |||
const message = "minTemp should not be greater than maxTemp"; | |||
formProps.setError("minTemp", { | |||
message: message, | |||
type: "required", | |||
}); | |||
formProps.setError("maxTemp", { | |||
message: message, | |||
type: "required", | |||
}); | |||
hasErrors = true; | |||
} | |||
// checking passing rate | |||
if (data.passingRate && data.passingRate > 100) { | |||
const message = "passingRate should not be greater than 100%"; | |||
formProps.setError("passingRate", { | |||
message: message, | |||
type: "required", | |||
}); | |||
} | |||
// checking sampling rate | |||
if (data.sampleRate && data.sampleRate > 100) { | |||
const message = "sampleRate should not be greater than 100%"; | |||
formProps.setError("sampleRate", { | |||
message: message, | |||
type: "required", | |||
}); | |||
} | |||
if (hasErrors) { | |||
setServerError(t("An error has occurred. Please try again later.")); | |||
return false; | |||
} | |||
console.log("data posted"); | |||
console.log(data); | |||
// do api | |||
var response: CreateProductMaterialResponse | undefined; | |||
if (type === TypeEnum.MATERIAL) { | |||
response = await saveMaterial(data as CreateMaterialInputs); | |||
} else if (type === TypeEnum.PRODUCT) { | |||
response = await saveProduct(data as CreateProductInputs); | |||
} | |||
if (response) { | |||
if (!Boolean(response.id)) { | |||
formProps.setError(response.errorPosition!!, { | |||
message: response.message!!, | |||
type: "required", | |||
}); | |||
throw response; | |||
} else if (Boolean(response.id)) { | |||
router.replace(redirPath); | |||
} | |||
} else { | |||
throw "no response"; | |||
} | |||
} catch (e) { | |||
// backend error | |||
setServerError(t("An error has occurred. Please try again later.")); | |||
console.log(e); | |||
} | |||
}, | |||
[router, t] | |||
); | |||
// multiple tabs | |||
const onSubmitError = useCallback<SubmitErrorHandler<CreateInputsFields>>( | |||
(errors) => {}, | |||
[] | |||
); | |||
return ( | |||
<> | |||
<FormProvider {...formProps}> | |||
<Stack | |||
spacing={2} | |||
component="form" | |||
onSubmit={formProps.handleSubmit(onSubmit, onSubmitError)} | |||
> | |||
<Grid> | |||
<Typography mb={2} variant="h4"> | |||
{t(`${mode} ${title}`)} | |||
</Typography> | |||
</Grid> | |||
{serverError && ( | |||
<Typography variant="body2" color="error" alignSelf="flex-end"> | |||
{serverError} | |||
</Typography> | |||
)} | |||
{type === TypeEnum.MATERIAL && <MaterialDetails />} | |||
{type === TypeEnum.PRODUCT && <ProductDetails />} | |||
<Stack direction="row" justifyContent="flex-end" gap={1}> | |||
<Button | |||
name="submit" | |||
variant="contained" | |||
startIcon={<Check />} | |||
type="submit" | |||
// disabled={submitDisabled} | |||
> | |||
{isEditMode ? t("Save") : t("Confirm")} | |||
</Button> | |||
<Button | |||
variant="outlined" | |||
startIcon={<Close />} | |||
onClick={handleCancel} | |||
> | |||
{t("Cancel")} | |||
</Button> | |||
</Stack> | |||
</Stack> | |||
</FormProvider> | |||
</> | |||
); | |||
}; | |||
export default CreateProductMaterial; |
@@ -5,7 +5,7 @@ import Stack from "@mui/material/Stack"; | |||
import React from "react"; | |||
// Can make this nicer | |||
export const CreateMaterialLoading: React.FC = () => { | |||
export const CreateProductMaterialLoading: React.FC = () => { | |||
return ( | |||
<> | |||
<Card> | |||
@@ -37,4 +37,4 @@ export const CreateMaterialLoading: React.FC = () => { | |||
); | |||
}; | |||
export default CreateMaterialLoading; | |||
export default CreateProductMaterialLoading; |
@@ -0,0 +1,77 @@ | |||
import { TypeEnum } from "@/app/utils/typeEnum"; | |||
import CreateProductMaterial from "./CreateProductMaterial"; | |||
import CreateMaterialLoading from "./CreateProductMaterialLoading"; | |||
import { CreateMaterialInputs } from "@/app/api/settings/material/actions"; | |||
import { CreateProductInputs } from "@/app/api/settings/product/actions"; | |||
import { fetchMaterial, MaterialResult } from "@/app/api/settings/material"; | |||
interface SubComponents { | |||
Loading: typeof CreateMaterialLoading; | |||
} | |||
type CreateMaterialProps = { | |||
// isEditMode: false; | |||
type: TypeEnum; | |||
}; | |||
type EditMaterialProps = { | |||
// isEditMode: true; | |||
type: TypeEnum; | |||
}; | |||
type CreateProductProps = { | |||
// isEditMode: false; | |||
type: TypeEnum; | |||
}; | |||
type EditProductProps = { | |||
// isEditMode: true; | |||
type: TypeEnum; | |||
}; | |||
type Props = | |||
| CreateMaterialProps | |||
| EditMaterialProps | |||
| CreateProductProps | |||
| EditProductProps; | |||
export type CreateInputsFields = CreateMaterialInputs | CreateProductInputs; | |||
const CreateProductMaterialWrapper: React.FC<Props & { id?: number }> & | |||
SubComponents = async ({ type, id }) => { | |||
var defaultValues: Partial<CreateInputsFields> = {}; | |||
if (id && type === TypeEnum.MATERIAL) { | |||
const result = await fetchMaterial(id); | |||
console.log(result) | |||
defaultValues = { | |||
id: result.id, | |||
code: result.name, | |||
name: result.name, | |||
isConsumables: result.isConsumables, | |||
description: result.description, | |||
type: result.type, | |||
remarks: result.remarks, | |||
shelfLife: result.shelfLife, | |||
countryOfOrigin: result.countryOfOrigin, | |||
minHumid: result.minHumid, | |||
maxHumid: result.maxHumid, | |||
minTemp: result.minTemp, | |||
maxTemp: result.maxTemp, | |||
sampleRate: result.sampleRate, | |||
passingRate: result.passingRate, | |||
netWeight: result.netWeight, | |||
uom: result.uom, | |||
weightUnit: result.weightUnit, | |||
}; | |||
} | |||
if (id && type === TypeEnum.PRODUCT) { | |||
defaultValues = {}; | |||
} | |||
return ( | |||
<CreateProductMaterial | |||
isEditMode={Boolean(id)} | |||
type={type} | |||
defaultValues={defaultValues} | |||
/> | |||
); | |||
}; | |||
CreateProductMaterialWrapper.Loading = CreateMaterialLoading; | |||
export default CreateProductMaterialWrapper; |
@@ -0,0 +1,340 @@ | |||
"use client"; | |||
import { CreateMaterialInputs } from "@/app/api/settings/material/actions"; | |||
import { | |||
Box, | |||
Card, | |||
CardContent, | |||
Checkbox, | |||
FilledInputProps, | |||
FormControlLabel, | |||
FormGroup, | |||
Grid, | |||
InputBaseComponentProps, | |||
OutlinedInputProps, | |||
Stack, | |||
TextField, | |||
Typography, | |||
} from "@mui/material"; | |||
import { Controller, useFormContext } from "react-hook-form"; | |||
import { useTranslation } from "react-i18next"; | |||
import InputDataGrid from "../InputDataGrid"; | |||
import { useCallback, useEffect, useMemo, useState } from "react"; | |||
import { GridColDef, GridRowModel } from "@mui/x-data-grid"; | |||
import { InputDataGridProps, TableRow } from "../InputDataGrid/InputDataGrid"; | |||
import { TypeEnum } from "@/app/utils/typeEnum"; | |||
import { NumberInputProps } from "./NumberInputProps"; | |||
type Props = { | |||
// isEditMode: boolean; | |||
// type: TypeEnum; | |||
}; | |||
export type EntryError = | |||
| { | |||
[field in keyof CreateMaterialInputs]?: string; | |||
} | |||
| undefined; | |||
const MaterialDetails: React.FC<Props> = ({}) => { | |||
const { | |||
t, | |||
i18n: { language }, | |||
} = useTranslation(); | |||
const { | |||
register, | |||
formState: { errors, defaultValues, touchedFields }, | |||
watch, | |||
control, | |||
setValue, | |||
getValues, | |||
reset, | |||
resetField, | |||
setError, | |||
clearErrors, | |||
} = useFormContext<CreateMaterialInputs>(); | |||
// textfield error msg locale problem | |||
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<CreateMaterialInputs>, 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={"isConsumables"} | |||
control={control} | |||
render={({ field: props }) => ( | |||
<Checkbox | |||
{...props} | |||
checked={props.value} | |||
onChange={(e) => props.onChange(e.target.checked)} | |||
/> | |||
)} | |||
/> | |||
} | |||
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 item xs={6}> | |||
<InputDataGrid<CreateMaterialInputs, EntryError> | |||
_formKey={"type"} | |||
columns={typeColumns} | |||
validateRow={validationTest} | |||
/> | |||
</Grid> | |||
<Grid item xs={6}> | |||
<InputDataGrid<CreateMaterialInputs, EntryError> | |||
_formKey={"uom"} | |||
columns={uomColumns} | |||
validateRow={validationTest} | |||
/> | |||
</Grid> | |||
<Grid item xs={12}> | |||
<InputDataGrid<CreateMaterialInputs, EntryError> | |||
_formKey={"weightUnit"} | |||
columns={weightUnitColumns} | |||
validateRow={validationTest} | |||
/> | |||
</Grid> | |||
</Grid> | |||
</Box> | |||
</CardContent> | |||
</Card> | |||
); | |||
}; | |||
export default MaterialDetails; |
@@ -0,0 +1,5 @@ | |||
import { InputBaseComponentProps } from "@mui/material"; | |||
export var NumberInputProps: InputBaseComponentProps = { | |||
step: 0.01, | |||
}; |
@@ -14,20 +14,25 @@ import { useTranslation } from "react-i18next"; | |||
import InputDataGrid from "../InputDataGrid"; | |||
import { useCallback, useMemo, useState } from "react"; | |||
import { GridColDef, GridRowModel } from "@mui/x-data-grid"; | |||
import { | |||
InputDataGridProps, | |||
TableRow, | |||
} from "../InputDataGrid/InputDataGrid"; | |||
import { InputDataGridProps, TableRow } from "../InputDataGrid/InputDataGrid"; | |||
import { TypeEnum } from "@/app/utils/typeEnum"; | |||
import { CreateProductInputs } from "@/app/api/settings/product/actions"; | |||
import { NumberInputProps } from "./NumberInputProps"; | |||
type Props = { | |||
isEditMode: boolean; | |||
// isEditMode: boolean; | |||
// type: TypeEnum; | |||
}; | |||
export type EntryError = | |||
| { | |||
[field in keyof CreateMaterialInputs]?: string; | |||
[field in keyof CreateProductInputs]?: string; | |||
} | |||
| undefined; | |||
const MaterialDetails: React.FC<Props> = ({ isEditMode }) => { | |||
const ProductDetails: React.FC<Props> = ({ | |||
}) => { | |||
const { | |||
t, | |||
i18n: { language }, | |||
@@ -44,59 +49,65 @@ const MaterialDetails: React.FC<Props> = ({ isEditMode }) => { | |||
resetField, | |||
setError, | |||
clearErrors, | |||
} = useFormContext<CreateMaterialInputs>(); | |||
const typeColumns = useMemo<GridColDef[]>(() => [ | |||
{ | |||
field: "type", | |||
headerName: 'type', | |||
flex: 1, | |||
editable: true, | |||
} | |||
], []); | |||
const uomColumns = useMemo<GridColDef[]>(() => [ | |||
{ | |||
field: "uom", | |||
headerName: 'uom', | |||
flex: 1, | |||
editable: true, | |||
} | |||
], []); | |||
const validationTest = useCallback((newRow: GridRowModel<TableRow<Partial<CreateMaterialInputs>, EntryError>>): EntryError => { | |||
const error: EntryError = {}; | |||
console.log(newRow) | |||
return Object.keys(error).length > 0 ? error : undefined; | |||
// return undefined | |||
}, []); | |||
// const MaterialTypeInputGridProps: InputDataGridProps< | |||
// Partial<CreateMaterialInputs>, | |||
// EntryError | |||
// > = { | |||
// _formKey: "type", | |||
// columns: typeColumns, | |||
// validateRow: validationTest, | |||
// }; | |||
// const MaterialUomInputGridProps: InputDataGridProps< | |||
// Partial<CreateMaterialInputs>, | |||
// EntryError | |||
// > = { | |||
// _formKey: "uom", | |||
// columns: uomColumns, | |||
// validateRow: validationTest, | |||
// }; | |||
// const MaterialTypeInputGrid = useInputDataGrid( | |||
// MaterialTypeInputGridProps | |||
// ); | |||
// const MaterialUomInputGrid = useInputDataGrid( | |||
// MaterialUomInputGridProps | |||
// ); | |||
} = useFormContext<CreateProductInputs>(); | |||
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<CreateProductInputs>, 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> | |||
<Typography variant="overline" display="block" marginBlockEnd={1}> | |||
{t("Material Details")} | |||
{t("Product Details")} | |||
</Typography> | |||
<Grid container spacing={2} columns={{ xs: 6, sm: 12 }}> | |||
<Grid item xs={6}> | |||
@@ -107,6 +118,7 @@ const MaterialDetails: React.FC<Props> = ({ isEditMode }) => { | |||
required: "name required!", | |||
})} | |||
error={Boolean(errors.name)} | |||
helperText={errors.name?.message} | |||
/> | |||
</Grid> | |||
<Grid item xs={6}> | |||
@@ -117,26 +129,27 @@ const MaterialDetails: React.FC<Props> = ({ isEditMode }) => { | |||
required: "code required!", | |||
})} | |||
error={Boolean(errors.code)} | |||
helperText={errors.code?.message} | |||
/> | |||
</Grid> | |||
<Grid item xs={6}> | |||
<TextField | |||
label={t("description")} | |||
fullWidth | |||
{...register("description", { | |||
required: "description required!", | |||
})} | |||
error={Boolean(errors.description)} | |||
{...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}> | |||
@@ -147,66 +160,97 @@ const MaterialDetails: React.FC<Props> = ({ isEditMode }) => { | |||
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", { | |||
required: "maxHumid required!", | |||
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)} | |||
error={Boolean(errors.passingRate)} // change backend for null or not null | |||
helperText={errors.passingRate?.message} | |||
/> | |||
</Grid> | |||
<Grid item xs={6} /> | |||
@@ -215,39 +259,52 @@ const MaterialDetails: React.FC<Props> = ({ isEditMode }) => { | |||
label={t("remarks")} | |||
fullWidth | |||
{...register("remarks", { | |||
required: "remarks required!", | |||
// 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 item xs={6}> | |||
<InputDataGrid<CreateMaterialInputs, EntryError> | |||
<InputDataGrid<CreateProductInputs, EntryError> | |||
_formKey={"type"} | |||
columns={typeColumns} | |||
validateRow={validationTest} | |||
/> | |||
</Grid> | |||
<Grid item xs={6}> | |||
<InputDataGrid<CreateMaterialInputs, EntryError> | |||
<InputDataGrid<CreateProductInputs, EntryError> | |||
_formKey={"uom"} | |||
columns={uomColumns} | |||
validateRow={validationTest} | |||
/> | |||
</Grid> | |||
<Grid item xs={12}> | |||
<InputDataGrid<CreateProductInputs, EntryError> | |||
_formKey={"weightUnit"} | |||
columns={weightUnitColumns} | |||
validateRow={validationTest} | |||
/> | |||
</Grid> | |||
</Grid> | |||
</Box> | |||
</CardContent> | |||
</Card> | |||
); | |||
}; | |||
export default MaterialDetails; | |||
export default ProductDetails; |
@@ -0,0 +1 @@ | |||
export { default } from "./CreateProductMaterialWrapper"; |
@@ -107,34 +107,31 @@ function InputDataGrid<T, E>({ | |||
[apiRef, rowModesModel] | |||
); | |||
console.log("asdasdasd") | |||
const processRowUpdate = useCallback( | |||
( | |||
newRow: GridValidRowModel, | |||
originalRow: GridValidRowModel, | |||
// GridRowModel<TableRow<T, E>>, | |||
// originalRow: GridRowModel<TableRow<T, E>> | |||
newRow: GridRowModel<TableRow<T, E>>, | |||
originalRow: GridRowModel<TableRow<T, E>> | |||
) => { | |||
///////////////// | |||
// validation here | |||
// const errors = validateRow(newRow); | |||
const errors = validateRow(newRow); | |||
console.log(newRow) | |||
// if (errors) { | |||
// throw new ProcessRowUpdateError( | |||
// originalRow, | |||
// "validation error", | |||
// errors, | |||
// ); | |||
// } | |||
if (errors) { | |||
throw new ProcessRowUpdateError( | |||
originalRow, | |||
"validation error", | |||
errors, | |||
); | |||
} | |||
///////////////// | |||
const { _isNew, _error, ...updatedRow } = newRow; | |||
const rowToSave = { | |||
...updatedRow, | |||
} as TableRow<T, E>; /// test | |||
console.log(rowToSave) | |||
// setRows((rw) => | |||
// rw.map((r) => (getRowId(r) === getRowId(originalRow) ? rowToSave : r)) | |||
// ); | |||
setRows((rw) => | |||
rw.map((r) => (getRowId(r) === getRowId(originalRow) ? rowToSave : r)) | |||
); | |||
return rowToSave; | |||
}, | |||
[validateRow, getRowId] | |||
@@ -190,7 +187,7 @@ function InputDataGrid<T, E>({ | |||
field: "actions", | |||
type: "actions", | |||
headerName: "", | |||
width: 100, | |||
flex: 0.5, | |||
cellClassName: "actions", | |||
getActions: ({ id }: { id: GridRowId }) => { | |||
const isInEditMode = rowModesModel[id]?.mode === GridRowModes.Edit; | |||
@@ -230,18 +227,10 @@ function InputDataGrid<T, E>({ | |||
], | |||
[columns,rowModesModel, handleSave, handleCancel, handleDelete] | |||
); | |||
// const processRowUpdate = (newRow: GridRowModel) => { | |||
// const updatedRow = { ...newRow, isNew: false }; | |||
// console.log("asdasdasdsda") | |||
// // setRows(rows.map((row) => (row.id === newRow.id ? updatedRow : row))); | |||
// return updatedRow; | |||
// }; | |||
// sync useForm | |||
useEffect(() => { | |||
console.log(formKey) | |||
console.log(rows) | |||
// console.log(formKey) | |||
// console.log(rows) | |||
setValue(formKey, rows); | |||
}, [formKey, rows]); | |||
@@ -294,7 +283,7 @@ function InputDataGrid<T, E>({ | |||
}, | |||
}} | |||
disableColumnMenu | |||
processRowUpdate={processRowUpdate} | |||
processRowUpdate={processRowUpdate as any} | |||
// onRowEditStop={handleRowEditStop} | |||
rowModesModel={rowModesModel} | |||
onRowModesModelChange={setRowModesModel} | |||
@@ -1,96 +0,0 @@ | |||
"use client" | |||
import { useCallback, useMemo, useState } from "react"; | |||
import SearchBox, { Criterion } from "../SearchBox"; | |||
import { MaterialResult } from "@/app/api/settings/material"; | |||
import { useTranslation } from "react-i18next"; | |||
import SearchResults, { Column } from "../SearchResults"; | |||
import { EditNote } from "@mui/icons-material"; | |||
import { useRouter, useSearchParams } from "next/navigation"; | |||
import { GridDeleteIcon } from "@mui/x-data-grid"; | |||
type Props = { | |||
materials: MaterialResult[] | |||
} | |||
type SearchQuery = Partial<Omit<MaterialResult, "id">>; | |||
type SearchParamNames = keyof SearchQuery; | |||
const MaterialSearch: React.FC<Props> = ({ | |||
materials | |||
}) => { | |||
const [filteredMaterials, setFilteredMaterials] = useState<MaterialResult[]>(materials) | |||
const { t } = useTranslation("materials"); | |||
const router = useRouter(); | |||
const searchCriteria: Criterion<SearchParamNames>[] = useMemo( | |||
() => [ | |||
{ label: t("Code"), paramName: "code", type: "text" }, | |||
{ label: t("Name"), paramName: "name", type: "text" }, | |||
], [t, materials]) | |||
const onMaterialClick = useCallback( | |||
(material: MaterialResult) => { | |||
}, | |||
[router], | |||
); | |||
const onDeleteClick = useCallback( | |||
(material: MaterialResult) => { | |||
}, | |||
[router], | |||
); | |||
const columns = useMemo<Column<MaterialResult>[]>(() => [ | |||
{ | |||
name: "id", | |||
label: t("Details"), | |||
onClick: onMaterialClick, | |||
buttonIcon: <EditNote />, | |||
}, | |||
{ | |||
name: "code", | |||
label: t("Code"), | |||
}, | |||
{ | |||
name: "name", | |||
label: t("Name"), | |||
}, | |||
{ | |||
name: "action", | |||
label: t(""), | |||
buttonIcon: <GridDeleteIcon />, | |||
onClick: onDeleteClick, | |||
}, | |||
], [filteredMaterials]) | |||
const onReset = useCallback(() => { | |||
setFilteredMaterials(materials); | |||
}, [materials]); | |||
return ( | |||
<> | |||
<SearchBox | |||
criteria={searchCriteria} | |||
onSearch={(query) => { | |||
setFilteredMaterials( | |||
materials.filter( | |||
(mat) => | |||
mat.code.toLowerCase().includes(query.code.toLowerCase()) && | |||
mat.name.toLowerCase().includes(query.name.toLowerCase()) | |||
), | |||
); | |||
}} | |||
onReset={onReset} | |||
/> | |||
<SearchResults<MaterialResult> | |||
items={filteredMaterials} | |||
columns={columns} | |||
/> | |||
</> | |||
) | |||
} | |||
export default MaterialSearch |
@@ -1,22 +0,0 @@ | |||
import { fetchMaterials, MaterialResult } from "@/app/api/settings/material"; | |||
import MaterialSearch from "./MaterialSearch"; | |||
import MaterialSearchLoading from "./MaterialSearchLoading"; | |||
interface SubComponents { | |||
Loading: typeof MaterialSearchLoading; | |||
} | |||
const MaterialSearchWrapper: React.FC & SubComponents = async () => { | |||
const materials = await fetchMaterials(); | |||
console.log(materials) | |||
return ( | |||
<MaterialSearch | |||
materials={materials} | |||
/> | |||
) | |||
} | |||
MaterialSearchWrapper.Loading = MaterialSearchLoading; | |||
export default MaterialSearchWrapper; |
@@ -1 +0,0 @@ | |||
export { default } from "./MaterialSearchWrapper"; |
@@ -193,8 +193,8 @@ const NavigationContent: React.FC = () => { | |||
}, | |||
{ | |||
icon: <RequestQuote />, | |||
label: "Finished Goods", | |||
path: "/settings/user", | |||
label: "Product", | |||
path: "/settings/product", | |||
}, | |||
{ | |||
icon: <RequestQuote />, | |||
@@ -0,0 +1,114 @@ | |||
"use client"; | |||
import { useCallback, useMemo, useState } from "react"; | |||
import SearchBox, { Criterion } from "../SearchBox"; | |||
import { MaterialResult } from "@/app/api/settings/material"; | |||
import { useTranslation } from "react-i18next"; | |||
import SearchResults, { Column } from "../SearchResults"; | |||
import { EditNote } from "@mui/icons-material"; | |||
import { useRouter, useSearchParams } from "next/navigation"; | |||
import { GridDeleteIcon } from "@mui/x-data-grid"; | |||
import { TypeEnum } from "@/app/utils/typeEnum"; | |||
import { ProductResult } from "@/app/api/settings/product"; | |||
export type ListResult = MaterialResult | ProductResult; | |||
type Props = { | |||
productMaterial: ListResult[]; | |||
type: TypeEnum; | |||
}; | |||
type SearchQuery = Partial<Omit<ListResult, "id">>; | |||
type SearchParamNames = keyof SearchQuery; | |||
const MaterialSearch: React.FC<Props> = ({ productMaterial, type }) => { | |||
const [filteredItems, setFilteredItems] = useState<ListResult[]>(productMaterial); | |||
const { t } = useTranslation(type); | |||
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) { | |||
} | |||
return searchCriteria | |||
}, | |||
[t, productMaterial] | |||
); | |||
const onDetailClick = useCallback( | |||
(productMaterial: ListResult) => { | |||
router.push(`/settings/${type}/edit?id=${productMaterial.id}`); | |||
}, | |||
[type, router] | |||
); | |||
const onDeleteClick = useCallback( | |||
(productMaterial: ListResult) => {}, | |||
[router] | |||
); | |||
const columns = useMemo<Column<ListResult>[]>( | |||
() => [ | |||
{ | |||
name: "id", | |||
label: t("Details"), | |||
onClick: onDetailClick, | |||
buttonIcon: <EditNote />, | |||
}, | |||
{ | |||
name: "code", | |||
label: t("Code"), | |||
}, | |||
{ | |||
name: "name", | |||
label: t("Name"), | |||
}, | |||
{ | |||
name: "action", | |||
label: t(""), | |||
buttonIcon: <GridDeleteIcon />, | |||
onClick: onDeleteClick, | |||
}, | |||
], | |||
[filteredItems] | |||
); | |||
const onReset = useCallback(() => { | |||
setFilteredItems(productMaterial); | |||
}, [productMaterial]); | |||
return ( | |||
<> | |||
<SearchBox | |||
criteria={searchCriteria} | |||
onSearch={(query) => { | |||
setFilteredItems( | |||
productMaterial.filter((pm) => { | |||
if (type === TypeEnum.MATERIAL) { | |||
return ( | |||
pm.code.toLowerCase().includes(query.code.toLowerCase()) && | |||
pm.name.toLowerCase().includes(query.name.toLowerCase()) | |||
); | |||
} else if (type === TypeEnum.PRODUCT) { | |||
return ( | |||
pm.code.toLowerCase().includes(query.code.toLowerCase()) && | |||
pm.name.toLowerCase().includes(query.name.toLowerCase()) | |||
); | |||
} | |||
}) | |||
); | |||
}} | |||
onReset={onReset} | |||
/> | |||
<SearchResults<ListResult> items={filteredItems} columns={columns} /> | |||
</> | |||
); | |||
}; | |||
export default MaterialSearch; |
@@ -5,7 +5,7 @@ import Stack from "@mui/material/Stack"; | |||
import React from "react"; | |||
// Can make this nicer | |||
export const MaterialSearchLoading: React.FC = () => { | |||
export const ProductMaterialSearchLoading: React.FC = () => { | |||
return ( | |||
<> | |||
<Card> | |||
@@ -37,4 +37,4 @@ export const MaterialSearchLoading: React.FC = () => { | |||
); | |||
}; | |||
export default MaterialSearchLoading; | |||
export default ProductMaterialSearchLoading; |
@@ -0,0 +1,30 @@ | |||
import { fetchAllMaterials, MaterialResult } from "@/app/api/settings/material"; | |||
import ProductMaterialSearch, { ListResult } from "./ProductMaterialSearch"; | |||
import MaterialSearchLoading from "./ProductMaterialSearchLoading"; | |||
import { SearchParams } from "@/app/utils/fetchUtil"; | |||
import { TypeEnum } from "@/app/utils/typeEnum"; | |||
interface SubComponents { | |||
Loading: typeof MaterialSearchLoading; | |||
} | |||
type Props = { | |||
type: TypeEnum; | |||
}; | |||
const ProductMaterialSearchWrapper: React.FC<Props> & SubComponents = async ({ | |||
type | |||
}) => { | |||
var result: ListResult[] = [] | |||
if (TypeEnum.PRODUCT === type) { | |||
result = [] | |||
} else if (TypeEnum.MATERIAL === type) { | |||
result = await fetchAllMaterials(); | |||
} | |||
return <ProductMaterialSearch productMaterial={result} type={TypeEnum.MATERIAL} />; | |||
}; | |||
ProductMaterialSearchWrapper.Loading = MaterialSearchLoading; | |||
export default ProductMaterialSearchWrapper; |
@@ -0,0 +1 @@ | |||
export { default } from "./ProductMaterialSearchWrapper"; |