From cce567983ccd5235d7d3ccc420d7064f69a3fe16 Mon Sep 17 00:00:00 2001 From: "MSI\\derek" Date: Fri, 8 Aug 2025 11:30:07 +0800 Subject: [PATCH] update --- src/app/(main)/layout.tsx | 2 +- src/app/api/inventory/index.ts | 1 + src/app/api/po/actions.ts | 2 + src/app/global.css | 6 +- .../ViewByFGDetails.tsx | 4 + src/components/PoDetail/PoDetail.tsx | 104 ++++- src/components/PoDetail/PoInputGrid.tsx | 328 +++++++++----- src/components/PoDetail/QCDatagrid.tsx | 400 ++++++++++++++++++ src/components/PoDetail/QcFormVer2.tsx | 242 +++++++++++ .../PoDetail/QcStockInModalVer2.tsx | 149 +++++++ src/components/PoDetail/StockInFormVer2.tsx | 323 ++++++++++++++ src/components/PoDetail/dummyQcTemplate.tsx | 23 + .../RoughSchedule/RoughSchedileSearchView.tsx | 2 +- .../StyledDataGrid/StyledDataGrid.tsx | 3 + src/i18n/zh/purchaseOrder.json | 13 + 15 files changed, 1475 insertions(+), 127 deletions(-) create mode 100644 src/components/PoDetail/QCDatagrid.tsx create mode 100644 src/components/PoDetail/QcFormVer2.tsx create mode 100644 src/components/PoDetail/QcStockInModalVer2.tsx create mode 100644 src/components/PoDetail/StockInFormVer2.tsx create mode 100644 src/components/PoDetail/dummyQcTemplate.tsx diff --git a/src/app/(main)/layout.tsx b/src/app/(main)/layout.tsx index 02da7f5..dc236bc 100644 --- a/src/app/(main)/layout.tsx +++ b/src/app/(main)/layout.tsx @@ -13,7 +13,7 @@ import { UploadProvider } from "@/components/UploadProvider/UploadProvider"; import SessionProviderWrapper from "@/components/SessionProviderWrapper/SessionProviderWrapper"; import QrCodeScannerProvider from "@/components/QrCodeScannerProvider/QrCodeScannerProvider"; import { I18nProvider } from "@/i18n"; - +import "src/app/global.css" export default async function MainLayout({ children, }: { diff --git a/src/app/api/inventory/index.ts b/src/app/api/inventory/index.ts index cdb9294..5e4c335 100644 --- a/src/app/api/inventory/index.ts +++ b/src/app/api/inventory/index.ts @@ -39,6 +39,7 @@ export interface InventoryLotLineResult { uom: string; qtyPerSmallestUnit: number; baseUom: string; + stockInLineId: number } export interface InventoryLotLineItem { diff --git a/src/app/api/po/actions.ts b/src/app/api/po/actions.ts index 779d9f1..47862fe 100644 --- a/src/app/api/po/actions.ts +++ b/src/app/api/po/actions.ts @@ -38,8 +38,10 @@ export interface PurchaseQcResult { } export interface StockInInput { status: string; + poCode: string; productLotNo?: string; dnNo?: string; + itemName: string; invoiceNo?: string; receiptDate: string; acceptedQty: number; diff --git a/src/app/global.css b/src/app/global.css index d4d058e..7d2ff9a 100644 --- a/src/app/global.css +++ b/src/app/global.css @@ -1,3 +1,7 @@ @tailwind components; -@tailwind utilities; \ No newline at end of file +@tailwind utilities; + +html, body { + overscroll-behavior: none; +} \ No newline at end of file diff --git a/src/components/DetailedScheduleDetail/ViewByFGDetails.tsx b/src/components/DetailedScheduleDetail/ViewByFGDetails.tsx index 7c39ab2..3e43eee 100644 --- a/src/components/DetailedScheduleDetail/ViewByFGDetails.tsx +++ b/src/components/DetailedScheduleDetail/ViewByFGDetails.tsx @@ -160,6 +160,7 @@ const ViewByFGDetails: React.FC = ({ apiRef, isEdit, type, onReleaseClick type: "input-number", style: { textAlign: "right", + // width: "100px", }, renderCell: (row) => { if (typeof row.demandQty == "number") { @@ -174,6 +175,7 @@ const ViewByFGDetails: React.FC = ({ apiRef, isEdit, type, onReleaseClick type: "read-only", style: { textAlign: "left", + // width: "100px", }, renderCell: (row) => { return row.uomName; @@ -185,6 +187,7 @@ const ViewByFGDetails: React.FC = ({ apiRef, isEdit, type, onReleaseClick type: "read-only", style: { textAlign: "right", + // width: "100px", }, renderCell: (row) => { return @@ -196,6 +199,7 @@ const ViewByFGDetails: React.FC = ({ apiRef, isEdit, type, onReleaseClick type: "read-only", style: { textAlign: "right", + // width: "100px", }, // editable: true, }, diff --git a/src/components/PoDetail/PoDetail.tsx b/src/components/PoDetail/PoDetail.tsx index 67ab0e8..d958351 100644 --- a/src/components/PoDetail/PoDetail.tsx +++ b/src/components/PoDetail/PoDetail.tsx @@ -84,9 +84,15 @@ const PoDetail: React.FC = ({ po, qc, warehouse }) => { const [rows, setRows] = useState( purchaseOrder.pol || [], ); - const params = useSearchParams(); + const searchParams = useSearchParams(); // const [currPoStatus, setCurrPoStatus] = useState(purchaseOrder.status); + const removeParam = (paramToRemove: string) => { + const newParams = new URLSearchParams(searchParams.toString()); + newParams.delete(paramToRemove); + window.history.replaceState({}, '', `${window.location.pathname}?${newParams}`); + }; + const handleCompletePo = useCallback(async () => { const checkRes = await checkPolAndCompletePo(purchaseOrder.id); console.log(checkRes); @@ -107,6 +113,8 @@ const PoDetail: React.FC = ({ po, qc, warehouse }) => { function Row(props: { row: PurchaseOrderLine }) { const { row } = props; + const [firstReceiveQty, setFirstReceiveQty] = useState() + const [secondReceiveQty, setSecondReceiveQty] = useState() const [open, setOpen] = useState(false); const [processedQty, setProcessedQty] = useState(row.processed); const [currStatus, setCurrStatus] = useState(row.status); @@ -155,11 +163,30 @@ const PoDetail: React.FC = ({ po, qc, warehouse }) => { {row.price} {/* {row.expiryDate} */} {t(`${currStatus.toLowerCase()}`)} + {/* + 0 + + + + */} {/* */} - + + {/* */} @@ -260,7 +287,21 @@ const PoDetail: React.FC = ({ po, qc, warehouse }) => { } }, [purchaseOrder.status, t, handleStartPo, handleCompletePo]); - console.log(window.innerWidth) + const FIRST_IN_FIELD = "firstInQty" + const SECOND_IN_FIELD = "secondInQty" + + const renderFieldCondition = useCallback((field: "firstInQty" | "secondInQty"): boolean => { + switch (field) { + case FIRST_IN_FIELD: + + return true; + case SECOND_IN_FIELD: + return true; + default: + return false; // Default case + } + }, []); + return ( <> = ({ po, qc, warehouse }) => { - - - + */} - + + + + ) : undefined} = ({ po, qc, warehouse }) => { onChange={handleTabChange} variant="scrollable" > - + {/* */} {/* */} @@ -315,7 +394,8 @@ const PoDetail: React.FC = ({ po, qc, warehouse }) => { onClose={onCloseScanner} warehouse={warehouse} /> - + {/* */} + {/* */} {/* tab 1 */} @@ -336,6 +416,10 @@ const PoDetail: React.FC = ({ po, qc, warehouse }) => { {t("price")} {/* {t("expiryDate")} */} {t("status")} + {/* start == true && firstInQty == null ? no hide : hide*/} + {/* {renderFieldCondition(FIRST_IN_FIELD) ? {t("receivedQty")} : undefined} */} + {/* start == true && firstInQty == null ? hide and disabled : no hide*/} + {/* {renderFieldCondition(SECOND_IN_FIELD) ? {t("dnQty")} : undefined} */} {/* {"add icon button"} */} diff --git a/src/components/PoDetail/PoInputGrid.tsx b/src/components/PoDetail/PoInputGrid.tsx index a290bb8..261e13c 100644 --- a/src/components/PoDetail/PoInputGrid.tsx +++ b/src/components/PoDetail/PoInputGrid.tsx @@ -57,6 +57,7 @@ import { fetchQcResult } from "@/app/api/qc/actions"; import PoQcStockInModal from "./PoQcStockInModal"; import DoDisturbIcon from "@mui/icons-material/DoDisturb"; import { useSession } from "next-auth/react"; +import PoQcStockInModalVer2 from "./QcStockInModalVer2"; interface ResultWithId { id: number; @@ -255,6 +256,40 @@ function PoInputGrid({ }, [fetchQcDefaultValue, openQcModal], ); + + const [newOpen, setNewOpen] = useState(false); + const closeNewModal = useCallback(() => { + setNewOpen(false); + }, []); + const openNewModal = useCallback(() => { + setNewOpen(true); + }, []); + + const handleNewQC = useCallback( + (id: GridRowId, params: any) => async () => { + setBtnIsLoading(true); + setRowModesModel((prev) => ({ + ...prev, + [id]: { mode: GridRowModes.View }, + })); + const qcResult = await fetchQcDefaultValue(id); + console.log(params.row); + console.log(qcResult); + setModalInfo({ + ...params.row, + qcResult: qcResult, + }); + // set default values + setTimeout(() => { + // open qc modal + console.log("delayed"); + openNewModal(); + setBtnIsLoading(false); + }, 200); + }, + [fetchQcDefaultValue, openNewModal], + ); + const handleEscalation = useCallback( (id: GridRowId, params: any) => () => { // setBtnIsLoading(true); @@ -373,20 +408,38 @@ function PoInputGrid({ { field: "itemNo", headerName: t("itemNo"), - width: 120, + width: 100, + // flex: 0.4, + }, + { + field: "dnNo", + headerName: t("dnNo"), + width: 100, + renderCell: () => { + return <>DN0000001 + } + // flex: 0.4, + }, + { + field: "dnDate", + headerName: t("dnDate"), + width: 100, + renderCell: () => { + return <>07/08/2025 + } // flex: 0.4, }, { field: "itemName", headerName: t("itemName"), - width: 120, + width: 100, // flex: 0.6, }, { field: "acceptedQty", headerName: t("acceptedQty"), // flex: 0.5, - width: 120, + width: 100, type: "number", // editable: true, // replace with tooltip + content @@ -417,7 +470,7 @@ function PoInputGrid({ { field: "status", headerName: t("status"), - width: 120, + width: 70, // flex: 0.5, renderCell: (params) => { return t(`${params.row.status}`); @@ -426,12 +479,13 @@ function PoInputGrid({ { field: "actions", type: "actions", - headerName: `${t("start")} | ${t("qc")} | ${t("escalation")} | ${t( - "stock in", - )} | ${t("putaway")} | ${t("delete")}`, + // headerName: `${t("start")} | ${t("qc")} | ${t("escalation")} | ${t( + // "stock in", + // )} | ${t("putaway")} | ${t("delete")}`, + headerName: "動作", // headerName: "start | qc | escalation | stock in | putaway | delete", width: 300, - // flex: 1.5, + // flex: 2, cellClassName: "actions", getActions: (params) => { // console.log(params.row.status); @@ -440,130 +494,158 @@ function PoInputGrid({ // console.log(session?.user?.abilities?.includes("APPROVAL")); return [ } + icon={} label="start" sx={{ color: "primary.main", // marginRight: 1, }} - disabled={!(stockInLineStatusMap[status] === 0)} - // set _isNew to false after posting - // or check status - onClick={handleStart(params.row.id, params)} - color="inherit" - key="edit" - />, - } - label="qc" - sx={{ - color: "primary.main", - // marginRight: 1, - }} - disabled={ - // stockInLineStatusMap[status] === 9 || - stockInLineStatusMap[status] < 1 - } - // set _isNew to false after posting - // or check status - onClick={handleQC(params.row.id, params)} - color="inherit" - key="edit" - />, - } - label="escalation" - sx={{ - color: "primary.main", - // marginRight: 1, - }} - disabled={ - stockInLineStatusMap[status] === 9 || - stockInLineStatusMap[status] <= 0 || - stockInLineStatusMap[status] >= 5 - } + // disabled={!(stockInLineStatusMap[status] === 0)} // set _isNew to false after posting // or check status - onClick={handleEscalation(params.row.id, params)} + onClick={handleNewQC(params.row.id, params)} color="inherit" key="edit" />, } - label="stockin" - sx={{ - color: "primary.main", - // marginRight: 1, - }} - disabled={ - stockInLineStatusMap[status] === 9 || - stockInLineStatusMap[status] <= 2 || - stockInLineStatusMap[status] >= 7 || - (stockInLineStatusMap[status] >= 3 && - stockInLineStatusMap[status] <= 5 && - !session?.user?.abilities?.includes("APPROVAL")) - } - // set _isNew to false after posting - // or check status - onClick={handleStockIn(params.row.id, params)} - color="inherit" - key="edit" - />, - } - label="putaway" + icon={} + label="start" sx={{ color: "primary.main", // marginRight: 1, }} - disabled={ - stockInLineStatusMap[status] === 9 || - stockInLineStatusMap[status] < 7 - } + // disabled={!(stockInLineStatusMap[status] === 0)} // set _isNew to false after posting // or check status - onClick={handlePutAway(params.row.id, params)} + onClick={handleStart(params.row.id, params)} color="inherit" key="edit" />, // } + // icon={} + // label="start" + // sx={{ + // color: "primary.main", + // // marginRight: 1, + // }} + // disabled={!(stockInLineStatusMap[status] === 0)} + // // set _isNew to false after posting + // // or check status + // onClick={handleStart(params.row.id, params)} + // color="inherit" + // key="edit" + // />, + // } + // label="qc" + // sx={{ + // color: "primary.main", + // // marginRight: 1, + // }} + // disabled={ + // // stockInLineStatusMap[status] === 9 || + // stockInLineStatusMap[status] < 1 + // } + // // set _isNew to false after posting + // // or check status + // onClick={handleQC(params.row.id, params)} + // color="inherit" + // key="edit" + // />, + // } + // label="escalation" + // sx={{ + // color: "primary.main", + // // marginRight: 1, + // }} + // disabled={ + // stockInLineStatusMap[status] === 9 || + // stockInLineStatusMap[status] <= 0 || + // stockInLineStatusMap[status] >= 5 + // } + // // set _isNew to false after posting + // // or check status + // onClick={handleEscalation(params.row.id, params)} + // color="inherit" + // key="edit" + // />, + // } + // label="stockin" + // sx={{ + // color: "primary.main", + // // marginRight: 1, + // }} + // disabled={ + // stockInLineStatusMap[status] === 9 || + // stockInLineStatusMap[status] <= 2 || + // stockInLineStatusMap[status] >= 7 || + // (stockInLineStatusMap[status] >= 3 && + // stockInLineStatusMap[status] <= 5 && + // !session?.user?.abilities?.includes("APPROVAL")) + // } + // // set _isNew to false after posting + // // or check status + // onClick={handleStockIn(params.row.id, params)} + // color="inherit" + // key="edit" + // />, + // } // label="putaway" // sx={{ // color: "primary.main", // // marginRight: 1, // }} - // disabled={stockInLineStatusMap[status] === 9 || stockInLineStatusMap[status] !== 8} + // disabled={ + // stockInLineStatusMap[status] === 9 || + // stockInLineStatusMap[status] < 7 + // } // // set _isNew to false after posting // // or check status - // onClick={handleQrCode(params.row.id, params)} + // onClick={handlePutAway(params.row.id, params)} + // color="inherit" + // key="edit" + // />, + // // } + // // label="putaway" + // // sx={{ + // // color: "primary.main", + // // // marginRight: 1, + // // }} + // // disabled={stockInLineStatusMap[status] === 9 || stockInLineStatusMap[status] !== 8} + // // // set _isNew to false after posting + // // // or check status + // // onClick={handleQrCode(params.row.id, params)} + // // color="inherit" + // // key="edit" + // // />, + // = 1 ? ( + // + // ) : ( + // + // ) + // } + // label="Delete" + // sx={{ + // color: "error.main", + // }} + // disabled={ + // stockInLineStatusMap[status] >= 7 && + // stockInLineStatusMap[status] <= 9 + // } + // onClick={ + // stockInLineStatusMap[status] === 0 + // ? handleDelete(params.row.id) + // : handleReject(params.row.id, params) + // } // color="inherit" // key="edit" // />, - = 1 ? ( - - ) : ( - - ) - } - label="Delete" - sx={{ - color: "error.main", - }} - disabled={ - stockInLineStatusMap[status] >= 7 && - stockInLineStatusMap[status] <= 9 - } - onClick={ - stockInLineStatusMap[status] === 0 - ? handleDelete(params.row.id) - : handleReject(params.row.id, params) - } - color="inherit" - key="edit" - />, ]; }, }, @@ -594,6 +676,7 @@ function PoInputGrid({ }, })); }, [currQty, getRowId, itemDetail]); + const validation = useCallback( ( newRow: GridRowModel, @@ -654,20 +737,22 @@ function PoInputGrid({ ); const footer = ( - - - + <> + {/* + + */} + ); - + return ( <> + {modalInfo !== undefined && ( + <> + + + ) + } {modalInfo !== undefined && ( <> { + _isNew: boolean; + _error: E; +} + +interface SelectionResult { + active: boolean; + _isNew: boolean; + _error: E; +} +type Result = DefaultResult | SelectionResult; + +export type TableRow = Partial< + V & { + isActive: boolean | undefined; + _isNew: boolean; + _error: E; + } & ResultWithId +>; + +export interface InputDataGridProps { + apiRef: MutableRefObject; + checkboxSelection: false | undefined; + _formKey: keyof T; + columns: GridColDef[]; + validateRow: (newRow: GridRowModel>) => E; + needAdd?: boolean; +} + +export interface SelectionInputDataGridProps { + // thinking how do + apiRef: MutableRefObject; + checkboxSelection: true; + _formKey: keyof T; + columns: GridColDef[]; + validateRow: (newRow: GridRowModel>) => E; + needAdd?: boolean; +} + +export type Props = + | InputDataGridProps + | SelectionInputDataGridProps; +export class ProcessRowUpdateError extends Error { + public readonly row: T; + public readonly errors: E | undefined; + constructor(row: T, message?: string, errors?: E) { + super(message); + this.row = row; + this.errors = errors; + + Object.setPrototypeOf(this, ProcessRowUpdateError.prototype); + } +} +// T == CreatexxxInputs map of the form's fields +// V == target field input inside CreatexxxInputs, e.g. qcChecks: ItemQc[], V = ItemQc +// E == error +function QcDatagrid({ + apiRef, + checkboxSelection = false, + _formKey, + columns, + validateRow, + needAdd, +}: Props) { + const { + t, + // i18n: { language }, + } = useTranslation("common"); + const formKey = _formKey.toString(); + const { setValue, getValues } = useFormContext(); + const [rowModesModel, setRowModesModel] = useState({}); + // const apiRef = useGridApiRef(); + const getRowId = useCallback>>( + (row) => row.id! as number, + [], + ); + const list: TableRow[] = getValues(formKey); + // console.log(list) + const [rows, setRows] = useState[]>(() => { + const list: TableRow[] = getValues(formKey); + return list && list.length > 0 ? list : []; + }); + // const originalRows = list && list.length > 0 ? list : []; + const originalRows = useMemo(() => ( + list && list.length > 0 ? list : [] + ), [list]) + + // const originalRowModel = originalRows.filter((li) => li.isActive).map(i => i.id) as GridRowSelectionModel + const [rowSelectionModel, setRowSelectionModel] = + useState(() => { + // const rowModel = list.filter((li) => li.isActive).map(i => i.id) as GridRowSelectionModel + const rowModel: GridRowSelectionModel = getValues( + `${formKey}_active`, + ) as GridRowSelectionModel; + console.log(rowModel); + return rowModel; + }); + + const handleSave = useCallback( + (id: GridRowId) => () => { + setRowModesModel((prevRowModesModel) => ({ + ...prevRowModesModel, + [id]: { mode: GridRowModes.View }, + })); + }, + [], + ); + const onProcessRowUpdateError = useCallback( + (updateError: ProcessRowUpdateError) => { + const errors = updateError.errors; + const row = updateError.row; + console.log(errors); + apiRef.current.updateRows([{ ...row, _error: errors }]); + }, + [apiRef], + ); + + const processRowUpdate = useCallback( + ( + newRow: GridRowModel>, + originalRow: GridRowModel>, + ) => { + ///////////////// + // validation here + const errors = validateRow(newRow); + console.log(newRow); + if (errors) { + throw new ProcessRowUpdateError( + originalRow, + "validation error", + errors, + ); + } + ///////////////// + const { _isNew, _error, ...updatedRow } = newRow; + const rowToSave = { + ...updatedRow, + } as TableRow; /// test + console.log(rowToSave); + setRows((rw) => + rw.map((r) => (getRowId(r) === getRowId(originalRow) ? rowToSave : r)), + ); + return rowToSave; + }, + [validateRow, getRowId], + ); + + const addRow = useCallback(() => { + const newEntry = { id: Date.now(), _isNew: true } as TableRow; + setRows((prev) => [...prev, newEntry]); + setRowModesModel((model) => ({ + ...model, + [getRowId(newEntry)]: { + mode: GridRowModes.Edit, + // fieldToFocus: "team", /// test + }, + })); + }, [getRowId]); + + const reset = useCallback(() => { + setRowModesModel({}); + setRows(originalRows); + }, [originalRows]); + + const handleCancel = useCallback( + (id: GridRowId) => () => { + setRowModesModel((model) => ({ + ...model, + [id]: { mode: GridRowModes.View, ignoreModifications: true }, + })); + const editedRow = rows.find((row) => getRowId(row) === id); + if (editedRow?._isNew) { + setRows((rw) => rw.filter((r) => getRowId(r) !== id)); + } else { + setRows((rw) => + rw.map((r) => (getRowId(r) === id ? { ...r, _error: undefined } : r)), + ); + } + }, + [rows, getRowId], + ); + + const handleDelete = useCallback( + (id: GridRowId) => () => { + setRows((prevRows) => prevRows.filter((row) => getRowId(row) !== id)); + }, + [getRowId], + ); + + const _columns = useMemo( + () => [ + ...columns, + { + field: "actions", + type: "actions", + headerName: "", + flex: 0.5, + cellClassName: "actions", + getActions: ({ id }: { id: GridRowId }) => { + const isInEditMode = rowModesModel[id]?.mode === GridRowModes.Edit; + if (isInEditMode) { + return [ + } + label="Save" + key="edit" + sx={{ + color: "primary.main", + }} + onClick={handleSave(id)} + />, + } + label="Cancel" + key="edit" + onClick={handleCancel(id)} + />, + ]; + } + return [ + } + label="Delete" + sx={{ + color: "error.main", + }} + onClick={handleDelete(id)} + color="inherit" + key="edit" + />, + ]; + }, + }, + ], + [columns, rowModesModel, handleSave, handleCancel, handleDelete], + ); + // sync useForm + useEffect(() => { + // console.log(formKey) + // console.log(rows) + setValue(formKey, rows); + }, [formKey, rows, setValue]); + + const footer = ( + + + + + ); + // const handleRowEditStop: GridEventListener<'rowEditStop'> = (params, event) => { + // if (params.reason === GridRowEditStopReasons.rowFocusOut) { + // event.defaultMuiPrevented = true; + // } + // }; + + return ( + } + // checkbox selection + checkboxSelection={checkboxSelection} + disableRowSelectionOnClick={checkboxSelection} + onRowSelectionModelChange={(newRowSelectionModel) => { + if (checkboxSelection) { + setRowSelectionModel(newRowSelectionModel); + setValue("qcChecks_active", newRowSelectionModel); + } + }} + rowSelectionModel={rowSelectionModel} + apiRef={apiRef} + rows={rows} + columns={!checkboxSelection ? _columns : columns} + editMode="row" + 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 + processRowUpdate={processRowUpdate as any} + // onRowEditStop={handleRowEditStop} + rowModesModel={rowModesModel} + onRowModesModelChange={setRowModesModel} + onProcessRowUpdateError={onProcessRowUpdateError} + getCellClassName={(params: GridCellParams>) => { + let classname = ""; + if (params.row._error) { + classname = "hasError"; + } + return classname; + }} + slots={ + !checkboxSelection + ? { + footer: FooterToolbar, + noRowsOverlay: NoRowsOverlay, + } + : undefined + } + slotProps={ + !checkboxSelection && Boolean(needAdd) + ? { + footer: { child: footer }, + } + : undefined + // slotProps={renderFooter ? { + // footer: { child: footer }, + // }: undefined + } + /> + ); +} +const FooterToolbar: React.FC = ({ child }) => { + return {child}; +}; +const NoRowsOverlay: React.FC = () => { + const { t } = useTranslation("home"); + return ( + + {t("Add some entries!")} + + ); +}; +export default QcDatagrid; diff --git a/src/components/PoDetail/QcFormVer2.tsx b/src/components/PoDetail/QcFormVer2.tsx new file mode 100644 index 0000000..670e04a --- /dev/null +++ b/src/components/PoDetail/QcFormVer2.tsx @@ -0,0 +1,242 @@ +"use client"; + +import { PurchaseQcResult, PurchaseQCInput } from "@/app/api/po/actions"; +import { + Box, + Card, + CardContent, + Grid, + Stack, + Tab, + Tabs, + TabsProps, + TextField, + Tooltip, + Typography, +} from "@mui/material"; +import { useFormContext } from "react-hook-form"; +import { useTranslation } from "react-i18next"; +import StyledDataGrid from "../StyledDataGrid"; +import { useCallback, useEffect, useMemo, useState } 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 { GridEditInputCell } from "@mui/x-data-grid"; +import { StockInLine } from "@/app/api/po"; +import { stockInLineStatusMap } from "@/app/utils/formatUtil"; +import { fetchQcItemCheck, fetchQcResult } from "@/app/api/qc/actions"; +import { QcItemWithChecks } from "@/app/api/qc"; +import axios from "@/app/(main)/axios/axiosInstance"; +import { NEXT_PUBLIC_API_URL } from "@/config/api"; +import axiosInstance from "@/app/(main)/axios/axiosInstance"; + +interface Props { + itemDetail: StockInLine; + qc: QcItemWithChecks[]; + disabled: boolean; +} +type EntryError = + | { + [field in keyof PurchaseQcResult]?: string; + } + | undefined; + +type PoQcRow = TableRow, EntryError>; +// fetchQcItemCheck +const QcFormVer2: React.FC = ({ qc, itemDetail, disabled }) => { + const { t } = useTranslation("purchaseOrder"); + const apiRef = useGridApiRef(); + const { + register, + formState: { errors, defaultValues, touchedFields }, + watch, + control, + setValue, + getValues, + reset, + resetField, + setError, + clearErrors, + } = useFormContext(); + console.log(itemDetail); + console.log(defaultValues); + const [tabIndex, setTabIndex] = useState(0); + + const handleTabChange = useCallback>( + (_e, newValue) => { + setTabIndex(newValue); + }, + [], + ); + + //// validate form + const accQty = watch("acceptedQty"); + const validateForm = useCallback(() => { + console.log(accQty); + if (accQty > itemDetail.acceptedQty) { + setError("acceptedQty", { + message: `${t("acceptedQty must not greater than")} ${ + itemDetail.acceptedQty + }`, + type: "required", + }); + } + if (accQty < 1) { + setError("acceptedQty", { + message: t("minimal value is 1"), + type: "required", + }); + } + if (isNaN(accQty)) { + setError("acceptedQty", { + message: t("value must be a number"), + type: "required", + }); + } + }, [accQty]); + + useEffect(() => { + clearErrors(); + validateForm(); + }, [clearErrors, validateForm]); + + const columns = useMemo( + () => [ + { + field: "qcItemId", + headerName: t("qc Check"), + flex: 1, + editable: !disabled, + valueFormatter(params) { + const row = params.id ? params.api.getRow(params.id) : null; + if (!row) { + return null; + } + const Qc = qc.find((q) => q.id === row.qcItemId); + return Qc ? `${Qc.code} - ${Qc.name}` : t("Please select QC"); + }, + renderCell(params: GridRenderCellParams) { + console.log(params.value); + return {params.formattedValue}; + }, + renderEditCell(params: GridRenderEditCellParams) { + const errorMessage = + params.row._error?.[params.field as keyof PurchaseQcResult]; + console.log(errorMessage); + const content = ( + { + await params.api.setEditCellValue({ + id: params.id, + field: "qcItemId", + value: qcItemId, + }); + // await params.api.setEditCellValue({ + // id: params.id, + // field: "type", + // value: "determine1", + // }); + }} + /> + ); + return errorMessage ? ( + + {content} + + ) : ( + content + ); + }, + }, + { + field: "failQty", + headerName: t("failQty"), + flex: 1, + editable: !disabled, + type: "number", + renderEditCell(params: GridRenderEditCellParams) { + // const recordQty = params.row.qty + // if (recordQty !== undefined) { + // setUnrecordQty((prev) => prev - recordQty) + // } + const errorMessage = + params.row._error?.[params.field as keyof PurchaseQcResult]; + const content = ; + return errorMessage ? ( + + {content} + + ) : ( + content + ); + }, + }, + ], + [qc], + ); + /// validate datagrid + const validation = useCallback( + (newRow: GridRowModel): EntryError => { + const error: EntryError = {}; + const { qcItemId, failQty } = newRow; + if (!qcItemId || qcItemId <= 0) { + error["qcItemId"] = t("select qc"); + } + if (!failQty || failQty <= 0) { + error["failQty"] = t("enter a failQty"); + } + if (failQty && failQty > itemDetail.acceptedQty) { + error["failQty"] = t("qty too big"); + } + return Object.keys(error).length > 0 ? error : undefined; + }, + [], + ); + + useEffect(() => { + console.log(itemDetail); + const status = "receiving"; + // switch (itemDetail.status) { + // case 'pending': + // status = "receiving" + // break; + // } + setValue("status", status); + }, [itemDetail]); + + return ( + <> + + + + + + + + + + ); +}; +export default QcFormVer2; diff --git a/src/components/PoDetail/QcStockInModalVer2.tsx b/src/components/PoDetail/QcStockInModalVer2.tsx new file mode 100644 index 0000000..2e5148e --- /dev/null +++ b/src/components/PoDetail/QcStockInModalVer2.tsx @@ -0,0 +1,149 @@ +"use client"; + +import { StockInLine } from "@/app/api/po"; +import { ModalFormInput, PurchaseQcResult } from "@/app/api/po/actions"; +import { QcItemWithChecks } from "@/app/api/qc"; +import { Box, Button, Grid, Modal, ModalProps, Stack, Typography } from "@mui/material"; +import { Dispatch, SetStateAction, useCallback, useState } from "react"; +import { FormProvider, SubmitHandler, useForm } from "react-hook-form"; +import { StockInLineRow } from "./PoInputGrid"; +import { useTranslation } from "react-i18next"; +import StockInForm from "./StockInForm"; +import StockInFormVer2 from "./StockInFormVer2"; +import QcFormVer2 from "./QcFormVer2"; + +const style = { + position: "absolute", + top: "50%", + left: "50%", + transform: "translate(-50%, -50%)", + overflowY: "scroll", + bgcolor: "background.paper", + pt: 5, + px: 5, + pb: 10, + display: "block", + width: { xs: "60%", sm: "60%", md: "60%" }, +}; + +interface CommonProps extends Omit { + // setRows: Dispatch>; + setEntries?: Dispatch>; + setStockInLine?: Dispatch>; + itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] }; + setItemDetail: Dispatch< + SetStateAction< + | (StockInLine & { + warehouseId?: number; + }) + | undefined + > + >; + qc?: QcItemWithChecks[]; + warehouse?: any[]; +// type: "qc" | "stockIn" | "escalation" | "putaway" | "reject"; +} + +interface Props extends CommonProps{ + itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] }; + +} +const PoQcStockInModalVer2: React.FC = ({ + // type, + // setRows, + setEntries, + setStockInLine, + open, + onClose, + itemDetail, + setItemDetail, + qc, + warehouse, +}) => { + const { + t, + i18n: { language }, + } = useTranslation("purchaseOrder"); + const formProps = useForm({ + defaultValues: { + ...itemDetail, + // receiptDate: itemDetail.receiptDate || dayjs().add(-1, "month").format(INPUT_DATE_FORMAT), + // warehouseId: itemDetail.defaultWarehouseId || 0 + }, + }); + + const closeHandler = useCallback>( + (...args) => { + onClose?.(...args); + // reset(); + }, + [onClose], + ); + + const [submissionType, setSubmissionType] = useState<"stockIn" | "qc" | "escalate" | undefined>(undefined) + const onSubmit = useCallback>( + async (data, event) => { + console.log(event!.nativeEvent) + // divide 3 section for this submition + // switch (submissionType) { + // submit stock in data + // submit qc data + // submit putaway + // } + + }, [submissionType]) + + return ( + <> + + + + + + + {t("qc processing")} + + + + + + + + + + + + + + + + + + ) +} +export default PoQcStockInModalVer2 \ No newline at end of file diff --git a/src/components/PoDetail/StockInFormVer2.tsx b/src/components/PoDetail/StockInFormVer2.tsx new file mode 100644 index 0000000..25ec068 --- /dev/null +++ b/src/components/PoDetail/StockInFormVer2.tsx @@ -0,0 +1,323 @@ +"use client"; + +import { + PurchaseQcResult, + PurchaseQCInput, + StockInInput, +} from "@/app/api/po/actions"; +import { + Box, + Card, + CardContent, + Grid, + Stack, + TextField, + Tooltip, + Typography, +} from "@mui/material"; +import { Controller, useFormContext } from "react-hook-form"; +import { useTranslation } from "react-i18next"; +import StyledDataGrid from "../StyledDataGrid"; +import { useCallback, useEffect, 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"; +import { DatePicker, LocalizationProvider } from "@mui/x-date-pickers"; +import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; +import { INPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; +import dayjs from "dayjs"; +// change PurchaseQcResult to stock in entry props +interface Props { + itemDetail: StockInLine; + // qc: QcItemWithChecks[]; + disabled: boolean; +} +type EntryError = + | { + [field in keyof StockInInput]?: string; + } + | undefined; + +// type PoQcRow = TableRow, EntryError>; + +const StockInFormVer2: React.FC = ({ + // qc, + itemDetail, + disabled, +}) => { + const { + t, + i18n: { language }, + } = useTranslation("purchaseOrder"); + const apiRef = useGridApiRef(); + const { + register, + formState: { errors, defaultValues, touchedFields }, + watch, + control, + setValue, + getValues, + reset, + resetField, + setError, + clearErrors, + } = useFormContext(); + console.log(itemDetail); + + useEffect(() => { + console.log("triggered"); + // receiptDate default tdy + setValue("receiptDate", dayjs().add(0, "month").format(INPUT_DATE_FORMAT)); + setValue("status", "received"); + }, [setValue]); + + useEffect(() => { + console.log(errors); + }, [errors]); + + const productionDate = watch("productionDate"); + const expiryDate = watch("expiryDate"); + + useEffect(() => { + console.log(productionDate); + console.log(expiryDate); + if (expiryDate) clearErrors(); + if (productionDate) clearErrors(); + }, [productionDate, expiryDate, clearErrors]); + + console.log(itemDetail) + + return ( + + {/* + + {t("Stock In Detail")} + + */} + + + + + + + + + + + + { + return ( + + { + if (!date) return; + // setValue("receiptDate", date.format(INPUT_DATE_FORMAT)); + field.onChange(date); + }} + inputRef={field.ref} + slotProps={{ + textField: { + // required: true, + error: Boolean(errors.receiptDate?.message), + helperText: errors.receiptDate?.message, + }, + }} + /> + + ); + }} + /> + + + { + return ( + + { + if (!date) return; + setValue( + "productionDate", + date.format(INPUT_DATE_FORMAT), + ); + // field.onChange(date); + }} + inputRef={field.ref} + slotProps={{ + textField: { + // required: true, + error: Boolean(errors.productionDate?.message), + helperText: errors.productionDate?.message, + }, + }} + /> + + ); + }} + /> + + + + + + { + return ( + + { + console.log(date); + if (!date) return; + console.log(date.format(INPUT_DATE_FORMAT)); + setValue("expiryDate", date.format(INPUT_DATE_FORMAT)); + // field.onChange(date); + }} + inputRef={field.ref} + slotProps={{ + textField: { + // required: true, + error: Boolean(errors.expiryDate?.message), + helperText: errors.expiryDate?.message, + }, + }} + /> + + ); + }} + /> + + + + + {/* + + */} + + + {/* + + apiRef={apiRef} + checkboxSelection={false} + _formKey={"qcCheck"} + columns={columns} + validateRow={validationTest} + /> + */} + + + ); +}; +export default StockInFormVer2; diff --git a/src/components/PoDetail/dummyQcTemplate.tsx b/src/components/PoDetail/dummyQcTemplate.tsx new file mode 100644 index 0000000..d148b24 --- /dev/null +++ b/src/components/PoDetail/dummyQcTemplate.tsx @@ -0,0 +1,23 @@ +const dummyQCData = [ + { + id: 1, + qcItem: "目測", + isPassed: undefined, + failedQty: undefined, + remarks: undefined, + }, + { + id: 2, + qcItem: "目測2", + isPassed: undefined, + failedQty: undefined, + remarks: undefined, + }, + { + id: 3, + qcItem: "目測3", + isPassed: undefined, + failedQty: undefined, + remarks: undefined, + }, +] \ No newline at end of file diff --git a/src/components/RoughSchedule/RoughSchedileSearchView.tsx b/src/components/RoughSchedule/RoughSchedileSearchView.tsx index 21cdf27..e627f87 100644 --- a/src/components/RoughSchedule/RoughSchedileSearchView.tsx +++ b/src/components/RoughSchedule/RoughSchedileSearchView.tsx @@ -253,7 +253,7 @@ const RSOverview: React.FC = ({ type, defaultInputs }) => { // setFilterObj({}); // setTempSelectedValue({}); refetchData(defaultInputs, "reset"); - }, []); + }, [defaultInputs, refetchData]); const testRoughScheduleClick = useCallback(async () => { try { diff --git a/src/components/StyledDataGrid/StyledDataGrid.tsx b/src/components/StyledDataGrid/StyledDataGrid.tsx index a69460e..163b4f6 100644 --- a/src/components/StyledDataGrid/StyledDataGrid.tsx +++ b/src/components/StyledDataGrid/StyledDataGrid.tsx @@ -17,6 +17,9 @@ const StyledDataGrid = styled(DataGrid)(({ theme }) => ({ "& .MuiDataGrid-columnSeparator": { color: theme.palette.primary.main, }, + "& .MuiDataGrid-row:nth-of-type(even)": { + backgroundColor: theme.palette.grey[200], // Light grey for even rows + }, })); export default StyledDataGrid; diff --git a/src/i18n/zh/purchaseOrder.json b/src/i18n/zh/purchaseOrder.json index 10ecb8e..7377887 100644 --- a/src/i18n/zh/purchaseOrder.json +++ b/src/i18n/zh/purchaseOrder.json @@ -82,6 +82,19 @@ "Po Code": "採購訂單編號", "No Warehouse": "沒有倉庫", "Please scan warehouse qr code.": "請掃描倉庫 QR 碼。", + + "receivedQty": "已來貨數量", + "dnQty": "送貨單數量", + + "Accept submit": "接受來貨", + "qc processing": "處理來貨及品檢", + "putawayBtn": "上架", + "dnNo": "送貨單編號", + "dnDate": "送貨單日期", + + "submitStockIn": "更新來貨資料", + "QC Info": "品檢資料", + "Escalation History": "品檢資料", "Reject": "拒絕", "submit": "提交",