| @@ -0,0 +1,32 @@ | |||||
| import { SearchParams } from "@/app/utils/fetchUtil"; | |||||
| import { TypeEnum } from "@/app/utils/typeEnum"; | |||||
| import CreateProductMaterial from "@/components/CreateItem"; | |||||
| import PoDetail from "@/components/PoDetail"; | |||||
| import { I18nProvider, getServerI18n } from "@/i18n"; | |||||
| import { Typography } from "@mui/material"; | |||||
| import isString from "lodash/isString"; | |||||
| import { notFound } from "next/navigation"; | |||||
| type Props = {} & SearchParams; | |||||
| const PoEdit: React.FC<Props> = async ({ searchParams }) => { | |||||
| const type = "po"; | |||||
| const { t } = await getServerI18n(type); | |||||
| console.log(searchParams["id"]) | |||||
| 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]}> | |||||
| <PoDetail id={id} /> | |||||
| </I18nProvider> | |||||
| </> | |||||
| ); | |||||
| }; | |||||
| export default PoEdit; | |||||
| @@ -0,0 +1,48 @@ | |||||
| import { preloadClaims } from "@/app/api/claims"; | |||||
| import ClaimSearch from "@/components/ClaimSearch"; | |||||
| import PoSearch from "@/components/PoSearch"; | |||||
| 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: "Purchase Order", | |||||
| }; | |||||
| const production: React.FC = async () => { | |||||
| const { t } = await getServerI18n("claims"); | |||||
| // preloadClaims(); | |||||
| return ( | |||||
| <> | |||||
| <Stack | |||||
| direction="row" | |||||
| justifyContent="space-between" | |||||
| flexWrap="wrap" | |||||
| rowGap={2} | |||||
| > | |||||
| <Typography variant="h4" marginInlineEnd={2}> | |||||
| {t("Purchase Order")} | |||||
| </Typography> | |||||
| {/* <Button | |||||
| variant="contained" | |||||
| startIcon={<Add />} | |||||
| LinkComponent={Link} | |||||
| href="/po/create" | |||||
| > | |||||
| {t("Create Po")} | |||||
| </Button> */} | |||||
| </Stack> | |||||
| <Suspense fallback={<PoSearch.Loading />}> | |||||
| <PoSearch /> | |||||
| </Suspense> | |||||
| </> | |||||
| ); | |||||
| }; | |||||
| export default production; | |||||
| @@ -0,0 +1,65 @@ | |||||
| "use server"; | |||||
| import { BASE_API_URL } from "@/config/api"; | |||||
| // import { ServerFetchError, serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil"; | |||||
| import { revalidateTag } from "next/cache"; | |||||
| import { cache } from "react"; | |||||
| import { PoResult, StockInLine } from "."; | |||||
| import { serverFetchJson } from "@/app/utils/fetchUtil"; | |||||
| // import { BASE_API_URL } from "@/config/api"; | |||||
| export interface PostStockInLiineResponse<T> { | |||||
| id: number | null; | |||||
| name: string; | |||||
| code: string; | |||||
| message: string | null; | |||||
| errorPosition: string | keyof T; | |||||
| entity: StockInLine | StockInLine[] | |||||
| } | |||||
| export interface StockInLineEntry { | |||||
| id?: number | |||||
| itemId: number | |||||
| purchaseOrderId: number | |||||
| purchaseOrderLineId: number | |||||
| acceptedQty: number | |||||
| status?: string | |||||
| } | |||||
| export interface PurchaseQcCheck { | |||||
| qcCheckId: number; | |||||
| qty: number; | |||||
| } | |||||
| export interface PurchaseQCInput { | |||||
| sampleRate: number; | |||||
| sampleWeight: number; | |||||
| totalWeight: number; | |||||
| qcCheck: PurchaseQcCheck[]; | |||||
| } | |||||
| export const testFetch = cache(async (id: number) => { | |||||
| return serverFetchJson<PoResult>(`${BASE_API_URL}/po/detail/${id}`, { | |||||
| next: { tags: ["po"] }, | |||||
| }); | |||||
| }); | |||||
| export const createStockInLine = async (data: StockInLineEntry) => { | |||||
| const stockInLine = await serverFetchJson<PostStockInLiineResponse<StockInLineEntry>>(`${BASE_API_URL}/stockInLine/create`, { | |||||
| method: "POST", | |||||
| body: JSON.stringify(data), | |||||
| headers: { "Content-Type": "application/json" }, | |||||
| }); | |||||
| return stockInLine | |||||
| } | |||||
| export const updateStockInLine = async (data: StockInLineEntry) => { | |||||
| const stockInLine = await serverFetchJson<PostStockInLiineResponse<StockInLineEntry>>(`${BASE_API_URL}/stockInLine/update`, { | |||||
| method: "POST", | |||||
| body: JSON.stringify(data), | |||||
| headers: { "Content-Type": "application/json" }, | |||||
| }); | |||||
| return stockInLine | |||||
| } | |||||
| @@ -0,0 +1,55 @@ | |||||
| import { cache } from "react"; | |||||
| import "server-only"; | |||||
| import { serverFetchJson } from "@/app/utils/fetchUtil"; | |||||
| import { BASE_API_URL } from "@/config/api"; | |||||
| export interface PoResult { | |||||
| id: number | |||||
| code: string | |||||
| orderDate: string | |||||
| estimatedArrivalDate: string | |||||
| completedDate: string | |||||
| status: string | |||||
| pol?: PurchaseOrderLine[] | |||||
| } | |||||
| export interface PurchaseOrderLine { | |||||
| id: number | |||||
| purchaseOrderId: number | |||||
| itemId: number | |||||
| itemNo: string | |||||
| itemName: string | |||||
| qty: number | |||||
| price: number | |||||
| status: string | |||||
| stockInLine: StockInLine[] | |||||
| } | |||||
| export interface StockInLine { | |||||
| id: number | |||||
| stockInId: number | |||||
| purchaseOrderId?: number | |||||
| purchaseOrderLineId: number | |||||
| itemId: number | |||||
| itemNo: string | |||||
| itemName: string | |||||
| demandQty: number | |||||
| acceptedQty: number | |||||
| price: number | |||||
| priceUnit: string | |||||
| productDate: string | |||||
| shelfLifeDate: string | |||||
| status: string | |||||
| } | |||||
| export const fetchPoList = cache(async () => { | |||||
| return serverFetchJson<PoResult[]>(`${BASE_API_URL}/po/list`, { | |||||
| next: { tags: ["po"] }, | |||||
| }); | |||||
| }); | |||||
| export const fetchPoWithStockInLines = cache(async (id: number) => { | |||||
| return serverFetchJson<PoResult>(`${BASE_API_URL}/po/detail/${id}`, { | |||||
| next: { tags: ["po"] }, | |||||
| }); | |||||
| }); | |||||
| @@ -0,0 +1,12 @@ | |||||
| import { cache } from "react"; | |||||
| import "server-only"; | |||||
| export interface QcItemWithChecks { | |||||
| id: number; | |||||
| code: string; | |||||
| name: string; | |||||
| itemId: number; | |||||
| lowerLimit: number; | |||||
| upperLimit: number; | |||||
| description: string; | |||||
| } | |||||
| @@ -7,3 +7,14 @@ export const moneyFormatter = new Intl.NumberFormat("en-HK", { | |||||
| style: "currency", | style: "currency", | ||||
| currency: "HKD", | currency: "HKD", | ||||
| }); | }); | ||||
| export const stockInLineStatusMap: { [status: string]: number } = { | |||||
| draft: 0, | |||||
| pending: 1, | |||||
| qc: 2, | |||||
| determine1: 3, | |||||
| determine2: 4, | |||||
| determine3: 5, | |||||
| receiving: 6, | |||||
| completed: 7, | |||||
| }; | |||||
| @@ -62,7 +62,6 @@ export type TableRow<V, E> = Partial< | |||||
| >; | >; | ||||
| export interface InputDataGridProps<T, V, E> { | export interface InputDataGridProps<T, V, E> { | ||||
| // needAdd: boolean | undefined; | |||||
| apiRef: MutableRefObject<GridApiCommunity> | apiRef: MutableRefObject<GridApiCommunity> | ||||
| checkboxSelection: false | undefined; | checkboxSelection: false | undefined; | ||||
| _formKey: keyof T; | _formKey: keyof T; | ||||
| @@ -47,7 +47,7 @@ const NavigationContent: React.FC = () => { | |||||
| { | { | ||||
| icon: <RequestQuote />, | icon: <RequestQuote />, | ||||
| label: "Purchase Order", | label: "Purchase Order", | ||||
| path: "", | |||||
| path: "/po", | |||||
| }, | }, | ||||
| { | { | ||||
| icon: <RequestQuote />, | icon: <RequestQuote />, | ||||
| @@ -0,0 +1,154 @@ | |||||
| "use client"; | |||||
| import { fetchPoWithStockInLines, PoResult, PurchaseOrderLine, StockInLine } from "@/app/api/po"; | |||||
| import { | |||||
| Box, | |||||
| Button, | |||||
| Collapse, | |||||
| Grid, | |||||
| IconButton, | |||||
| Paper, | |||||
| Stack, | |||||
| Tab, | |||||
| Table, | |||||
| TableBody, | |||||
| TableCell, | |||||
| TableContainer, | |||||
| TableHead, | |||||
| TableRow, | |||||
| Tabs, | |||||
| TabsProps, | |||||
| TextField, | |||||
| Typography, | |||||
| } from "@mui/material"; | |||||
| import { useTranslation } from "react-i18next"; | |||||
| // import InputDataGrid, { TableRow } from "../InputDataGrid/InputDataGrid"; | |||||
| import { GridColDef, GridRowModel, useGridApiRef } from "@mui/x-data-grid"; | |||||
| import { testFetch } from "@/app/api/po/actions"; | |||||
| import { useCallback, useEffect, useMemo, useState } from "react"; | |||||
| import { FormProvider, useForm } from "react-hook-form"; | |||||
| import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown"; | |||||
| import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp"; | |||||
| import InputDataGrid, { | |||||
| TableRow as InputTableRow, | |||||
| } from "../InputDataGrid/InputDataGrid"; | |||||
| import PoInputGrid from "./PoInputGrid"; | |||||
| import { QcItemWithChecks } from "@/app/api/qc"; | |||||
| import { useSearchParams } from "next/navigation"; | |||||
| type Props = { | |||||
| po: PoResult; | |||||
| qc: QcItemWithChecks[] | |||||
| }; | |||||
| type EntryError = | |||||
| | { | |||||
| [field in keyof StockInLine]?: string; | |||||
| } | |||||
| | undefined; | |||||
| // type PolRow = TableRow<Partial<StockInLine>, EntryError>; | |||||
| const PoDetail: React.FC<Props> = ({ | |||||
| po, | |||||
| // poLine, | |||||
| qc | |||||
| }) => { | |||||
| const { t } = useTranslation(); | |||||
| const apiRef = useGridApiRef(); | |||||
| const [rows, setRows] = useState<PurchaseOrderLine[]>(po.pol || []); | |||||
| const params = useSearchParams() | |||||
| function Row(props: { row: PurchaseOrderLine }) { | |||||
| const { row } = props; | |||||
| const [open, setOpen] = useState(false); | |||||
| return ( | |||||
| <> | |||||
| <TableRow sx={{ "& > *": { borderBottom: "unset" }, color: "black" }}> | |||||
| <TableCell> | |||||
| <IconButton | |||||
| aria-label="expand row" | |||||
| size="small" | |||||
| onClick={() => setOpen(!open)} | |||||
| > | |||||
| {open ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />} | |||||
| </IconButton> | |||||
| </TableCell> | |||||
| <TableCell align="left">{row.itemNo}</TableCell> | |||||
| <TableCell align="left">{row.itemName}</TableCell> | |||||
| <TableCell align="left">{row.qty}</TableCell> | |||||
| {/* <TableCell align="left">{row.uom}</TableCell> */} | |||||
| <TableCell align="left">{row.price}</TableCell> | |||||
| {/* <TableCell align="left">{row.expiryDate}</TableCell> */} | |||||
| <TableCell align="left">{row.status}</TableCell> | |||||
| </TableRow> | |||||
| <TableRow> | |||||
| <TableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={6}> | |||||
| <Collapse in={open} timeout="auto" unmountOnExit> | |||||
| <Table> | |||||
| <TableBody> | |||||
| <TableRow> | |||||
| {/* <Button | |||||
| onClick={()=> { | |||||
| console.log(row) | |||||
| console.log(row.stockInLine) | |||||
| }} | |||||
| >console log</Button> */} | |||||
| <TableCell> | |||||
| <PoInputGrid | |||||
| qc={qc} | |||||
| setRows={setRows} | |||||
| itemDetail={row} | |||||
| stockInLine={row.stockInLine} | |||||
| /> | |||||
| </TableCell> | |||||
| </TableRow> | |||||
| </TableBody> | |||||
| </Table> | |||||
| </Collapse> | |||||
| </TableCell> | |||||
| </TableRow> | |||||
| </> | |||||
| ); | |||||
| } | |||||
| return ( | |||||
| <> | |||||
| <Stack | |||||
| spacing={2} | |||||
| // component="form" | |||||
| // onSubmit={formProps.handleSubmit(onSubmit, onSubmitError)} | |||||
| > | |||||
| <Grid> | |||||
| <Typography mb={2} variant="h4"> | |||||
| {po.code} | |||||
| </Typography> | |||||
| </Grid> | |||||
| <Grid> | |||||
| <TableContainer component={Paper}> | |||||
| <Table aria-label="collapsible table"> | |||||
| <TableHead> | |||||
| <TableRow> | |||||
| <TableCell /> {/* for the collapse button */} | |||||
| <TableCell>{t("itemNo")}</TableCell> | |||||
| <TableCell align="left">{t("itemName")}</TableCell> | |||||
| <TableCell align="left">{t("qty")}</TableCell> | |||||
| <TableCell align="left">{t("price")}</TableCell> | |||||
| {/* <TableCell align="left">{t("expiryDate")}</TableCell> */} | |||||
| <TableCell align="left">{t("status")}</TableCell> | |||||
| {/* <TableCell align="left">{"add icon button"}</TableCell> */} | |||||
| </TableRow> | |||||
| </TableHead> | |||||
| <TableBody> | |||||
| {rows.map((row) => ( | |||||
| <Row key={row.id} row={row} /> | |||||
| ))} | |||||
| </TableBody> | |||||
| </Table> | |||||
| </TableContainer> | |||||
| </Grid> | |||||
| </Stack> | |||||
| </> | |||||
| ); | |||||
| }; | |||||
| export default PoDetail; | |||||
| @@ -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 PoDetailLoading: 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 PoDetailLoading; | |||||
| @@ -0,0 +1,55 @@ | |||||
| import { fetchAllItems } from "@/app/api/settings/item"; | |||||
| // import ItemsSearch from "./ItemsSearch"; | |||||
| // import ItemsSearchLoading from "./ItemsSearchLoading"; | |||||
| import { SearchParams } from "@/app/utils/fetchUtil"; | |||||
| import { TypeEnum } from "@/app/utils/typeEnum"; | |||||
| import { notFound } from "next/navigation"; | |||||
| import { fetchPoWithStockInLines, PoResult } from "@/app/api/po"; | |||||
| import PoDetailLoading from "./PoDetailLoading"; | |||||
| import PoDetail from "./PoDetail"; | |||||
| import { QcItemWithChecks } from "@/app/api/qc"; | |||||
| interface SubComponents { | |||||
| Loading: typeof PoDetailLoading; | |||||
| } | |||||
| type Props = { | |||||
| id: number; | |||||
| }; | |||||
| const PoDetailWrapper: React.FC<Props> & SubComponents = async ({ id }) => { | |||||
| const [ | |||||
| poWithStockInLine | |||||
| ] = await Promise.all([ | |||||
| fetchPoWithStockInLines(id) | |||||
| ]) | |||||
| // const poWithStockInLine = await fetchPoWithStockInLines(id) | |||||
| console.log(poWithStockInLine) | |||||
| const qc: QcItemWithChecks[] = [ // just qc | |||||
| { | |||||
| id: 1, | |||||
| code: "code1", | |||||
| name: "name1", | |||||
| itemId: 1, | |||||
| lowerLimit: 1, | |||||
| upperLimit: 3, | |||||
| description: 'desc', | |||||
| }, | |||||
| { | |||||
| id: 2, | |||||
| code: "code2", | |||||
| name: "name2", | |||||
| itemId: 1, | |||||
| lowerLimit: 1, | |||||
| upperLimit: 3, | |||||
| description: 'desc', | |||||
| }, | |||||
| ] | |||||
| return <PoDetail po={poWithStockInLine} qc={qc} />; | |||||
| }; | |||||
| PoDetailWrapper.Loading = PoDetailLoading; | |||||
| export default PoDetailWrapper; | |||||
| @@ -0,0 +1,32 @@ | |||||
| import { | |||||
| Box, | |||||
| Button, | |||||
| Card, | |||||
| CardContent, | |||||
| Grid, | |||||
| Stack, | |||||
| Tab, | |||||
| Tabs, | |||||
| TabsProps, | |||||
| Typography, | |||||
| } from "@mui/material"; | |||||
| type Props = { | |||||
| // id?: number | |||||
| }; | |||||
| const PoInfoCard: React.FC<Props> = async ( | |||||
| { | |||||
| // id | |||||
| } | |||||
| ) => { | |||||
| return ( | |||||
| <> | |||||
| <Card> | |||||
| <CardContent> | |||||
| </CardContent> | |||||
| </Card> | |||||
| </> | |||||
| ); | |||||
| }; | |||||
| export default PoInfoCard; | |||||
| @@ -0,0 +1,435 @@ | |||||
| "use client"; | |||||
| import { | |||||
| FooterPropsOverrides, | |||||
| GridActionsCellItem, | |||||
| GridCellParams, | |||||
| GridRowId, | |||||
| GridRowIdGetter, | |||||
| GridRowModel, | |||||
| GridRowModes, | |||||
| GridRowModesModel, | |||||
| GridToolbarContainer, | |||||
| useGridApiRef, | |||||
| } from "@mui/x-data-grid"; | |||||
| import { | |||||
| Dispatch, | |||||
| MutableRefObject, | |||||
| SetStateAction, | |||||
| useCallback, | |||||
| useEffect, | |||||
| useMemo, | |||||
| useState, | |||||
| } from "react"; | |||||
| import StyledDataGrid from "../StyledDataGrid"; | |||||
| import { GridColDef } from "@mui/x-data-grid"; | |||||
| import { Box, Button, Grid, Typography } from "@mui/material"; | |||||
| import { useTranslation } from "react-i18next"; | |||||
| import { Add } from "@mui/icons-material"; | |||||
| import SaveIcon from "@mui/icons-material/Save"; | |||||
| import DeleteIcon from "@mui/icons-material/Delete"; | |||||
| import CancelIcon from "@mui/icons-material/Cancel"; | |||||
| import FactCheckIcon from "@mui/icons-material/FactCheck"; | |||||
| import ShoppingCartIcon from "@mui/icons-material/ShoppingCart"; | |||||
| import PoQcModal from "./PoQcModal"; | |||||
| import { QcItemWithChecks } from "src/app/api/qc"; | |||||
| import PlayArrowIcon from "@mui/icons-material/PlayArrow"; | |||||
| import { PurchaseOrderLine, StockInLine } from "@/app/api/po"; | |||||
| import { createStockInLine, testFetch } from "@/app/api/po/actions"; | |||||
| import { useSearchParams } from "next/navigation"; | |||||
| import { stockInLineStatusMap } from "@/app/utils/formatUtil"; | |||||
| interface ResultWithId { | |||||
| id: number; | |||||
| } | |||||
| interface Props { | |||||
| qc: QcItemWithChecks[]; | |||||
| setRows: Dispatch<SetStateAction<PurchaseOrderLine[]>>; | |||||
| itemDetail: PurchaseOrderLine; | |||||
| stockInLine: StockInLine[]; | |||||
| } | |||||
| export type StockInLineEntryError = { | |||||
| [field in keyof StockInLine]?: string; | |||||
| }; | |||||
| export type StockInLineRow = Partial< | |||||
| StockInLine & { | |||||
| isActive: boolean | undefined; | |||||
| _isNew: boolean; | |||||
| _error: StockInLineEntryError; | |||||
| } & ResultWithId | |||||
| >; | |||||
| class ProcessRowUpdateError extends Error { | |||||
| public readonly row: StockInLineRow; | |||||
| public readonly errors: StockInLineEntryError | undefined; | |||||
| constructor(row: StockInLineRow, message?: string, errors?: StockInLineEntryError) { | |||||
| super(message); | |||||
| this.row = row; | |||||
| this.errors = errors; | |||||
| Object.setPrototypeOf(this, ProcessRowUpdateError.prototype); | |||||
| } | |||||
| } | |||||
| function PoInputGrid({ qc, setRows, itemDetail, stockInLine }: Props) { | |||||
| const { t } = useTranslation("home"); | |||||
| const apiRef = useGridApiRef(); | |||||
| const [rowModesModel, setRowModesModel] = useState<GridRowModesModel>({}); | |||||
| const getRowId = useCallback<GridRowIdGetter<StockInLineRow>>( | |||||
| (row) => row.id as number, | |||||
| [] | |||||
| ); | |||||
| console.log(stockInLine); | |||||
| const [entries, setEntries] = useState<StockInLineRow[]>(stockInLine || []); | |||||
| const [modalInfo, setModalInfo] = useState<StockInLine>() | |||||
| const [qcOpen, setQcOpen] = useState(false); | |||||
| const [defaultQty, setDefaultQty] = useState(() => { | |||||
| const total = entries.reduce((acc, curr) => acc + (curr.acceptedQty || 0), 0); | |||||
| return itemDetail.qty - total; | |||||
| }); | |||||
| const params = useSearchParams() | |||||
| const refetchData = useCallback(async () => { | |||||
| const id = parseInt(params.get("id")!!) | |||||
| const res = await testFetch(id) | |||||
| const pol = res.pol!! | |||||
| console.log(pol) | |||||
| setRows(pol); | |||||
| }, [params]) | |||||
| const handleDelete = useCallback( | |||||
| (id: GridRowId) => () => { | |||||
| setEntries((es) => es.filter((e) => getRowId(e) !== id)); | |||||
| }, | |||||
| [getRowId] | |||||
| ); | |||||
| const handleStart = useCallback( | |||||
| (id: GridRowId, params: any) => () => { | |||||
| setRowModesModel((prev) => ({ | |||||
| ...prev, | |||||
| [id]: { mode: GridRowModes.View }, | |||||
| })); | |||||
| setTimeout(async () => { | |||||
| // post stock in line | |||||
| console.log("delayed"); | |||||
| console.log(params); | |||||
| const oldId = params.row.id | |||||
| console.log(oldId) | |||||
| const postData = { | |||||
| itemId: params.row.itemId, | |||||
| itemNo: params.row.itemNo, | |||||
| itemName: params.row.itemName, | |||||
| purchaseOrderId: params.row.purchaseOrderId, | |||||
| purchaseOrderLineId: params.row.purchaseOrderLineId, | |||||
| acceptedQty: params.row.acceptedQty, | |||||
| } | |||||
| const res = await createStockInLine(postData) | |||||
| console.log(res) | |||||
| // setEntries((prev) => prev.map((p) => p.id === oldId ? res.entity : p)) | |||||
| // do post directly to test | |||||
| // openStartModal(); | |||||
| }, 200); | |||||
| }, | |||||
| [] | |||||
| ); | |||||
| const handleQC = useCallback( | |||||
| (id: GridRowId, params: any) => () => { | |||||
| setRowModesModel((prev) => ({ | |||||
| ...prev, | |||||
| [id]: { mode: GridRowModes.View }, | |||||
| })); | |||||
| setModalInfo(params.row) | |||||
| setTimeout(() => { | |||||
| // open qc modal | |||||
| console.log("delayed"); | |||||
| openQcModal(); | |||||
| }, 200); | |||||
| }, | |||||
| [] | |||||
| ); | |||||
| const handleStockIn = useCallback( | |||||
| (id: GridRowId) => () => { | |||||
| setRowModesModel((prev) => ({ | |||||
| ...prev, | |||||
| [id]: { mode: GridRowModes.View }, | |||||
| })); | |||||
| setTimeout(() => { | |||||
| // open stock in modal | |||||
| // return the record with its status as pending | |||||
| // update layout | |||||
| console.log("delayed"); | |||||
| }, 200); | |||||
| }, | |||||
| [] | |||||
| ); | |||||
| const closeQcModal = useCallback(() => { | |||||
| setQcOpen(false); | |||||
| }, []); | |||||
| const openQcModal = useCallback(() => { | |||||
| setQcOpen(true); | |||||
| }, []); | |||||
| const columns = useMemo<GridColDef[]>( | |||||
| () => [ | |||||
| { | |||||
| field: "itemNo", | |||||
| flex: 1, | |||||
| }, | |||||
| { | |||||
| field: "itemName", | |||||
| flex: 1, | |||||
| }, | |||||
| { | |||||
| field: "acceptedQty", | |||||
| headerName: "qty", | |||||
| flex: 0.5, | |||||
| type: "number", | |||||
| editable: true, | |||||
| // replace with tooltip + content | |||||
| }, | |||||
| { | |||||
| field: "status", | |||||
| flex: 0.5, | |||||
| editable: true | |||||
| }, | |||||
| { | |||||
| field: "actions", | |||||
| type: "actions", | |||||
| headerName: "start | qc | stock in | delete", | |||||
| flex: 1, | |||||
| cellClassName: "actions", | |||||
| getActions: (params) => { | |||||
| // const stockInLineStatusMap: { [status: string]: number } = { | |||||
| // draft: 0, | |||||
| // pending: 1, | |||||
| // qc: 2, | |||||
| // determine1: 3, | |||||
| // determine2: 4, | |||||
| // determine3: 5, | |||||
| // receiving: 6, | |||||
| // completed: 7, | |||||
| // }; | |||||
| console.log(params.row.status); | |||||
| const status = params.row.status.toLowerCase() | |||||
| return [ | |||||
| <GridActionsCellItem | |||||
| icon={<PlayArrowIcon />} | |||||
| label="start" | |||||
| sx={{ | |||||
| color: "primary.main", | |||||
| }} | |||||
| disabled={!(stockInLineStatusMap[status] === 0)} | |||||
| // set _isNew to false after posting | |||||
| // or check status | |||||
| onClick={handleStart(params.row.id, params)} | |||||
| color="inherit" | |||||
| key="edit" | |||||
| />, | |||||
| <GridActionsCellItem | |||||
| icon={<FactCheckIcon />} | |||||
| label="qc" | |||||
| sx={{ | |||||
| color: "primary.main", | |||||
| }} | |||||
| disabled={stockInLineStatusMap[status] <= 0 || stockInLineStatusMap[status] >= 6} | |||||
| // set _isNew to false after posting | |||||
| // or check status | |||||
| onClick={handleQC(params.row.id, params)} | |||||
| color="inherit" | |||||
| key="edit" | |||||
| />, | |||||
| <GridActionsCellItem | |||||
| icon={<ShoppingCartIcon />} | |||||
| label="stockin" | |||||
| sx={{ | |||||
| color: "primary.main", | |||||
| }} | |||||
| disabled={stockInLineStatusMap[status] !== 6} | |||||
| // set _isNew to false after posting | |||||
| // or check status | |||||
| onClick={handleStockIn(params.row.id)} | |||||
| color="inherit" | |||||
| key="edit" | |||||
| />, | |||||
| <GridActionsCellItem | |||||
| icon={<DeleteIcon />} | |||||
| label="Delete" | |||||
| sx={{ | |||||
| color: "error.main", | |||||
| }} | |||||
| disabled={stockInLineStatusMap[status] !== 0} | |||||
| // disabled={Boolean(params.row.status)} | |||||
| onClick={handleDelete(params.row.id)} | |||||
| color="inherit" | |||||
| key="edit" | |||||
| />, | |||||
| ]; | |||||
| }, | |||||
| }, | |||||
| ], | |||||
| [] | |||||
| ); | |||||
| const addRow = useCallback(() => { | |||||
| const newEntry = { | |||||
| id: Date.now(), | |||||
| _isNew: true, | |||||
| itemId: itemDetail.itemId, | |||||
| purchaseOrderId: itemDetail.purchaseOrderId, | |||||
| purchaseOrderLineId: itemDetail.id, | |||||
| itemNo: itemDetail.itemNo, | |||||
| itemName: itemDetail.itemName, | |||||
| acceptedQty: defaultQty, | |||||
| status: "draft", | |||||
| }; | |||||
| setEntries((e) => [...e, newEntry]); | |||||
| setRowModesModel((model) => ({ | |||||
| ...model, | |||||
| [getRowId(newEntry)]: { | |||||
| mode: GridRowModes.Edit, | |||||
| // fieldToFocus: "projectId", | |||||
| }, | |||||
| })); | |||||
| }, [getRowId]); | |||||
| const validation = useCallback( | |||||
| ( | |||||
| newRow: GridRowModel<StockInLineRow> | |||||
| // rowModel: GridRowSelectionModel | |||||
| ): StockInLineEntryError | undefined => { | |||||
| const error: StockInLineEntryError = {}; | |||||
| console.log(newRow); | |||||
| console.log(defaultQty); | |||||
| if (newRow.acceptedQty && newRow.acceptedQty > defaultQty) { | |||||
| error["acceptedQty"] = "qty cannot be greater than remaining qty"; | |||||
| } | |||||
| return Object.keys(error).length > 0 ? error : undefined; | |||||
| }, | |||||
| [defaultQty] | |||||
| ); | |||||
| const processRowUpdate = useCallback( | |||||
| (newRow: GridRowModel<StockInLineRow>, originalRow: GridRowModel<StockInLineRow>) => { | |||||
| const errors = validation(newRow); // change to validation | |||||
| if (errors) { | |||||
| throw new ProcessRowUpdateError( | |||||
| originalRow, | |||||
| "validation error", | |||||
| errors | |||||
| ); | |||||
| } | |||||
| const { _isNew, _error, ...updatedRow } = newRow; | |||||
| const rowToSave = { | |||||
| ...updatedRow, | |||||
| } satisfies StockInLineRow; | |||||
| const newEntries = entries.map((e) => | |||||
| getRowId(e) === getRowId(originalRow) ? rowToSave : e | |||||
| ); | |||||
| setEntries(newEntries); | |||||
| //update remaining qty | |||||
| const total = newEntries.reduce((acc, curr) => acc + (curr.acceptedQty || 0), 0); | |||||
| setDefaultQty(itemDetail.qty - total); | |||||
| return rowToSave; | |||||
| }, | |||||
| [getRowId, entries] | |||||
| ); | |||||
| const onProcessRowUpdateError = useCallback( | |||||
| (updateError: ProcessRowUpdateError) => { | |||||
| const errors = updateError.errors; | |||||
| const oldRow = updateError.row; | |||||
| apiRef.current.updateRows([{ ...oldRow, _error: errors }]); | |||||
| }, | |||||
| [apiRef] | |||||
| ); | |||||
| useEffect(() => { | |||||
| const total = entries.reduce((acc, curr) => acc + (curr.acceptedQty || 0), 0); | |||||
| setDefaultQty(itemDetail.qty - total); | |||||
| }, [entries]); | |||||
| const footer = ( | |||||
| <Box display="flex" gap={2} alignItems="center"> | |||||
| <Button | |||||
| disableRipple | |||||
| variant="outlined" | |||||
| startIcon={<Add />} | |||||
| disabled={defaultQty <= 0} | |||||
| onClick={addRow} | |||||
| size="small" | |||||
| > | |||||
| {t("Record pol")} | |||||
| </Button> | |||||
| </Box> | |||||
| ); | |||||
| return ( | |||||
| <> | |||||
| <StyledDataGrid | |||||
| getRowId={getRowId} | |||||
| apiRef={apiRef} | |||||
| autoHeight | |||||
| sx={{ | |||||
| "--DataGrid-overlayHeight": "100px", | |||||
| ".MuiDataGrid-row .MuiDataGrid-cell.hasError": { | |||||
| border: "1px solid", | |||||
| borderColor: "error.main", | |||||
| }, | |||||
| ".MuiDataGrid-row .MuiDataGrid-cell.hasWarning": { | |||||
| border: "1px solid", | |||||
| borderColor: "warning.main", | |||||
| }, | |||||
| }} | |||||
| disableColumnMenu | |||||
| editMode="row" | |||||
| rows={entries} | |||||
| rowModesModel={rowModesModel} | |||||
| onRowModesModelChange={setRowModesModel} | |||||
| processRowUpdate={processRowUpdate} | |||||
| onProcessRowUpdateError={onProcessRowUpdateError} | |||||
| columns={columns} | |||||
| getCellClassName={(params: GridCellParams<StockInLineRow>) => { | |||||
| let classname = ""; | |||||
| if (params.row._error) { | |||||
| classname = "hasError"; | |||||
| } | |||||
| return classname; | |||||
| }} | |||||
| slots={{ | |||||
| footer: FooterToolbar, | |||||
| noRowsOverlay: NoRowsOverlay, | |||||
| }} | |||||
| slotProps={{ | |||||
| footer: { child: footer }, | |||||
| }} | |||||
| /> | |||||
| <> | |||||
| <PoQcModal | |||||
| setEntries={setEntries} | |||||
| qc={qc} | |||||
| open={qcOpen} | |||||
| onClose={closeQcModal} | |||||
| itemDetail={modalInfo!!} | |||||
| /> | |||||
| </> | |||||
| </> | |||||
| ); | |||||
| } | |||||
| const NoRowsOverlay: React.FC = () => { | |||||
| const { t } = useTranslation("home"); | |||||
| return ( | |||||
| <Box | |||||
| display="flex" | |||||
| justifyContent="center" | |||||
| alignItems="center" | |||||
| height="100%" | |||||
| > | |||||
| <Typography variant="caption">{t("Add some entries!")}</Typography> | |||||
| </Box> | |||||
| ); | |||||
| }; | |||||
| const FooterToolbar: React.FC<FooterPropsOverrides> = ({ child }) => { | |||||
| return <GridToolbarContainer sx={{ p: 2 }}>{child}</GridToolbarContainer>; | |||||
| }; | |||||
| export default PoInputGrid; | |||||
| @@ -0,0 +1,141 @@ | |||||
| "use client"; | |||||
| import { PurchaseQCInput, StockInLineEntry, updateStockInLine } from "@/app/api/po/actions"; | |||||
| import { Box, Button, Modal, ModalProps, Stack } from "@mui/material"; | |||||
| import { Dispatch, SetStateAction, useCallback, useEffect, useState } from "react"; | |||||
| import { FormProvider, SubmitHandler, useForm } from "react-hook-form"; | |||||
| import { useTranslation } from "react-i18next"; | |||||
| import QcForm from "./QcForm"; | |||||
| import { QcItemWithChecks } from "@/app/api/qc"; | |||||
| import { Check } from "@mui/icons-material"; | |||||
| import { StockInLine } from "@/app/api/po"; | |||||
| import { useSearchParams } from "next/navigation"; | |||||
| import { stockInLineStatusMap } from "@/app/utils/formatUtil"; | |||||
| import { StockInLineRow } from "./PoInputGrid"; | |||||
| // type: | |||||
| interface CommonProps extends Omit<ModalProps, "children"> { | |||||
| setEntries: Dispatch<SetStateAction<StockInLineRow[]>> | |||||
| itemDetail: StockInLine; | |||||
| qc?: QcItemWithChecks[]; | |||||
| warehouse?: any[]; | |||||
| } | |||||
| interface QcProps extends CommonProps { | |||||
| qc: QcItemWithChecks[]; | |||||
| } | |||||
| interface StockInProps extends CommonProps { | |||||
| // naming | |||||
| warehouse: any[]; | |||||
| } | |||||
| type Props = QcProps | StockInProps; | |||||
| const style = { | |||||
| position: "absolute", | |||||
| top: "50%", | |||||
| left: "50%", | |||||
| transform: "translate(-50%, -50%)", | |||||
| bgcolor: "background.paper", | |||||
| pt: 5, | |||||
| px: 5, | |||||
| pb: 10, | |||||
| width: { xs: "80%", sm: "80%", md: "80%" }, | |||||
| }; | |||||
| const PoQcModal: React.FC<Props> = ({ | |||||
| setEntries, | |||||
| open, | |||||
| onClose, | |||||
| itemDetail, | |||||
| qc, | |||||
| warehouse, | |||||
| }) => { | |||||
| console.log(itemDetail) | |||||
| const [serverError, setServerError] = useState(""); | |||||
| const { t } = useTranslation(); | |||||
| const params = useSearchParams() | |||||
| console.log(params.get("id")) | |||||
| const [defaultValues, setDefaultValues] = useState({}); | |||||
| const formProps = useForm<PurchaseQCInput>({ | |||||
| defaultValues: defaultValues ? defaultValues : {}, | |||||
| }); | |||||
| const errors = formProps.formState.errors; | |||||
| const closeHandler = useCallback<NonNullable<ModalProps["onClose"]>>( | |||||
| (...args) => { | |||||
| onClose?.(...args); | |||||
| // reset(); | |||||
| }, | |||||
| [onClose] | |||||
| ); | |||||
| useEffect(() => { | |||||
| setDefaultValues({}); | |||||
| }, []); | |||||
| const onSubmit = useCallback<SubmitHandler<PurchaseQCInput & {}>>( | |||||
| async (data, event) => { | |||||
| let hasErrors = false; | |||||
| console.log(errors); | |||||
| console.log(data); | |||||
| console.log(itemDetail); | |||||
| try { | |||||
| if (hasErrors) { | |||||
| setServerError(t("An error has occurred. Please try again later.")); | |||||
| return false; | |||||
| } | |||||
| // do post update stock in line | |||||
| // const reqStatus = stockInLineStatusMap | |||||
| const args: StockInLineEntry = { | |||||
| id: itemDetail.id, | |||||
| purchaseOrderId: parseInt(params.get("id")!!), | |||||
| purchaseOrderLineId: itemDetail.purchaseOrderLineId, | |||||
| itemId: itemDetail.itemId, | |||||
| acceptedQty: itemDetail.acceptedQty, | |||||
| status: "receiving", | |||||
| } | |||||
| console.log(args) | |||||
| const res = await updateStockInLine(args) | |||||
| // this.res.entity = list of entity | |||||
| for (const inLine in res.entity as StockInLine[]) { | |||||
| } | |||||
| console.log(res) | |||||
| // if (res) | |||||
| } catch (e) { | |||||
| // server error | |||||
| setServerError(t("An error has occurred. Please try again later.")); | |||||
| console.log(e); | |||||
| } | |||||
| }, | |||||
| [t, itemDetail] | |||||
| ); | |||||
| return ( | |||||
| <> | |||||
| <Modal open={open} onClose={closeHandler}> | |||||
| <FormProvider {...formProps}> | |||||
| <Box | |||||
| sx={style} | |||||
| component="form" | |||||
| onSubmit={formProps.handleSubmit(onSubmit)} | |||||
| > | |||||
| {qc && <QcForm qc={qc} itemDetail={itemDetail} />} | |||||
| <Stack direction="row" justifyContent="flex-end" gap={1}> | |||||
| <Button | |||||
| name="submit" | |||||
| variant="contained" | |||||
| startIcon={<Check />} | |||||
| type="submit" | |||||
| // disabled={submitDisabled} | |||||
| > | |||||
| {t("submit")} | |||||
| </Button> | |||||
| </Stack> | |||||
| </Box> | |||||
| </FormProvider> | |||||
| </Modal> | |||||
| </> | |||||
| ); | |||||
| }; | |||||
| export default PoQcModal; | |||||
| @@ -0,0 +1,215 @@ | |||||
| "use client"; | |||||
| import { PurchaseQcCheck, PurchaseQCInput } from "@/app/api/po/actions"; | |||||
| import { | |||||
| Box, | |||||
| Card, | |||||
| CardContent, | |||||
| Grid, | |||||
| Stack, | |||||
| TextField, | |||||
| Tooltip, | |||||
| Typography, | |||||
| } from "@mui/material"; | |||||
| import { useFormContext } from "react-hook-form"; | |||||
| import { useTranslation } from "react-i18next"; | |||||
| import StyledDataGrid from "../StyledDataGrid"; | |||||
| import { useCallback, useMemo } from "react"; | |||||
| import { | |||||
| GridColDef, | |||||
| GridRowIdGetter, | |||||
| GridRowModel, | |||||
| useGridApiContext, | |||||
| GridRenderCellParams, | |||||
| GridRenderEditCellParams, | |||||
| useGridApiRef, | |||||
| } from "@mui/x-data-grid"; | |||||
| import InputDataGrid from "../InputDataGrid"; | |||||
| import { TableRow } from "../InputDataGrid/InputDataGrid"; | |||||
| import TwoLineCell from "./TwoLineCell"; | |||||
| import QcSelect from "./QcSelect"; | |||||
| import { QcItemWithChecks } from "@/app/api/qc"; | |||||
| import { GridEditInputCell } from "@mui/x-data-grid"; | |||||
| import { StockInLine } from "@/app/api/po"; | |||||
| interface Props { | |||||
| itemDetail: StockInLine; | |||||
| qc: QcItemWithChecks[]; | |||||
| } | |||||
| type EntryError = | |||||
| | { | |||||
| [field in keyof PurchaseQcCheck]?: string; | |||||
| } | |||||
| | undefined; | |||||
| type PoQcRow = TableRow<Partial<PurchaseQcCheck>, EntryError>; | |||||
| const QcForm: React.FC<Props> = ({ | |||||
| qc, | |||||
| itemDetail, | |||||
| }) => { | |||||
| const { t } = useTranslation(); | |||||
| const apiRef = useGridApiRef(); | |||||
| const { | |||||
| register, | |||||
| formState: { errors, defaultValues, touchedFields }, | |||||
| watch, | |||||
| control, | |||||
| setValue, | |||||
| getValues, | |||||
| reset, | |||||
| resetField, | |||||
| setError, | |||||
| clearErrors, | |||||
| } = useFormContext<PurchaseQCInput>(); | |||||
| console.log(itemDetail) | |||||
| const columns = useMemo<GridColDef[]>( | |||||
| () => [ | |||||
| { | |||||
| field: "qcCheckId", | |||||
| headerName: "qc Check", | |||||
| flex: 1, | |||||
| editable: true, | |||||
| valueFormatter(params) { | |||||
| const row = params.id ? params.api.getRow<PoQcRow>(params.id) : null; | |||||
| if (!row) { | |||||
| return null; | |||||
| } | |||||
| const Qc = qc.find((q) => q.id === row.qcCheckId); | |||||
| return Qc ? `${Qc.code} - ${Qc.name}` : t("Please select QC"); | |||||
| }, | |||||
| renderCell(params: GridRenderCellParams<PoQcRow, number>) { | |||||
| console.log(params.value); | |||||
| return <TwoLineCell>{params.formattedValue}</TwoLineCell>; | |||||
| }, | |||||
| renderEditCell(params: GridRenderEditCellParams<PoQcRow, number>) { | |||||
| const errorMessage = | |||||
| params.row._error?.[params.field as keyof PurchaseQcCheck]; | |||||
| console.log(errorMessage); | |||||
| const content = ( | |||||
| <QcSelect | |||||
| allQcs={qc} | |||||
| value={params.row.qcCheckId} | |||||
| onQcSelect={async (qcCheckId) => { | |||||
| await params.api.setEditCellValue({ | |||||
| id: params.id, | |||||
| field: "qcCheckId", | |||||
| value: qcCheckId, | |||||
| }); | |||||
| }} | |||||
| /> | |||||
| ); | |||||
| return errorMessage ? ( | |||||
| <Tooltip title={t(errorMessage)}> | |||||
| <Box width="100%">{content}</Box> | |||||
| </Tooltip> | |||||
| ) : ( | |||||
| content | |||||
| ); | |||||
| }, | |||||
| }, | |||||
| { | |||||
| field: "qty", | |||||
| headerName: "qty", | |||||
| flex: 1, | |||||
| editable: true, | |||||
| type: "number", | |||||
| renderEditCell(params: GridRenderEditCellParams<PoQcRow>) { | |||||
| const errorMessage = | |||||
| params.row._error?.[params.field as keyof PurchaseQcCheck]; | |||||
| const content = <GridEditInputCell {...params} />; | |||||
| return errorMessage ? ( | |||||
| <Tooltip title={t(errorMessage)}> | |||||
| <Box width="100%">{content}</Box> | |||||
| </Tooltip> | |||||
| ) : ( | |||||
| content | |||||
| ); | |||||
| }, | |||||
| }, | |||||
| ], | |||||
| [] | |||||
| ); | |||||
| const validationTest = useCallback( | |||||
| (newRow: GridRowModel<PoQcRow>): EntryError => { | |||||
| const error: EntryError = {}; | |||||
| const { qcCheckId, qty } = newRow; | |||||
| if (!qcCheckId || qcCheckId <= 0) { | |||||
| error["qcCheckId"] = "select qc"; | |||||
| } | |||||
| if (!qty || qty <= 0) { | |||||
| error["qty"] = "enter a qty"; | |||||
| } | |||||
| return Object.keys(error).length > 0 ? error : undefined; | |||||
| }, | |||||
| [] | |||||
| ); | |||||
| return ( | |||||
| <Grid container justifyContent="flex-start" alignItems="flex-start"> | |||||
| <Grid item xs={12}> | |||||
| <Typography variant="h6" display="block" marginBlockEnd={1}> | |||||
| {t("Qc Detail")} | |||||
| </Typography> | |||||
| </Grid> | |||||
| <Grid | |||||
| container | |||||
| justifyContent="flex-start" | |||||
| alignItems="flex-start" | |||||
| spacing={2} | |||||
| sx={{ mt: 0.5 }} | |||||
| > | |||||
| <Grid item xs={4}> | |||||
| <TextField | |||||
| label={t("sampleRate")} | |||||
| fullWidth | |||||
| {...register("sampleRate", { | |||||
| required: "sampleRate required!", | |||||
| })} | |||||
| error={Boolean(errors.sampleRate)} | |||||
| helperText={errors.sampleRate?.message} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={4}> | |||||
| <TextField | |||||
| label={t("sampleWeight")} | |||||
| fullWidth | |||||
| {...register("sampleWeight", { | |||||
| required: "sampleWeight required!", | |||||
| })} | |||||
| error={Boolean(errors.sampleWeight)} | |||||
| helperText={errors.sampleWeight?.message} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={4}> | |||||
| <TextField | |||||
| label={t("totalWeight")} | |||||
| fullWidth | |||||
| {...register("totalWeight", { | |||||
| required: "totalWeight required!", | |||||
| })} | |||||
| error={Boolean(errors.totalWeight)} | |||||
| helperText={errors.totalWeight?.message} | |||||
| /> | |||||
| </Grid> | |||||
| </Grid> | |||||
| <Grid | |||||
| container | |||||
| justifyContent="flex-start" | |||||
| alignItems="flex-start" | |||||
| spacing={2} | |||||
| sx={{ mt: 0.5 }} | |||||
| > | |||||
| <Grid item xs={12}> | |||||
| <InputDataGrid<PurchaseQCInput, PurchaseQcCheck, EntryError> | |||||
| apiRef={apiRef} | |||||
| checkboxSelection={false} | |||||
| _formKey={"qcCheck"} | |||||
| columns={columns} | |||||
| validateRow={validationTest} | |||||
| /> | |||||
| </Grid> | |||||
| </Grid> | |||||
| </Grid> | |||||
| ); | |||||
| }; | |||||
| export default QcForm; | |||||
| @@ -0,0 +1,78 @@ | |||||
| import React, { useCallback, useMemo } from "react"; | |||||
| import { | |||||
| Autocomplete, | |||||
| Box, | |||||
| Checkbox, | |||||
| Chip, | |||||
| ListSubheader, | |||||
| MenuItem, | |||||
| TextField, | |||||
| Tooltip, | |||||
| } from "@mui/material"; | |||||
| import { QcItemWithChecks } from "@/app/api/qc"; | |||||
| import { useTranslation } from "react-i18next"; | |||||
| interface CommonProps { | |||||
| allQcs: QcItemWithChecks[]; | |||||
| error?: boolean; | |||||
| } | |||||
| interface SingleAutocompleteProps extends CommonProps { | |||||
| value: number | string | undefined; | |||||
| onQcSelect: (qcCheckId: number) => void | Promise<void>; | |||||
| // multiple: false; | |||||
| } | |||||
| type Props = SingleAutocompleteProps; | |||||
| const QcSelect: React.FC<Props> = ({ allQcs, value, error, onQcSelect }) => { | |||||
| const { t } = useTranslation("home"); | |||||
| const filteredQc = useMemo(() => { | |||||
| // do filtering here if any | |||||
| return allQcs; | |||||
| }, []); | |||||
| const options = useMemo(() => { | |||||
| return [ | |||||
| { | |||||
| value: -1, // think think sin | |||||
| label: t("None"), | |||||
| group: "default", | |||||
| }, | |||||
| ...filteredQc.map((q) => ({ | |||||
| value: q.id, | |||||
| label: `${q.code} - ${q.name}`, | |||||
| group: "existing", | |||||
| })), | |||||
| ]; | |||||
| }, [filteredQc]); | |||||
| const currentValue = options.find((o) => o.value === value) || options[0]; | |||||
| const onChange = useCallback( | |||||
| ( | |||||
| event: React.SyntheticEvent, | |||||
| newValue: { value: number; group: string } | { value: number }[] | |||||
| ) => { | |||||
| const singleNewVal = newValue as { | |||||
| value: number; | |||||
| group: string; | |||||
| }; | |||||
| onQcSelect(singleNewVal.value); | |||||
| }, | |||||
| [onQcSelect] | |||||
| ); | |||||
| return ( | |||||
| <Autocomplete | |||||
| noOptionsText={t("No Qc")} | |||||
| disableClearable | |||||
| fullWidth | |||||
| value={currentValue} | |||||
| onChange={onChange} | |||||
| getOptionLabel={(option) => option.label} | |||||
| options={options} | |||||
| renderInput={(params) => <TextField {...params} error={error} />} | |||||
| /> | |||||
| ); | |||||
| }; | |||||
| export default QcSelect; | |||||
| @@ -0,0 +1,24 @@ | |||||
| import { Box, Tooltip } from "@mui/material"; | |||||
| import React from "react"; | |||||
| const TwoLineCell: React.FC<{ children: React.ReactNode }> = ({ children }) => { | |||||
| return ( | |||||
| <Tooltip title={children}> | |||||
| <Box | |||||
| sx={{ | |||||
| whiteSpace: "normal", | |||||
| overflow: "hidden", | |||||
| textOverflow: "ellipsis", | |||||
| display: "-webkit-box", | |||||
| WebkitLineClamp: 2, | |||||
| WebkitBoxOrient: "vertical", | |||||
| lineHeight: "22px", | |||||
| }} | |||||
| > | |||||
| {children} | |||||
| </Box> | |||||
| </Tooltip> | |||||
| ); | |||||
| }; | |||||
| export default TwoLineCell; | |||||
| @@ -0,0 +1 @@ | |||||
| export { default } from "./PoDetailWrapper"; | |||||
| @@ -0,0 +1,93 @@ | |||||
| "use client"; | |||||
| import { PoResult } from "@/app/api/po"; | |||||
| import { useCallback, useMemo, useState } from "react"; | |||||
| import { useTranslation } from "react-i18next"; | |||||
| import { useRouter, useSearchParams } from "next/navigation"; | |||||
| import SearchBox, { Criterion } from "../SearchBox"; | |||||
| import SearchResults, { Column } from "../SearchResults"; | |||||
| import { EditNote } from "@mui/icons-material"; | |||||
| type Props = { | |||||
| po: PoResult[]; | |||||
| }; | |||||
| type SearchQuery = Partial<Omit<PoResult, "id">>; | |||||
| type SearchParamNames = keyof SearchQuery; | |||||
| const PoSearch: React.FC<Props> = ({ po }) => { | |||||
| const [filteredPo, setFilteredPo] = useState<PoResult[]>(po); | |||||
| const { t } = useTranslation("po"); | |||||
| const router = useRouter(); | |||||
| const searchCriteria: Criterion<SearchParamNames>[] = useMemo(() => { | |||||
| var searchCriteria: Criterion<SearchParamNames>[] = [ | |||||
| { label: t("Code"), paramName: "code", type: "text" }, | |||||
| // { label: t("Name"), paramName: "name", type: "text" }, | |||||
| ]; | |||||
| return searchCriteria; | |||||
| }, [t, po]); | |||||
| const onDetailClick = useCallback( | |||||
| (po: PoResult) => { | |||||
| router.push(`/po/edit?id=${po.id}`); | |||||
| }, | |||||
| [router] | |||||
| ); | |||||
| const onDeleteClick = useCallback( | |||||
| (po: PoResult) => {}, | |||||
| [router] | |||||
| ); | |||||
| const columns = useMemo<Column<PoResult>[]>( | |||||
| () => [ | |||||
| { | |||||
| 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, | |||||
| // }, | |||||
| ], | |||||
| [filteredPo] | |||||
| ); | |||||
| const onReset = useCallback(() => { | |||||
| setFilteredPo(po); | |||||
| }, [po]); | |||||
| return ( | |||||
| <> | |||||
| <SearchBox | |||||
| criteria={searchCriteria} | |||||
| onSearch={(query) => { | |||||
| setFilteredPo( | |||||
| po.filter((p) => { | |||||
| return ( | |||||
| p.code.toLowerCase().includes(query.code.toLowerCase()) | |||||
| // p.name.toLowerCase().includes(query.name.toLowerCase()) | |||||
| ); | |||||
| }) | |||||
| ); | |||||
| }} | |||||
| onReset={onReset} | |||||
| /> | |||||
| <SearchResults<PoResult> items={filteredPo} columns={columns}/> | |||||
| </> | |||||
| ); | |||||
| }; | |||||
| export default PoSearch; | |||||
| @@ -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 PoSearchLoading: 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 PoSearchLoading; | |||||
| @@ -0,0 +1,35 @@ | |||||
| import { fetchAllItems } from "@/app/api/settings/item"; | |||||
| // import ItemsSearch from "./ItemsSearch"; | |||||
| // import ItemsSearchLoading from "./ItemsSearchLoading"; | |||||
| import { SearchParams } from "@/app/utils/fetchUtil"; | |||||
| import { TypeEnum } from "@/app/utils/typeEnum"; | |||||
| import { notFound } from "next/navigation"; | |||||
| import PoSearchLoading from "./PoSearchLoading"; | |||||
| import PoSearch from "./PoSearch"; | |||||
| import { fetchPoList, PoResult } from "@/app/api/po"; | |||||
| interface SubComponents { | |||||
| Loading: typeof PoSearchLoading; | |||||
| } | |||||
| type Props = { | |||||
| // type: TypeEnum; | |||||
| }; | |||||
| const PoSearchWrapper: React.FC<Props> & SubComponents = async ( | |||||
| { | |||||
| // type, | |||||
| } | |||||
| ) => { | |||||
| const [ | |||||
| po | |||||
| ] = await Promise.all([ | |||||
| fetchPoList() | |||||
| ]); | |||||
| return <PoSearch po={po} />; | |||||
| }; | |||||
| PoSearchWrapper.Loading = PoSearchLoading; | |||||
| export default PoSearchWrapper; | |||||
| @@ -0,0 +1 @@ | |||||
| export { default } from "./PoSearchWrapper"; | |||||
| @@ -1,58 +1,63 @@ | |||||
| import { SaveQcItemInputs } from "@/app/api/settings/qcItem/actions" | |||||
| import { Box, Card, CardContent, Grid, Stack, TextField, Typography } from "@mui/material" | |||||
| import { useFormContext } from "react-hook-form" | |||||
| import { useTranslation } from "react-i18next" | |||||
| import { SaveQcItemInputs } from "@/app/api/settings/qcItem/actions"; | |||||
| import { | |||||
| Box, | |||||
| Card, | |||||
| CardContent, | |||||
| Grid, | |||||
| Stack, | |||||
| TextField, | |||||
| Typography, | |||||
| } from "@mui/material"; | |||||
| import { useFormContext } from "react-hook-form"; | |||||
| import { useTranslation } from "react-i18next"; | |||||
| const QcItemDetails = () => { | const QcItemDetails = () => { | ||||
| const { t } = useTranslation(); | |||||
| const { register } = useFormContext<SaveQcItemInputs>(); | |||||
| const { t } = useTranslation() | |||||
| const { | |||||
| register | |||||
| } = useFormContext<SaveQcItemInputs>() | |||||
| return ( | |||||
| <Card sx={{ display: "block" }}> | |||||
| <CardContent component={Stack} spacing={4}> | |||||
| <Box> | |||||
| <Typography variant={"overline"} display={"block"} marginBlockEnd={1}> | |||||
| {t("Qc Item Details")} | |||||
| </Typography> | |||||
| <Grid container spacing={2} columns={{ xs: 6, sm: 12 }}> | |||||
| <Grid item xs={6}> | |||||
| <TextField | |||||
| label={t("Code")} | |||||
| fullWidth | |||||
| {...register("code", { | |||||
| required: "Code required!", | |||||
| maxLength: 30, | |||||
| })} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={6}> | |||||
| <TextField | |||||
| label={t("Name")} | |||||
| fullWidth | |||||
| {...register("name", { | |||||
| required: "Name required!", | |||||
| maxLength: 30, | |||||
| })} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={12}> | |||||
| <TextField | |||||
| label={t("Description")} | |||||
| // multiline | |||||
| fullWidth | |||||
| {...register("description", { | |||||
| maxLength: 100, | |||||
| })} | |||||
| /> | |||||
| </Grid> | |||||
| </Grid> | |||||
| </Box> | |||||
| </CardContent> | |||||
| </Card> | |||||
| ); | |||||
| }; | |||||
| return ( | |||||
| <Card sx={{ display: "block" }}> | |||||
| <CardContent component={Stack} spacing={4}> | |||||
| <Box> | |||||
| <Typography variant={"overline"} display={"block"} marginBlockEnd={1}> | |||||
| {t("Qc Item Details")} | |||||
| </Typography> | |||||
| <Grid container spacing={2} columns={{ xs: 6, sm: 12 }}> | |||||
| <Grid item xs={6}> | |||||
| <TextField | |||||
| label={t("Code")} | |||||
| fullWidth | |||||
| {...register("code", { | |||||
| required: "Code required!", | |||||
| maxLength: 30 | |||||
| })} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={6}> | |||||
| <TextField | |||||
| label={t("Name")} | |||||
| fullWidth | |||||
| {...register("name", { | |||||
| required: "Name required!", | |||||
| maxLength: 30 | |||||
| })} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={12}> | |||||
| <TextField | |||||
| label={t("Description")} | |||||
| // multiline | |||||
| fullWidth | |||||
| {...register("description", { | |||||
| maxLength: 100 | |||||
| })} | |||||
| /> | |||||
| </Grid> | |||||
| </Grid> | |||||
| </Box> | |||||
| </CardContent> | |||||
| </Card> | |||||
| ) | |||||
| } | |||||
| export default QcItemDetails | |||||
| export default QcItemDetails; | |||||
| @@ -22,6 +22,7 @@ import EditableSearchResults, {Column} from "@/components/SearchResults/Editable | |||||
| type Props = { | type Props = { | ||||
| apiRef: MutableRefObject<GridApiCommunity> | apiRef: MutableRefObject<GridApiCommunity> | ||||
| isEdit: Boolean | |||||
| }; | }; | ||||
| type EntryError = | type EntryError = | ||||
| | { | | { | ||||
| @@ -34,9 +35,11 @@ export type FGRecord = { | |||||
| code: string; | code: string; | ||||
| name: string; | name: string; | ||||
| inStockQty: number; | inStockQty: number; | ||||
| productionQty: number; | |||||
| productionQty?: number; | |||||
| purchaseQty?: number | |||||
| } | } | ||||
| const ViewByFGDetails: React.FC<Props> = ({ apiRef, isEdit }) => { | const ViewByFGDetails: React.FC<Props> = ({ apiRef, isEdit }) => { | ||||
| const { | const { | ||||
| t, | t, | ||||
| @@ -58,35 +61,70 @@ const ViewByFGDetails: React.FC<Props> = ({ apiRef, isEdit }) => { | |||||
| '2025-05-17', | '2025-05-17', | ||||
| ]; | ]; | ||||
| const fakeRecordLine = useMemo<FGRecord[][]>( | |||||
| () => [ | |||||
| [ | |||||
| { id: 1, code: "mt1", name: "material 1", inStockQty: 10, purchaseQty: 1 }, | |||||
| { id: 2, code: "mt2", name: "material 2", inStockQty: 20, purchaseQty: 199 }, | |||||
| ], | |||||
| [ | |||||
| { id: 3, code: "mt3", name: "material 3", inStockQty: 30, purchaseQty: 3 }, | |||||
| { id: 4, code: "mt4", name: "material 4", inStockQty: 40, purchaseQty: 499 }, | |||||
| ], | |||||
| [ | |||||
| { id: 5, code: "mt5", name: "material 5", inStockQty: 50, purchaseQty: 5 }, | |||||
| { id: 6, code: "mt6", name: "material 6", inStockQty: 60, purchaseQty: 699 }, | |||||
| ], | |||||
| [ | |||||
| { id: 7, code: "mt7", name: "material 7", inStockQty: 70, purchaseQty: 7 }, | |||||
| { id: 8, code: "mt8", name: "material 8", inStockQty: 80, purchaseQty: 899 }, | |||||
| ], | |||||
| [ | |||||
| { id: 9, code: "mt9", name: "material 9", inStockQty: 90, purchaseQty: 9 }, | |||||
| { id: 10, code: "mt10", name: "material 10", inStockQty: 100, purchaseQty: 999 }, | |||||
| ], | |||||
| [ | |||||
| { id: 11, code: "mt11", name: "material 11", inStockQty: 110, purchaseQty: 11 }, | |||||
| { id: 12, code: "mt12", name: "material 12", inStockQty: 120, purchaseQty: 1299 }, | |||||
| ], | |||||
| [ | |||||
| { id: 13, code: "mt13", name: "material 13", inStockQty: 130, purchaseQty: 13 }, | |||||
| { id: 14, code: "mt14", name: "material 14", inStockQty: 140, purchaseQty: 1499 }, | |||||
| ], | |||||
| ], | |||||
| [] | |||||
| ); | |||||
| const fakeRecords = useMemo<FGRecord[][]>( | const fakeRecords = useMemo<FGRecord[][]>( | ||||
| () => [ | () => [ | ||||
| [ | [ | ||||
| { id: 1, code: "fg1", name: "finished good 1", inStockQty: 10, productionQty: 1 }, | |||||
| { id: 2, code: "fg2", name: "finished good 2", inStockQty: 20, productionQty: 199 }, | |||||
| { id: 1, code: "fg1", name: "finished good 1", inStockQty: 10, productionQty: 1, lines: [{ id: 1, code: "mt1", name: "material 1", inStockQty: 10, purchaseQty: 1 }] }, | |||||
| { id: 2, code: "fg2", name: "finished good 2", inStockQty: 20, productionQty: 199, lines: [{ id: 2, code: "mt2", name: "material 2", inStockQty: 20, purchaseQty: 199 }] }, | |||||
| ], | ], | ||||
| [ | [ | ||||
| { id: 3, code: "fg3", name: "finished good 3", inStockQty: 30, productionQty: 3 }, | |||||
| { id: 4, code: "fg4", name: "finished good 4", inStockQty: 40, productionQty: 499 }, | |||||
| { id: 3, code: "fg3", name: "finished good 3", inStockQty: 30, productionQty: 3, lines: [{ id: 3, code: "mt3", name: "material 3", inStockQty: 30, purchaseQty: 3 }] }, | |||||
| { id: 4, code: "fg4", name: "finished good 4", inStockQty: 40, productionQty: 499, lines: [{ id: 4, code: "mt4", name: "material 4", inStockQty: 40, purchaseQty: 499 }] }, | |||||
| ], | ], | ||||
| [ | [ | ||||
| { id: 5, code: "fg5", name: "finished good 5", inStockQty: 50, productionQty: 5 }, | |||||
| { id: 6, code: "fg6", name: "finished good 6", inStockQty: 60, productionQty: 699 }, | |||||
| { id: 5, code: "fg5", name: "finished good 5", inStockQty: 50, productionQty: 5, lines: [{ id: 5, code: "mt5", name: "material 5", inStockQty: 50, purchaseQty: 5 }] }, | |||||
| { id: 6, code: "fg6", name: "finished good 6", inStockQty: 60, productionQty: 699, lines: [{ id: 6, code: "mt6", name: "material 6", inStockQty: 60, purchaseQty: 699 }] }, | |||||
| ], | ], | ||||
| [ | [ | ||||
| { id: 7, code: "fg7", name: "finished good 7", inStockQty: 70, productionQty: 7 }, | |||||
| { id: 8, code: "fg8", name: "finished good 8", inStockQty: 80, productionQty: 899 }, | |||||
| { id: 7, code: "fg7", name: "finished good 7", inStockQty: 70, productionQty: 7, lines: [{ id: 7, code: "mt7", name: "material 7", inStockQty: 70, purchaseQty: 7 }] }, | |||||
| { id: 8, code: "fg8", name: "finished good 8", inStockQty: 80, productionQty: 899, lines: [{ id: 8, code: "mt8", name: "material 8", inStockQty: 80, purchaseQty: 899 }] }, | |||||
| ], | ], | ||||
| [ | [ | ||||
| { id: 9, code: "fg9", name: "finished good 9", inStockQty: 90, productionQty: 9 }, | |||||
| { id: 10, code: "fg10", name: "finished good 10", inStockQty: 100, productionQty: 999 }, | |||||
| { id: 9, code: "fg9", name: "finished good 9", inStockQty: 90, productionQty: 9, lines: [{ id: 9, code: "mt9", name: "material 9", inStockQty: 90, purchaseQty: 9 }] }, | |||||
| { id: 10, code: "fg10", name: "finished good 10", inStockQty: 100, productionQty: 999, lines: [{ id: 10, code: "mt10", name: "material 10", inStockQty: 100, purchaseQty: 999 }] }, | |||||
| ], | ], | ||||
| [ | [ | ||||
| { id: 11, code: "fg11", name: "finished good 11", inStockQty: 110, productionQty: 11 }, | |||||
| { id: 12, code: "fg12", name: "finished good 12", inStockQty: 120, productionQty: 1299 }, | |||||
| { id: 11, code: "fg11", name: "finished good 11", inStockQty: 110, productionQty: 11, lines: [{ id: 11, code: "mt11", name: "material 11", inStockQty: 110, purchaseQty: 11 }] }, | |||||
| { id: 12, code: "fg12", name: "finished good 12", inStockQty: 120, productionQty: 1299, lines: [{ id: 12, code: "mt12", name: "material 12", inStockQty: 120, purchaseQty: 1299 }] }, | |||||
| ], | ], | ||||
| [ | [ | ||||
| { id: 13, code: "fg13", name: "finished good 13", inStockQty: 130, productionQty: 13 }, | |||||
| { id: 14, code: "fg14", name: "finished good 14", inStockQty: 140, productionQty: 1499 }, | |||||
| { id: 13, code: "fg13", name: "finished good 13", inStockQty: 130, productionQty: 13, lines: [{ id: 13, code: "mt13", name: "material 13", inStockQty: 130, purchaseQty: 13 }] }, | |||||
| { id: 14, code: "fg14", name: "finished good 14", inStockQty: 140, productionQty: 1499, lines: [{ id: 14, code: "mt14", name: "material 14", inStockQty: 140, purchaseQty: 1499 }] }, | |||||
| ], | ], | ||||
| ], | ], | ||||
| [] | [] | ||||
| @@ -135,6 +173,7 @@ const ViewByFGDetails: React.FC<Props> = ({ apiRef, isEdit }) => { | |||||
| </Typography> | </Typography> | ||||
| <EditableSearchResults<FGRecord> | <EditableSearchResults<FGRecord> | ||||
| items={fakeRecords[index]} // Use the corresponding records for the day | items={fakeRecords[index]} // Use the corresponding records for the day | ||||
| isMockUp={true} | |||||
| columns={columns} | columns={columns} | ||||
| setPagingController={setPagingController} | setPagingController={setPagingController} | ||||
| pagingController={pagingController} | pagingController={pagingController} | ||||
| @@ -16,6 +16,10 @@ import CancelIcon from "@mui/icons-material/Close"; | |||||
| import DeleteIcon from "@mui/icons-material/Delete"; | import DeleteIcon from "@mui/icons-material/Delete"; | ||||
| import TextField from "@mui/material/TextField"; | import TextField from "@mui/material/TextField"; | ||||
| import MultiSelect from "@/components/SearchBox/MultiSelect"; | import MultiSelect from "@/components/SearchBox/MultiSelect"; | ||||
| import { Collapse } from "@mui/material"; | |||||
| import TempInputGridForMockUp from "./TempInputGridForMockUp"; | |||||
| import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown"; | |||||
| import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp"; | |||||
| export interface ResultWithId { | export interface ResultWithId { | ||||
| id: string | number; | id: string | number; | ||||
| @@ -41,6 +45,7 @@ export type Column<T extends ResultWithId> = | |||||
| interface Props<T extends ResultWithId> { | interface Props<T extends ResultWithId> { | ||||
| items: T[], | items: T[], | ||||
| isMockUp?: Boolean, | |||||
| columns: Column<T>[], | columns: Column<T>[], | ||||
| noWrapper?: boolean, | noWrapper?: boolean, | ||||
| setPagingController: (value: { pageNum: number; pageSize: number; totalCount: number }) => void, | setPagingController: (value: { pageNum: number; pageSize: number; totalCount: number }) => void, | ||||
| @@ -50,6 +55,7 @@ interface Props<T extends ResultWithId> { | |||||
| function EditableSearchResults<T extends ResultWithId>({ | function EditableSearchResults<T extends ResultWithId>({ | ||||
| items, | items, | ||||
| isMockUp, | |||||
| columns, | columns, | ||||
| noWrapper, | noWrapper, | ||||
| pagingController, | pagingController, | ||||
| @@ -62,7 +68,7 @@ function EditableSearchResults<T extends ResultWithId>({ | |||||
| const [rowsPerPage, setRowsPerPage] = useState(10); | const [rowsPerPage, setRowsPerPage] = useState(10); | ||||
| const [editingRowId, setEditingRowId] = useState<number | null>(null); | const [editingRowId, setEditingRowId] = useState<number | null>(null); | ||||
| const [editedItems, setEditedItems] = useState<T[]>(items); | const [editedItems, setEditedItems] = useState<T[]>(items); | ||||
| console.log(items) | |||||
| useEffect(()=>{ | useEffect(()=>{ | ||||
| setEditedItems(items) | setEditedItems(items) | ||||
| },[items]) | },[items]) | ||||
| @@ -116,6 +122,124 @@ function EditableSearchResults<T extends ResultWithId>({ | |||||
| } | } | ||||
| },[isEdit]) | },[isEdit]) | ||||
| function Row(props: { row: T }) { | |||||
| const { row } = props; | |||||
| const [open, setOpen] = useState(false); | |||||
| console.log(row) | |||||
| console.log(row.lines) | |||||
| return ( | |||||
| <> | |||||
| <TableRow hover tabIndex={-1} key={row.id}> | |||||
| { | |||||
| !isHideButton && <TableCell> | |||||
| {(editingRowId === row.id) ? ( | |||||
| <> | |||||
| <IconButton disabled={!isEdit} onClick={() => handleSaveClick(row)}> | |||||
| <SaveIcon/> | |||||
| </IconButton> | |||||
| <IconButton disabled={!isEdit} onClick={() => setEditingRowId(null)}> | |||||
| <CancelIcon/> | |||||
| </IconButton> | |||||
| <IconButton | |||||
| aria-label="expand row" | |||||
| size="small" | |||||
| onClick={() => setOpen(!open)} | |||||
| > | |||||
| {open ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />} | |||||
| </IconButton> | |||||
| </> | |||||
| ) : ( | |||||
| <> | |||||
| <IconButton disabled={!isEdit} | |||||
| onClick={() => handleEditClick(row.id as number)}> | |||||
| <EditIcon/> | |||||
| </IconButton> | |||||
| <IconButton disabled={!isEdit} | |||||
| onClick={() => handleDeleteClick(row.id as number)}> | |||||
| <DeleteIcon/> | |||||
| </IconButton> | |||||
| <IconButton | |||||
| aria-label="expand row" | |||||
| size="small" | |||||
| onClick={() => setOpen(!open)} | |||||
| > | |||||
| {open ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />} | |||||
| </IconButton> | |||||
| </> | |||||
| )} | |||||
| </TableCell> | |||||
| } | |||||
| {columns.map((column, idx) => { | |||||
| console.log(column) | |||||
| const columnName = column.field; | |||||
| return ( | |||||
| <TableCell key={`${columnName.toString()}-${idx}`}> | |||||
| {editingRowId === row.id ? ( | |||||
| (() => { | |||||
| switch (column.type) { | |||||
| case 'input': | |||||
| console.log(column.type) | |||||
| return ( | |||||
| <TextField | |||||
| hiddenLabel={true} | |||||
| fullWidth | |||||
| defaultValue={row[columnName] as string} | |||||
| onChange={(e) => handleInputChange(row.id as number, columnName, e.target.value)} | |||||
| /> | |||||
| ); | |||||
| case 'multi-select': | |||||
| return ( | |||||
| <MultiSelect | |||||
| //label={column.label} | |||||
| options={column.options} | |||||
| selectedValues={[]} | |||||
| onChange={(selectedValues) => handleInputChange(row.id as number, columnName, selectedValues)} | |||||
| /> | |||||
| ); | |||||
| case 'read-only': | |||||
| return ( | |||||
| <span> | |||||
| {row[columnName] as string} | |||||
| </span> | |||||
| ); | |||||
| default: | |||||
| return null; // Handle any default case if needed | |||||
| } | |||||
| })() | |||||
| ) : ( | |||||
| column.renderCell ? | |||||
| column.renderCell(row) | |||||
| : | |||||
| <span onDoubleClick={() => isEdit && handleEditClick(row.id as number)}> | |||||
| {row[columnName] as string} | |||||
| </span> | |||||
| )} | |||||
| </TableCell> | |||||
| ); | |||||
| })} | |||||
| </TableRow> | |||||
| <TableRow> | |||||
| <TableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={6}> | |||||
| <Collapse in={open} timeout="auto" unmountOnExit> | |||||
| <Table> | |||||
| <TableBody> | |||||
| <TableRow> | |||||
| <TableCell> | |||||
| <TempInputGridForMockUp | |||||
| stockInLine={row.lines as any[]} | |||||
| /> | |||||
| </TableCell> | |||||
| </TableRow> | |||||
| </TableBody> | |||||
| </Table> | |||||
| </Collapse> | |||||
| </TableCell> | |||||
| </TableRow> | |||||
| </> | |||||
| ) | |||||
| } | |||||
| const table = ( | const table = ( | ||||
| <> | <> | ||||
| <TableContainer sx={{ maxHeight: 440 }}> | <TableContainer sx={{ maxHeight: 440 }}> | ||||
| @@ -132,80 +256,7 @@ function EditableSearchResults<T extends ResultWithId>({ | |||||
| </TableHead> | </TableHead> | ||||
| <TableBody> | <TableBody> | ||||
| {(isAutoPaging ? editedItems.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) : editedItems).map((item) => ( | {(isAutoPaging ? editedItems.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) : editedItems).map((item) => ( | ||||
| <TableRow hover tabIndex={-1} key={item.id}> | |||||
| { | |||||
| !isHideButton && <TableCell> | |||||
| {(editingRowId === item.id) ? ( | |||||
| <> | |||||
| <IconButton disabled={!isEdit} onClick={() => handleSaveClick(item)}> | |||||
| <SaveIcon/> | |||||
| </IconButton> | |||||
| <IconButton disabled={!isEdit} onClick={() => setEditingRowId(null)}> | |||||
| <CancelIcon/> | |||||
| </IconButton> | |||||
| </> | |||||
| ) : ( | |||||
| <> | |||||
| <IconButton disabled={!isEdit} | |||||
| onClick={() => handleEditClick(item.id as number)}> | |||||
| <EditIcon/> | |||||
| </IconButton> | |||||
| <IconButton disabled={!isEdit} | |||||
| onClick={() => handleDeleteClick(item.id as number)}> | |||||
| <DeleteIcon/> | |||||
| </IconButton> | |||||
| </> | |||||
| )} | |||||
| </TableCell> | |||||
| } | |||||
| {columns.map((column, idx) => { | |||||
| const columnName = column.field; | |||||
| return ( | |||||
| <TableCell key={`${columnName.toString()}-${idx}`}> | |||||
| {editingRowId === item.id ? ( | |||||
| (() => { | |||||
| switch (column.type) { | |||||
| case 'input': | |||||
| return ( | |||||
| <TextField | |||||
| hiddenLabel={true} | |||||
| fullWidth | |||||
| defaultValue={item[columnName] as string} | |||||
| onChange={(e) => handleInputChange(item.id as number, columnName, e.target.value)} | |||||
| /> | |||||
| ); | |||||
| case 'multi-select': | |||||
| return ( | |||||
| <MultiSelect | |||||
| //label={column.label} | |||||
| options={column.options} | |||||
| selectedValues={[]} | |||||
| onChange={(selectedValues) => handleInputChange(item.id as number, columnName, selectedValues)} | |||||
| /> | |||||
| ); | |||||
| case 'read-only': | |||||
| return ( | |||||
| <span> | |||||
| {item[columnName] as string} | |||||
| </span> | |||||
| ); | |||||
| default: | |||||
| return null; // Handle any default case if needed | |||||
| } | |||||
| })() | |||||
| ) : ( | |||||
| column.renderCell ? | |||||
| column.renderCell(item) | |||||
| : | |||||
| <span onDoubleClick={() => isEdit && handleEditClick(item.id as number)}> | |||||
| {item[columnName] as string} | |||||
| </span> | |||||
| )} | |||||
| </TableCell> | |||||
| ); | |||||
| })} | |||||
| </TableRow> | |||||
| <Row key={item.id} row={item} /> | |||||
| ))} | ))} | ||||
| </TableBody> | </TableBody> | ||||
| </Table> | </Table> | ||||
| @@ -36,7 +36,7 @@ interface Props<T extends ResultWithId> { | |||||
| items: T[], | items: T[], | ||||
| columns: Column<T>[], | columns: Column<T>[], | ||||
| noWrapper?: boolean, | noWrapper?: boolean, | ||||
| setPagingController: (value: (((prevState: { pageNum: number; pageSize: number; totalCount: number }) => { | |||||
| setPagingController?: (value: (((prevState: { pageNum: number; pageSize: number; totalCount: number }) => { | |||||
| pageNum: number; | pageNum: number; | ||||
| pageSize: number; | pageSize: number; | ||||
| totalCount: number | totalCount: number | ||||
| @@ -163,7 +163,7 @@ function SearchResults<T extends ResultWithId>({ | |||||
| <TablePagination | <TablePagination | ||||
| rowsPerPageOptions={[10, 25, 100]} | rowsPerPageOptions={[10, 25, 100]} | ||||
| component="div" | component="div" | ||||
| count={pagingController.totalCount == 0 ? items.length : pagingController.totalCount} | |||||
| count={!pagingController || pagingController.totalCount == 0 ? items.length : pagingController.totalCount} | |||||
| rowsPerPage={rowsPerPage} | rowsPerPage={rowsPerPage} | ||||
| page={page} | page={page} | ||||
| onPageChange={handleChangePage} | onPageChange={handleChangePage} | ||||
| @@ -0,0 +1,419 @@ | |||||
| "use client"; | |||||
| import { | |||||
| FooterPropsOverrides, | |||||
| GridActionsCellItem, | |||||
| GridCellParams, | |||||
| GridRowId, | |||||
| GridRowIdGetter, | |||||
| GridRowModel, | |||||
| GridRowModes, | |||||
| GridRowModesModel, | |||||
| GridToolbarContainer, | |||||
| useGridApiRef, | |||||
| } from "@mui/x-data-grid"; | |||||
| import { | |||||
| Dispatch, | |||||
| MutableRefObject, | |||||
| SetStateAction, | |||||
| useCallback, | |||||
| useEffect, | |||||
| useMemo, | |||||
| useState, | |||||
| } from "react"; | |||||
| import StyledDataGrid from "../StyledDataGrid"; | |||||
| import { GridColDef } from "@mui/x-data-grid"; | |||||
| import { Box, Button, Grid, Typography } from "@mui/material"; | |||||
| import { useTranslation } from "react-i18next"; | |||||
| import { Add } from "@mui/icons-material"; | |||||
| import SaveIcon from "@mui/icons-material/Save"; | |||||
| import DeleteIcon from "@mui/icons-material/Delete"; | |||||
| import CancelIcon from "@mui/icons-material/Cancel"; | |||||
| import FactCheckIcon from "@mui/icons-material/FactCheck"; | |||||
| import ShoppingCartIcon from "@mui/icons-material/ShoppingCart"; | |||||
| // import PoQcModal from "./PoQcModal"; | |||||
| import { QcItemWithChecks } from "src/app/api/qc"; | |||||
| import PlayArrowIcon from "@mui/icons-material/PlayArrow"; | |||||
| import { createStockInLine, testFetch } from "@/app/api/po/actions"; | |||||
| import { useSearchParams } from "next/navigation"; | |||||
| import { stockInLineStatusMap } from "@/app/utils/formatUtil"; | |||||
| interface ResultWithId { | |||||
| id: number; | |||||
| } | |||||
| interface Props { | |||||
| // qc: QcItemWithChecks[]; | |||||
| // setRows: Dispatch<SetStateAction<PurchaseOrderLine[]>>; | |||||
| // itemDetail: PurchaseOrderLine; | |||||
| stockInLine: any[]; | |||||
| } | |||||
| export type StockInLineEntryError = { | |||||
| [field in keyof any]?: string; | |||||
| }; | |||||
| export type StockInLineRow = Partial< | |||||
| any & { | |||||
| isActive: boolean | undefined; | |||||
| _isNew: boolean; | |||||
| _error: StockInLineEntryError; | |||||
| } & ResultWithId | |||||
| >; | |||||
| class ProcessRowUpdateError extends Error { | |||||
| public readonly row: StockInLineRow; | |||||
| public readonly errors: StockInLineEntryError | undefined; | |||||
| constructor(row: StockInLineRow, message?: string, errors?: StockInLineEntryError) { | |||||
| super(message); | |||||
| this.row = row; | |||||
| this.errors = errors; | |||||
| Object.setPrototypeOf(this, ProcessRowUpdateError.prototype); | |||||
| } | |||||
| } | |||||
| function TempInputGridForMockUp({ stockInLine }: Props) { | |||||
| const { t } = useTranslation("home"); | |||||
| const apiRef = useGridApiRef(); | |||||
| const [rowModesModel, setRowModesModel] = useState<GridRowModesModel>({}); | |||||
| const getRowId = useCallback<GridRowIdGetter<StockInLineRow>>( | |||||
| (row) => row.id as number, | |||||
| [] | |||||
| ); | |||||
| console.log(stockInLine); | |||||
| const [entries, setEntries] = useState<StockInLineRow[]>(stockInLine || []); | |||||
| const [modalInfo, setModalInfo] = useState<any>() | |||||
| const [qcOpen, setQcOpen] = useState(false); | |||||
| // const [defaultQty, setDefaultQty] = useState(() => { | |||||
| // const total = entries.reduce((acc, curr) => acc + (curr.acceptedQty || 0), 0); | |||||
| // return itemDetail.qty - total; | |||||
| // }); | |||||
| const params = useSearchParams() | |||||
| const handleDelete = useCallback( | |||||
| (id: GridRowId) => () => { | |||||
| setEntries((es) => es.filter((e) => getRowId(e) !== id)); | |||||
| }, | |||||
| [getRowId] | |||||
| ); | |||||
| const handleStart = useCallback( | |||||
| (id: GridRowId, params: any) => () => { | |||||
| setRowModesModel((prev) => ({ | |||||
| ...prev, | |||||
| [id]: { mode: GridRowModes.View }, | |||||
| })); | |||||
| setTimeout(async () => { | |||||
| // post stock in line | |||||
| console.log("delayed"); | |||||
| console.log(params); | |||||
| const oldId = params.row.id | |||||
| console.log(oldId) | |||||
| const postData = { | |||||
| itemId: params.row.itemId, | |||||
| itemNo: params.row.itemNo, | |||||
| itemName: params.row.itemName, | |||||
| purchaseOrderId: params.row.purchaseOrderId, | |||||
| purchaseOrderLineId: params.row.purchaseOrderLineId, | |||||
| acceptedQty: params.row.acceptedQty, | |||||
| } | |||||
| const res = await createStockInLine(postData) | |||||
| console.log(res) | |||||
| // setEntries((prev) => prev.map((p) => p.id === oldId ? res.entity : p)) | |||||
| // do post directly to test | |||||
| // openStartModal(); | |||||
| }, 200); | |||||
| }, | |||||
| [] | |||||
| ); | |||||
| const handleQC = useCallback( | |||||
| (id: GridRowId, params: any) => () => { | |||||
| setRowModesModel((prev) => ({ | |||||
| ...prev, | |||||
| [id]: { mode: GridRowModes.View }, | |||||
| })); | |||||
| setModalInfo(params.row) | |||||
| setTimeout(() => { | |||||
| // open qc modal | |||||
| console.log("delayed"); | |||||
| openQcModal(); | |||||
| }, 200); | |||||
| }, | |||||
| [] | |||||
| ); | |||||
| const handleStockIn = useCallback( | |||||
| (id: GridRowId) => () => { | |||||
| setRowModesModel((prev) => ({ | |||||
| ...prev, | |||||
| [id]: { mode: GridRowModes.View }, | |||||
| })); | |||||
| setTimeout(() => { | |||||
| // open stock in modal | |||||
| // return the record with its status as pending | |||||
| // update layout | |||||
| console.log("delayed"); | |||||
| }, 200); | |||||
| }, | |||||
| [] | |||||
| ); | |||||
| const closeQcModal = useCallback(() => { | |||||
| setQcOpen(false); | |||||
| }, []); | |||||
| const openQcModal = useCallback(() => { | |||||
| setQcOpen(true); | |||||
| }, []); | |||||
| const columns = useMemo<GridColDef[]>( | |||||
| () => [ | |||||
| { | |||||
| field: "code", | |||||
| flex: 1, | |||||
| }, | |||||
| { | |||||
| field: "name", | |||||
| flex: 1, | |||||
| }, | |||||
| { | |||||
| field: "inStockQty", | |||||
| headerName: "inStockQty", | |||||
| flex: 0.5, | |||||
| type: "number", | |||||
| editable: true, | |||||
| // replace with tooltip + content | |||||
| }, | |||||
| { | |||||
| field: "purchaseQty", | |||||
| headerName: "purchaseQty", | |||||
| flex: 0.5, | |||||
| editable: true | |||||
| }, | |||||
| // { | |||||
| // field: "actions", | |||||
| // type: "actions", | |||||
| // headerName: "start | qc | stock in | delete", | |||||
| // flex: 1, | |||||
| // cellClassName: "actions", | |||||
| // getActions: (params) => { | |||||
| // // const stockInLineStatusMap: { [status: string]: number } = { | |||||
| // // draft: 0, | |||||
| // // pending: 1, | |||||
| // // qc: 2, | |||||
| // // determine1: 3, | |||||
| // // determine2: 4, | |||||
| // // determine3: 5, | |||||
| // // receiving: 6, | |||||
| // // completed: 7, | |||||
| // // }; | |||||
| // console.log(params.row.status); | |||||
| // const status = params.row.status.toLowerCase() | |||||
| // return [ | |||||
| // <GridActionsCellItem | |||||
| // icon={<PlayArrowIcon />} | |||||
| // label="start" | |||||
| // sx={{ | |||||
| // color: "primary.main", | |||||
| // }} | |||||
| // disabled={!(stockInLineStatusMap[status] === 0)} | |||||
| // // set _isNew to false after posting | |||||
| // // or check status | |||||
| // onClick={handleStart(params.row.id, params)} | |||||
| // color="inherit" | |||||
| // key="edit" | |||||
| // />, | |||||
| // <GridActionsCellItem | |||||
| // icon={<FactCheckIcon />} | |||||
| // label="qc" | |||||
| // sx={{ | |||||
| // color: "primary.main", | |||||
| // }} | |||||
| // disabled={stockInLineStatusMap[status] <= 0 || stockInLineStatusMap[status] >= 6} | |||||
| // // set _isNew to false after posting | |||||
| // // or check status | |||||
| // onClick={handleQC(params.row.id, params)} | |||||
| // color="inherit" | |||||
| // key="edit" | |||||
| // />, | |||||
| // <GridActionsCellItem | |||||
| // icon={<ShoppingCartIcon />} | |||||
| // label="stockin" | |||||
| // sx={{ | |||||
| // color: "primary.main", | |||||
| // }} | |||||
| // disabled={stockInLineStatusMap[status] !== 6} | |||||
| // // set _isNew to false after posting | |||||
| // // or check status | |||||
| // onClick={handleStockIn(params.row.id)} | |||||
| // color="inherit" | |||||
| // key="edit" | |||||
| // />, | |||||
| // <GridActionsCellItem | |||||
| // icon={<DeleteIcon />} | |||||
| // label="Delete" | |||||
| // sx={{ | |||||
| // color: "error.main", | |||||
| // }} | |||||
| // disabled={stockInLineStatusMap[status] !== 0} | |||||
| // // disabled={Boolean(params.row.status)} | |||||
| // onClick={handleDelete(params.row.id)} | |||||
| // color="inherit" | |||||
| // key="edit" | |||||
| // />, | |||||
| // ]; | |||||
| // }, | |||||
| // }, | |||||
| ], | |||||
| [] | |||||
| ); | |||||
| // const addRow = useCallback(() => { | |||||
| // const newEntry = { | |||||
| // id: Date.now(), | |||||
| // _isNew: true, | |||||
| // itemId: itemDetail.itemId, | |||||
| // purchaseOrderId: itemDetail.purchaseOrderId, | |||||
| // purchaseOrderLineId: itemDetail.id, | |||||
| // itemNo: itemDetail.itemNo, | |||||
| // itemName: itemDetail.itemName, | |||||
| // acceptedQty: defaultQty, | |||||
| // status: "draft", | |||||
| // }; | |||||
| // setEntries((e) => [...e, newEntry]); | |||||
| // setRowModesModel((model) => ({ | |||||
| // ...model, | |||||
| // [getRowId(newEntry)]: { | |||||
| // mode: GridRowModes.Edit, | |||||
| // // fieldToFocus: "projectId", | |||||
| // }, | |||||
| // })); | |||||
| // }, [getRowId]); | |||||
| const validation = useCallback( | |||||
| ( | |||||
| newRow: GridRowModel<StockInLineRow> | |||||
| // rowModel: GridRowSelectionModel | |||||
| ): StockInLineEntryError | undefined => { | |||||
| const error: StockInLineEntryError = {}; | |||||
| console.log(newRow); | |||||
| // if (newRow.acceptedQty && newRow.acceptedQty > defaultQty) { | |||||
| // error["acceptedQty"] = "qty cannot be greater than remaining qty"; | |||||
| // } | |||||
| return Object.keys(error).length > 0 ? error : undefined; | |||||
| }, | |||||
| [] | |||||
| ); | |||||
| const processRowUpdate = useCallback( | |||||
| (newRow: GridRowModel<StockInLineRow>, originalRow: GridRowModel<StockInLineRow>) => { | |||||
| const errors = validation(newRow); // change to validation | |||||
| if (errors) { | |||||
| throw new ProcessRowUpdateError( | |||||
| originalRow, | |||||
| "validation error", | |||||
| errors | |||||
| ); | |||||
| } | |||||
| const { _isNew, _error, ...updatedRow } = newRow; | |||||
| const rowToSave = { | |||||
| ...updatedRow, | |||||
| } satisfies StockInLineRow; | |||||
| const newEntries = entries.map((e) => | |||||
| getRowId(e) === getRowId(originalRow) ? rowToSave : e | |||||
| ); | |||||
| setEntries(newEntries); | |||||
| //update remaining qty | |||||
| const total = newEntries.reduce((acc, curr) => acc + (curr.acceptedQty || 0), 0); | |||||
| // setDefaultQty(itemDetail.qty - total); | |||||
| return rowToSave; | |||||
| }, | |||||
| [getRowId, entries] | |||||
| ); | |||||
| const onProcessRowUpdateError = useCallback( | |||||
| (updateError: ProcessRowUpdateError) => { | |||||
| const errors = updateError.errors; | |||||
| const oldRow = updateError.row; | |||||
| apiRef.current.updateRows([{ ...oldRow, _error: errors }]); | |||||
| }, | |||||
| [apiRef] | |||||
| ); | |||||
| // useEffect(() => { | |||||
| // const total = entries.reduce((acc, curr) => acc + (curr.acceptedQty || 0), 0); | |||||
| // setDefaultQty(itemDetail.qty - total); | |||||
| // }, [entries]); | |||||
| // const footer = ( | |||||
| // <Box display="flex" gap={2} alignItems="center"> | |||||
| // <Button | |||||
| // disableRipple | |||||
| // variant="outlined" | |||||
| // startIcon={<Add />} | |||||
| // disabled={defaultQty <= 0} | |||||
| // onClick={addRow} | |||||
| // size="small" | |||||
| // > | |||||
| // {t("Record pol")} | |||||
| // </Button> | |||||
| // </Box> | |||||
| // ); | |||||
| return ( | |||||
| <> | |||||
| <StyledDataGrid | |||||
| getRowId={getRowId} | |||||
| apiRef={apiRef} | |||||
| autoHeight | |||||
| sx={{ | |||||
| "--DataGrid-overlayHeight": "100px", | |||||
| ".MuiDataGrid-row .MuiDataGrid-cell.hasError": { | |||||
| border: "1px solid", | |||||
| borderColor: "error.main", | |||||
| }, | |||||
| ".MuiDataGrid-row .MuiDataGrid-cell.hasWarning": { | |||||
| border: "1px solid", | |||||
| borderColor: "warning.main", | |||||
| }, | |||||
| }} | |||||
| disableColumnMenu | |||||
| editMode="row" | |||||
| rows={entries} | |||||
| rowModesModel={rowModesModel} | |||||
| onRowModesModelChange={setRowModesModel} | |||||
| processRowUpdate={processRowUpdate} | |||||
| onProcessRowUpdateError={onProcessRowUpdateError} | |||||
| columns={columns} | |||||
| getCellClassName={(params: GridCellParams<StockInLineRow>) => { | |||||
| let classname = ""; | |||||
| if (params.row._error) { | |||||
| classname = "hasError"; | |||||
| } | |||||
| return classname; | |||||
| }} | |||||
| // slots={{ | |||||
| // footer: FooterToolbar, | |||||
| // noRowsOverlay: NoRowsOverlay, | |||||
| // }} | |||||
| // slotProps={{ | |||||
| // footer: { child: footer }, | |||||
| // }} | |||||
| /> | |||||
| </> | |||||
| ); | |||||
| } | |||||
| const NoRowsOverlay: React.FC = () => { | |||||
| const { t } = useTranslation("home"); | |||||
| return ( | |||||
| <Box | |||||
| display="flex" | |||||
| justifyContent="center" | |||||
| alignItems="center" | |||||
| height="100%" | |||||
| > | |||||
| <Typography variant="caption">{t("Add some entries!")}</Typography> | |||||
| </Box> | |||||
| ); | |||||
| }; | |||||
| const FooterToolbar: React.FC<FooterPropsOverrides> = ({ child }) => { | |||||
| return <GridToolbarContainer sx={{ p: 2 }}>{child}</GridToolbarContainer>; | |||||
| }; | |||||
| export default TempInputGridForMockUp; | |||||