Bläddra i källkod

update item save with qc checks

feature/axios_provider
MSI\derek 4 månader sedan
förälder
incheckning
bc0e3000cb
24 ändrade filer med 516 tillägg och 1204 borttagningar
  1. +0
    -24
      src/app/(main)/settings/byProduct/create/page.tsx
  2. +0
    -24
      src/app/(main)/settings/byProduct/edit/page.tsx
  3. +0
    -48
      src/app/(main)/settings/byProduct/page.tsx
  4. +5
    -5
      src/app/(main)/settings/items/create/page.tsx
  5. +2
    -2
      src/app/(main)/settings/items/edit/page.tsx
  6. +5
    -5
      src/app/(main)/settings/items/page.tsx
  7. +0
    -24
      src/app/(main)/settings/material/create/page.tsx
  8. +0
    -31
      src/app/(main)/settings/material/edit/page.tsx
  9. +0
    -48
      src/app/(main)/settings/material/page.tsx
  10. +20
    -21
      src/app/api/settings/item/actions.ts
  11. +20
    -14
      src/app/api/settings/item/index.ts
  12. +22
    -0
      src/app/api/settings/qcCheck/actions.ts
  13. +1
    -0
      src/app/utils/fetchUtil.ts
  14. +0
    -294
      src/components/CreateItem/ByProductDetails.tsx
  15. +85
    -94
      src/components/CreateItem/CreateItem.tsx
  16. +22
    -21
      src/components/CreateItem/CreateItemWrapper.tsx
  17. +0
    -325
      src/components/CreateItem/MaterialDetails.tsx
  18. +70
    -154
      src/components/CreateItem/ProductDetails.tsx
  19. +163
    -0
      src/components/CreateItem/QcDetails.tsx
  20. +72
    -22
      src/components/InputDataGrid/InputDataGrid.tsx
  21. +12
    -31
      src/components/ItemsSearch/ItemsSearch.tsx
  22. +4
    -4
      src/components/ItemsSearch/ItemsSearchWrapper.tsx
  23. +1
    -1
      src/components/Logo/Logo.tsx
  24. +12
    -12
      src/components/NavigationContent/NavigationContent.tsx

+ 0
- 24
src/app/(main)/settings/byProduct/create/page.tsx Visa fil

@@ -1,24 +0,0 @@
import { SearchParams } from "@/app/utils/fetchUtil";
import { TypeEnum } from "@/app/utils/typeEnum";
import CreateProductMaterial from "@/components/CreateItem";
import { I18nProvider, getServerI18n } from "@/i18n";
import { Typography } from "@mui/material";
import { isString } from "lodash";

type Props = {} & SearchParams;

const byProductSetting: React.FC<Props> = async ({ searchParams }) => {
const type = TypeEnum.BYPRODUCT;
const { t } = await getServerI18n(type);
console.log(searchParams);

return (
<>
{/* <Typography variant="h4">{t("Create Material")}</Typography> */}
<I18nProvider namespaces={[type.toString()]}>
<CreateProductMaterial type={type} />
</I18nProvider>
</>
);
};
export default byProductSetting;

+ 0
- 24
src/app/(main)/settings/byProduct/edit/page.tsx Visa fil

@@ -1,24 +0,0 @@
import { SearchParams } from "@/app/utils/fetchUtil";
import { TypeEnum } from "@/app/utils/typeEnum";
import CreateProductMaterial from "@/components/CreateItem";
import { I18nProvider, getServerI18n } from "@/i18n";
import { Typography } from "@mui/material";
import { isString } from "lodash";

type Props = {} & SearchParams;

const byProductSetting: React.FC<Props> = async ({ searchParams }) => {
const type = TypeEnum.BYPRODUCT;
const { t } = await getServerI18n(type);
console.log(searchParams);

return (
<>
{/* <Typography variant="h4">{t("Create Material")}</Typography> */}
<I18nProvider namespaces={[type]}>
<CreateProductMaterial type={type} />
</I18nProvider>
</>
);
};
export default byProductSetting;

+ 0
- 48
src/app/(main)/settings/byProduct/page.tsx Visa fil

@@ -1,48 +0,0 @@
import { TypeEnum } from "@/app/utils/typeEnum";
import MaterialSearch from "@/components/ItemsSearch";
import { getServerI18n } from "@/i18n";
import Add from "@mui/icons-material/Add";
import Button from "@mui/material/Button";
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import { Metadata } from "next";
import Link from "next/link";
import { Suspense } from "react";

export const metadata: Metadata = {
title: "ByProduct",
};

const materialSetting: React.FC = async () => {
const byProduct = TypeEnum.BYPRODUCT
const { t } = await getServerI18n(byProduct);
// preloadClaims();

return (
<>
<Stack
direction="row"
justifyContent="space-between"
flexWrap="wrap"
rowGap={2}
>
<Typography variant="h4" marginInlineEnd={2}>
{t("ByProduct")}
</Typography>
<Button
variant="contained"
startIcon={<Add />}
LinkComponent={Link}
href="byProduct/create"
>
{t("Create by-Product")}
</Button>
</Stack>
<Suspense fallback={<MaterialSearch.Loading />}>
<MaterialSearch type={byProduct} />
</Suspense>
</>
);
};

export default materialSetting;

src/app/(main)/settings/product/create/page.tsx → src/app/(main)/settings/items/create/page.tsx Visa fil

@@ -1,6 +1,6 @@
import { SearchParams } from "@/app/utils/fetchUtil";
import { TypeEnum } from "@/app/utils/typeEnum";
import CreateProductMaterial from "@/components/CreateItem";
import CreateItem from "@/components/CreateItem";
import { I18nProvider, getServerI18n } from "@/i18n";
import { Typography } from "@mui/material";
import isString from "lodash/isString";
@@ -8,13 +8,13 @@ import isString from "lodash/isString";
type Props = {} & SearchParams;

const materialSetting: React.FC<Props> = async ({ searchParams }) => {
const type = TypeEnum.PRODUCT;
const { t } = await getServerI18n(type);
// const type = TypeEnum.PRODUCT;
const { t } = await getServerI18n("items");
return (
<>
{/* <Typography variant="h4">{t("Create Material")}</Typography> */}
<I18nProvider namespaces={[type]}>
<CreateProductMaterial type={type}/>
<I18nProvider namespaces={["items"]}>
<CreateItem />
</I18nProvider>
</>
);

src/app/(main)/settings/product/edit/page.tsx → src/app/(main)/settings/items/edit/page.tsx Visa fil

@@ -9,7 +9,7 @@ import { notFound } from "next/navigation";
type Props = {} & SearchParams;

const productSetting: React.FC<Props> = async ({ searchParams }) => {
const type = TypeEnum.PRODUCT;
const type = "items";
const { t } = await getServerI18n(type);
const id = isString(searchParams["id"])
? parseInt(searchParams["id"])
@@ -21,7 +21,7 @@ const productSetting: React.FC<Props> = async ({ searchParams }) => {
<>
{/* <Typography variant="h4">{t("Create Material")}</Typography> */}
<I18nProvider namespaces={[type]}>
<CreateProductMaterial type={type} id={id} />
<CreateProductMaterial id={id} />
</I18nProvider>
</>
);

src/app/(main)/settings/product/page.tsx → src/app/(main)/settings/items/page.tsx Visa fil

@@ -1,5 +1,5 @@
import { TypeEnum } from "@/app/utils/typeEnum";
import MaterialSearch from "@/components/ItemsSearch";
import ItemsSearch from "@/components/ItemsSearch";
import { getServerI18n } from "@/i18n";
import Add from "@mui/icons-material/Add";
import Button from "@mui/material/Button";
@@ -29,17 +29,17 @@ const productSetting: React.FC = async () => {
<Typography variant="h4" marginInlineEnd={2}>
{t("Product")}
</Typography>
<Button
{/* <Button
variant="contained"
startIcon={<Add />}
LinkComponent={Link}
href="product/create"
>
{t("Create product")}
</Button>
</Button> */}
</Stack>
<Suspense fallback={<MaterialSearch.Loading />}>
<MaterialSearch type={project} />
<Suspense fallback={<ItemsSearch.Loading />}>
<ItemsSearch />
</Suspense>
</>
);

+ 0
- 24
src/app/(main)/settings/material/create/page.tsx Visa fil

@@ -1,24 +0,0 @@
import { SearchParams } from "@/app/utils/fetchUtil";
import { TypeEnum } from "@/app/utils/typeEnum";
import CreateProductMaterial from "@/components/CreateItem";
import { I18nProvider, getServerI18n } from "@/i18n";
import { Typography } from "@mui/material";
import { isString } from "lodash";

type Props = {} & SearchParams;

const materialSetting: React.FC<Props> = async ({ searchParams }) => {
const type = TypeEnum.MATERIAL;
const { t } = await getServerI18n(type);
console.log(searchParams);

return (
<>
{/* <Typography variant="h4">{t("Create Material")}</Typography> */}
<I18nProvider namespaces={[type]}>
<CreateProductMaterial type={type} />
</I18nProvider>
</>
);
};
export default materialSetting;

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

@@ -1,31 +0,0 @@
import { SearchParams } from "@/app/utils/fetchUtil";
import { TypeEnum } from "@/app/utils/typeEnum";
import CreateProductMaterial from "@/components/CreateItem";
import { I18nProvider, getServerI18n } from "@/i18n";
import { Typography } from "@mui/material";
import { isString } from "lodash";
import { notFound } from "next/navigation";

type Props = {} & SearchParams;

const materialSetting: React.FC<Props> = async ({ searchParams }) => {
const type = TypeEnum.MATERIAL;
const { t } = await getServerI18n(type);
console.log(searchParams);
const id = isString(searchParams["id"])
? parseInt(searchParams["id"])
: undefined;
console.log(id)
if (!id) {
notFound();
}
return (
<>
{/* <Typography variant="h4">{t("Create Material")}</Typography> */}
<I18nProvider namespaces={[type.toString()]}>
<CreateProductMaterial type={type} id={id} />
</I18nProvider>
</>
);
};
export default materialSetting;

+ 0
- 48
src/app/(main)/settings/material/page.tsx Visa fil

@@ -1,48 +0,0 @@
import { TypeEnum } from "@/app/utils/typeEnum";
import MaterialSearch from "@/components/ItemsSearch";
import { getServerI18n } from "@/i18n";
import Add from "@mui/icons-material/Add";
import Button from "@mui/material/Button";
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import { Metadata } from "next";
import Link from "next/link";
import { Suspense } from "react";

export const metadata: Metadata = {
title: "Material",
};

const materialSetting: React.FC = async () => {
const material = TypeEnum.MATERIAL
const { t } = await getServerI18n("material");
// preloadClaims();

return (
<>
<Stack
direction="row"
justifyContent="space-between"
flexWrap="wrap"
rowGap={2}
>
<Typography variant="h4" marginInlineEnd={2}>
{t("Material")}
</Typography>
<Button
variant="contained"
startIcon={<Add />}
LinkComponent={Link}
href="material/create"
>
{t("Create material")}
</Button>
</Stack>
<Suspense fallback={<MaterialSearch.Loading />}>
<MaterialSearch type={material} />
</Suspense>
</>
);
};

export default materialSetting;

+ 20
- 21
src/app/api/settings/item/actions.ts Visa fil

@@ -2,20 +2,23 @@
import { ServerFetchError, serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil";
import { revalidateTag } from "next/cache";
import { BASE_API_URL } from "@/config/api";
import { HTMLInputTypeAttribute } from "react";
import { CreateItemResponse } from "../../utils";
import { ItemQc } from ".";
import { QcChecksInputs } from "../qcCheck/actions";

// export type TypeInputs = {
// id: number;
// name: string
// }
// export type UomInputs = {
// uom: string
// }
// export type WeightUnitInputs = {
// weightUnit: string
// conversion: number
// }


export type TypeInputs = {
id: number;
name: string
}
export type UomInputs = {
uom: string
}
export type WeightUnitInputs = {
weightUnit: string
conversion: number
}
export type CreateItemInputs = {
id?: string | number
code: string;
@@ -24,14 +27,10 @@ export type CreateItemInputs = {
remarks?: string | undefined;
shelfLife?: Number | undefined;
countryOfOrigin?: string | undefined;
minHumid?: number | undefined;
maxHumid?: number | undefined;
minTemp?: number | undefined;
maxTemp?: number | undefined;
sampleRate?: number | undefined;
passingRate?: number | undefined;
netWeight: number;
typeId: number;
maxQty: number;
type: string;
qcChecks: QcChecksInputs[]
qcChecks_active: number[]
}

export const saveItem = async (data: CreateItemInputs) => {
@@ -43,4 +42,4 @@ export const saveItem = async (data: CreateItemInputs) => {
});
revalidateTag("items");
return item
};
};

+ 20
- 14
src/app/api/settings/item/index.ts Visa fil

@@ -2,7 +2,18 @@ import { cache } from "react";
import "server-only";
import { serverFetchJson } from "@/app/utils/fetchUtil";
import { BASE_API_URL } from "@/config/api";
import { TypeInputs, UomInputs, WeightUnitInputs } from "./actions";
// import { TypeInputs, UomInputs, WeightUnitInputs } from "./actions";

export type ItemQc = {
id: number; // id = qc_check #
name: string;
code: string;
description: string | undefined;
instruction: string | undefined;
lowerLimit: number | undefined;
upperLimit: number | undefined;
isActive: boolean | undefined;
}

export type ItemsResult = {
id: string | number
@@ -12,20 +23,15 @@ export type ItemsResult = {
remarks: string | undefined;
shelfLife: Number | undefined;
countryOfOrigin: string | undefined;
minHumid: number | undefined;
maxHumid: number | undefined;
minTemp: number | undefined;
maxTemp: number | undefined;
sampleRate: number | undefined;
passingRate: number | undefined;
netWeight: number | undefined;
// uom: UomInputs[];
// weightUnit: WeightUnitInputs[];
type: TypeInputs;
maxQty: number | undefined;
type: string;
qcChecks: ItemQc[]
action?: any
}


export type Result = {
item: ItemsResult
qcChecks: ItemQc[]
}
export const fetchAllItems = cache(async () => {
return serverFetchJson<ItemsResult[]>(`${BASE_API_URL}/items`, {
next: { tags: ["items"] },
@@ -34,7 +40,7 @@ export const fetchAllItems = cache(async () => {


export const fetchItem = cache(async (id: number) => {
return serverFetchJson<ItemsResult>(`${BASE_API_URL}/items/details/${id}`, {
return serverFetchJson<Result>(`${BASE_API_URL}/items/details/${id}`, {
next: { tags: ["items"] },
});
});

+ 22
- 0
src/app/api/settings/qcCheck/actions.ts Visa fil

@@ -0,0 +1,22 @@
"use server";
import { ServerFetchError, serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil";
import { revalidateTag } from "next/cache";
import { BASE_API_URL } from "@/config/api";
import { CreateItemResponse } from "../../utils";
import { ItemQc } from "../item";

export type QcChecksInputs = {

} & Partial<ItemQc>


export const saveItemQcChecks = async (data: QcChecksInputs[]) => {
// try {
const res = await serverFetchJson<CreateItemResponse<QcChecksInputs>>(`${BASE_API_URL}/qcCheck/new`, {
method: "POST",
body: JSON.stringify(data),
headers: { "Content-Type": "application/json" },
});
revalidateTag("items");
return res
};

+ 1
- 0
src/app/utils/fetchUtil.ts Visa fil

@@ -56,6 +56,7 @@ type FetchParams = Parameters<typeof fetch>;

export async function serverFetchJson<T>(...args: FetchParams) {
const response = await serverFetch(...args);
console.log(response.status)
if (response.ok) {
if (response.status === 204) {
return response.status as T


+ 0
- 294
src/components/CreateItem/ByProductDetails.tsx Visa fil

@@ -1,294 +0,0 @@
"use client";
import { CreateItemInputs } from "@/app/api/settings/item/actions";
import {
Box,
Card,
CardContent,
Grid,
Stack,
TextField,
Typography,
} from "@mui/material";
import { useFormContext } from "react-hook-form";
import { useTranslation } from "react-i18next";
import InputDataGrid from "../InputDataGrid";
import { useCallback, useEffect, useMemo, useState } from "react";
import { GridColDef, GridRowModel } from "@mui/x-data-grid";
import { InputDataGridProps, TableRow } from "../InputDataGrid/InputDataGrid";
import { TypeEnum } from "@/app/utils/typeEnum";
import { NumberInputProps } from "./NumberInputProps";

type Props = {
// isEditMode: boolean;
// type: TypeEnum;
};

export type EntryError =
| {
[field in keyof CreateItemInputs]?: string;
}
| undefined;

const ByProductDetails: React.FC<Props> = ({}) => {
const {
t,
i18n: { language },
} = useTranslation();

const {
register,
formState: { errors, defaultValues, touchedFields },
watch,
control,
setValue,
getValues,
reset,
resetField,
setError,
clearErrors,
} = useFormContext<CreateItemInputs>();
const typeColumns = useMemo<GridColDef[]>(
() => [
{
field: "type",
headerName: "type",
flex: 1,
editable: true,
},
],
[]
);
const weightUnitColumns = useMemo<GridColDef[]>(
() => [
{
field: "weightUnit",
headerName: "Weight Unit",
flex: 1,
editable: true,
},
{
field: "conversion",
headerName: "conversion", // show base unit
flex: 1,
type: "number",
editable: true,
},
],
[]
);
const uomColumns = useMemo<GridColDef[]>(
() => [
{
field: "uom",
headerName: "uom",
flex: 1,
editable: true,
},
],
[]
);

const validationTest = useCallback(
(
newRow: GridRowModel<TableRow<Partial<CreateItemInputs>, EntryError>>
): EntryError => {
const error: EntryError = {};
console.log(newRow);
return Object.keys(error).length > 0 ? error : undefined;
},
[]
);

const validateUom = useCallback(
(
newRow: GridRowModel<TableRow<Partial<CreateItemInputs>, EntryError>>
): EntryError => {
const error: EntryError = {};
console.log(newRow);
// if (!newRow.uom) {
// error.uom = "Uom cannot be empty";
// }
return Object.keys(error).length > 0 ? error : undefined;
},
[]
);
useEffect(() => {
console.log(errors)
}, [errors])
return (
<Card sx={{ display: "block" }}>
<CardContent component={Stack} spacing={4}>
<Box>
<Typography variant="overline" display="block" marginBlockEnd={1}>
{t("Product Details")}
</Typography>
<Grid container spacing={2} columns={{ xs: 6, sm: 12 }}>
<Grid item xs={6}>
<TextField
label={t("Name")}
fullWidth
{...register("name", {
required: "name required!",
})}
error={Boolean(errors.name)}
helperText={errors.name?.message}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("Code")}
fullWidth
{...register("code", {
required: "code required!",
})}
error={Boolean(errors.code)}
helperText={errors.code?.message}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("description")}
fullWidth
{...register("description")}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("shelfLife")}
type="number"
fullWidth
{...register("shelfLife", {
valueAsNumber: true,
required: "shelfLife required!",
})}
error={Boolean(errors.shelfLife)}
helperText={errors.shelfLife?.message}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("countryOfOrigin")}
fullWidth
{...register("countryOfOrigin", {
required: "countryOfOrigin required!",
})}
error={Boolean(errors.countryOfOrigin)}
helperText={errors.countryOfOrigin?.message}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("minHumid")}
type="number"
fullWidth
inputProps={NumberInputProps}
{...register("minHumid", {
valueAsNumber: true,
required: "minHumid required!",
})}
error={Boolean(errors.minHumid)}
helperText={errors.minHumid?.message}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("maxHumid")}
type="number"
fullWidth
inputProps={NumberInputProps}
{...register("maxHumid", {
valueAsNumber: true,
})}
error={Boolean(errors.maxHumid)}
helperText={errors.maxHumid?.message}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("minTemp")}
type="number"
fullWidth
inputProps={NumberInputProps}
{...register("minTemp", {
valueAsNumber: true,
required: "minTemp required!",
})}
error={Boolean(errors.minTemp)}
helperText={errors.minTemp?.message}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("maxTemp")}
type="number"
fullWidth
inputProps={NumberInputProps}
{...register("maxTemp", {
valueAsNumber: true,
required: "maxTemp required!",
})}
error={Boolean(errors.maxTemp)}
helperText={errors.maxTemp?.message}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("sampleRate")}
type="number"
fullWidth
inputProps={NumberInputProps}
{...register("sampleRate", {
valueAsNumber: true,
required: "sampleRate required!",
})}
error={Boolean(errors.sampleRate)}
helperText={errors.sampleRate?.message}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("passingRate")}
type="number"
fullWidth
inputProps={NumberInputProps}
{...register("passingRate", {
valueAsNumber: true,
required: "passingRate required!",
})}
error={Boolean(errors.passingRate)} // change backend for null or not null
helperText={errors.passingRate?.message}
/>
</Grid>
<Grid item xs={6} />
<Grid item xs={6}>
<TextField
label={t("remarks")}
fullWidth
{...register("remarks", {
// required: "remarks required!",
})}
error={Boolean(errors.remarks)}
helperText={errors.remarks?.message}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("netWeight")}
type="number"
fullWidth
inputProps={NumberInputProps}
{...register("netWeight", {
valueAsNumber: true,
required: "netWeight required!",
})}
error={Boolean(errors.netWeight)}
helperText={errors.netWeight?.message}
/>
</Grid>
</Grid>
</Box>
</CardContent>
</Card>
);
};
export default ByProductDetails;

+ 85
- 94
src/components/CreateItem/CreateItem.tsx Visa fil

@@ -14,140 +14,126 @@ import {
useForm,
} from "react-hook-form";
import { deleteDialog } from "../Swal/CustomAlerts";
import { Box, Button, Grid, Stack, Typography } from "@mui/material";
import { Box, Button, Grid, Stack, Tab, Tabs, TabsProps, Typography } from "@mui/material";
import { Check, Close, EditNote } from "@mui/icons-material";
import { TypeEnum } from "@/app/utils/typeEnum";
import MaterialDetails from "./MaterialDetails";
import ProductDetails from "./ProductDetails";
import { CreateItemResponse } from "@/app/api/utils";
import ByProductDetails from "./ByProductDetails";
import QcDetails from "./QcDetails";
import { ItemQc } from "@/app/api/settings/item";
import { saveItemQcChecks } from "@/app/api/settings/qcCheck/actions";
import { useGridApiRef } from "@mui/x-data-grid";

type Props = {
isEditMode: boolean;
type: TypeEnum;
// type: TypeEnum;
defaultValues: Partial<CreateItemInputs> | undefined;
qcChecks: ItemQc[]
};

const CreateItem: React.FC<Props> = ({
isEditMode,
type,
// type,
defaultValues,
qcChecks
}) => {
console.log(type)
// console.log(type)
const apiRef = useGridApiRef();
const params = useSearchParams()
console.log(params.get("id"))
const [serverError, setServerError] = useState("");
const [tabIndex, setTabIndex] = useState(0);
const { t } = useTranslation();
const router = useRouter();
const [typeId, title, mode, redirPath] = useMemo(() => {
var typeId = TypeEnum.CONSUMABLE_ID
const title = "Product / Material"
const [mode, redirPath] = useMemo(() => {
// var typeId = TypeEnum.CONSUMABLE_ID
var title = "";
var mode = "";
var redirPath = "";
if (type === TypeEnum.MATERIAL) {
typeId = TypeEnum.MATERIAL_ID
title = "Material";
redirPath = "/settings/material";
}
if (type === TypeEnum.PRODUCT) {
typeId = TypeEnum.PRODUCT_ID
// if (type === TypeEnum.MATERIAL) {
// typeId = TypeEnum.MATERIAL_ID
// title = "Material";
// redirPath = "/settings/material";
// }
// if (type === TypeEnum.PRODUCT) {
// typeId = TypeEnum.PRODUCT_ID
title = "Product";
redirPath = "/settings/product";
}
if (type === TypeEnum.BYPRODUCT) {
typeId = TypeEnum.BYPRODUCT_ID
title = "By-Product";
redirPath = "/settings/byProduct";
}
redirPath = "/settings/items";
// }
// if (type === TypeEnum.BYPRODUCT) {
// typeId = TypeEnum.BYPRODUCT_ID
// title = "By-Product";
// redirPath = "/settings/byProduct";
// }
if (isEditMode) {
mode = "Edit";
} else {
mode = "Create";
}
return [typeId, title, mode, redirPath];
}, [type, isEditMode]);
console.log(typeId)
return [mode, redirPath];
}, [isEditMode]);
// console.log(typeId)
const formProps = useForm<CreateItemInputs>({
defaultValues: defaultValues ? defaultValues : {
typeId: typeId
},
});
const errors = formProps.formState.errors;

const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>(
(_e, newValue) => {
setTabIndex(newValue);
},
[],
);

const handleCancel = () => {
router.replace(`/settings/${type}`);
router.replace(`/settings/product`);
};
const onSubmit = useCallback<SubmitHandler<CreateItemInputs>>(
const onSubmit = useCallback<SubmitHandler<CreateItemInputs & {}>>(
async (data, event) => {
let hasErrors = false;
console.log("dasasdasd")
console.log(errors)
// console.log(apiRef.current.getCellValue(2, "lowerLimit"))
// apiRef.current.
try {
// checking humid input
if (data.maxHumid && data.minHumid!! > data.maxHumid!!) {
const message = "minHumid should not be greater than maxHumid";
formProps.setError("minHumid", {
message: message,
type: "required",
});
formProps.setError("maxHumid", {
message: message,
type: "required",
});
hasErrors = true;
}
// checking temp input
if (data.maxTemp && data.minTemp!! > data.maxTemp!!) {
const message = "minTemp should not be greater than maxTemp";
formProps.setError("minTemp", {
message: message,
type: "required",
});
formProps.setError("maxTemp", {
message: message,
type: "required",
});
hasErrors = true;
}
// checking temp input
if (data.netWeight && data.netWeight < 0) {
const message = "netWeight should not be greater than 0";
formProps.setError("netWeight", {
message: message,
type: "required",
});
}
// checking passing rate
if (data.passingRate && (data.passingRate < 0 || data.passingRate > 100)) {
const message = "passingRate should not be greater than 100%";
formProps.setError("passingRate", {
message: message,
type: "required",
});
}
// checking sampling rate
if (data.sampleRate && (data.sampleRate < 0 || data.sampleRate > 100)) {
const message = "sampleRate should not be greater than 100%";
formProps.setError("sampleRate", {
message: message,
type: "required",
});
}
if (hasErrors) {
setServerError(t("An error has occurred. Please try again later."));
return false;
}
console.log("data posted");
console.log(data);
const qcCheck = data.qcChecks.length > 0 ? data.qcChecks.filter((q) => data.qcChecks_active.includes(q.id!!)).map((qc) => {
return {
qcItemId: qc.id,
instruction: qc.instruction,
lowerLimit: qc.lowerLimit,
upperLimit: qc.upperLimit,
itemId: parseInt(params.get("id")!.toString())
}
}) : []

const test = data.qcChecks.filter((q) => data.qcChecks_active.includes(q.id!!))
// TODO:
// 1. check field ( directly modify col def / check here )
// 2. set error change tab index
console.log(test)
console.log(qcCheck)
// return
// do api
var response = await saveItem(data);
if (response) {
if (!Boolean(response.id)) {
formProps.setError(response.errorPosition!! as keyof CreateItemInputs, {
message: response.message!!,
console.log("asdad")
var responseI = await saveItem(data);
console.log("asdad")
var responseQ = await saveItemQcChecks(qcCheck)
if (responseI && responseQ) {
if (!Boolean(responseI.id)) {
formProps.setError(responseI.errorPosition!! as keyof CreateItemInputs, {
message: responseI.message!!,
type: "required",
});
} else if (Boolean(response.id)) {
} else if (!Boolean(responseQ.id)) {
} else if (Boolean(responseI.id) && Boolean(responseQ.id)) {
router.replace(redirPath);
}
}
@@ -157,7 +143,7 @@ const CreateItem: React.FC<Props> = ({
console.log(e);
}
},
[router, t]
[apiRef, router, t]
);

// multiple tabs
@@ -174,19 +160,24 @@ const CreateItem: React.FC<Props> = ({
component="form"
onSubmit={formProps.handleSubmit(onSubmit, onSubmitError)}
>
<Grid>
<Typography mb={2} variant="h4">
{t(`${mode} ${title}`)}
</Typography>
</Grid>
<Grid>
<Typography mb={2} variant="h4">
{t(`${mode} ${title}`)}
</Typography>
</Grid>
<Tabs value={tabIndex} onChange={handleTabChange} variant="scrollable">
<Tab label={t("Product / Material Details")} iconPosition="end"/>
<Tab label={t("Qc items")} iconPosition="end" />
</Tabs>
{serverError && (
<Typography variant="body2" color="error" alignSelf="flex-end">
{serverError}
</Typography>
)}
{type === TypeEnum.MATERIAL && <MaterialDetails />}
{type === TypeEnum.PRODUCT && <ProductDetails />}
{type === TypeEnum.BYPRODUCT && <ByProductDetails />}
{tabIndex === 0 && <ProductDetails />}
{tabIndex === 1 && <QcDetails apiRef={apiRef} />}
{/* {type === TypeEnum.MATERIAL && <MaterialDetails />} */}
{/* {type === TypeEnum.BYPRODUCT && <ByProductDetails />} */}
<Stack direction="row" justifyContent="flex-end" gap={1}>
<Button
name="submit"


+ 22
- 21
src/components/CreateItem/CreateItemWrapper.tsx Visa fil

@@ -4,47 +4,48 @@ import CreateItemLoading from "./CreateItemLoading";
import { CreateItemInputs } from "@/app/api/settings/item/actions";
import { notFound } from "next/navigation";
import { fetchItem } from "@/app/api/settings/item";
import { fetchQcItems } from "@/app/api/settings/qcItem";
interface SubComponents {
Loading: typeof CreateItemLoading;
}

type Props = {
id?: number
type: TypeEnum;
// type: TypeEnum;
};

const CreateItemWrapper: React.FC<Props> &
SubComponents = async ({ type, id }) => {
SubComponents = async ({ id }) => {
var result
var defaultValues: Partial<CreateItemInputs> | undefined
console.log(type)
var defaultValues: Partial<CreateItemInputs> | undefined
// console.log(type)
var qcChecks
if (id) {
result = await fetchItem(id);
console.log(result)
const item = result.item
qcChecks = result.qcChecks
const activeRows = qcChecks.filter(it => it.isActive).map(i => i.id)
console.log(qcChecks)
defaultValues = {
typeId: result?.type.id,
id: result?.id,
code: result?.code,
name: result?.name,
description: result?.description,
remarks: result?.remarks,
shelfLife: result?.shelfLife,
countryOfOrigin: result?.countryOfOrigin,
minHumid: result?.minHumid,
maxHumid: result?.maxHumid,
minTemp: result?.minTemp,
maxTemp: result?.maxTemp,
sampleRate: result?.sampleRate,
passingRate: result?.passingRate,
netWeight: result?.netWeight
type: item?.type,
id: item?.id,
code: item?.code,
name: item?.name,
description: item?.description,
remarks: item?.remarks,
shelfLife: item?.shelfLife,
countryOfOrigin: item?.countryOfOrigin,
maxQty: item?.maxQty,
qcChecks: qcChecks,
qcChecks_active: activeRows
};
}
return (
<CreateItem
isEditMode={Boolean(id)}
type={type}
defaultValues={defaultValues}
qcChecks={qcChecks || []}
/>
);
};


+ 0
- 325
src/components/CreateItem/MaterialDetails.tsx Visa fil

@@ -1,325 +0,0 @@
"use client";
import { CreateItemInputs } from "@/app/api/settings/item/actions";
import {
Box,
Card,
CardContent,
Checkbox,
FilledInputProps,
FormControlLabel,
FormGroup,
Grid,
InputBaseComponentProps,
OutlinedInputProps,
Stack,
TextField,
Typography,
} from "@mui/material";
import { Controller, useFormContext } from "react-hook-form";
import { useTranslation } from "react-i18next";
import InputDataGrid from "../InputDataGrid";
import { useCallback, useEffect, useMemo, useState } from "react";
import { GridColDef, GridRowModel } from "@mui/x-data-grid";
import { InputDataGridProps, TableRow } from "../InputDataGrid/InputDataGrid";
import { TypeEnum } from "@/app/utils/typeEnum";
import { NumberInputProps } from "./NumberInputProps";

type Props = {
// isEditMode: boolean;
// type: TypeEnum;
};

export type EntryError =
| {
[field in keyof CreateItemInputs]?: string;
}
| undefined;

const MaterialDetails: React.FC<Props> = ({}) => {
const {
t,
i18n: { language },
} = useTranslation();

const {
register,
formState: { errors, defaultValues, touchedFields },
watch,
control,
setValue,
getValues,
reset,
resetField,
setError,
clearErrors,
} = useFormContext<CreateItemInputs>();
// textfield error msg locale problem
console.log(defaultValues)
const typeColumns = useMemo<GridColDef[]>(
() => [
{
field: "type",
headerName: "type",
flex: 1,
editable: true,
},
],
[]
);
const weightUnitColumns = useMemo<GridColDef[]>(
() => [
{
field: "weightUnit",
headerName: "Weight Unit",
flex: 1,
editable: true,
},
{
field: "conversion",
headerName: "conversion", // show base unit
flex: 1,
type: "number",
editable: true,
},
],
[]
);
const uomColumns = useMemo<GridColDef[]>(
() => [
{
field: "uom",
headerName: "uom",
flex: 1,
editable: true,
},
],
[]
);

const validationTest = useCallback(
(
newRow: GridRowModel<TableRow<Partial<CreateItemInputs>, EntryError>>
): EntryError => {
const error: EntryError = {};
console.log(newRow);
return Object.keys(error).length > 0 ? error : undefined;
},
[]
);

return (
<Card sx={{ display: "block" }}>
<CardContent component={Stack} spacing={4}>
<Box>
<Stack direction="row" justifyContent="space-between" gap={1}>
<Typography variant="overline" display="block" marginBlockEnd={1}>
{t("Material Details")}
</Typography>
<FormControlLabel
control={
<Controller
name={"typeId"}
control={control}
render={({ field: props }) => {
console.log(props)
return (
<Checkbox
{...props}
checked={props.value === TypeEnum.CONSUMABLE_ID}
onChange={(e) => {
const newValue = e.target.checked ? TypeEnum.CONSUMABLE_ID : TypeEnum.MATERIAL_ID;
props.onChange(newValue);
}}
/>
)}}
/>
}
label={
<Typography
variant="overline"
display="block"
marginBlockEnd={1}
>
{t("Consumables")}
</Typography>
}
/>
</Stack>
<Grid container spacing={2} columns={{ xs: 6, sm: 12 }}>
<Grid item xs={6}>
<TextField
label={t("Name")}
fullWidth
{...register("name", {
required: "name required!",
})}
error={Boolean(errors.name)}
helperText={errors.name?.message}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("Code")}
fullWidth
{...register("code", {
required: "code required!",
})}
error={Boolean(errors.code)}
helperText={errors.code?.message}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("description")}
fullWidth
{...register("description")}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("shelfLife")}
type="number"
fullWidth
{...register("shelfLife", {
valueAsNumber: true,
required: "shelfLife required!",
})}
error={Boolean(errors.shelfLife)}
helperText={errors.shelfLife?.message}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("countryOfOrigin")}
fullWidth
{...register("countryOfOrigin", {
required: "countryOfOrigin required!",
})}
error={Boolean(errors.countryOfOrigin)}
helperText={errors.countryOfOrigin?.message}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("minHumid")}
type="number"
fullWidth
inputProps={NumberInputProps}
{...register("minHumid", {
valueAsNumber: true,
// min: 0,
// max: watch("maxHumid") || 100,
required: "minHumid required!",
})}
error={Boolean(errors.minHumid)}
helperText={errors.minHumid?.message}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("maxHumid")}
type="number"
fullWidth
inputProps={NumberInputProps}
{...register("maxHumid", {
valueAsNumber: true,
})}
error={Boolean(errors.maxHumid)}
helperText={errors.maxHumid?.message}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("minTemp")}
type="number"
fullWidth
inputProps={NumberInputProps}
{...register("minTemp", {
valueAsNumber: true,
required: "minTemp required!",
})}
error={Boolean(errors.minTemp)}
helperText={errors.minTemp?.message}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("maxTemp")}
type="number"
fullWidth
inputProps={NumberInputProps}
{...register("maxTemp", {
valueAsNumber: true,
// min: watch("minTemp"),
required: "maxTemp required!",
})}
error={Boolean(errors.maxTemp)}
helperText={errors.maxTemp?.message}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("sampleRate")}
type="number"
fullWidth
inputProps={NumberInputProps}
{...register("sampleRate", {
valueAsNumber: true,
min: 0,
max: 100,
required: "sampleRate required!",
})}
error={Boolean(errors.sampleRate)}
helperText={errors.sampleRate?.message}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("passingRate")}
type="number"
fullWidth
inputProps={NumberInputProps}
{...register("passingRate", {
valueAsNumber: true,
min: 0,
max: 100,
required: "passingRate required!",
})}
error={Boolean(errors.passingRate)} // change backend for null or not null
helperText={errors.passingRate?.message}
/>
</Grid>
<Grid item xs={6} />
<Grid item xs={6}>
<TextField
label={t("remarks")}
fullWidth
{...register("remarks", {
// required: "remarks required!",
})}
error={Boolean(errors.remarks)}
helperText={errors.remarks?.message}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("netWeight")}
type="number"
fullWidth
inputProps={NumberInputProps}
{...register("netWeight", {
valueAsNumber: true,
min: 0,
required: "netWeight required!",
})}
error={Boolean(errors.netWeight)}
helperText={errors.netWeight?.message}
/>
</Grid>
</Grid>
</Box>
</CardContent>
</Card>
);
};
export default MaterialDetails;

+ 70
- 154
src/components/CreateItem/ProductDetails.tsx Visa fil

@@ -19,19 +19,11 @@ import { NumberInputProps } from "./NumberInputProps";
import { CreateItemInputs } from "@/app/api/settings/item/actions";

type Props = {
// isEditMode: boolean;
// type: TypeEnum;
// isEditMode: boolean;
// type: TypeEnum;
};

export type EntryError =
| {
[field in keyof CreateItemInputs]?: string;
}
| undefined;

const ProductDetails: React.FC<Props> = ({
}) => {
const ProductDetails: React.FC<Props> = ({}) => {
const {
t,
i18n: { language },
@@ -49,57 +41,57 @@ const ProductDetails: React.FC<Props> = ({
setError,
clearErrors,
} = useFormContext<CreateItemInputs>();
const typeColumns = useMemo<GridColDef[]>(
() => [
{
field: "type",
headerName: "type",
flex: 1,
editable: true,
},
],
[]
);
const weightUnitColumns = useMemo<GridColDef[]>(
() => [
{
field: "weightUnit",
headerName: "Weight Unit",
flex: 1,
editable: true,
},
{
field: "conversion",
headerName: "conversion", // show base unit
flex: 1,
type: "number",
editable: true,
},
],
[]
);
const uomColumns = useMemo<GridColDef[]>(
() => [
{
field: "uom",
headerName: "uom",
flex: 1,
editable: true,
},
],
[]
);
// const typeColumns = useMemo<GridColDef[]>(
// () => [
// {
// field: "type",
// headerName: "type",
// flex: 1,
// editable: true,
// },
// ],
// []
// );
// const weightUnitColumns = useMemo<GridColDef[]>(
// () => [
// {
// field: "weightUnit",
// headerName: "Weight Unit",
// flex: 1,
// editable: true,
// },
// {
// field: "conversion",
// headerName: "conversion", // show base unit
// flex: 1,
// type: "number",
// editable: true,
// },
// ],
// []
// );
// const uomColumns = useMemo<GridColDef[]>(
// () => [
// {
// field: "uom",
// headerName: "uom",
// flex: 1,
// editable: true,
// },
// ],
// []
// );

const validationTest = useCallback(
(
newRow: GridRowModel<TableRow<Partial<CreateItemInputs>, EntryError>>
): EntryError => {
const error: EntryError = {};
console.log(newRow);
return Object.keys(error).length > 0 ? error : undefined;
},
[]
);
// const validationTest = useCallback(
// (
// newRow: GridRowModel<TableRow<Partial<CreateItemInputs>, EntryError>>
// ): EntryError => {
// const error: EntryError = {};
// console.log(newRow);
// return Object.keys(error).length > 0 ? error : undefined;
// },
// []
// );

return (
<Card sx={{ display: "block" }}>
@@ -131,6 +123,17 @@ const ProductDetails: React.FC<Props> = ({
helperText={errors.code?.message}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("Type")}
fullWidth
{...register("type", {
required: "type required!",
})}
error={Boolean(errors.type)}
helperText={errors.type?.message}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("description")}
@@ -162,93 +165,6 @@ const ProductDetails: React.FC<Props> = ({
helperText={errors.countryOfOrigin?.message}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("minHumid")}
type="number"
fullWidth
inputProps={NumberInputProps}
{...register("minHumid", {
valueAsNumber: true,
// min: 0,
// max: watch("maxHumid") || 100,
required: "minHumid required!",
})}
error={Boolean(errors.minHumid)}
helperText={errors.minHumid?.message}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("maxHumid")}
type="number"
fullWidth
inputProps={NumberInputProps}
{...register("maxHumid", {
valueAsNumber: true,
})}
error={Boolean(errors.maxHumid)}
helperText={errors.maxHumid?.message}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("minTemp")}
type="number"
fullWidth
inputProps={NumberInputProps}
{...register("minTemp", {
valueAsNumber: true,
required: "minTemp required!",
})}
error={Boolean(errors.minTemp)}
helperText={errors.minTemp?.message}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("maxTemp")}
type="number"
fullWidth
inputProps={NumberInputProps}
{...register("maxTemp", {
valueAsNumber: true,
// min: watch("minTemp"),
required: "maxTemp required!",
})}
error={Boolean(errors.maxTemp)}
helperText={errors.maxTemp?.message}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("sampleRate")}
type="number"
fullWidth
inputProps={NumberInputProps}
{...register("sampleRate", {
valueAsNumber: true,
required: "sampleRate required!",
})}
error={Boolean(errors.sampleRate)}
helperText={errors.sampleRate?.message}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("passingRate")}
type="number"
fullWidth
inputProps={NumberInputProps}
{...register("passingRate", {
valueAsNumber: true,
required: "passingRate required!",
})}
error={Boolean(errors.passingRate)} // change backend for null or not null
helperText={errors.passingRate?.message}
/>
</Grid>
<Grid item xs={6} />
<Grid item xs={6}>
<TextField
label={t("remarks")}
@@ -262,17 +178,17 @@ const ProductDetails: React.FC<Props> = ({
</Grid>
<Grid item xs={6}>
<TextField
label={t("netWeight")}
label={t("maxQty")}
type="number"
fullWidth
inputProps={NumberInputProps}
{...register("netWeight", {
{...register("maxQty", {
valueAsNumber: true,
min: 0,
required: "netWeight required!",
required: "maxQty required!",
})}
error={Boolean(errors.netWeight)}
helperText={errors.netWeight?.message}
error={Boolean(errors.maxQty)}
helperText={errors.maxQty?.message}
/>
</Grid>
{/* <Grid item xs={6}>
@@ -296,7 +212,7 @@ const ProductDetails: React.FC<Props> = ({
validateRow={validationTest}
/>
</Grid>*/}
</Grid>
</Grid>
</Box>
</CardContent>
</Card>


+ 163
- 0
src/components/CreateItem/QcDetails.tsx Visa fil

@@ -0,0 +1,163 @@
"use client";

import { CreateItemInputs } from "@/app/api/settings/item/actions";
import {
GridColDef,
GridRowModel,
GridRenderEditCellParams,
GridEditInputCell,
GridRowSelectionModel,
useGridApiRef,
} from "@mui/x-data-grid";
import { MutableRefObject, useCallback, useMemo } from "react";
import { useFormContext } from "react-hook-form";
import { useTranslation } from "react-i18next";
import InputDataGrid, { TableRow } from "../InputDataGrid/InputDataGrid";
import { Box, Grid, Tooltip } from "@mui/material";
import { ItemQc } from "@/app/api/settings/item";
import { QcChecksInputs } from "@/app/api/settings/qcCheck/actions";
import { GridApiCommunity } from "@mui/x-data-grid/internals";
import { RiceBowl } from "@mui/icons-material";

type Props = {
apiRef: MutableRefObject<GridApiCommunity>
};
type EntryError =
| {
[field in keyof QcChecksInputs]?: string;
}
| undefined;

type QcRow = TableRow<Partial<QcChecksInputs>, EntryError>
const QcDetails: React.FC<Props> = ({ apiRef }) => {
const {
t,
i18n: { language },
} = useTranslation();

const {
register,
formState: { errors, defaultValues, touchedFields },
watch,
control,
setValue,
getValues,
reset,
resetField,
setError,
clearErrors,
} = useFormContext<CreateItemInputs>();
// const apiRef = useGridApiRef();
const qcColumns = useMemo<GridColDef[]>(
() => [
{
field: "name",
headerName: "name",
flex: 1,
// editable: true,
},
{
field: "code",
headerName: "code",
flex: 1,
// editable: true,
},
{
field: "description",
headerName: "Description",
flex: 1,
// editable: true,
},
{
field: "instruction",
headerName: "Instruction",
flex: 1,
editable: true,
},
{
field: "lowerLimit",
headerName: "lowerLimit",
flex: 1,
editable: true,
type: "number",
renderEditCell(params: GridRenderEditCellParams<QcRow>) {
const errorMessage = params.row._error?.[params.field as keyof QcChecksInputs];
const content = (
<GridEditInputCell
{...params}
// inputProps={{ min: 0 }}
/>
);
return errorMessage ? (
<Tooltip title={t(errorMessage)}>
<Box width="100%">{content}</Box>
</Tooltip>
) : (
content
);
},
},
{
field: "upperLimit",
headerName: "upperLimit",
flex: 1,
editable: true,
type: "number",
renderEditCell(params: GridRenderEditCellParams<QcRow>) {
const errorMessage = params.row._error?.[params.field as keyof QcChecksInputs];
const content = (
<GridEditInputCell
{...params}
// inputProps={{ min: 0 }}
/>
);
return errorMessage ? (
<Tooltip title={t(errorMessage)}>
<Box width="100%">{content}</Box>
</Tooltip>
) : (
content
);
},
},
],
[]
);

const validationTest = useCallback(
(
newRow: GridRowModel<TableRow<Partial<QcChecksInputs>, EntryError>>,
// rowModel: GridRowSelectionModel
): EntryError => {
const error: EntryError = {};
console.log(newRow);
if (!newRow.lowerLimit) {
error["lowerLimit"] = "lower limit cannot be null"
}
// if (rowModel.find(r => r == newRow.id)) {

// }
if (newRow.lowerLimit && newRow.upperLimit && newRow.lowerLimit > newRow.upperLimit) {
error["lowerLimit"] = "lower limit should not be greater than upper limit"
error["upperLimit"] = "lower limit should not be greater than upper limit"
}
return Object.keys(error).length > 0 ? error : undefined;
},
[]
);

return (
<Grid container>
<Grid item xs={12}>
<InputDataGrid<CreateItemInputs, QcChecksInputs, EntryError>
apiRef={apiRef}
checkboxSelection={true}
_formKey={"qcChecks"}
columns={qcColumns}
validateRow={validationTest}
/>
</Grid>
</Grid>
);
};
export default QcDetails;

+ 72
- 22
src/components/InputDataGrid/InputDataGrid.tsx Visa fil

@@ -1,6 +1,7 @@
"use client"
import {
Dispatch,
MutableRefObject,
SetStateAction,
useCallback,
useEffect,
@@ -20,6 +21,7 @@ import {
GridRowModel,
GridRowModes,
GridRowModesModel,
GridRowSelectionModel,
GridToolbarContainer,
GridValidRowModel,
useGridApiRef,
@@ -31,6 +33,7 @@ import CancelIcon from "@mui/icons-material/Cancel";
import { Add } from "@mui/icons-material";
import { Box, Button, Typography } from "@mui/material";
import { useTranslation } from "react-i18next";
import { GridApiCommunity } from "@mui/x-data-grid/internals";

interface ResultWithId {
id: string | number;
@@ -38,19 +41,44 @@ interface ResultWithId {
// export type InputGridProps = {
// [key: string]: any
// }
export type TableRow<T, E> = Partial<
T & {
interface DefaultResult<E> {
_isNew: boolean;
_error: E;
}

interface SelectionResult<E> {
active: boolean;
_isNew: boolean;
_error: E;
}
type Result<E> = DefaultResult<E> | SelectionResult<E>

export type TableRow<V, E> = Partial<
V & {
isActive: boolean | undefined;
_isNew: boolean;
_error: E;
} & ResultWithId
>;

export type InputDataGridProps<T, E> = {
export interface InputDataGridProps<T, V, E> {
// needAdd: boolean | undefined;
apiRef: MutableRefObject<GridApiCommunity>
checkboxSelection: false | undefined;
_formKey: keyof T;
columns: GridColDef[];
validateRow: (newRow: GridRowModel<TableRow<T, E>>) => E;
validateRow: (newRow: GridRowModel<TableRow<V, E>>) => E;
};

export interface SelectionInputDataGridProps<T, V, E> { // thinking how do
apiRef: MutableRefObject<GridApiCommunity>
checkboxSelection: true;
_formKey: keyof T;
columns: GridColDef[];
validateRow: (newRow: GridRowModel<TableRow<V, E>>) => E;
}

export type Props<T, V, E> = InputDataGridProps<T, V, E> | SelectionInputDataGridProps<T, V, E>
export class ProcessRowUpdateError<T, E> extends Error {
public readonly row: T;
public readonly errors: E | undefined;
@@ -62,12 +90,16 @@ export class ProcessRowUpdateError<T, E> extends Error {
Object.setPrototypeOf(this, ProcessRowUpdateError.prototype);
}
}

function InputDataGrid<T, E>({
// T == CreatexxxInputs
// V == target field input inside CreatexxxInputs, e.g. qcChecks: ItemQc[], V = ItemQc
// E == error
function InputDataGrid<T, V, E>({
apiRef,
checkboxSelection,
_formKey,
columns,
validateRow,
}: InputDataGridProps<T, E>) {
}: Props<T, V, E>) {
const {
t,
// i18n: { language },
@@ -76,17 +108,24 @@ function InputDataGrid<T, E>({
const { setValue, getValues } = useFormContext();
const [rowModesModel, setRowModesModel] =
useState<GridRowModesModel>({});
const apiRef = useGridApiRef();
const getRowId = useCallback<GridRowIdGetter<TableRow<T, E>>>(
// const apiRef = useGridApiRef();
const getRowId = useCallback<GridRowIdGetter<TableRow<V, E>>>(
(row) => row.id!! as number,
[]
);
const list: TableRow<T, E>[] = getValues(formKey);
const [rows, setRows] = useState<TableRow<T, E>[]>(() => {
const list: TableRow<T, E>[] = getValues(formKey);
const list: TableRow<V, E>[] = getValues(formKey);
const [rows, setRows] = useState<TableRow<V, E>[]>(() => {
const list: TableRow<V, E>[] = getValues(formKey);
return list && list.length > 0 ? list : [];
});
const originalRows = list && list.length > 0 ? list : [];
// const originalRowModel = originalRows.filter((li) => li.isActive).map(i => i.id) as GridRowSelectionModel
const [rowSelectionModel, setRowSelectionModel] = useState<GridRowSelectionModel>(() => {
// const rowModel = list.filter((li) => li.isActive).map(i => i.id) as GridRowSelectionModel
const rowModel: GridRowSelectionModel = getValues(`${formKey}_active`) as GridRowSelectionModel
console.log(rowModel)
return rowModel
});

const handleSave = useCallback(
(id: GridRowId) => () => {
@@ -109,8 +148,8 @@ function InputDataGrid<T, E>({

const processRowUpdate = useCallback(
(
newRow: GridRowModel<TableRow<T, E>>,
originalRow: GridRowModel<TableRow<T, E>>
newRow: GridRowModel<TableRow<V, E>>,
originalRow: GridRowModel<TableRow<V, E>>
) => {
/////////////////
// validation here
@@ -127,7 +166,7 @@ function InputDataGrid<T, E>({
const { _isNew, _error, ...updatedRow } = newRow;
const rowToSave = {
...updatedRow,
} as TableRow<T, E>; /// test
} as TableRow<V, E>; /// test
console.log(rowToSave)
setRows((rw) =>
rw.map((r) => (getRowId(r) === getRowId(originalRow) ? rowToSave : r))
@@ -138,7 +177,7 @@ function InputDataGrid<T, E>({
);

const addRow = useCallback(() => {
const newEntry = { id: Date.now(), _isNew: true } as TableRow<T, E>;
const newEntry = { id: Date.now(), _isNew: true } as TableRow<V, E>;
setRows((prev) => [...prev, newEntry]);
setRowModesModel((model) => ({
...model,
@@ -225,7 +264,7 @@ function InputDataGrid<T, E>({
},
},
],
[columns,rowModesModel, handleSave, handleCancel, handleDelete]
[columns, rowModesModel, handleSave, handleCancel, handleDelete]
);
// sync useForm
useEffect(() => {
@@ -266,9 +305,20 @@ function InputDataGrid<T, E>({
<StyledDataGrid
// {...props}
// getRowId={getRowId as GridRowIdGetter<GridValidRowModel>}
// checkbox selection
checkboxSelection={checkboxSelection}
disableRowSelectionOnClick={checkboxSelection}
onRowSelectionModelChange={(newRowSelectionModel) => {
if (checkboxSelection) {
setRowSelectionModel(newRowSelectionModel);
setValue("qcChecks_active", newRowSelectionModel)
}
}}
rowSelectionModel={rowSelectionModel}

apiRef={apiRef}
rows={rows}
columns={_columns}
columns={!checkboxSelection ? _columns : columns}
editMode="row"
autoHeight
sx={{
@@ -295,13 +345,13 @@ function InputDataGrid<T, E>({
}
return classname;
}}
slots={{
slots={!checkboxSelection ? {
footer: FooterToolbar,
noRowsOverlay: NoRowsOverlay,
}}
slotProps={{
} : undefined}
slotProps={!checkboxSelection ? {
footer: { child: footer },
}}
} : undefined}
/>
)
}


+ 12
- 31
src/components/ItemsSearch/ItemsSearch.tsx Visa fil

@@ -12,28 +12,21 @@ import { TypeEnum } from "@/app/utils/typeEnum";

type Props = {
items: ItemsResult[];
type: TypeEnum;
};
type SearchQuery = Partial<Omit<ItemsResult, "id">>;
type SearchParamNames = keyof SearchQuery;

const ItemsSearch: React.FC<Props> = ({ items, type }) => {
const ItemsSearch: React.FC<Props> = ({ items }) => {
const [filteredItems, setFilteredItems] = useState<ItemsResult[]>(items);
const { t } = useTranslation(type.toString());
const { t } = useTranslation("items");
const router = useRouter();

const searchCriteria: Criterion<SearchParamNames>[] = useMemo(
() => {
var searchCriteria: Criterion<SearchParamNames>[] = []
if (type === TypeEnum.MATERIAL) {
searchCriteria = [
{ label: t("Code"), paramName: "code", type: "text" },
{ label: t("Name"), paramName: "name", type: "text" },
]
}
if (type === TypeEnum.MATERIAL) {
}
var searchCriteria: Criterion<SearchParamNames>[] = [
{ label: t("Code"), paramName: "code", type: "text" },
{ label: t("Name"), paramName: "name", type: "text" },
]
return searchCriteria
},
[t, items]
@@ -41,9 +34,9 @@ const ItemsSearch: React.FC<Props> = ({ items, type }) => {

const onDetailClick = useCallback(
(item: ItemsResult) => {
router.push(`/settings/${type}/edit?id=${item.id}`);
router.push(`/settings/items/edit?id=${item.id}`);
},
[type, router]
[router]
);

const onDeleteClick = useCallback(
@@ -88,22 +81,10 @@ const ItemsSearch: React.FC<Props> = ({ items, type }) => {
onSearch={(query) => {
setFilteredItems(
items.filter((pm) => {
if (type === TypeEnum.MATERIAL) {
return (
pm.code.toLowerCase().includes(query.code.toLowerCase()) &&
pm.name.toLowerCase().includes(query.name.toLowerCase())
);
} else if (type === TypeEnum.PRODUCT) {
return (
pm.code.toLowerCase().includes(query.code.toLowerCase()) &&
pm.name.toLowerCase().includes(query.name.toLowerCase())
);
} else if (type === TypeEnum.BYPRODUCT) {
return (
pm.code.toLowerCase().includes(query.code.toLowerCase()) &&
pm.name.toLowerCase().includes(query.name.toLowerCase())
);
}
return (
pm.code.toLowerCase().includes(query.code.toLowerCase()) &&
pm.name.toLowerCase().includes(query.name.toLowerCase())
);
})
);
}}


+ 4
- 4
src/components/ItemsSearch/ItemsSearchWrapper.tsx Visa fil

@@ -10,15 +10,15 @@ interface SubComponents {
}

type Props = {
type: TypeEnum;
// type: TypeEnum;
};

const ItemsSearchWrapper: React.FC<Props> & SubComponents = async ({
type,
// type,
}) => {
console.log(type)
// console.log(type)
var result = await fetchAllItems()
return <ItemsSearch items={result} type={type} />;
return <ItemsSearch items={result} />;
};

ItemsSearchWrapper.Loading = ItemsSearchLoading;


+ 1
- 1
src/components/Logo/Logo.tsx Visa fil

@@ -13,7 +13,7 @@ const Logo: React.FC<Props> = ({ width, height }) => {
>
<g
id="svgGroup"
stroke-linecap="round"
strokeLinecap="round"
fill-rule="evenodd"
font-size="9pt"
stroke="#000"


+ 12
- 12
src/components/NavigationContent/NavigationContent.tsx Visa fil

@@ -181,20 +181,20 @@ const NavigationContent: React.FC = () => {
label: "User Group",
path: "/settings/user",
},
// {
// icon: <RequestQuote />,
// label: "Material",
// path: "/settings/material",
// },
// {
// icon: <RequestQuote />,
// label: "By-product",
// path: "/settings/byProduct",
// },
{
icon: <RequestQuote />,
label: "Material",
path: "/settings/material",
},
{
icon: <RequestQuote />,
label: "By-product",
path: "/settings/byProduct",
},
{
icon: <RequestQuote />,
label: "Product",
path: "/settings/product",
label: "Items",
path: "/settings/items",
},
{
icon: <RequestQuote />,


Laddar…
Avbryt
Spara