@@ -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< | |||
@@ -14,7 +14,7 @@ export interface PoResult { | |||
supplier: string; | |||
estimatedArrivalDate: string; | |||
completedDate: string; | |||
itemDetail?: String; | |||
itemDetail?: string; | |||
escalated: boolean; | |||
status: string; | |||
pol?: PurchaseOrderLine[]; | |||
@@ -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> | |||
); | |||
@@ -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> | |||
</> | |||
); | |||
} | |||
@@ -812,6 +812,7 @@ function PoInputGrid({ | |||
setStockInLine={setStockInLine} | |||
setItemDetail={setModalInfo} | |||
qc={qc} | |||
warehouse={warehouse} | |||
open={newOpen} | |||
onClose={closeNewModal} | |||
itemDetail={modalInfo} | |||
@@ -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 | |||
@@ -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 |
@@ -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> | |||
</> | |||
@@ -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> | |||
</> | |||
@@ -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 | |||
} | |||
] |
@@ -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]); | |||
@@ -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": "提交", | |||