Ver a proveniência

save mat and prod

feature/axios_provider
MSI\derek há 5 meses
ascendente
cometimento
ff77b326a1
31 ficheiros alterados com 1207 adições e 375 eliminações
  1. +7
    -4
      src/app/(main)/settings/material/create/page.tsx
  2. +31
    -0
      src/app/(main)/settings/material/edit/page.tsx
  3. +6
    -4
      src/app/(main)/settings/material/page.tsx
  4. +22
    -0
      src/app/(main)/settings/product/create/page.tsx
  5. +29
    -0
      src/app/(main)/settings/product/edit/page.tsx
  6. +48
    -0
      src/app/(main)/settings/product/page.tsx
  7. +26
    -6
      src/app/api/settings/material/actions.ts
  8. +29
    -7
      src/app/api/settings/material/index.ts
  9. +37
    -0
      src/app/api/settings/product/actions.ts
  10. +40
    -0
      src/app/api/settings/product/index.ts
  11. +4
    -0
      src/app/utils/typeEnum.ts
  12. +0
    -88
      src/components/CreateMaterial/CreateMaterial.tsx
  13. +0
    -23
      src/components/CreateMaterial/CreateMaterialWrapper.tsx
  14. +0
    -1
      src/components/CreateMaterial/index.ts
  15. +202
    -0
      src/components/CreateProductMaterial/CreateProductMaterial.tsx
  16. +2
    -2
      src/components/CreateProductMaterial/CreateProductMaterialLoading.tsx
  17. +77
    -0
      src/components/CreateProductMaterial/CreateProductMaterialWrapper.tsx
  18. +340
    -0
      src/components/CreateProductMaterial/MaterialDetails.tsx
  19. +5
    -0
      src/components/CreateProductMaterial/NumberInputProps.ts
  20. +120
    -63
      src/components/CreateProductMaterial/ProductDetails.tsx
  21. +1
    -0
      src/components/CreateProductMaterial/index.ts
  22. +17
    -28
      src/components/InputDataGrid/InputDataGrid.tsx
  23. +15
    -26
      src/components/Logo/Logo.tsx
  24. +0
    -96
      src/components/MaterialSearch/MaterialSearch.tsx
  25. +0
    -22
      src/components/MaterialSearch/MaterialSearchWrapper.tsx
  26. +0
    -1
      src/components/MaterialSearch/index.ts
  27. +2
    -2
      src/components/NavigationContent/NavigationContent.tsx
  28. +114
    -0
      src/components/ProductMaterialSearch/ProductMaterialSearch.tsx
  29. +2
    -2
      src/components/ProductMaterialSearch/ProductMaterialSearchLoading.tsx
  30. +30
    -0
      src/components/ProductMaterialSearch/ProductMaterialSearchWrapper.tsx
  31. +1
    -0
      src/components/ProductMaterialSearch/index.ts

+ 7
- 4
src/app/(main)/settings/material/create/page.tsx Ver ficheiro

@@ -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>
</>
);


+ 31
- 0
src/app/(main)/settings/material/edit/page.tsx Ver ficheiro

@@ -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;

+ 6
- 4
src/app/(main)/settings/material/page.tsx Ver ficheiro

@@ -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>
</>
);


+ 22
- 0
src/app/(main)/settings/product/create/page.tsx Ver ficheiro

@@ -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;

+ 29
- 0
src/app/(main)/settings/product/edit/page.tsx Ver ficheiro

@@ -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;

+ 48
- 0
src/app/(main)/settings/product/page.tsx Ver ficheiro

@@ -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;

+ 26
- 6
src/app/api/settings/material/actions.ts Ver ficheiro

@@ -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
};

+ 29
- 7
src/app/api/settings/material/index.ts Ver ficheiro

@@ -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"] },
});
});

+ 37
- 0
src/app/api/settings/product/actions.ts Ver ficheiro

@@ -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
};

+ 40
- 0
src/app/api/settings/product/index.ts Ver ficheiro

@@ -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"] },
});
});

+ 4
- 0
src/app/utils/typeEnum.ts Ver ficheiro

@@ -0,0 +1,4 @@
export enum TypeEnum {
PRODUCT = "product",
MATERIAL = "material",
}

+ 0
- 88
src/components/CreateMaterial/CreateMaterial.tsx Ver ficheiro

@@ -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;

+ 0
- 23
src/components/CreateMaterial/CreateMaterialWrapper.tsx Ver ficheiro

@@ -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

+ 0
- 1
src/components/CreateMaterial/index.ts Ver ficheiro

@@ -1 +0,0 @@
export { default } from "./CreateMaterialWrapper";

+ 202
- 0
src/components/CreateProductMaterial/CreateProductMaterial.tsx Ver ficheiro

@@ -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;

src/components/CreateMaterial/CreateMaterialLoading.tsx → src/components/CreateProductMaterial/CreateProductMaterialLoading.tsx Ver ficheiro

@@ -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;

+ 77
- 0
src/components/CreateProductMaterial/CreateProductMaterialWrapper.tsx Ver ficheiro

@@ -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;

+ 340
- 0
src/components/CreateProductMaterial/MaterialDetails.tsx Ver ficheiro

@@ -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;

+ 5
- 0
src/components/CreateProductMaterial/NumberInputProps.ts Ver ficheiro

@@ -0,0 +1,5 @@
import { InputBaseComponentProps } from "@mui/material";

export var NumberInputProps: InputBaseComponentProps = {
step: 0.01,
};

src/components/CreateMaterial/MaterialDetails.tsx → src/components/CreateProductMaterial/ProductDetails.tsx Ver ficheiro

@@ -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;

+ 1
- 0
src/components/CreateProductMaterial/index.ts Ver ficheiro

@@ -0,0 +1 @@
export { default } from "./CreateProductMaterialWrapper";

+ 17
- 28
src/components/InputDataGrid/InputDataGrid.tsx Ver ficheiro

@@ -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}


+ 15
- 26
src/components/Logo/Logo.tsx
A apresentação das diferenças no ficheiro foi suprimida por ser demasiado grande
Ver ficheiro


+ 0
- 96
src/components/MaterialSearch/MaterialSearch.tsx Ver ficheiro

@@ -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

+ 0
- 22
src/components/MaterialSearch/MaterialSearchWrapper.tsx Ver ficheiro

@@ -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;

+ 0
- 1
src/components/MaterialSearch/index.ts Ver ficheiro

@@ -1 +0,0 @@
export { default } from "./MaterialSearchWrapper";

+ 2
- 2
src/components/NavigationContent/NavigationContent.tsx Ver ficheiro

@@ -193,8 +193,8 @@ const NavigationContent: React.FC = () => {
},
{
icon: <RequestQuote />,
label: "Finished Goods",
path: "/settings/user",
label: "Product",
path: "/settings/product",
},
{
icon: <RequestQuote />,


+ 114
- 0
src/components/ProductMaterialSearch/ProductMaterialSearch.tsx Ver ficheiro

@@ -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;

src/components/MaterialSearch/MaterialSearchLoading.tsx → src/components/ProductMaterialSearch/ProductMaterialSearchLoading.tsx Ver ficheiro

@@ -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;

+ 30
- 0
src/components/ProductMaterialSearch/ProductMaterialSearchWrapper.tsx Ver ficheiro

@@ -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;

+ 1
- 0
src/components/ProductMaterialSearch/index.ts Ver ficheiro

@@ -0,0 +1 @@
export { default } from "./ProductMaterialSearchWrapper";

Carregando…
Cancelar
Guardar