| @@ -37,6 +37,7 @@ const material: React.FC<Props> = async ({ searchParams }) => { | |||
| > | |||
| {t("Create Claim")} | |||
| </Button> | |||
| </Stack> | |||
| {/* <Suspense fallback={<MaterialSearch.Loading />}> | |||
| <MaterialSearch /> | |||
| @@ -0,0 +1,22 @@ | |||
| import { SearchParams } from "@/app/utils/fetchUtil"; | |||
| import { TypeEnum } from "@/app/utils/typeEnum"; | |||
| import CreateEquipmentType from "@/components/CreateEquipmentType"; | |||
| 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("common"); | |||
| return ( | |||
| <> | |||
| {/* <Typography variant="h4">{t("Create Material")}</Typography> */} | |||
| <I18nProvider namespaces={["common"]}> | |||
| <CreateEquipmentType /> | |||
| </I18nProvider> | |||
| </> | |||
| ); | |||
| }; | |||
| export default materialSetting; | |||
| @@ -0,0 +1,29 @@ | |||
| import { SearchParams } from "@/app/utils/fetchUtil"; | |||
| import { TypeEnum } from "@/app/utils/typeEnum"; | |||
| import CreateEquipmentType from "@/components/CreateEquipmentType"; | |||
| 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 = "common"; | |||
| 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]}> | |||
| <CreateEquipmentType id={id} /> | |||
| </I18nProvider> | |||
| </> | |||
| ); | |||
| }; | |||
| export default productSetting; | |||
| @@ -0,0 +1,52 @@ | |||
| import { TypeEnum } from "@/app/utils/typeEnum"; | |||
| import EquipmentTypeSearch from "@/components/EquipmentTypeSearch"; | |||
| 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"; | |||
| import { fetchAllEquipmentTypes } from "@/app/api/settings/equipmentType"; | |||
| import { I18nProvider } from "@/i18n"; | |||
| export const metadata: Metadata = { | |||
| title: "Equipment Type", | |||
| }; | |||
| const productSetting: React.FC = async () => { | |||
| const type = "common"; | |||
| const { t } = await getServerI18n(type); | |||
| const equipmentTypes = await fetchAllEquipmentTypes(); | |||
| // preloadClaims(); | |||
| return ( | |||
| <> | |||
| <Stack | |||
| direction="row" | |||
| justifyContent="space-between" | |||
| flexWrap="wrap" | |||
| rowGap={2} | |||
| > | |||
| <Typography variant="h4" marginInlineEnd={2}> | |||
| {t("Equipment Type")} | |||
| </Typography> | |||
| {/* <Button | |||
| variant="contained" | |||
| startIcon={<Add />} | |||
| LinkComponent={Link} | |||
| href="product/create" | |||
| > | |||
| {t("Create product")} | |||
| </Button> */} | |||
| </Stack> | |||
| <Suspense fallback={<EquipmentTypeSearch.Loading />}> | |||
| <I18nProvider namespaces={["common","project"]}> | |||
| <EquipmentTypeSearch /> | |||
| </I18nProvider> | |||
| </Suspense> | |||
| </> | |||
| ); | |||
| }; | |||
| export default productSetting; | |||
| @@ -10,7 +10,7 @@ import Link from "next/link"; | |||
| import { Suspense } from "react"; | |||
| import RoughScheduleLoading from "@/components/RoughScheduleSetting/RoughScheduleLoading"; | |||
| import RoughScheduleSetting from "@/components/RoughScheduleSetting/RoughScheduleSetting"; | |||
| import { I18nProvider } from "@/i18n"; | |||
| export const metadata: Metadata = { | |||
| title: "Demand Forecast Setting", | |||
| }; | |||
| @@ -40,9 +40,11 @@ const roughScheduleSetting: React.FC = async () => { | |||
| {t("Create product")} | |||
| </Button> */} | |||
| </Stack> | |||
| <Suspense fallback={<RoughScheduleLoading.Loading />}> | |||
| <RoughScheduleSetting /> | |||
| </Suspense> | |||
| <I18nProvider namespaces={[ "common", "project"]}> | |||
| <Suspense fallback={<RoughScheduleLoading.Loading />}> | |||
| <RoughScheduleSetting /> | |||
| </Suspense> | |||
| </I18nProvider> | |||
| </> | |||
| ); | |||
| }; | |||
| @@ -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 { CreateEquipmentTypeResponse } from "../../utils"; | |||
| // export type TypeInputs = { | |||
| // id: number; | |||
| // name: string | |||
| // } | |||
| // export type UomInputs = { | |||
| // uom: string | |||
| // } | |||
| // export type WeightUnitInputs = { | |||
| // weightUnit: string | |||
| // conversion: number | |||
| // } | |||
| export type CreateEquipmentTypeInputs = { | |||
| id?: string | number | |||
| code: string; | |||
| name: string; | |||
| description?: string | undefined; | |||
| } | |||
| export const saveEquipmentType = async (data: CreateEquipmentTypeInputs) => { | |||
| // try { | |||
| const equipmentType = await serverFetchJson<CreateEquipmentTypeResponse<CreateEquipmentTypeInputs>>(`${BASE_API_URL}/EquipmentType/save`, { | |||
| method: "POST", | |||
| body: JSON.stringify(data), | |||
| headers: { "Content-Type": "application/json" }, | |||
| }); | |||
| revalidateTag("EquipmentType"); | |||
| return equipmentType | |||
| }; | |||
| @@ -0,0 +1,33 @@ | |||
| import { cache } from "react"; | |||
| import "server-only"; | |||
| // import { serverFetchJson } from "@/app/utils/fetchUtil"; | |||
| // import { BASE_API_URL } from "@/config/api"; | |||
| import { serverFetchJson } from "../../../utils/fetchUtil"; | |||
| import { BASE_API_URL } from "../../../../config/api"; | |||
| export { default } from "../../../../components/CreateEquipmentType/CreateEquipmentType"; | |||
| // import { TypeInputs, UomInputs, WeightUnitInputs } from "./actions"; | |||
| export type EquipmentTypeResult = { | |||
| id: string | number | |||
| code: string; | |||
| name: string; | |||
| description: string | undefined; | |||
| action?: any | |||
| } | |||
| export type Result = { | |||
| equipmentType: EquipmentTypeResult | |||
| } | |||
| export const fetchAllEquipmentTypes = cache(async () => { | |||
| return serverFetchJson<EquipmentTypeResult[]>(`${BASE_API_URL}/EquipmentType`, { | |||
| next: { tags: ["equipmentTypes"] }, | |||
| }); | |||
| }); | |||
| export const fetchEquipmentType = cache(async (id: number) => { | |||
| return serverFetchJson<EquipmentTypeResult>(`${BASE_API_URL}/EquipmentType/details/${id}`, { | |||
| next: { tags: ["equipmentTypes"] }, | |||
| }); | |||
| }); | |||
| @@ -5,6 +5,13 @@ export interface CreateItemResponse<T> { | |||
| message: string | null; | |||
| errorPosition: string | keyof T; | |||
| } | |||
| export interface CreateEquipmentTypeResponse<T> { | |||
| id: number | null; | |||
| name: string; | |||
| code: string; | |||
| message: string | null; | |||
| errorPosition: string | keyof T; | |||
| } | |||
| export interface RecordsRes<T>{ | |||
| records: T | |||
| @@ -0,0 +1,193 @@ | |||
| "use client"; | |||
| import { useCallback, useEffect, useMemo, useState } from "react"; | |||
| import { useRouter, useSearchParams } from "next/navigation"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import { | |||
| CreateEquipmentTypeInputs, | |||
| saveEquipmentType, | |||
| } from "@/app/api/settings/equipmentType/actions"; | |||
| import { | |||
| FormProvider, | |||
| SubmitErrorHandler, | |||
| SubmitHandler, | |||
| useForm, | |||
| } from "react-hook-form"; | |||
| import { deleteDialog } from "../Swal/CustomAlerts"; | |||
| 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 EquipmentTypeDetails from "./EquipmentTypeDetails"; | |||
| import { CreateItemResponse } from "@/app/api/utils"; | |||
| 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; | |||
| defaultValues: Partial<CreateEquipmentTypeInputs> | undefined; | |||
| }; | |||
| const CreateItem: React.FC<Props> = ({ | |||
| isEditMode, | |||
| // type, | |||
| defaultValues, | |||
| }) => { | |||
| // 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("common"); | |||
| const router = useRouter(); | |||
| const title = "Equipment Type" | |||
| 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 | |||
| title = "Equipment Type"; | |||
| redirPath = "/settings/equipmentType"; | |||
| // } | |||
| // if (type === TypeEnum.BYPRODUCT) { | |||
| // typeId = TypeEnum.BYPRODUCT_ID | |||
| // title = "By-Product"; | |||
| // redirPath = "/settings/byProduct"; | |||
| // } | |||
| if (isEditMode) { | |||
| mode = "Edit"; | |||
| } else { | |||
| mode = "Create"; | |||
| } | |||
| return [mode, redirPath]; | |||
| }, [isEditMode]); | |||
| // console.log(typeId) | |||
| const formProps = useForm<CreateEquipmentTypeInputs>({ | |||
| defaultValues: defaultValues ? defaultValues : { | |||
| }, | |||
| }); | |||
| const errors = formProps.formState.errors; | |||
| const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>( | |||
| (_e, newValue) => { | |||
| setTabIndex(newValue); | |||
| }, | |||
| [], | |||
| ); | |||
| const handleCancel = () => { | |||
| router.replace(`/settings/equipmentType`); | |||
| }; | |||
| const onSubmit = useCallback<SubmitHandler<CreateEquipmentTypeInputs & {}>>( | |||
| async (data, event) => { | |||
| let hasErrors = false; | |||
| console.log(errors) | |||
| // console.log(apiRef.current.getCellValue(2, "lowerLimit")) | |||
| // apiRef.current. | |||
| try { | |||
| if (hasErrors) { | |||
| setServerError(t("An error has occurred. Please try again later.")); | |||
| return false; | |||
| } | |||
| console.log("data posted"); | |||
| console.log(data); | |||
| // TODO: | |||
| // 1. check field ( directly modify col def / check here ) | |||
| // 2. set error change tab index | |||
| // return | |||
| // do api | |||
| console.log("asdad") | |||
| var responseI = await saveEquipmentType(data); | |||
| console.log("asdad") | |||
| // var responseQ = await saveItemQcChecks(qcCheck) | |||
| if (responseI) { | |||
| if (!Boolean(responseI.id)) { | |||
| formProps.setError(responseI.errorPosition!! as keyof CreateEquipmentTypeInputs, { | |||
| message: responseI.message!!, | |||
| type: "required", | |||
| }) | |||
| } else if (Boolean(responseI.id)) { | |||
| router.replace(redirPath); | |||
| } | |||
| } | |||
| } catch (e) { | |||
| // backend error | |||
| setServerError(t("An error has occurred. Please try again later.")); | |||
| console.log(e); | |||
| } | |||
| }, | |||
| [apiRef, router, t] | |||
| ); | |||
| // multiple tabs | |||
| const onSubmitError = useCallback<SubmitErrorHandler<CreateEquipmentTypeInputs>>( | |||
| (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> | |||
| <Tabs value={tabIndex} onChange={handleTabChange} variant="scrollable"> | |||
| <Tab label={t("Equipment Type Details")} iconPosition="end"/> | |||
| {/* <Tab label={t("Qc items")} iconPosition="end" /> */} | |||
| </Tabs> | |||
| {serverError && ( | |||
| <Typography variant="body2" color="error" alignSelf="flex-end"> | |||
| {serverError} | |||
| </Typography> | |||
| )} | |||
| {tabIndex === 0 && <EquipmentTypeDetails isEditMode={isEditMode} />} | |||
| {/* {tabIndex === 1 && <QcDetails apiRef={apiRef} />} */} | |||
| {/* {type === TypeEnum.MATERIAL && <MaterialDetails />} */} | |||
| {/* {type === TypeEnum.BYPRODUCT && <ByProductDetails />} */} | |||
| {/* | |||
| <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 CreateItem; | |||
| @@ -0,0 +1,40 @@ | |||
| import Card from "@mui/material/Card"; | |||
| import CardContent from "@mui/material/CardContent"; | |||
| import Skeleton from "@mui/material/Skeleton"; | |||
| import Stack from "@mui/material/Stack"; | |||
| import React from "react"; | |||
| // Can make this nicer | |||
| export const CreateItemLoading: React.FC = () => { | |||
| return ( | |||
| <> | |||
| <Card> | |||
| <CardContent> | |||
| <Stack spacing={2}> | |||
| <Skeleton variant="rounded" height={60} /> | |||
| <Skeleton variant="rounded" height={60} /> | |||
| <Skeleton variant="rounded" height={60} /> | |||
| <Skeleton | |||
| variant="rounded" | |||
| height={50} | |||
| width={100} | |||
| sx={{ alignSelf: "flex-end" }} | |||
| /> | |||
| </Stack> | |||
| </CardContent> | |||
| </Card> | |||
| <Card>CreateMaterial | |||
| <CardContent> | |||
| <Stack spacing={2}> | |||
| <Skeleton variant="rounded" height={40} /> | |||
| <Skeleton variant="rounded" height={40} /> | |||
| <Skeleton variant="rounded" height={40} /> | |||
| <Skeleton variant="rounded" height={40} /> | |||
| </Stack> | |||
| </CardContent> | |||
| </Card> | |||
| </> | |||
| ); | |||
| }; | |||
| export default CreateItemLoading; | |||
| @@ -0,0 +1,43 @@ | |||
| import { TypeEnum } from "@/app/utils/typeEnum"; | |||
| import CreateEquipmentType from "./CreateEquipmentType"; | |||
| import CreateEquipmentTypeLoading from "./CreateEquipmentTypeLoading"; | |||
| import { CreateEquipmentTypeInputs } from "@/app/api/settings/equipmentType/actions"; | |||
| import { notFound } from "next/navigation"; | |||
| import { fetchEquipmentType } from "@/app/api/settings/equipmentType"; | |||
| interface SubComponents { | |||
| Loading: typeof CreateEquipmentTypeLoading; | |||
| } | |||
| type Props = { | |||
| id?: number | |||
| // type: TypeEnum; | |||
| }; | |||
| const CreateEquipmentTypeWrapper: React.FC<Props> & | |||
| SubComponents = async ({ id }) => { | |||
| var result | |||
| var defaultValues: Partial<CreateEquipmentTypeInputs> | undefined | |||
| // console.log(type) | |||
| var qcChecks | |||
| if (id) { | |||
| result = await fetchEquipmentType(id); | |||
| const equipmentType = result | |||
| console.log(equipmentType) | |||
| defaultValues = { | |||
| id: equipmentType?.id, | |||
| code: equipmentType?.code, | |||
| name: equipmentType?.name, | |||
| description: equipmentType?.description, | |||
| }; | |||
| } | |||
| return ( | |||
| <CreateEquipmentType | |||
| isEditMode={Boolean(id)} | |||
| defaultValues={defaultValues} | |||
| /> | |||
| ); | |||
| }; | |||
| CreateEquipmentTypeWrapper.Loading = CreateEquipmentTypeLoading; | |||
| export default CreateEquipmentTypeWrapper; | |||
| @@ -0,0 +1,199 @@ | |||
| "use client"; | |||
| import { | |||
| Box, | |||
| Button, | |||
| Card, | |||
| CardContent, | |||
| Grid, | |||
| Stack, | |||
| TextField, | |||
| Typography, | |||
| } from "@mui/material"; | |||
| import { Check, Close, EditNote } from "@mui/icons-material"; | |||
| import { useFormContext } from "react-hook-form"; | |||
| 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 { TypeEnum } from "@/app/utils/typeEnum"; | |||
| import { NumberInputProps } from "./NumberInputProps"; | |||
| import { CreateEquipmentTypeInputs } from "@/app/api/settings/equipmentType/actions"; | |||
| import { RestartAlt } from "@mui/icons-material"; | |||
| type Props = { | |||
| // isEditMode: boolean; | |||
| // type: TypeEnum; | |||
| isEditMode: boolean; | |||
| // type: TypeEnum; | |||
| defaultValues: Partial<CreateEquipmentTypeInputs> | undefined; | |||
| }; | |||
| const ProductDetails: React.FC<Props> = ({isEditMode}) => { | |||
| const { | |||
| t, | |||
| i18n: { language }, | |||
| } = useTranslation(); | |||
| const { | |||
| register, | |||
| formState: { errors, defaultValues, touchedFields }, | |||
| watch, | |||
| control, | |||
| setValue, | |||
| getValues, | |||
| reset, | |||
| resetField, | |||
| setError, | |||
| clearErrors, | |||
| } = useFormContext<CreateEquipmentTypeInputs>(); | |||
| // 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 handleCancel = () => { | |||
| router.replace(`/settings/equipmentType`); | |||
| }; | |||
| return ( | |||
| <Card sx={{ display: "block" }}> | |||
| <CardContent component={Stack} spacing={4}> | |||
| <Box> | |||
| <Typography variant="overline" display="block" marginBlockEnd={1}> | |||
| {t("Equipment Type 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={12}> | |||
| <Stack direction="row" justifyContent="flex-start" spacing={2} sx={{ mt: 2 }}> | |||
| <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> | |||
| <Button | |||
| variant="outlined" | |||
| startIcon={<RestartAlt />} | |||
| onClick={() => reset()} | |||
| > | |||
| {t("Reset")} | |||
| </Button> | |||
| </Stack> | |||
| </Grid> | |||
| {/* <Grid item xs={6}> | |||
| <InputDataGrid<CreateItemInputs, EntryError> | |||
| _formKey={"type"} | |||
| columns={typeColumns} | |||
| validateRow={validationTest} | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={6}> | |||
| <InputDataGrid<CreateItemInputs, EntryError> | |||
| _formKey={"uom"} | |||
| columns={uomColumns} | |||
| validateRow={validationTest} | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={12}> | |||
| <InputDataGrid<CreateItemInputs, EntryError> | |||
| _formKey={"weightUnit"} | |||
| columns={weightUnitColumns} | |||
| validateRow={validationTest} | |||
| /> | |||
| </Grid>*/} | |||
| </Grid> | |||
| </Box> | |||
| </CardContent> | |||
| </Card> | |||
| ); | |||
| }; | |||
| export default ProductDetails; | |||
| @@ -0,0 +1,5 @@ | |||
| import { InputBaseComponentProps } from "@mui/material"; | |||
| export var NumberInputProps: InputBaseComponentProps = { | |||
| step: 0.01, | |||
| }; | |||
| @@ -0,0 +1 @@ | |||
| export { default } from "./CreateEquipmentTypeWrapper"; | |||
| @@ -174,10 +174,11 @@ const CreateItem: React.FC<Props> = ({ | |||
| {serverError} | |||
| </Typography> | |||
| )} | |||
| {tabIndex === 0 && <ProductDetails />} | |||
| {tabIndex === 0 && <ProductDetails isEditMode={isEditMode} />} | |||
| {tabIndex === 1 && <QcDetails apiRef={apiRef} />} | |||
| {/* {type === TypeEnum.MATERIAL && <MaterialDetails />} */} | |||
| {/* {type === TypeEnum.BYPRODUCT && <ByProductDetails />} */} | |||
| {/* | |||
| <Stack direction="row" justifyContent="flex-end" gap={1}> | |||
| <Button | |||
| name="submit" | |||
| @@ -196,6 +197,7 @@ const CreateItem: React.FC<Props> = ({ | |||
| {t("Cancel")} | |||
| </Button> | |||
| </Stack> | |||
| */} | |||
| </Stack> | |||
| </FormProvider> | |||
| </> | |||
| @@ -1,6 +1,7 @@ | |||
| "use client"; | |||
| import { | |||
| Box, | |||
| Button, | |||
| Card, | |||
| CardContent, | |||
| Grid, | |||
| @@ -8,22 +9,28 @@ import { | |||
| TextField, | |||
| Typography, | |||
| } from "@mui/material"; | |||
| import { Check, Close, EditNote } from "@mui/icons-material"; | |||
| import { useFormContext } from "react-hook-form"; | |||
| 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 { TypeEnum } from "@/app/utils/typeEnum"; | |||
| import { NumberInputProps } from "./NumberInputProps"; | |||
| import { CreateItemInputs } from "@/app/api/settings/item/actions"; | |||
| import { RestartAlt } from "@mui/icons-material"; | |||
| type Props = { | |||
| // isEditMode: boolean; | |||
| // type: TypeEnum; | |||
| isEditMode: boolean; | |||
| // type: TypeEnum; | |||
| defaultValues: Partial<CreateItemInputs> | undefined; | |||
| qcChecks: ItemQc[] | |||
| }; | |||
| const ProductDetails: React.FC<Props> = ({}) => { | |||
| const ProductDetails: React.FC<Props> = ({isEditMode}) => { | |||
| const { | |||
| t, | |||
| i18n: { language }, | |||
| @@ -92,7 +99,9 @@ const ProductDetails: React.FC<Props> = ({}) => { | |||
| // }, | |||
| // [] | |||
| // ); | |||
| const handleCancel = () => { | |||
| router.replace(`/settings/product`); | |||
| }; | |||
| return ( | |||
| <Card sx={{ display: "block" }}> | |||
| <CardContent component={Stack} spacing={4}> | |||
| @@ -191,6 +200,33 @@ const ProductDetails: React.FC<Props> = ({}) => { | |||
| helperText={errors.maxQty?.message} | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={0}> | |||
| <Stack direction="row" justifyContent="flex-end" spacing={2} sx={{ mt: 2 }}> | |||
| <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> | |||
| <Button | |||
| variant="outlined" | |||
| startIcon={<RestartAlt />} | |||
| onClick={() => reset()} | |||
| > | |||
| {t("Reset")} | |||
| </Button> | |||
| </Stack> | |||
| </Grid> | |||
| {/* <Grid item xs={6}> | |||
| <InputDataGrid<CreateItemInputs, EntryError> | |||
| _formKey={"type"} | |||
| @@ -0,0 +1,147 @@ | |||
| "use client"; | |||
| import { useCallback, useEffect, useMemo, useState } from "react"; | |||
| import SearchBox, { Criterion } from "../SearchBox"; | |||
| import { EquipmentTypeResult } from "@/app/api/settings/equipmentType"; | |||
| 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 axios from "axios"; | |||
| import { BASE_API_URL, NEXT_PUBLIC_API_URL } from "@/config/api"; | |||
| import axiosInstance from "@/app/(main)/axios/axiosInstance"; | |||
| type Props = { | |||
| equipmentTypes: EquipmentTypeResult[]; | |||
| }; | |||
| type SearchQuery = Partial<Omit<EquipmentTypeResult, "id">>; | |||
| type SearchParamNames = keyof SearchQuery; | |||
| const EquipmentTypeSearch: React.FC<Props> = ({ equipmentTypes }) => { | |||
| const [filteredEquipmentTypes, setFilteredEquipmentTypes] = useState<EquipmentTypeResult[]>(equipmentTypes); | |||
| const { t } = useTranslation("common"); | |||
| const router = useRouter(); | |||
| const [filterObj, setFilterObj] = useState({}); | |||
| const [pagingController, setPagingController] = useState({ | |||
| pageNum: 1, | |||
| pageSize: 10, | |||
| // totalCount: 0, | |||
| }); | |||
| const [totalCount, setTotalCount] = useState(0) | |||
| const searchCriteria: Criterion<SearchParamNames>[] = useMemo(() => { | |||
| var searchCriteria: Criterion<SearchParamNames>[] = [ | |||
| { label: t("Code"), paramName: "code", type: "text" }, | |||
| { label: t("Description"), paramName: "description", type: "text" }, | |||
| ]; | |||
| return searchCriteria; | |||
| }, [t, equipmentTypes]); | |||
| const onDetailClick = useCallback( | |||
| (equipmentType: EquipmentTypeResult) => { | |||
| router.push(`/settings/equipmentType/edit?id=${equipmentType.id}`); | |||
| }, | |||
| [router] | |||
| ); | |||
| const onDeleteClick = useCallback((equipmentType: EquipmentTypeResult) => {}, [router]); | |||
| const columns = useMemo<Column<EquipmentTypeResult>[]>( | |||
| () => [ | |||
| { | |||
| name: "id", | |||
| label: t("Details"), | |||
| onClick: onDetailClick, | |||
| buttonIcon: <EditNote />, | |||
| }, | |||
| { | |||
| name: "code", | |||
| label: t("Code"), | |||
| }, | |||
| { | |||
| name: "description", | |||
| label: t("Description"), | |||
| }, | |||
| { | |||
| name: "action", | |||
| label: t(""), | |||
| buttonIcon: <GridDeleteIcon />, | |||
| onClick: onDeleteClick, | |||
| }, | |||
| ], | |||
| [filteredEquipmentTypes] | |||
| ); | |||
| const refetchData = useCallback( | |||
| async (filterObj: SearchQuery) => { | |||
| const authHeader = axiosInstance.defaults.headers["Authorization"]; | |||
| if (!authHeader) { | |||
| return; // Exit the function if the token is not set | |||
| } | |||
| const params = { | |||
| pageNum: pagingController.pageNum, | |||
| pageSize: pagingController.pageSize, | |||
| ...filterObj, | |||
| }; | |||
| try { | |||
| const response = await axiosInstance.get<EquipmentTypeResult[]>( | |||
| `${NEXT_PUBLIC_API_URL}/EquipmentType/getRecordByPage`, | |||
| { params } | |||
| ); | |||
| console.log(response); | |||
| if (response.status == 200) { | |||
| setFilteredEquipmentTypes(response.data.records); | |||
| setTotalCount(response.data.total) | |||
| return response; // Return the data from the response | |||
| } else { | |||
| throw "400"; | |||
| } | |||
| } catch (error) { | |||
| console.error("Error fetching equipment types:", error); | |||
| throw error; // Rethrow the error for further handling | |||
| } | |||
| }, | |||
| [axiosInstance, pagingController.pageNum, pagingController.pageSize] | |||
| ); | |||
| useEffect(() => { | |||
| refetchData(filterObj); | |||
| }, [filterObj, pagingController.pageNum, pagingController.pageSize]); | |||
| const onReset = useCallback(() => { | |||
| setFilteredEquipmentTypes(equipmentTypes); | |||
| }, [equipmentTypes]); | |||
| return ( | |||
| <> | |||
| <SearchBox | |||
| criteria={searchCriteria} | |||
| onSearch={(query) => { | |||
| // setFilteredItems( | |||
| // equipmentTypes.filter((pm) => { | |||
| // return ( | |||
| // pm.code.toLowerCase().includes(query.code.toLowerCase()) && | |||
| // pm.name.toLowerCase().includes(query.name.toLowerCase()) | |||
| // ); | |||
| // }) | |||
| // ); | |||
| setFilterObj({ | |||
| ...query, | |||
| }); | |||
| }} | |||
| onReset={onReset} | |||
| /> | |||
| <SearchResults<EquipmentTypeResult> | |||
| items={filteredEquipmentTypes} | |||
| columns={columns} | |||
| setPagingController={setPagingController} | |||
| pagingController={pagingController} | |||
| totalCount={totalCount} | |||
| isAutoPaging={false} | |||
| /> | |||
| </> | |||
| ); | |||
| }; | |||
| export default EquipmentTypeSearch; | |||
| @@ -0,0 +1,40 @@ | |||
| import Card from "@mui/material/Card"; | |||
| import CardContent from "@mui/material/CardContent"; | |||
| import Skeleton from "@mui/material/Skeleton"; | |||
| import Stack from "@mui/material/Stack"; | |||
| import React from "react"; | |||
| // Can make this nicer | |||
| export const EquipmentTypeSearchLoading: React.FC = () => { | |||
| return ( | |||
| <> | |||
| <Card> | |||
| <CardContent> | |||
| <Stack spacing={2}> | |||
| <Skeleton variant="rounded" height={60} /> | |||
| <Skeleton variant="rounded" height={60} /> | |||
| <Skeleton variant="rounded" height={60} /> | |||
| <Skeleton | |||
| variant="rounded" | |||
| height={50} | |||
| width={100} | |||
| sx={{ alignSelf: "flex-end" }} | |||
| /> | |||
| </Stack> | |||
| </CardContent> | |||
| </Card> | |||
| <Card> | |||
| <CardContent> | |||
| <Stack spacing={2}> | |||
| <Skeleton variant="rounded" height={40} /> | |||
| <Skeleton variant="rounded" height={40} /> | |||
| <Skeleton variant="rounded" height={40} /> | |||
| <Skeleton variant="rounded" height={40} /> | |||
| </Stack> | |||
| </CardContent> | |||
| </Card> | |||
| </> | |||
| ); | |||
| }; | |||
| export default EquipmentTypeSearchLoading; | |||
| @@ -0,0 +1,26 @@ | |||
| import { fetchAllEquipmentTypes, } from "@/app/api/settings/equipmentType"; | |||
| import EquipmentTypeSearchLoading from "./EquipmentTypeSearchLoading"; | |||
| import { SearchParams } from "@/app/utils/fetchUtil"; | |||
| import { TypeEnum } from "@/app/utils/typeEnum"; | |||
| import { notFound } from "next/navigation"; | |||
| import EquipmentTypeSearch from "./EquipmentTypeSearch"; | |||
| interface SubComponents { | |||
| Loading: typeof EquipmentTypeSearchLoading; | |||
| } | |||
| type Props = { | |||
| // type: TypeEnum; | |||
| }; | |||
| const EquipmentTypeSearchWrapper: React.FC<Props> & SubComponents = async ({ | |||
| // type, | |||
| }) => { | |||
| // console.log(type) | |||
| // var result = await fetchAllEquipmentTypes() | |||
| return <EquipmentTypeSearch equipmentTypes={[]} />; | |||
| }; | |||
| EquipmentTypeSearchWrapper.Loading = EquipmentTypeSearchLoading; | |||
| export default EquipmentTypeSearchWrapper; | |||
| @@ -0,0 +1 @@ | |||
| export { default } from "./EquipmentTypeSearchWrapper"; | |||
| @@ -221,7 +221,7 @@ const NavigationContent: React.FC = () => { | |||
| { | |||
| icon: <RequestQuote />, | |||
| label: "Equipment Type", | |||
| path: "/settings/user", | |||
| path: "/settings/equipmentType", | |||
| }, | |||
| { | |||
| icon: <RequestQuote />, | |||
| @@ -1,22 +1,6 @@ | |||
| { | |||
| "Overview": "概述", | |||
| "Qc Item": "品質檢驗項目", | |||
| "Dashboard": "儀表板", | |||
| "dashboard": "儀表板", | |||
| "Raw Material": "原料", | |||
| "Purchase Order": "採購訂單", | |||
| "Pick Order": "提料單", | |||
| "View item In-out And inventory Ledger": "存貨", | |||
| "Inventory": "存貨", | |||
| "Delivery": "送貨", | |||
| "Delivery Order": "送貨單", | |||
| "Scheduling": "生產計劃", | |||
| "Demand Forecast Setting": "粗排設定", | |||
| "Demand Forecast": "粗排", | |||
| "FG & Material Demand Forecast Detail": "成品 & 原料粗排細節", | |||
| "Detail Scheduling": "細排", | |||
| "FG Production Schedule": "成品生產計劃", | |||
| "Settings": "設定", | |||
| "Edit": "編輯", | |||
| "Search Criteria": "搜尋條件", | |||
| @@ -46,7 +30,6 @@ | |||
| "Supplier": "供應商", | |||
| "Purchase Order":"採購單", | |||
| "Demand Forecast":"需求預測", | |||
| "Purchase Order":"採購單", | |||
| "Pick Order":"挑選貨單", | |||
| "Deliver Order":"交貨單", | |||
| "Project":"專案", | |||
| @@ -74,5 +57,12 @@ | |||
| "scheduling":"排程", | |||
| "settings": "設定", | |||
| "items": "物料", | |||
| "edit":"編輯" | |||
| "edit":"編輯", | |||
| "Edit Equipment Type":"設備類型詳情", | |||
| "equipmentType":"設備類型", | |||
| "Description":"描述", | |||
| "Details": "詳情", | |||
| "Equipment Type Details":"設備類型詳情", | |||
| "Save":"儲存", | |||
| "Cancel":"取消" | |||
| } | |||
| @@ -5,8 +5,9 @@ | |||
| "Actions": "動作", | |||
| "Product": "產品", | |||
| "Details": "詳情", | |||
| "View BoM": "查看 BoM" | |||
| "View BoM": "查看 BoM", | |||
| "description": "描述", | |||
| "details": "詳情" | |||