Ver código fonte

update

master
MSI\derek 2 semanas atrás
pai
commit
fa93870c23
12 arquivos alterados com 539 adições e 579 exclusões
  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 Ver arquivo

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

export type ModalFormInput = Partial<


+ 1
- 1
src/app/api/po/index.ts Ver arquivo

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


+ 7
- 3
src/components/InputDataGrid/InputDataGrid.tsx Ver arquivo

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


+ 47
- 35
src/components/PoDetail/EscalationComponent.tsx Ver arquivo

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

interface NameOption {
value: string;
@@ -30,7 +33,15 @@ interface FormData {
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 [formData, setFormData] = useState<FormData>({
name: '',
@@ -48,9 +59,9 @@ function EscalationComponent(): JSX.Element {
];

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

return (
// <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
control={
<Checkbox
@@ -91,38 +104,36 @@ function EscalationComponent(): JSX.Element {
}
/>
</Box>

<Collapse in={!isCollapsed}>
<Collapse in={isCollapsed}>
<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>
<InputLabel id="name-label">姓名</InputLabel>
<Select
labelId="name-label"
<select
id="name"
name="name"
value={formData.name}
label="姓名"
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) => (
<MenuItem key={option.value} value={option.value}>
<option key={option.value} value={option.value}>
{option.label}
</MenuItem>
</option>
))}
</Select>
</select>
</FormControl>

<TextField
fullWidth
id="quantity"
@@ -147,18 +158,19 @@ function EscalationComponent(): JSX.Element {
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>
</Collapse>
</Paper>
</>
);
}


+ 1
- 0
src/components/PoDetail/PoInputGrid.tsx Ver arquivo

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


+ 67
- 15
src/components/PoDetail/PutawayForm.tsx Ver arquivo

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

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

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

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

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

const defaultOption = {
value: 0, // think think sin
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 validateForm = useCallback(() => {
// console.log(accQty);
@@ -265,6 +267,44 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled }) => {
return undefined;
}, [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 (
<Grid container justifyContent="flex-start" alignItems="flex-start">
<Grid item xs={12}>
@@ -331,8 +371,10 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled }) => {
<TextField
label={t("productionDate")}
fullWidth
value={dayjs(itemDetail.productionDate)
.add(-1, "month")
value={
// dayjs(itemDetail.productionDate)
dayjs()
// .add(-1, "month")
.format(OUTPUT_DATE_FORMAT)}
disabled
/>
@@ -341,8 +383,10 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled }) => {
<TextField
label={t("expiryDate")}
fullWidth
value={dayjs(itemDetail.expiryDate)
.add(-1, "month")
value={
// dayjs(itemDetail.expiryDate)
dayjs()
.add(20, "day")
.format(OUTPUT_DATE_FORMAT)}
disabled
/>
@@ -364,7 +408,7 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled }) => {
/>
</FormControl>
</Grid>
<Grid item xs={5.5}>
{/* <Grid item xs={5.5}>
<TextField
label={t("acceptedQty")}
fullWidth
@@ -384,9 +428,9 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled }) => {
<Button disabled={disabled} onClick={onOpenScanner}>
{t("bind")}
</Button>
</Grid>
<Grid item xs={5.5}>
{/* <Controller
</Grid> */}
{/* <Grid item xs={5.5}>
<Controller
control={control}
name="warehouseId"
render={({ field }) => {
@@ -412,7 +456,7 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled }) => {
/>
);
}}
/> */}
/>
<FormControl fullWidth>
<Autocomplete
noOptionsText={t("No Warehouse")}
@@ -441,13 +485,21 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled }) => {
)}
/>
</FormControl>
</Grid>
</Grid> */}
<Grid
item
xs={12}
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


+ 66
- 395
src/components/PoDetail/QCDatagrid.tsx Ver arquivo

@@ -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 Ver arquivo

@@ -26,6 +26,7 @@ import {
GridRenderCellParams,
GridRenderEditCellParams,
useGridApiRef,
GridRowSelectionModel,
} from "@mui/x-data-grid";
import InputDataGrid from "../InputDataGrid";
import { TableRow } from "../InputDataGrid/InputDataGrid";
@@ -39,6 +40,10 @@ 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";
import EscalationComponent from "./EscalationComponent";
import QcDataGrid from "./QCDatagrid";
import StockInFormVer2 from "./StockInFormVer2";
import { dummyEscalationHistory } from "./dummyQcTemplate";

interface Props {
itemDetail: StockInLine;
@@ -68,10 +73,24 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled }) => {
setError,
clearErrors,
} = useFormContext<PurchaseQCInput>();
console.log(itemDetail);
console.log(defaultValues);
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"]>>(
(_e, newValue) => {
setTabIndex(newValue);
@@ -112,79 +131,89 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled }) => {

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,
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,
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
const validation = useCallback(
@@ -226,14 +255,66 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled }) => {
spacing={2}
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>
</>


+ 123
- 46
src/components/PoDetail/QcStockInModalVer2.tsx Ver arquivo

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

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%" },
// height: { xs: "60%", sm: "60%", md: "60%" },
};

interface CommonProps extends Omit<ModalProps, "children"> {
@@ -60,6 +62,7 @@ const PoQcStockInModalVer2: React.FC<Props> = ({
qc,
warehouse,
}) => {
console.log(warehouse)
const {
t,
i18n: { language },
@@ -67,6 +70,7 @@ const PoQcStockInModalVer2: React.FC<Props> = ({
const formProps = useForm<ModalFormInput>({
defaultValues: {
...itemDetail,
putawayLine: dummyPutawayLine
// receiptDate: itemDetail.receiptDate || dayjs().add(-1, "month").format(INPUT_DATE_FORMAT),
// warehouseId: itemDetail.defaultWarehouseId || 0
},
@@ -79,11 +83,23 @@ const PoQcStockInModalVer2: React.FC<Props> = ({
},
[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 onSubmit = useCallback<SubmitHandler<ModalFormInput>>(
async (data, event) => {
console.log(event!.nativeEvent)
// closeHandler({}, "backdropClick");
// for now go to putaway form
onOpenPutaway()

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

return (
<>
{/* {itemDetail !== undefined && (
<PutawayForm
itemDetail={itemDetail}
warehouse={warehouse!}
disabled={false}
/>
)} */}
<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>
</FormProvider>
</>


+ 40
- 1
src/components/PoDetail/dummyQcTemplate.tsx Ver arquivo

@@ -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,
qcItem: "目測",
isPassed: undefined,
isFailed: undefined,
failedQty: undefined,
remarks: undefined,
},
@@ -10,6 +22,7 @@ const dummyQCData = [
id: 2,
qcItem: "目測2",
isPassed: undefined,
isFailed: undefined,
failedQty: undefined,
remarks: undefined,
},
@@ -17,7 +30,33 @@ const dummyQCData = [
id: 3,
qcItem: "目測3",
isPassed: undefined,
isFailed: undefined,
failedQty: 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 Ver arquivo

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

const onDetailClick = useCallback(
(po: PoResult) => {
router.push(`/po/edit?id=${po.id}`);
router.push(`/po/edit?id=${po.id}&start=true`);
},
[router],
);
@@ -111,7 +111,7 @@ const PoSearch: React.FC<Props> = ({
return "N/A"
}
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);
}
},
[fetchPoListClient],
[],
);

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

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


+ 14
- 1
src/i18n/zh/purchaseOrder.json Ver arquivo

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

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

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


Carregando…
Cancelar
Salvar