Browse Source

update

master
MSI\derek 2 weeks ago
parent
commit
fa93870c23
12 changed files with 539 additions and 579 deletions
  1. +8
    -2
      src/app/api/po/actions.ts
  2. +1
    -1
      src/app/api/po/index.ts
  3. +7
    -3
      src/components/InputDataGrid/InputDataGrid.tsx
  4. +47
    -35
      src/components/PoDetail/EscalationComponent.tsx
  5. +1
    -0
      src/components/PoDetail/PoInputGrid.tsx
  6. +67
    -15
      src/components/PoDetail/PutawayForm.tsx
  7. +66
    -395
      src/components/PoDetail/QCDatagrid.tsx
  8. +157
    -76
      src/components/PoDetail/QcFormVer2.tsx
  9. +123
    -46
      src/components/PoDetail/QcStockInModalVer2.tsx
  10. +40
    -1
      src/components/PoDetail/dummyQcTemplate.tsx
  11. +8
    -4
      src/components/PoSearch/PoSearch.tsx
  12. +14
    -1
      src/i18n/zh/purchaseOrder.json

+ 8
- 2
src/app/api/po/actions.ts View File

@@ -65,12 +65,18 @@ export interface EscalationInput {
acceptedQty: number; // this is the qty to be escalated acceptedQty: number; // this is the qty to be escalated
// escalationQty: number // escalationQty: number
} }
export interface PutawayLine {
id?: number
qty: number
warehouseId: number;
warehouse: string;
printQty: number
}
export interface PutawayInput { export interface PutawayInput {
status: string; status: string;
acceptedQty: number; acceptedQty: number;
warehouseId: number; warehouseId: number;
// handler: string
// stockInLine: StockInLineEntry[]
putawayLine: PutawayLine[]
} }


export type ModalFormInput = Partial< export type ModalFormInput = Partial<


+ 1
- 1
src/app/api/po/index.ts View File

@@ -14,7 +14,7 @@ export interface PoResult {
supplier: string; supplier: string;
estimatedArrivalDate: string; estimatedArrivalDate: string;
completedDate: string; completedDate: string;
itemDetail?: String;
itemDetail?: string;
escalated: boolean; escalated: boolean;
status: string; status: string;
pol?: PurchaseOrderLine[]; pol?: PurchaseOrderLine[];


+ 7
- 3
src/components/InputDataGrid/InputDataGrid.tsx View File

@@ -123,11 +123,13 @@ function InputDataGrid<T, V, E>({
[], [],
); );
const list: TableRow<V, E>[] = getValues(formKey); const list: TableRow<V, E>[] = getValues(formKey);
// console.log(list)
console.log(list)
const [rows, setRows] = useState<TableRow<V, E>[]>(() => { const [rows, setRows] = useState<TableRow<V, E>[]>(() => {
const list: TableRow<V, E>[] = getValues(formKey); const list: TableRow<V, E>[] = getValues(formKey);
console.log(list)
return list && list.length > 0 ? list : []; return list && list.length > 0 ? list : [];
}); });
console.log(rows)
// const originalRows = list && list.length > 0 ? list : []; // const originalRows = list && list.length > 0 ? list : [];
const originalRows = useMemo(() => ( const originalRows = useMemo(() => (
list && list.length > 0 ? list : [] list && list.length > 0 ? list : []
@@ -298,7 +300,8 @@ function InputDataGrid<T, V, E>({
onClick={addRow} onClick={addRow}
size="small" size="small"
> >
{t("Add Record")}
新增
{/* {t("Add Record")} */}
</Button> </Button>
<Button <Button
disableRipple disableRipple
@@ -307,7 +310,8 @@ function InputDataGrid<T, V, E>({
onClick={reset} onClick={reset}
size="small" size="small"
> >
{t("Clean Record")}
{/* {t("Clean Record")} */}
清除
</Button> </Button>
</Box> </Box>
); );


+ 47
- 35
src/components/PoDetail/EscalationComponent.tsx View File

@@ -14,10 +14,13 @@ import {
Typography, Typography,
RadioGroup, RadioGroup,
Radio, Radio,
Stack,
Autocomplete,
} from '@mui/material'; } from '@mui/material';
import { SelectChangeEvent } from '@mui/material/Select'; import { SelectChangeEvent } from '@mui/material/Select';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import ExpandLessIcon from '@mui/icons-material/ExpandLess'; import ExpandLessIcon from '@mui/icons-material/ExpandLess';
import { useTranslation } from 'react-i18next';


interface NameOption { interface NameOption {
value: string; value: string;
@@ -30,7 +33,15 @@ interface FormData {
message: string; message: string;
} }


function EscalationComponent(): JSX.Element {
interface Props {
forSupervisor: boolean
}

const EscalationComponent: React.FC<Props> = ({
forSupervisor
}) => {
const { t } = useTranslation("purchaseOrder");
const [isCollapsed, setIsCollapsed] = useState<boolean>(false); const [isCollapsed, setIsCollapsed] = useState<boolean>(false);
const [formData, setFormData] = useState<FormData>({ const [formData, setFormData] = useState<FormData>({
name: '', name: '',
@@ -48,9 +59,9 @@ function EscalationComponent(): JSX.Element {
]; ];


const handleInputChange = ( const handleInputChange = (
e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement> | SelectChangeEvent<string>
event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement> | SelectChangeEvent<string>
): void => { ): void => {
const { name, value } = e.target;
const { name, value } = event.target;
setFormData((prev) => ({ setFormData((prev) => ({
...prev, ...prev,
[name]: value, [name]: value,
@@ -69,8 +80,10 @@ function EscalationComponent(): JSX.Element {


return ( return (
// <Paper elevation={3} sx={{ maxWidth: 400, mx: 'auto', p: 3 }}> // <Paper elevation={3} sx={{ maxWidth: 400, mx: 'auto', p: 3 }}>
<Paper elevation={3} sx={{ mx: 'auto', p: 3 }}>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
<>
<Paper>
{/* <Paper elevation={3} sx={{ mx: 'auto', p: 3 }}> */}
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
<FormControlLabel <FormControlLabel
control={ control={
<Checkbox <Checkbox
@@ -91,38 +104,36 @@ function EscalationComponent(): JSX.Element {
} }
/> />
</Box> </Box>

<Collapse in={!isCollapsed}>
<Collapse in={isCollapsed}>
<Box component="form" onSubmit={handleSubmit} sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}> <Box component="form" onSubmit={handleSubmit} sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
<FormControl>
<RadioGroup
row
aria-labelledby="demo-radio-buttons-group-label"
defaultValue="pass"
name="radio-buttons-group"
>
<FormControlLabel value="pass" control={<Radio />} label="合格" />
<FormControlLabel value="fail" control={<Radio />} label="不合格" />
</RadioGroup>
</FormControl>
{forSupervisor ? (
<FormControl>
<RadioGroup
row
aria-labelledby="demo-radio-buttons-group-label"
defaultValue="pass"
name="radio-buttons-group"
>
<FormControlLabel value="pass" control={<Radio />} label="合格" />
<FormControlLabel value="fail" control={<Radio />} label="不合格" />
</RadioGroup>
</FormControl>
): undefined}
<FormControl fullWidth> <FormControl fullWidth>
<InputLabel id="name-label">姓名</InputLabel>
<Select
labelId="name-label"
<select
id="name" id="name"
name="name" name="name"
value={formData.name} value={formData.name}
label="姓名"
onChange={handleInputChange} onChange={handleInputChange}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white"
> >
{nameOptions.map((option: NameOption) => ( {nameOptions.map((option: NameOption) => (
<MenuItem key={option.value} value={option.value}>
<option key={option.value} value={option.value}>
{option.label} {option.label}
</MenuItem>
</option>
))} ))}
</Select>
</select>
</FormControl> </FormControl>

<TextField <TextField
fullWidth fullWidth
id="quantity" id="quantity"
@@ -147,18 +158,19 @@ function EscalationComponent(): JSX.Element {
placeholder="請輸入您的備註" placeholder="請輸入您的備註"
/> />


<Button
type="submit"
variant="contained"
color="primary"
fullWidth
sx={{ mt: 1 }}
>
提交
</Button>
<Stack direction="row" justifyContent="flex-end" gap={1}>
<Button
type="submit"
variant="contained"
color="primary"
>
{t("update qc info")}
</Button>
</Stack>
</Box> </Box>
</Collapse> </Collapse>
</Paper> </Paper>
</>
); );
} }



+ 1
- 0
src/components/PoDetail/PoInputGrid.tsx View File

@@ -812,6 +812,7 @@ function PoInputGrid({
setStockInLine={setStockInLine} setStockInLine={setStockInLine}
setItemDetail={setModalInfo} setItemDetail={setModalInfo}
qc={qc} qc={qc}
warehouse={warehouse}
open={newOpen} open={newOpen}
onClose={closeNewModal} onClose={closeNewModal}
itemDetail={modalInfo} itemDetail={modalInfo}


+ 67
- 15
src/components/PoDetail/PutawayForm.tsx View File

@@ -1,6 +1,6 @@
"use client"; "use client";


import { PurchaseQcResult, PutawayInput } from "@/app/api/po/actions";
import { PurchaseQcResult, PutawayInput, PutawayLine } from "@/app/api/po/actions";
import { import {
Autocomplete, Autocomplete,
Box, Box,
@@ -50,6 +50,7 @@ import { QrCodeInfo } from "@/app/api/qrcode";
import { useQrCodeScannerContext } from "../QrCodeScannerProvider/QrCodeScannerProvider"; import { useQrCodeScannerContext } from "../QrCodeScannerProvider/QrCodeScannerProvider";
import dayjs from "dayjs"; import dayjs from "dayjs";
import arraySupport from "dayjs/plugin/arraySupport"; import arraySupport from "dayjs/plugin/arraySupport";
import { dummyPutawayLine } from "./dummyQcTemplate";
dayjs.extend(arraySupport); dayjs.extend(arraySupport);


interface Props { interface Props {
@@ -60,11 +61,11 @@ interface Props {
} }
type EntryError = type EntryError =
| { | {
[field in keyof PurchaseQcResult]?: string;
[field in keyof PutawayLine]?: string;
} }
| undefined; | undefined;


// type PoQcRow = TableRow<Partial<PurchaseQcResult>, EntryError>;
type PutawayRow = TableRow<Partial<PutawayLine>, EntryError>;


const style = { const style = {
position: "absolute", position: "absolute",
@@ -100,6 +101,7 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled }) => {
// do filtering here if any // do filtering here if any
return warehouse; return warehouse;
}, []); }, []);

const defaultOption = { const defaultOption = {
value: 0, // think think sin value: 0, // think think sin
label: t("Select warehouse"), label: t("Select warehouse"),
@@ -141,7 +143,7 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled }) => {
}, },
[], [],
); );
console.log(watch("putawayLine"))
// const accQty = watch("acceptedQty"); // const accQty = watch("acceptedQty");
// const validateForm = useCallback(() => { // const validateForm = useCallback(() => {
// console.log(accQty); // console.log(accQty);
@@ -265,6 +267,44 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled }) => {
return undefined; return undefined;
}, [options]); }, [options]);


const columns = useMemo<GridColDef[]>(
() => [
{
field: "qty",
headerName: t("qty"),
flex: 1,
// renderCell(params) {
// return <>100</>
// },
},
{
field: "warehouse",
headerName: t("warehouse"),
flex: 1,
// renderCell(params) {
// return <>{filteredWarehouse[0].name}</>
// },
},
{
field: "printQty",
headerName: t("printQty"),
flex: 1,
// renderCell(params) {
// return <>100</>
// },
},
], [])

const validation = useCallback(
(newRow: GridRowModel<PutawayRow>): EntryError => {
const error: EntryError = {};
const { qty, warehouseId, printQty } = newRow;

return Object.keys(error).length > 0 ? error : undefined;
},
[],
);

return ( return (
<Grid container justifyContent="flex-start" alignItems="flex-start"> <Grid container justifyContent="flex-start" alignItems="flex-start">
<Grid item xs={12}> <Grid item xs={12}>
@@ -331,8 +371,10 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled }) => {
<TextField <TextField
label={t("productionDate")} label={t("productionDate")}
fullWidth fullWidth
value={dayjs(itemDetail.productionDate)
.add(-1, "month")
value={
// dayjs(itemDetail.productionDate)
dayjs()
// .add(-1, "month")
.format(OUTPUT_DATE_FORMAT)} .format(OUTPUT_DATE_FORMAT)}
disabled disabled
/> />
@@ -341,8 +383,10 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled }) => {
<TextField <TextField
label={t("expiryDate")} label={t("expiryDate")}
fullWidth fullWidth
value={dayjs(itemDetail.expiryDate)
.add(-1, "month")
value={
// dayjs(itemDetail.expiryDate)
dayjs()
.add(20, "day")
.format(OUTPUT_DATE_FORMAT)} .format(OUTPUT_DATE_FORMAT)}
disabled disabled
/> />
@@ -364,7 +408,7 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled }) => {
/> />
</FormControl> </FormControl>
</Grid> </Grid>
<Grid item xs={5.5}>
{/* <Grid item xs={5.5}>
<TextField <TextField
label={t("acceptedQty")} label={t("acceptedQty")}
fullWidth fullWidth
@@ -384,9 +428,9 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled }) => {
<Button disabled={disabled} onClick={onOpenScanner}> <Button disabled={disabled} onClick={onOpenScanner}>
{t("bind")} {t("bind")}
</Button> </Button>
</Grid>
<Grid item xs={5.5}>
{/* <Controller
</Grid> */}
{/* <Grid item xs={5.5}>
<Controller
control={control} control={control}
name="warehouseId" name="warehouseId"
render={({ field }) => { render={({ field }) => {
@@ -412,7 +456,7 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled }) => {
/> />
); );
}} }}
/> */}
/>
<FormControl fullWidth> <FormControl fullWidth>
<Autocomplete <Autocomplete
noOptionsText={t("No Warehouse")} noOptionsText={t("No Warehouse")}
@@ -441,13 +485,21 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled }) => {
)} )}
/> />
</FormControl> </FormControl>
</Grid>
</Grid> */}
<Grid <Grid
item item
xs={12} xs={12}
style={{ display: "flex", justifyContent: "center" }} style={{ display: "flex", justifyContent: "center" }}
> >
<QrCode content={qrContent} sx={{ width: 200, height: 200 }} />
{/* <QrCode content={qrContent} sx={{ width: 200, height: 200 }} /> */}
<InputDataGrid<PutawayInput, PutawayLine, EntryError>
apiRef={apiRef}
checkboxSelection={false}
_formKey={"putawayLine"}
columns={columns}
validateRow={validation}
needAdd={true}
/>
</Grid> </Grid>
</Grid> </Grid>
{/* <Grid {/* <Grid


+ 66
- 395
src/components/PoDetail/QCDatagrid.tsx View File

@@ -1,400 +1,71 @@
"use client";
import {
Dispatch,
MutableRefObject,
SetStateAction,
useCallback,
useEffect,
useMemo,
useState,
} from "react";
import StyledDataGrid from "../StyledDataGrid";
import {
FooterPropsOverrides,
GridActionsCellItem,
GridCellParams,
GridColDef,
GridEventListener,
GridRowEditStopReasons,
GridRowId,
GridRowIdGetter,
GridRowModel,
GridRowModes,
GridRowModesModel,
GridRowSelectionModel,
GridToolbarContainer,
GridValidRowModel,
useGridApiRef,
} from "@mui/x-data-grid";
import { set, useFormContext } from "react-hook-form";
import SaveIcon from "@mui/icons-material/Save";
import DeleteIcon from "@mui/icons-material/Delete";
import CancelIcon from "@mui/icons-material/Cancel";
import { Add } from "@mui/icons-material";
import { Box, Button, Typography } from "@mui/material";
import { useTranslation } from "react-i18next";
import {
GridApiCommunity,
GridSlotsComponentsProps,
} from "@mui/x-data-grid/internals";
// T == CreatexxxInputs map of the form's fields
// V == target field input inside CreatexxxInputs, e.g. qcChecks: ItemQc[], V = ItemQc
// E == error
interface ResultWithId {
id: string | number;
}
// export type InputGridProps = {
// [key: string]: any
// }
interface DefaultResult<E> {
_isNew: boolean;
_error: E;
}

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

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

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

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

export type Props<T, V, E> =
| InputDataGridProps<T, V, E>
| SelectionInputDataGridProps<T, V, E>;
export class ProcessRowUpdateError<T, E> extends Error {
public readonly row: T;
public readonly errors: E | undefined;
constructor(row: T, message?: string, errors?: E) {
super(message);
this.row = row;
this.errors = errors;
"use client"


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<T, V, E>({
apiRef,
checkboxSelection = false,
_formKey,
columns,
validateRow,
needAdd,
}: Props<T, V, E>) {
const {
t,
// i18n: { language },
} = useTranslation("common");
const formKey = _formKey.toString();
const { setValue, getValues } = useFormContext();
const [rowModesModel, setRowModesModel] = useState<GridRowModesModel>({});
// const apiRef = useGridApiRef();
const getRowId = useCallback<GridRowIdGetter<TableRow<V, E>>>(
(row) => row.id! as number,
[],
);
const list: TableRow<V, E>[] = getValues(formKey);
// console.log(list)
const [rows, setRows] = useState<TableRow<V, E>[]>(() => {
const list: TableRow<V, E>[] = getValues(formKey);
return list && list.length > 0 ? list : [];
});
// const originalRows = list && list.length > 0 ? list : [];
const 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<GridRowSelectionModel>(() => {
// const rowModel = list.filter((li) => li.isActive).map(i => i.id) as GridRowSelectionModel
const rowModel: GridRowSelectionModel = getValues(
`${formKey}_active`,
) as GridRowSelectionModel;
console.log(rowModel);
return rowModel;
});

const handleSave = useCallback(
(id: GridRowId) => () => {
setRowModesModel((prevRowModesModel) => ({
...prevRowModesModel,
[id]: { mode: GridRowModes.View },
}));
},
[],
);
const onProcessRowUpdateError = useCallback(
(updateError: ProcessRowUpdateError<T, E>) => {
const errors = updateError.errors;
const row = updateError.row;
console.log(errors);
apiRef.current.updateRows([{ ...row, _error: errors }]);
},
[apiRef],
);

const processRowUpdate = useCallback(
(
newRow: GridRowModel<TableRow<V, E>>,
originalRow: GridRowModel<TableRow<V, E>>,
) => {
/////////////////
// 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<V, E>; /// 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<V, E>;
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],
);
import { MutableRefObject } from "react";
import StyledDataGrid from "../StyledDataGrid"
import { GridApiCommunity } from "@mui/x-data-grid/internals";
import { GridColDef } from "@mui/x-data-grid";
import { useTranslation } from "react-i18next";
import { dummyQCData, QcData } from "./dummyQcTemplate";
import { Checkbox } from "@mui/material";


const _columns = useMemo<GridColDef[]>(
() => [
...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 [
<GridActionsCellItem
icon={<SaveIcon />}
label="Save"
key="edit"
sx={{
color: "primary.main",
}}
onClick={handleSave(id)}
/>,
<GridActionsCellItem
icon={<CancelIcon />}
label="Cancel"
key="edit"
onClick={handleCancel(id)}
/>,
];
}
return [
<GridActionsCellItem
icon={<DeleteIcon />}
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]);
interface Props {
// apiRef: MutableRefObject<GridApiCommunity>;
};


const footer = (
<Box display="flex" gap={2} alignItems="center">
<Button
disableRipple
variant="outlined"
startIcon={<Add />}
onClick={addRow}
size="small"
>
{t("Add Record")}
</Button>
<Button
disableRipple
variant="outlined"
startIcon={<Add />}
onClick={reset}
size="small"
>
{t("Clean Record")}
</Button>
</Box>
);
// const handleRowEditStop: GridEventListener<'rowEditStop'> = (params, event) => {
// if (params.reason === GridRowEditStopReasons.rowFocusOut) {
// event.defaultMuiPrevented = true;
// }
// };
const QcDataGrid: React.FC<Props> = ({
// apiRef
}) => {
const { t } = useTranslation("purchaseOrder");


return (
<StyledDataGrid
// {...props}
// getRowId={getRowId as GridRowIdGetter<GridValidRowModel>}
// checkbox selection
checkboxSelection={checkboxSelection}
disableRowSelectionOnClick={checkboxSelection}
onRowSelectionModelChange={(newRowSelectionModel) => {
if (checkboxSelection) {
setRowSelectionModel(newRowSelectionModel);
setValue("qcChecks_active", newRowSelectionModel);
}
}}
rowSelectionModel={rowSelectionModel}
apiRef={apiRef}
rows={rows}
columns={!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<TableRow<T, E>>) => {
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 columns: GridColDef[] = [
{
field: "qcItem",
headerName: t("qcItem"),
flex: 1,
},
{
field: "isPassed",
headerName: t("passed"),
flex: 1,
renderCell: (params) => (
<Checkbox
checked={params.value}
// onChange={() => handleCheckboxChange(params.id)}
/>
),
},
{
field: "isFailed",
headerName: t("failed"),
flex: 1,
renderCell: (params) => (
<Checkbox
checked={params.value}
// onChange={() => handleCheckboxChange(params.id)}
/>
),
},
{
field: "failedQty",
headerName: t("failedQty"),
flex: 1,
editable: true,
},
{
field: "remarks",
headerName: t("remarks"),
flex: 1,
editable: true,
},
]
return (
<StyledDataGrid
// apiRef={apiRef}
autoHeight
editMode="row"
rows={dummyQCData}
columns={columns}
/>
)
} }
const FooterToolbar: React.FC<FooterPropsOverrides> = ({ child }) => {
return <GridToolbarContainer sx={{ p: 2 }}>{child}</GridToolbarContainer>;
};
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>
);
};
export default QcDatagrid;
export default QcDataGrid

+ 157
- 76
src/components/PoDetail/QcFormVer2.tsx View File

@@ -26,6 +26,7 @@ import {
GridRenderCellParams, GridRenderCellParams,
GridRenderEditCellParams, GridRenderEditCellParams,
useGridApiRef, useGridApiRef,
GridRowSelectionModel,
} from "@mui/x-data-grid"; } from "@mui/x-data-grid";
import InputDataGrid from "../InputDataGrid"; import InputDataGrid from "../InputDataGrid";
import { TableRow } from "../InputDataGrid/InputDataGrid"; import { TableRow } from "../InputDataGrid/InputDataGrid";
@@ -39,6 +40,10 @@ import { QcItemWithChecks } from "@/app/api/qc";
import axios from "@/app/(main)/axios/axiosInstance"; import axios from "@/app/(main)/axios/axiosInstance";
import { NEXT_PUBLIC_API_URL } from "@/config/api"; import { NEXT_PUBLIC_API_URL } from "@/config/api";
import axiosInstance from "@/app/(main)/axios/axiosInstance"; import axiosInstance from "@/app/(main)/axios/axiosInstance";
import EscalationComponent from "./EscalationComponent";
import QcDataGrid from "./QCDatagrid";
import StockInFormVer2 from "./StockInFormVer2";
import { dummyEscalationHistory } from "./dummyQcTemplate";


interface Props { interface Props {
itemDetail: StockInLine; itemDetail: StockInLine;
@@ -68,10 +73,24 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled }) => {
setError, setError,
clearErrors, clearErrors,
} = useFormContext<PurchaseQCInput>(); } = useFormContext<PurchaseQCInput>();
console.log(itemDetail);
console.log(defaultValues);
const [tabIndex, setTabIndex] = useState(0); const [tabIndex, setTabIndex] = useState(0);
const [rowSelectionModel, setRowSelectionModel] = useState<GridRowSelectionModel>()


const column = useMemo<GridColDef[]>(
() => [
{
field: "escalation",
headerName: t("escalation"),
flex: 1,
},
{
field: "supervisor",
headerName: t("supervisor"),
flex: 1,
},
], []
)
const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>( const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>(
(_e, newValue) => { (_e, newValue) => {
setTabIndex(newValue); setTabIndex(newValue);
@@ -112,79 +131,89 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled }) => {


const columns = useMemo<GridColDef[]>( const columns = useMemo<GridColDef[]>(
() => [ () => [
// {
// field: "qcItemId",
// headerName: t("qc Check"),
// flex: 1,
// editable: !disabled,
// 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.qcItemId);
// 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 PurchaseQcResult];
// console.log(errorMessage);
// const content = (
// <QcSelect
// allQcs={qc}
// value={params.row.qcItemId}
// onQcSelect={async (qcItemId) => {
// await params.api.setEditCellValue({
// id: params.id,
// field: "qcItemId",
// value: qcItemId,
// });
// // await params.api.setEditCellValue({
// // id: params.id,
// // field: "type",
// // value: "determine1",
// // });
// }}
// />
// );
// return errorMessage ? (
// <Tooltip title={errorMessage}>
// <Box width="100%">{content}</Box>
// </Tooltip>
// ) : (
// content
// );
// },
// },
// {
// field: "failQty",
// headerName: t("failQty"),
// flex: 1,
// editable: !disabled,
// type: "number",
// renderEditCell(params: GridRenderEditCellParams<PoQcRow>) {
// // const recordQty = params.row.qty
// // if (recordQty !== undefined) {
// // setUnrecordQty((prev) => prev - recordQty)
// // }
// const errorMessage =
// params.row._error?.[params.field as keyof PurchaseQcResult];
// const content = <GridEditInputCell {...params} />;
// return errorMessage ? (
// <Tooltip title={t(errorMessage)}>
// <Box width="100%">{content}</Box>
// </Tooltip>
// ) : (
// content
// );
// },
// },
{ {
field: "qcItemId",
headerName: t("qc Check"),
field: "escalation",
headerName: t("escalation"),
flex: 1, flex: 1,
editable: !disabled,
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.qcItemId);
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 PurchaseQcResult];
console.log(errorMessage);
const content = (
<QcSelect
allQcs={qc}
value={params.row.qcItemId}
onQcSelect={async (qcItemId) => {
await params.api.setEditCellValue({
id: params.id,
field: "qcItemId",
value: qcItemId,
});
// await params.api.setEditCellValue({
// id: params.id,
// field: "type",
// value: "determine1",
// });
}}
/>
);
return errorMessage ? (
<Tooltip title={errorMessage}>
<Box width="100%">{content}</Box>
</Tooltip>
) : (
content
);
},
}, },
{ {
field: "failQty",
headerName: t("failQty"),
field: "supervisor",
headerName: t("supervisor"),
flex: 1, flex: 1,
editable: !disabled,
type: "number",
renderEditCell(params: GridRenderEditCellParams<PoQcRow>) {
// const recordQty = params.row.qty
// if (recordQty !== undefined) {
// setUnrecordQty((prev) => prev - recordQty)
// }
const errorMessage =
params.row._error?.[params.field as keyof PurchaseQcResult];
const content = <GridEditInputCell {...params} />;
return errorMessage ? (
<Tooltip title={t(errorMessage)}>
<Box width="100%">{content}</Box>
</Tooltip>
) : (
content
);
},
}, },
], ],
[qc],
[],
); );
/// validate datagrid /// validate datagrid
const validation = useCallback( const validation = useCallback(
@@ -226,14 +255,66 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled }) => {
spacing={2} spacing={2}
sx={{ mt: 0.5 }} sx={{ mt: 0.5 }}
> >
<Tabs
value={tabIndex}
onChange={handleTabChange}
variant="scrollable"
>
<Tab label={t("QC Info")} iconPosition="end" />
<Tab label={t("Escalation History")} iconPosition="end" />
</Tabs>
<Grid item xs={12}>
<Tabs
value={tabIndex}
onChange={handleTabChange}
variant="scrollable"
>
<Tab label={t("QC Info")} iconPosition="end" />
<Tab label={t("Escalation History")} iconPosition="end" />
</Tabs>
</Grid>
{tabIndex == 0 && (
<>
<Grid item xs={12}>
<QcDataGrid/>
</Grid>
<Grid item xs={4}>
<TextField
label={t("acceptedQty")}
fullWidth
/>
</Grid>
<Grid item xs={12}>
<EscalationComponent forSupervisor={false}/>
</Grid>
</>
)}
{tabIndex == 1 && (
<>
{/* <Grid item xs={12}>
<StockInFormVer2
itemDetail={itemDetail}
disabled={false}
/>
</Grid> */}
<Grid item xs={12}>
<Typography variant="h6" display="block" marginBlockEnd={1}>
{t("Escalation Info")}
</Typography>
</Grid>
<Grid item xs={12}>
<StyledDataGrid
rows={dummyEscalationHistory}
columns={columns}
onRowSelectionModelChange={(newRowSelectionModel) => {
setRowSelectionModel(newRowSelectionModel);
}}
/>
</Grid>
<Grid item xs={12}>
<Typography variant="h6" display="block" marginBlockEnd={1}>
{t("Escalation Result")}
</Typography>
</Grid>
<Grid item xs={12}>
<EscalationComponent
forSupervisor={true}
/>
</Grid>
</>
)}
</Grid> </Grid>
</Grid> </Grid>
</> </>


+ 123
- 46
src/components/PoDetail/QcStockInModalVer2.tsx View File

@@ -11,19 +11,21 @@ import { useTranslation } from "react-i18next";
import StockInForm from "./StockInForm"; import StockInForm from "./StockInForm";
import StockInFormVer2 from "./StockInFormVer2"; import StockInFormVer2 from "./StockInFormVer2";
import QcFormVer2 from "./QcFormVer2"; import QcFormVer2 from "./QcFormVer2";
import PutawayForm from "./PutawayForm";
import { dummyPutawayLine } from "./dummyQcTemplate";


const style = { const style = {
position: "absolute", position: "absolute",
top: "50%", top: "50%",
left: "50%", left: "50%",
transform: "translate(-50%, -50%)", transform: "translate(-50%, -50%)",
overflowY: "scroll",
bgcolor: "background.paper", bgcolor: "background.paper",
pt: 5, pt: 5,
px: 5, px: 5,
pb: 10, pb: 10,
display: "block", display: "block",
width: { xs: "60%", sm: "60%", md: "60%" }, width: { xs: "60%", sm: "60%", md: "60%" },
// height: { xs: "60%", sm: "60%", md: "60%" },
}; };


interface CommonProps extends Omit<ModalProps, "children"> { interface CommonProps extends Omit<ModalProps, "children"> {
@@ -60,6 +62,7 @@ const PoQcStockInModalVer2: React.FC<Props> = ({
qc, qc,
warehouse, warehouse,
}) => { }) => {
console.log(warehouse)
const { const {
t, t,
i18n: { language }, i18n: { language },
@@ -67,6 +70,7 @@ const PoQcStockInModalVer2: React.FC<Props> = ({
const formProps = useForm<ModalFormInput>({ const formProps = useForm<ModalFormInput>({
defaultValues: { defaultValues: {
...itemDetail, ...itemDetail,
putawayLine: dummyPutawayLine
// receiptDate: itemDetail.receiptDate || dayjs().add(-1, "month").format(INPUT_DATE_FORMAT), // receiptDate: itemDetail.receiptDate || dayjs().add(-1, "month").format(INPUT_DATE_FORMAT),
// warehouseId: itemDetail.defaultWarehouseId || 0 // warehouseId: itemDetail.defaultWarehouseId || 0
}, },
@@ -79,11 +83,23 @@ const PoQcStockInModalVer2: React.FC<Props> = ({
}, },
[onClose], [onClose],
); );
const [openPutaway, setOpenPutaway] = useState(false)
const onOpenPutaway = useCallback(() => {
setOpenPutaway(true);
}, []);

const onClosePutaway = useCallback(() => {
setOpenPutaway(false);
}, []);


const [submissionType, setSubmissionType] = useState<"stockIn" | "qc" | "escalate" | undefined>(undefined) const [submissionType, setSubmissionType] = useState<"stockIn" | "qc" | "escalate" | undefined>(undefined)
const onSubmit = useCallback<SubmitHandler<ModalFormInput>>( const onSubmit = useCallback<SubmitHandler<ModalFormInput>>(
async (data, event) => { async (data, event) => {
console.log(event!.nativeEvent) console.log(event!.nativeEvent)
// closeHandler({}, "backdropClick");
// for now go to putaway form
onOpenPutaway()

// divide 3 section for this submition // divide 3 section for this submition
// switch (submissionType) { // switch (submissionType) {
// submit stock in data // submit stock in data
@@ -95,52 +111,113 @@ const PoQcStockInModalVer2: React.FC<Props> = ({


return ( return (
<> <>
{/* {itemDetail !== undefined && (
<PutawayForm
itemDetail={itemDetail}
warehouse={warehouse!}
disabled={false}
/>
)} */}
<FormProvider {...formProps}> <FormProvider {...formProps}>
<Modal open={open} onClose={closeHandler} sx={{ overflowY: "scroll" }}>
<Box
sx={style}
component="form"
onSubmit={formProps.handleSubmit(onSubmit)}
>
<Grid container justifyContent="flex-start" alignItems="flex-start">
<Grid item xs={12}>
<Typography variant="h6" display="block" marginBlockEnd={1}>
{t("qc processing")}
</Typography>
</Grid>
<Grid item xs={12}>
<StockInFormVer2
itemDetail={itemDetail}
disabled={false}
/>
</Grid>
</Grid>
<Stack direction="row" justifyContent="flex-end" gap={1}>
<Button
id="stockIn"
type="button"
variant="contained"
color="primary"
>
{t("submitStockIn")}
</Button>
</Stack>
<Grid container justifyContent="flex-start" alignItems="flex-start">
<QcFormVer2
qc={qc!}
itemDetail={itemDetail}
disabled={false}
/>
</Grid>
<Button
id="qc"
type="button"
variant="contained"
color="secondary"
>
Submit QC
</Button>
</Box>
<Modal open={open} onClose={closeHandler}>
<Box
sx={{
...style,
padding: 2, // Add padding to the Box
maxHeight: '90vh', // Limit the height of the modal
overflowY: 'auto', // Enable scrolling if content overflows
marginLeft: 3,
marginRight: 3,
}}
component="form"
onSubmit={formProps.handleSubmit(onSubmit)}
>
{openPutaway ? (
<>
<PutawayForm
itemDetail={itemDetail}
warehouse={warehouse!}
disabled={false}
/>
<Stack direction="row" justifyContent="flex-end" gap={1}>
<Button
id="qc"
type="button"
variant="contained"
color="secondary"
sx={{ mt: 1 }}
>
{t("print")}
</Button>
<Button
id="qc"
type="submit"
variant="contained"
color="secondary"
sx={{ mt: 1 }}
>
{t("confirm putaway")}
</Button>
</Stack>
</>
) : (
<>
<Grid container justifyContent="flex-start" alignItems="flex-start">
<Grid item xs={12}>
<Typography variant="h6" display="block" marginBlockEnd={1}>
{t("qc processing")}
</Typography>
</Grid>
<Grid item xs={12}>
<StockInFormVer2
itemDetail={itemDetail}
disabled={false}
/>
</Grid>
</Grid>
<Stack direction="row" justifyContent="flex-end" gap={1}>
<Button
id="stockIn"
type="button"
variant="contained"
color="primary"
>
{t("submitStockIn")}
</Button>
</Stack>
<Grid container justifyContent="flex-start" alignItems="flex-start">
<QcFormVer2
qc={qc!}
itemDetail={itemDetail}
disabled={false}
/>
</Grid>
<Stack direction="row" justifyContent="flex-end" gap={1}>
<Button
id="qc"
type="button"
variant="contained"
color="secondary"
sx={{ mt: 1 }}
>
{t("email supplier")}
</Button>
<Button
id="qc"
type="submit"
variant="contained"
color="secondary"
sx={{ mt: 1 }}
>
{t("confirm putaway")}
</Button>
</Stack>
</>
)

}
</Box>
</Modal> </Modal>
</FormProvider> </FormProvider>
</> </>


+ 40
- 1
src/components/PoDetail/dummyQcTemplate.tsx View File

@@ -1,8 +1,20 @@
const dummyQCData = [
import { PutawayLine } from "@/app/api/po/actions"

export interface QcData {
id: number,
qcItem: string,
isPassed: boolean | undefined
isFailed: boolean | undefined
failedQty: number | undefined
remarks: string | undefined
}

export const dummyQCData: QcData[] = [
{ {
id: 1, id: 1,
qcItem: "目測", qcItem: "目測",
isPassed: undefined, isPassed: undefined,
isFailed: undefined,
failedQty: undefined, failedQty: undefined,
remarks: undefined, remarks: undefined,
}, },
@@ -10,6 +22,7 @@ const dummyQCData = [
id: 2, id: 2,
qcItem: "目測2", qcItem: "目測2",
isPassed: undefined, isPassed: undefined,
isFailed: undefined,
failedQty: undefined, failedQty: undefined,
remarks: undefined, remarks: undefined,
}, },
@@ -17,7 +30,33 @@ const dummyQCData = [
id: 3, id: 3,
qcItem: "目測3", qcItem: "目測3",
isPassed: undefined, isPassed: undefined,
isFailed: undefined,
failedQty: undefined, failedQty: undefined,
remarks: undefined, remarks: undefined,
}, },
]

export interface EscalationData {
id: number,
escalation: string,
supervisor: string,
}


export const dummyEscalationHistory: EscalationData[] = [
{
id: 1,
escalation: "上報1",
supervisor: "陳大文"
},
]

export const dummyPutawayLine: PutawayLine[] = [
{
id: 1,
qty: 100,
warehouseId: 1,
warehouse: "W001 - 憶兆 3樓A倉",
printQty: 100
}
] ]

+ 8
- 4
src/components/PoSearch/PoSearch.tsx View File

@@ -65,11 +65,11 @@ const PoSearch: React.FC<Props> = ({
}, },
]; ];
return searchCriteria; return searchCriteria;
}, [t, po]);
}, [t]);


const onDetailClick = useCallback( const onDetailClick = useCallback(
(po: PoResult) => { (po: PoResult) => {
router.push(`/po/edit?id=${po.id}`);
router.push(`/po/edit?id=${po.id}&start=true`);
}, },
[router], [router],
); );
@@ -111,7 +111,7 @@ const PoSearch: React.FC<Props> = ({
return "N/A" return "N/A"
} }
const items = params.itemDetail.split(",") const items = params.itemDetail.split(",")
return items.map((item) => <p>{item}</p>)
return items.map((item) => <Grid key={item}>{item}</Grid>)
}, },
}, },
{ {
@@ -167,9 +167,13 @@ const PoSearch: React.FC<Props> = ({
setTotalCount(res.total); setTotalCount(res.total);
} }
}, },
[fetchPoListClient],
[],
); );


useEffect(() => {
console.log(filteredPo)
}, [filteredPo])

useEffect(() => { useEffect(() => {
newPageFetch(pagingController, filterArgs); newPageFetch(pagingController, filterArgs);
}, [newPageFetch, pagingController, filterArgs]); }, [newPageFetch, pagingController, filterArgs]);


+ 14
- 1
src/i18n/zh/purchaseOrder.json View File

@@ -102,7 +102,20 @@


"submitStockIn": "更新來貨資料", "submitStockIn": "更新來貨資料",
"QC Info": "品檢資料", "QC Info": "品檢資料",
"Escalation History": "品檢資料",
"Escalation History": "上報記錄",
"Escalation Info": "上報資料",
"Escalation Result": "上報結果",
"update qc info": "更新品檢資料",
"email supplier": "電郵供應商",
"confirm putaway": "確定及上架",
"warehouse": "倉庫",
"qcItem": "檢查項目",
"passed": "合格",
"failed": "不合格",
"failedQty": "不合格數",
"remarks": "備註",


"Reject": "拒絕", "Reject": "拒絕",
"submit": "提交", "submit": "提交",


Loading…
Cancel
Save