| @@ -33,11 +33,12 @@ export interface StockInLineEntry { | |||
| expiryDate?: string; | |||
| } | |||
| export interface PurchaseQcResult { | |||
| export interface PurchaseQcResult{ | |||
| qcItemId: number; | |||
| isPassed: boolean, | |||
| isPassed: boolean; | |||
| failQty: number; | |||
| remarks?: string | |||
| remarks?: string; | |||
| } | |||
| export interface StockInInput { | |||
| status: string; | |||
| @@ -15,6 +15,16 @@ export interface QcItemWithChecks { | |||
| description: string | undefined; | |||
| } | |||
| export interface QcData { | |||
| id: number, | |||
| code: string, | |||
| name: string, | |||
| qcDescription: string, | |||
| isPassed: boolean | undefined | |||
| failQty: number | undefined | |||
| remarks: string | undefined | |||
| } | |||
| export const fetchQcItemCheckList = cache(async () => { | |||
| return serverFetchJson<QcItemWithChecks[]>(`${BASE_API_URL}/qc/list`, { | |||
| next: { tags: ["qc"] }, | |||
| @@ -0,0 +1,19 @@ | |||
| import {Box, CircularProgress, Grid} from "@mui/material"; | |||
| export const LoadingComponent: React.FC = () => { | |||
| return ( | |||
| <> | |||
| <Grid item xs={12} md={12} lg={12} justifyContent="space-between" alignItems="center" marginTop={10}> | |||
| <Box | |||
| display="flex" | |||
| justifyContent="center" | |||
| alignItems="center" | |||
| // autoheight="true" | |||
| > | |||
| <CircularProgress /> | |||
| </Box> | |||
| </Grid> | |||
| </> | |||
| ) | |||
| } | |||
| export default LoadingComponent; | |||
| @@ -84,6 +84,7 @@ export interface SelectionInputDataGridProps<T, V, E> { | |||
| columns: GridColDef[]; | |||
| validateRow: (newRow: GridRowModel<TableRow<V, E>>) => E; | |||
| needAdd?: boolean; | |||
| showRemoveBtn?: boolean; | |||
| } | |||
| export type Props<T, V, E> = | |||
| @@ -83,10 +83,18 @@ const EscalationComponent: React.FC<Props> = ({ | |||
| return ( | |||
| // <Paper elevation={3} sx={{ maxWidth: 400, mx: 'auto', p: 3 }}> | |||
| <> | |||
| <Paper> | |||
| <Paper sx={{padding: 2}}> | |||
| {/* <Paper elevation={3} sx={{ mx: 'auto', p: 3 }}> */} | |||
| <Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}> | |||
| <FormControlLabel | |||
| <Box sx={{ display: 'flex', alignItems: 'center' }}> | |||
| <Typography variant="body1">上報結果</Typography> | |||
| {/* {isCollapsed ? ( | |||
| <ExpandLessIcon sx={{ ml: 1 }} /> | |||
| ) : ( | |||
| <ExpandMoreIcon sx={{ ml: 1 }} /> | |||
| )} */} | |||
| </Box> | |||
| {/* <FormControlLabel | |||
| control={ | |||
| <Checkbox | |||
| checked={isCollapsed} | |||
| @@ -104,23 +112,10 @@ const EscalationComponent: React.FC<Props> = ({ | |||
| )} | |||
| </Box> | |||
| } | |||
| /> | |||
| /> */} | |||
| </Box> | |||
| <Collapse in={isCollapsed}> | |||
| <Box component="form" onSubmit={handleSubmit} sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}> | |||
| {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> | |||
| <select | |||
| id="name" | |||
| @@ -136,7 +131,31 @@ const EscalationComponent: React.FC<Props> = ({ | |||
| ))} | |||
| </select> | |||
| </FormControl> | |||
| <TextField | |||
| {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} | |||
| {forSupervisor && (<TextField | |||
| fullWidth | |||
| id="decision" | |||
| name="decision" | |||
| label="上報結果" | |||
| type="radio" | |||
| // value={formData.decision} | |||
| onChange={handleInputChange} | |||
| InputProps={{ inputProps: { min: 1 } }} | |||
| placeholder="請決定上報結果" | |||
| />)} | |||
| {/* <TextField | |||
| fullWidth | |||
| id="quantity" | |||
| name="quantity" | |||
| @@ -146,21 +165,21 @@ const EscalationComponent: React.FC<Props> = ({ | |||
| onChange={handleInputChange} | |||
| InputProps={{ inputProps: { min: 1 } }} | |||
| placeholder="請輸入數量" | |||
| /> | |||
| /> */} | |||
| <TextField | |||
| fullWidth | |||
| id="message" | |||
| name="message" | |||
| label="備註" | |||
| label="上報原因" | |||
| multiline | |||
| rows={4} | |||
| value={formData.message} | |||
| onChange={handleInputChange} | |||
| placeholder="請輸入您的備註" | |||
| placeholder="請輸入上報原因" | |||
| /> | |||
| <Stack direction="row" justifyContent="flex-end" gap={1}> | |||
| {/* <Stack direction="row" justifyContent="flex-end" gap={1}> | |||
| <Button | |||
| type="submit" | |||
| variant="contained" | |||
| @@ -168,7 +187,7 @@ const EscalationComponent: React.FC<Props> = ({ | |||
| > | |||
| {t("update qc info")} | |||
| </Button> | |||
| </Stack> | |||
| </Stack> */} | |||
| </Box> | |||
| </Collapse> | |||
| </Paper> | |||
| @@ -77,6 +77,7 @@ import dayjs, { Dayjs } from "dayjs"; | |||
| import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; | |||
| import { DatePicker, LocalizationProvider, zhHK } from "@mui/x-date-pickers"; | |||
| import { debounce } from "lodash"; | |||
| import LoadingComponent from "../General/LoadingComponent"; | |||
| //import { useRouter } from "next/navigation"; | |||
| @@ -115,7 +116,7 @@ const PoSearchList: React.FC<{ | |||
| return ( | |||
| <Paper sx={{ p: 2, maxHeight: "480px", overflow: "auto", minWidth: "300px", height: "480px" }}> | |||
| <Typography variant="h6" gutterBottom> | |||
| {t("Purchase Orders")} | |||
| {t("Purchase Order")} | |||
| </Typography> | |||
| <TextField | |||
| label={t("Search")} | |||
| @@ -133,44 +134,49 @@ const PoSearchList: React.FC<{ | |||
| ), | |||
| }} | |||
| /> | |||
| <List dense sx={{ width: '100%' }}> | |||
| {filteredPoList.map((poItem, index) => ( | |||
| <div key={poItem.id}> | |||
| <ListItem disablePadding sx={{ width: '100%' }}> | |||
| <ListItemButton | |||
| selected={selectedPoId === poItem.id} | |||
| onClick={() => onSelect(poItem)} | |||
| sx={{ | |||
| width: '100%', | |||
| "&.Mui-selected": { | |||
| backgroundColor: "primary.light", | |||
| "&:hover": { | |||
| {(filteredPoList.length > 0)? ( | |||
| <List dense sx={{ width: '100%' }}> | |||
| {filteredPoList.map((poItem, index) => ( | |||
| <div key={poItem.id}> | |||
| <ListItem disablePadding sx={{ width: '100%' }}> | |||
| <ListItemButton | |||
| selected={selectedPoId === poItem.id} | |||
| onClick={() => onSelect(poItem)} | |||
| sx={{ | |||
| width: '100%', | |||
| "&.Mui-selected": { | |||
| backgroundColor: "primary.light", | |||
| "&:hover": { | |||
| backgroundColor: "primary.light", | |||
| }, | |||
| }, | |||
| }, | |||
| }} | |||
| > | |||
| <ListItemText | |||
| primary={ | |||
| <Typography variant="body2" sx={{ wordBreak: 'break-all' }}> | |||
| {poItem.code} | |||
| </Typography> | |||
| } | |||
| secondary={ | |||
| <Typography variant="caption" color="text.secondary"> | |||
| {t(`${poItem.status.toLowerCase()}`)} | |||
| </Typography> | |||
| } | |||
| /> | |||
| </ListItemButton> | |||
| </ListItem> | |||
| {index < filteredPoList.length - 1 && <Divider />} | |||
| </div> | |||
| ))} | |||
| </List> | |||
| }} | |||
| > | |||
| <ListItemText | |||
| primary={ | |||
| <Typography variant="body2" sx={{ wordBreak: 'break-all' }}> | |||
| {poItem.code} | |||
| </Typography> | |||
| } | |||
| secondary={ | |||
| <Typography variant="caption" color="text.secondary"> | |||
| {t(`${poItem.status.toLowerCase()}`)} | |||
| </Typography> | |||
| } | |||
| /> | |||
| </ListItemButton> | |||
| </ListItem> | |||
| {index < filteredPoList.length - 1 && <Divider />} | |||
| </div> | |||
| ))} | |||
| </List>) : ( | |||
| <LoadingComponent/> | |||
| ) | |||
| } | |||
| {searchTerm && ( | |||
| <Typography variant="caption" color="text.secondary" sx={{ mt: 1, display: "block" }}> | |||
| {t("Found")} {filteredPoList.length} {t("of")} {poList.length} {t("items")} | |||
| {`${t("Found")} ${filteredPoList.length} ${t("Purchase Order")}`} | |||
| {/* {`${t("Found")} ${filteredPoList.length} of ${poList.length} ${t("Item")}`} */} | |||
| </Typography> | |||
| )} | |||
| </Paper> | |||
| @@ -184,7 +190,7 @@ interface PolInputResult { | |||
| const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||
| const cameras = useContext(CameraContext); | |||
| console.log(cameras); | |||
| // console.log(cameras); | |||
| const { t } = useTranslation("purchaseOrder"); | |||
| const apiRef = useGridApiRef(); | |||
| const [purchaseOrder, setPurchaseOrder] = useState({ ...po }); | |||
| @@ -212,6 +218,8 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||
| const router = useRouter(); | |||
| const [poList, setPoList] = useState<PoResult[]>([]); | |||
| const [selectedPoId, setSelectedPoId] = useState(po.id); | |||
| const [focusField, setFocusField] = useState<HTMLInputElement>(); | |||
| const currentPoId = searchParams.get('id'); | |||
| const selectedIdsParam = searchParams.get('selectedIds'); | |||
| // const [selectedRowId, setSelectedRowId] = useState<number | null>(null); | |||
| @@ -261,6 +269,7 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||
| setProcessedQty(result.pol[0].processed); | |||
| } | |||
| } | |||
| // if (focusField) {console.log(focusField);focusField.focus();} | |||
| } | |||
| } catch (error) { | |||
| console.error("Failed to fetch PO detail:", error); | |||
| @@ -448,6 +457,8 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||
| // setPolInputList(() => temp) | |||
| }, 300), [rowIndex]); | |||
| // const [focusField, setFocusField] = useState<HTMLInputElement>(); | |||
| const purchaseToStockRatio = (row.stockUom.purchaseRatioN ?? 1) / (row.stockUom.purchaseRatioD ?? 1) * (row.stockUom.stockRatioD ?? 1) / (row.stockUom.stockRatioN ?? 1) | |||
| return ( | |||
| <> | |||
| @@ -498,6 +509,7 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||
| variant="outlined" | |||
| defaultValue={polInputList[rowIndex]?.lotNo ?? ''} | |||
| onChange={handleChange} | |||
| // onFocus={(e) => {setFocusField(e.target as HTMLInputElement);}} | |||
| /> | |||
| </TableCell> | |||
| <TableCell align="center"> | |||
| @@ -557,6 +569,8 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||
| </> | |||
| ); | |||
| } | |||
| // ROW END | |||
| const [tabIndex, setTabIndex] = useState(0); | |||
| const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>( | |||
| (_e, newValue) => { | |||
| @@ -19,7 +19,7 @@ type Props = { | |||
| po: PoResult; | |||
| }; | |||
| const PoInfoCard: React.FC<Props> = async ( | |||
| const PoInfoCard: React.FC<Props> = ( | |||
| { | |||
| // id | |||
| po | |||
| @@ -41,20 +41,20 @@ import { GridEditInputCell } from "@mui/x-data-grid"; | |||
| import { StockInLine } from "@/app/api/po"; | |||
| import { stockInLineStatusMap } from "@/app/utils/formatUtil"; | |||
| import { fetchQcItemCheck, fetchQcResult } from "@/app/api/qc/actions"; | |||
| import { QcItemWithChecks } from "@/app/api/qc"; | |||
| import { QcItemWithChecks, QcData } 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, dummyQCData, QcData } from "./dummyQcTemplate"; | |||
| import { dummyEscalationHistory, dummyQCData } from "./dummyQcTemplate"; | |||
| import { ModalFormInput } from "@/app/api/dashboard/actions"; | |||
| import { escape } from "lodash"; | |||
| import { PanoramaSharp } from "@mui/icons-material"; | |||
| interface Props { | |||
| itemDetail: StockInLine; | |||
| itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] }; | |||
| qc: QcItemWithChecks[]; | |||
| disabled: boolean; | |||
| qcItems: QcData[] | |||
| @@ -88,8 +88,10 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||
| const [tabIndex, setTabIndex] = useState(0); | |||
| const [rowSelectionModel, setRowSelectionModel] = useState<GridRowSelectionModel>(); | |||
| const [escalationHistory, setEscalationHistory] = useState(dummyEscalationHistory); | |||
| const [qcResult, setQcResult] = useState(); | |||
| // const [qcResult, setQcResult] = useState(); | |||
| const qcAccept = watch("qcAccept"); | |||
| const qcResult = watch("qcResult"); | |||
| console.log(qcResult); | |||
| // const [qcAccept, setQcAccept] = useState(true); | |||
| // const [qcItems, setQcItems] = useState(dummyQCData) | |||
| @@ -184,28 +186,29 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||
| const qcColumns: GridColDef[] = [ | |||
| { | |||
| field: "qcItem", | |||
| field: "code", | |||
| headerName: t("qcItem"), | |||
| flex: 2, | |||
| renderCell: (params) => ( | |||
| <Box> | |||
| <b>{params.value}</b><br/> | |||
| {params.row.qcDescription}<br/> | |||
| {params.row.name}<br/> | |||
| </Box> | |||
| ), | |||
| }, | |||
| { | |||
| field: 'isPassed', | |||
| field: 'qcResult', | |||
| headerName: t("qcResult"), | |||
| flex: 1.5, | |||
| renderCell: (params) => { | |||
| const currentValue = params.value; | |||
| const currentValue = params.row; | |||
| console.log(currentValue.row); | |||
| return ( | |||
| <FormControl> | |||
| <RadioGroup | |||
| row | |||
| aria-labelledby="demo-radio-buttons-group-label" | |||
| value={currentValue === undefined ? "" : (currentValue ? "true" : "false")} | |||
| value={currentValue.isPassed === undefined ? (currentValue.failQty!==undefined?(currentValue.failQty==0?"true":"false"):"") : (currentValue.isPassed ? "true" : "false")} | |||
| onChange={(e) => { | |||
| const value = e.target.value; | |||
| setQcItems((prev) => | |||
| @@ -218,9 +221,9 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||
| value="true" | |||
| control={<Radio />} | |||
| label="合格" | |||
| disabled={itemDetail.status.toLowerCase() == "completed"} | |||
| disabled={disabled} | |||
| sx={{ | |||
| color: currentValue === true ? "green" : "inherit", | |||
| color: currentValue.isPassed === true ? "green" : "inherit", | |||
| "& .Mui-checked": {color: "green"} | |||
| }} | |||
| /> | |||
| @@ -228,9 +231,9 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||
| value="false" | |||
| control={<Radio />} | |||
| label="不合格" | |||
| disabled={itemDetail.status.toLowerCase() == "completed"} | |||
| disabled={disabled} | |||
| sx={{ | |||
| color: currentValue === false ? "red" : "inherit", | |||
| color: currentValue.isPassed === false ? "red" : "inherit", | |||
| "& .Mui-checked": {color: "red"} | |||
| }} | |||
| /> | |||
| @@ -249,7 +252,7 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||
| type="number" | |||
| size="small" | |||
| value={!params.row.isPassed? (params.value ?? '') : '0'} | |||
| disabled={params.row.isPassed || itemDetail.status.toLowerCase() == "completed"} | |||
| disabled={params.row.isPassed || disabled} | |||
| onChange={(e) => { | |||
| const v = e.target.value; | |||
| const next = v === '' ? undefined : Number(v); | |||
| @@ -257,6 +260,7 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||
| setQcItems((prev) => | |||
| prev.map((r) => (r.id === params.id ? { ...r, failQty: next } : r)) | |||
| ); | |||
| // setValue(`failQty`,failQty); | |||
| }} | |||
| onClick={(e) => e.stopPropagation()} | |||
| onMouseDown={(e) => e.stopPropagation()} | |||
| @@ -274,7 +278,7 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||
| <TextField | |||
| size="small" | |||
| value={params.value ?? ''} | |||
| disabled={itemDetail.status.toLowerCase() == "completed"} | |||
| disabled={disabled} | |||
| onChange={(e) => { | |||
| const remarks = e.target.value; | |||
| // const next = v === '' ? undefined : Number(v); | |||
| @@ -283,6 +287,9 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||
| prev.map((r) => (r.id === params.id ? { ...r, remarks: remarks } : r)) | |||
| ); | |||
| }} | |||
| // {...register(`qcResult.${params.row.rowIndex}.remarks`, { | |||
| // required: "remarks required!", | |||
| // })} | |||
| onClick={(e) => e.stopPropagation()} | |||
| onMouseDown={(e) => e.stopPropagation()} | |||
| onKeyDown={(e) => e.stopPropagation()} | |||
| @@ -293,11 +300,6 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||
| }, | |||
| ] | |||
| useEffect(() => { | |||
| console.log(itemDetail); | |||
| }, [itemDetail]); | |||
| // Set initial value for acceptQty | |||
| useEffect(() => { | |||
| if (itemDetail?.demandQty > 0) { //!== undefined) { | |||
| @@ -308,9 +310,9 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||
| }, [itemDetail?.demandQty, itemDetail?.acceptedQty, setValue]); | |||
| // const [openCollapse, setOpenCollapse] = useState(false) | |||
| const [isCollapsed, setIsCollapsed] = useState<boolean>(false); | |||
| const [isCollapsed, setIsCollapsed] = useState<boolean>(true); | |||
| const onFailedOpenCollapse = useCallback((qcItems: QcData[]) => { | |||
| const onFailedOpenCollapse = useCallback((qcItems: PurchaseQcResult[]) => { | |||
| const isFailed = qcItems.some((qc) => !qc.isPassed) | |||
| console.log(isFailed) | |||
| if (isFailed) { | |||
| @@ -327,10 +329,11 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||
| useEffect(() => { | |||
| console.log(itemDetail); | |||
| console.log("ItemDetail in QC:", itemDetail); | |||
| }, [itemDetail]); | |||
| useEffect(() => { | |||
| // onFailedOpenCollapse(qcItems) // This function is no longer needed | |||
| }, [qcItems]); // Removed onFailedOpenCollapse from dependency array | |||
| @@ -366,19 +369,10 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||
| /> */} | |||
| <StyledDataGrid | |||
| columns={qcColumns} | |||
| rows={qcItems} | |||
| rows={disabled? qcResult:qcItems} | |||
| autoHeight | |||
| /> | |||
| </Grid> | |||
| {!qcAccept && ( | |||
| <Grid item xs={12}> | |||
| <EscalationComponent | |||
| forSupervisor={false} | |||
| isCollapsed={isCollapsed} | |||
| setIsCollapsed={setIsCollapsed} | |||
| /> | |||
| </Grid>)} | |||
| </> | |||
| )} | |||
| {tabIndex == 1 && ( | |||
| @@ -425,7 +419,7 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||
| field.onChange(value); | |||
| }} | |||
| > | |||
| <FormControlLabel disabled={itemDetail.status.toLowerCase() == "completed"} | |||
| <FormControlLabel disabled={disabled} | |||
| value="true" control={<Radio />} label="接受" /> | |||
| <Box sx={{mr:2}}> | |||
| <TextField | |||
| @@ -434,7 +428,7 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||
| sx={{ width: '150px' }} | |||
| value={qcAccept? accQty : 0 } | |||
| defaultValue={accQty} | |||
| disabled={!qcAccept || itemDetail.status.toLowerCase() == "completed"} | |||
| disabled={!qcAccept || disabled} | |||
| {...register("acceptQty", { | |||
| required: "acceptQty required!", | |||
| })} | |||
| @@ -442,7 +436,7 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||
| helperText={errors.acceptQty?.message} | |||
| /> | |||
| </Box> | |||
| <FormControlLabel disabled={itemDetail.status.toLowerCase() == "completed"} | |||
| <FormControlLabel disabled={disabled} | |||
| value="false" control={<Radio />} | |||
| sx={{"& .Mui-checked": {color: "red"}}} | |||
| label="不接受及上報" /> | |||
| @@ -451,6 +445,14 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||
| /> | |||
| </FormControl> | |||
| </Grid> | |||
| {!qcAccept && ( | |||
| <Grid item xs={12}> | |||
| <EscalationComponent | |||
| forSupervisor={false} | |||
| isCollapsed={isCollapsed} | |||
| setIsCollapsed={setIsCollapsed} | |||
| /> | |||
| </Grid>)} | |||
| {/* {qcAccept && <Grid item xs={12}> | |||
| <Typography variant="h6" display="block" marginBlockEnd={1}> | |||
| {t("Escalation Result")} | |||
| @@ -1,7 +1,7 @@ | |||
| "use client"; | |||
| import { StockInLine } from "@/app/api/po"; | |||
| import { ModalFormInput, PurchaseQcResult, StockInLineEntry, updateStockInLine } from "@/app/api/po/actions"; | |||
| import { QcItemWithChecks } from "@/app/api/qc"; | |||
| import { QcItemWithChecks, QcData } from "@/app/api/qc"; | |||
| import { | |||
| Box, | |||
| Button, | |||
| @@ -19,7 +19,7 @@ import StockInForm from "./StockInForm"; | |||
| import StockInFormVer2 from "./StockInFormVer2"; | |||
| import QcFormVer2 from "./QcFormVer2"; | |||
| import PutawayForm from "./PutawayForm"; | |||
| import { dummyPutawayLine, dummyQCData, QcData } from "./dummyQcTemplate"; | |||
| import { dummyPutawayLine, dummyQCData } from "./dummyQcTemplate"; | |||
| import { useGridApiRef } from "@mui/x-data-grid"; | |||
| import {submitDialogWithWarning} from "../Swal/CustomAlerts"; | |||
| import { PurchaseQCInput, PutawayInput } from "@/app/api/dashboard/actions"; | |||
| @@ -36,7 +36,7 @@ const style = { | |||
| px: 5, | |||
| pb: 10, | |||
| display: "block", | |||
| width: { xs: "60%", sm: "60%", md: "60%" }, | |||
| width: { xs: "90%", sm: "90%", md: "90%" }, | |||
| // height: { xs: "60%", sm: "60%", md: "60%" }, | |||
| }; | |||
| interface CommonProps extends Omit<ModalProps, "children"> { | |||
| @@ -113,6 +113,12 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||
| setOpenPutaway(isPutaway); | |||
| }, [open]) | |||
| const [isCompleted, setIsCompleted] = useState(false); | |||
| useEffect(() => { | |||
| setIsCompleted(itemDetail.status.toLowerCase() == "completed") | |||
| }, [itemDetail]); | |||
| const [openPutaway, setOpenPutaway] = useState(false); | |||
| const onOpenPutaway = useCallback(() => { | |||
| @@ -155,21 +161,25 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||
| // Get QC data from the shared form context | |||
| const qcAccept = data.qcAccept; | |||
| const acceptQty = data.acceptQty as number; | |||
| const qcResults = qcItems; | |||
| // const qcResults = isCompleted? data.qcResult as PurchaseQcResult[] : qcItems; | |||
| // Validate QC data | |||
| const validationErrors : string[] = []; | |||
| // Check if all QC items have results | |||
| const itemsWithoutResult = qcItems.filter(item => item.isPassed === undefined); | |||
| const itemsWithoutResult = qcResults.filter(item => item.isPassed === undefined); | |||
| if (itemsWithoutResult.length > 0) { | |||
| validationErrors.push(`${t("QC items without result")}: ${itemsWithoutResult.map(item => item.qcItem).join(', ')}`); | |||
| validationErrors.push(`${t("QC items without result")}`); | |||
| // validationErrors.push(`${t("QC items without result")}: ${itemsWithoutResult.map(item => item.code).join(', ')}`); | |||
| } | |||
| // Check if failed items have failed quantity | |||
| const failedItemsWithoutQty = qcItems.filter(item => | |||
| const failedItemsWithoutQty = qcResults.filter(item => | |||
| item.isPassed === false && (!item.failQty || item.failQty <= 0) | |||
| ); | |||
| if (failedItemsWithoutQty.length > 0) { | |||
| validationErrors.push(`${t("Failed items must have failed quantity")}: ${failedItemsWithoutQty.map(item => item.qcItem).join(', ')}`); | |||
| validationErrors.push(`${t("Failed items must have failed quantity")}`); | |||
| // validationErrors.push(`${t("Failed items must have failed quantity")}: ${failedItemsWithoutQty.map(item => item.code).join(', ')}`); | |||
| } | |||
| // Check if QC accept decision is made | |||
| @@ -205,9 +215,9 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||
| qcAccept: qcAccept? qcAccept : false, | |||
| acceptQty: acceptQty? acceptQty : 0, | |||
| qcResult: qcItems.map(item => ({ | |||
| qcResult: qcResults.map(item => ({ | |||
| qcItemId: item.id, | |||
| // qcItem: item.qcItem, | |||
| // code: item.code, | |||
| // qcDescription: item.qcDescription, | |||
| isPassed: item.isPassed? item.isPassed : false, | |||
| failQty: (item.failQty && !item.isPassed) ? item.failQty : 0, | |||
| @@ -329,7 +339,7 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||
| const acceptQty = formProps.watch("acceptedQty") | |||
| const checkQcIsPassed = useCallback((qcItems: QcData[]) => { | |||
| const checkQcIsPassed = useCallback((qcItems: PurchaseQcResult[]) => { | |||
| const isPassed = qcItems.every((qc) => qc.isPassed); | |||
| console.log(isPassed) | |||
| if (isPassed) { | |||
| @@ -343,7 +353,7 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||
| useEffect(() => { | |||
| // maybe check if submitted before | |||
| console.log(qcItems) | |||
| checkQcIsPassed(qcItems) | |||
| // checkQcIsPassed(qcItems) | |||
| }, [qcItems, checkQcIsPassed]) | |||
| return ( | |||
| @@ -368,7 +378,7 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||
| <PutawayForm | |||
| itemDetail={itemDetail} | |||
| warehouse={warehouse!} | |||
| disabled={false} | |||
| disabled={isCompleted} | |||
| /> | |||
| <Stack direction="row" justifyContent="flex-end" gap={1}> | |||
| <Button | |||
| @@ -406,7 +416,7 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||
| </Typography> | |||
| </Grid> | |||
| <Grid item xs={12}> | |||
| <StockInFormVer2 itemDetail={itemDetail} disabled={false} /> | |||
| <StockInFormVer2 itemDetail={itemDetail} disabled={isCompleted} /> | |||
| </Grid> | |||
| </Grid> | |||
| {/* <Stack direction="row" justifyContent="flex-end" gap={1}> | |||
| @@ -428,13 +438,13 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||
| <QcFormVer2 | |||
| qc={qc!} | |||
| itemDetail={itemDetail} | |||
| disabled={false} | |||
| disabled={isCompleted} | |||
| qcItems={qcItems} | |||
| setQcItems={setQcItems} | |||
| /> | |||
| </Grid> | |||
| <Stack direction="row" justifyContent="flex-end" gap={1}> | |||
| {itemDetail.status.toLowerCase() != "completed" && (<Button | |||
| {!isCompleted && (<Button | |||
| id="qcSubmit" | |||
| type="button" | |||
| variant="contained" | |||
| @@ -123,7 +123,7 @@ const StockInFormVer2: React.FC<Props> = ({ | |||
| {...register("dnNo", { | |||
| // required: "productLotNo required!", | |||
| })} | |||
| disabled={itemDetail.status.toLowerCase() == "completed"} | |||
| disabled={disabled} | |||
| // error={Boolean(errors.productLotNo)} | |||
| // helperText={errors.productLotNo?.message} | |||
| /> | |||
| @@ -205,7 +205,7 @@ const StockInFormVer2: React.FC<Props> = ({ | |||
| {...register("productLotNo", { | |||
| // required: "productLotNo required!", | |||
| })} | |||
| disabled={itemDetail.status.toLowerCase() == "completed"} | |||
| disabled={disabled} | |||
| error={Boolean(errors.productLotNo)} | |||
| helperText={errors.productLotNo?.message} | |||
| /> | |||
| @@ -226,7 +226,7 @@ const StockInFormVer2: React.FC<Props> = ({ | |||
| sx={{ width: "100%" }} | |||
| label={t("productionDate")} | |||
| value={productionDate ? dayjs(productionDate) : undefined} | |||
| disabled={itemDetail.status.toLowerCase() == "completed"} | |||
| disabled={disabled} | |||
| onChange={(date) => { | |||
| if (!date) return; | |||
| setValue( | |||
| @@ -275,7 +275,7 @@ const StockInFormVer2: React.FC<Props> = ({ | |||
| sx={{ width: "100%" }} | |||
| label={t("expiryDate")} | |||
| value={expiryDate ? dayjs(expiryDate) : undefined} | |||
| disabled={itemDetail.status.toLowerCase() == "completed"} | |||
| disabled={disabled} | |||
| onChange={(date) => { | |||
| console.log(date); | |||
| if (!date) return; | |||
| @@ -322,7 +322,7 @@ const StockInFormVer2: React.FC<Props> = ({ | |||
| <TextField | |||
| label={t("acceptedQty")} | |||
| fullWidth | |||
| disabled={itemDetail.status.toLowerCase() == "completed"} | |||
| disabled={disabled} | |||
| {...register("acceptedQty", { | |||
| required: "acceptedQty required!", | |||
| })} | |||
| @@ -1,51 +1,57 @@ | |||
| import { PutawayLine } from "@/app/api/po/actions" | |||
| import { QcData } from "@/app/api/qc" | |||
| export interface QcData { | |||
| id: number, | |||
| qcItem: string, | |||
| qcDescription: string, | |||
| isPassed: boolean | undefined | |||
| failQty: number | undefined | |||
| remarks: string | undefined | |||
| } | |||
| // export interface QcData { | |||
| // qcItemId: number, | |||
| // qcItem: string, | |||
| // qcDescription: string, | |||
| // isPassed: boolean | undefined | |||
| // failQty: number | undefined | |||
| // remarks: string | undefined | |||
| // } | |||
| export const dummyQCData: QcData[] = [ | |||
| { | |||
| id: 1, | |||
| qcItem: "包裝", | |||
| id: 4, | |||
| code: "包裝", | |||
| qcDescription: "有破爛、污糟、脹袋、積水、與實物不符等任何一種情況,則不合格", | |||
| name: "有破爛、污糟、脹袋、積水、與實物不符等任何一種情況,則不合格", | |||
| isPassed: undefined, | |||
| failQty: undefined, | |||
| remarks: undefined, | |||
| }, | |||
| { | |||
| id: 2, | |||
| qcItem: "肉質", | |||
| id: 5, | |||
| code: "肉質", | |||
| qcDescription: "肉質鬆散,則不合格", | |||
| name: "肉質鬆散,則不合格", | |||
| isPassed: undefined, | |||
| failQty: undefined, | |||
| remarks: undefined, | |||
| }, | |||
| { | |||
| id: 3, | |||
| qcItem: "顔色", | |||
| qcDescription: "不是食材應有的顔色、顔色不均匀、出現其他顔色、腌料/醬顔色不均匀,油脂部分變綠色、黃色,", | |||
| id: 6, | |||
| code: "顔色", | |||
| qcDescription: "不是食材應有的顔色、顔色不均匀、出現其他顔色、腌料/醬顔色不均匀,油脂部分變綠色、黃色,則不合格", | |||
| name: "不是食材應有的顔色、顔色不均匀、出現其他顔色、腌料/醬顔色不均匀,油脂部分變綠色、黃色,則不合格", | |||
| isPassed: undefined, | |||
| failQty: undefined, | |||
| remarks: undefined, | |||
| }, | |||
| { | |||
| id: 4, | |||
| qcItem: "狀態", | |||
| id: 7, | |||
| code: "狀態", | |||
| qcDescription: "有結晶、結霜、解凍跡象、發霉、散發異味等任何一種情況,則不合格", | |||
| name: "有結晶、結霜、解凍跡象、發霉、散發異味等任何一種情況,則不合格", | |||
| isPassed: undefined, | |||
| failQty: undefined, | |||
| remarks: undefined, | |||
| }, | |||
| { | |||
| id: 5, | |||
| qcItem: "異物", | |||
| id: 8, | |||
| code: "異物", | |||
| qcDescription: "有不屬於本食材的雜質,則不合格", | |||
| name: "有不屬於本食材的雜質,則不合格", | |||
| isPassed: undefined, | |||
| failQty: undefined, | |||
| remarks: undefined, | |||
| @@ -1,147 +1,135 @@ | |||
| { | |||
| "Purchase Order": "採購訂單", | |||
| "Purchase Receipt": "處理採購來貨", | |||
| "Code": "編號", | |||
| "OrderDate": "下單日期", | |||
| "Order Date": "下單日期", | |||
| "Order Date To": "下單日期至", | |||
| "ETA": "預計到貨日期", | |||
| "ETA To": "預計到貨日期至", | |||
| "Details": "詳情", | |||
| "Supplier": "供應商", | |||
| "Status": "來貨狀態", | |||
| "Escalated": "上報狀態", | |||
| "NotEscalated": "無上報", | |||
| "Do you want to start?": "確定開始嗎?", | |||
| "Start": "開始", | |||
| "Start Success": "開始成功", | |||
| "Start Fail": "開始失敗", | |||
| "Start PO": "開始採購訂單", | |||
| "Do you want to complete?": "確定完成嗎?", | |||
| "Cancel": "取消", | |||
| "Complete": "完成", | |||
| "Complete Success": "完成成功", | |||
| "Complete Fail": "完成失敗", | |||
| "Complete PO": "完成採購訂單", | |||
| "General": "一般", | |||
| "Bind Storage": "綁定倉位", | |||
| "Po No.": "採購訂單編號", | |||
| "PO No.": "採購訂單編號", | |||
| "itemNo": "貨品編號", | |||
| "itemName": "貨品名稱", | |||
| "Item Detail": "貨品詳情", | |||
| "Item Code": "貨品編號", | |||
| "Item Name": "貨品名稱", | |||
| "Item Qty": "貨品數量", | |||
| "Item Accepted Qty": "貨品已收貨數量", | |||
| "Item Purchase UoM": "貨品計量單位", | |||
| "qty": "訂單數量", | |||
| "uom": "計量單位", | |||
| "Stock UoM": "庫存單位", | |||
| "Stock In Qty": "收貨數量", | |||
| "total weight": "總重量", | |||
| "weight unit": "重量單位", | |||
| "price": "訂單貨值", | |||
| "processed": "已上架數量", | |||
| "expiryDate": "到期日", | |||
| "acceptedQty": "是次來貨數量", | |||
| "putawayQty": "上架數量", | |||
| "acceptQty": "揀收數量", | |||
| "printQty": "列印數量", | |||
| "qcResult": "品檢結果", | |||
| "weight": "重量", | |||
| "start": "開始", | |||
| "qc": "質量控制", | |||
| "escalation": "上報", | |||
| "stock in": "入庫", | |||
| "putaway": "上架", | |||
| "delete": "刪除", | |||
| "Accept quantity must be greater than 0": "揀收數量不能少於1", | |||
| "QC items without result": "請完成品檢結果", | |||
| "Failed items must have failed quantity": "請輸入不合格數量", | |||
| "qty cannot be greater than remaining qty": "數量不能大於剩餘數量", | |||
| "acceptQty must not greater than": "揀收數量不能大於", | |||
| "Record pol": "記錄採購訂單", | |||
| "Add some entries!": "添加條目!", | |||
| "draft": "草稿", | |||
| "pending": "待處理", | |||
| "determine1": "上報1", | |||
| "determine2": "上報2", | |||
| "determine3": "上報3", | |||
| "receiving": "收貨中", | |||
| "received": "已檢收", | |||
| "completed": "已上架", | |||
| "rejected": "已拒絕及上報", | |||
| "status": "狀態", | |||
| "acceptedQty must not greater than": "接受數量不得大於", | |||
| "minimal value is 1": "最小值為1", | |||
| "value must be a number": "值必須是數字", | |||
| "qc Check": "質量控制檢查", | |||
| "Please select QC": "請選擇質量控制", | |||
| "failQty": "失敗數量", | |||
| "select qc": "選擇質量控制", | |||
| "enter a failQty": "請輸入失敗數量", | |||
| "qty too big": "數量過大", | |||
| "sampleRate": "抽樣率", | |||
| "sampleWeight": "樣本重量", | |||
| "totalWeight": "總重量", | |||
| "Escalation": "上報", | |||
| "to be processed": "待處理", | |||
| "supervisor": "管理層", | |||
| "Stock In Detail": "入庫詳情", | |||
| "productLotNo": "貨品批號", | |||
| "receiptDate": "收貨日期", | |||
| "acceptedWeight": "接受重量", | |||
| "productionDate": "生產日期", | |||
| "reportQty": "上報數量", | |||
| "Default Warehouse": "預設倉庫", | |||
| "Select warehouse": "選擇倉庫", | |||
| "Putaway Detail": "上架詳情", | |||
| "LotNo": "批號", | |||
| "Po Code": "採購訂單編號", | |||
| "No Warehouse": "沒有倉庫", | |||
| "Please scan warehouse qr code.": "請掃描倉庫 QR 碼。", | |||
| "receivedQty": "已來貨數量", | |||
| "dnQty": "送貨單數量", | |||
| "Accept submit": "接受來貨", | |||
| "qc processing": "處理來貨及品檢", | |||
| "putaway processing": "處理來貨及上架", | |||
| "view stockin": "查看收貨及品檢", | |||
| "putaway processing": "處理來貨及上架", | |||
| "putawayBtn": "上架", | |||
| "dnNo": "送貨單編號", | |||
| "dnDate": "送貨單日期", | |||
| "submitStockIn": "更新來貨資料", | |||
| "QC Info": "品檢資料", | |||
| "Escalation History": "上報記錄", | |||
| "Escalation Info": "上報資料", | |||
| "Escalation Result": "上報結果", | |||
| "update qc info": "更新品檢資料", | |||
| "email supplier": "電郵供應商", | |||
| "confirm putaway": "確定及上架", | |||
| "confirm qc result": "確定品檢結果", | |||
| "warehouse": "倉庫", | |||
| "qcItem": "品檢項目", | |||
| "passed": "接受", | |||
| "failed": "不接受", | |||
| "failedQty": "不合格數", | |||
| "remarks": "備註", | |||
| "Reject": "拒絕", | |||
| "submit": "提交", | |||
| "print": "列印", | |||
| "bind": "綁定" | |||
| } | |||
| "Purchase Order": "採購訂單", | |||
| "Purchase Receipt": "處理採購來貨", | |||
| "Code": "編號", | |||
| "OrderDate": "下單日期", | |||
| "Order Date": "下單日期", | |||
| "Order Date To": "下單日期至", | |||
| "ETA": "預計到貨日期", | |||
| "ETA To": "預計到貨日期至", | |||
| "Details": "詳情", | |||
| "Supplier": "供應商", | |||
| "Status": "來貨狀態", | |||
| "Escalated": "上報狀態", | |||
| "NotEscalated": "無上報", | |||
| "Do you want to start?": "確定開始嗎?", | |||
| "Start": "開始", | |||
| "Start Success": "開始成功", | |||
| "Start Fail": "開始失敗", | |||
| "Start PO": "開始採購訂單", | |||
| "Do you want to complete?": "確定完成嗎?", | |||
| "Cancel": "取消", | |||
| "Complete": "完成", | |||
| "Complete Success": "完成成功", | |||
| "Complete Fail": "完成失敗", | |||
| "Complete PO": "完成採購訂單", | |||
| "General": "一般", | |||
| "Bind Storage": "綁定倉位", | |||
| "Po No.": "採購訂單編號", | |||
| "PO No.": "採購訂單編號", | |||
| "itemNo": "貨品編號", | |||
| "itemName": "貨品名稱", | |||
| "Item Detail": "貨品詳情", | |||
| "Item Code": "貨品編號", | |||
| "Item Name": "貨品名稱", | |||
| "Item Qty": "貨品數量", | |||
| "Item": "貨品", | |||
| "Item Accepted Qty": "貨品已收貨數量", | |||
| "Item Purchase UoM": "貨品計量單位", | |||
| "qty": "訂單數量", | |||
| "uom": "計量單位", | |||
| "Stock UoM": "庫存單位", | |||
| "Stock In Qty": "收貨數量", | |||
| "total weight": "總重量", | |||
| "weight unit": "重量單位", | |||
| "price": "訂單貨值", | |||
| "processed": "已上架數量", | |||
| "expiryDate": "到期日", | |||
| "acceptedQty": "是次來貨數量", | |||
| "putawayQty": "上架數量", | |||
| "acceptQty": "揀收數量", | |||
| "printQty": "列印數量", | |||
| "qcResult": "品檢結果", | |||
| "weight": "重量", | |||
| "start": "開始", | |||
| "qc": "質量控制", | |||
| "escalation": "上報", | |||
| "stock in": "入庫", | |||
| "putaway": "上架", | |||
| "delete": "刪除", | |||
| "Accept quantity must be greater than 0": "揀收數量不能少於1", | |||
| "QC items without result": "請完成品檢結果", | |||
| "Failed items must have failed quantity": "請輸入不合格數量", | |||
| "qty cannot be greater than remaining qty": "數量不能大於剩餘數量", | |||
| "acceptQty must not greater than": "揀收數量不能大於", | |||
| "Record pol": "記錄採購訂單", | |||
| "Add some entries!": "添加條目!", | |||
| "draft": "草稿", | |||
| "pending": "待處理", | |||
| "determine1": "上報1", | |||
| "determine2": "上報2", | |||
| "determine3": "上報3", | |||
| "receiving": "收貨中", | |||
| "received": "已檢收", | |||
| "completed": "已上架", | |||
| "rejected": "已拒絕及上報", | |||
| "status": "狀態", | |||
| "acceptedQty must not greater than": "接受數量不得大於", | |||
| "minimal value is 1": "最小值為1", | |||
| "value must be a number": "值必須是數字", | |||
| "qc Check": "質量控制檢查", | |||
| "Please select QC": "請選擇質量控制", | |||
| "failQty": "失敗數量", | |||
| "select qc": "選擇質量控制", | |||
| "enter a failQty": "請輸入失敗數量", | |||
| "qty too big": "數量過大", | |||
| "sampleRate": "抽樣率", | |||
| "sampleWeight": "樣本重量", | |||
| "totalWeight": "總重量", | |||
| "Escalation": "上報", | |||
| "to be processed": "待處理", | |||
| "supervisor": "管理層", | |||
| "Stock In Detail": "入庫詳情", | |||
| "productLotNo": "貨品批號", | |||
| "receiptDate": "收貨日期", | |||
| "acceptedWeight": "接受重量", | |||
| "productionDate": "生產日期", | |||
| "reportQty": "上報數量", | |||
| "Default Warehouse": "預設倉庫", | |||
| "Select warehouse": "選擇倉庫", | |||
| "Putaway Detail": "上架詳情", | |||
| "LotNo": "批號", | |||
| "Po Code": "採購訂單編號", | |||
| "No Warehouse": "沒有倉庫", | |||
| "Please scan warehouse qr code.": "請掃描倉庫 QR 碼。", | |||
| "receivedQty": "已來貨數量", | |||
| "dnQty": "送貨單數量", | |||
| "Accept submit": "接受來貨", | |||
| "qc processing": "處理來貨及品檢", | |||
| "putaway processing": "處理來貨及上架", | |||
| "view stockin": "查看收貨及品檢", | |||
| "putawayBtn": "上架", | |||
| "dnNo": "送貨單編號", | |||
| "dnDate": "送貨單日期", | |||
| "submitStockIn": "更新來貨資料", | |||
| "QC Info": "品檢資料", | |||
| "Escalation History": "上報記錄", | |||
| "Escalation Info": "上報資料", | |||
| "Escalation Result": "上報結果", | |||
| "update qc info": "更新品檢資料", | |||
| "email supplier": "電郵供應商", | |||
| "confirm putaway": "確定及上架", | |||
| "confirm qc result": "確定品檢結果", | |||
| "warehouse": "倉庫", | |||
| "qcItem": "品檢項目", | |||
| "passed": "接受", | |||
| "failed": "不接受", | |||
| "failedQty": "不合格數", | |||
| "remarks": "備註", | |||
| "Reject": "拒絕", | |||
| "submit": "提交", | |||
| "print": "列印", | |||
| "bind": "綁定", | |||
| "Search": "搜尋", | |||
| "Found": "已找到" | |||
| } | |||