Browse Source

update qc checking

master
kelvinsuen 4 days ago
parent
commit
60d588bc0a
7 changed files with 161 additions and 74 deletions
  1. +66
    -0
      src/components/CollapsibleCard/CollapsibleCard.tsx
  2. +1
    -0
      src/components/CollapsibleCard/index.ts
  3. +0
    -42
      src/components/DashboardPage/CollapsibleCard.tsx
  4. +8
    -2
      src/components/DashboardPage/DashboardPage.tsx
  5. +74
    -23
      src/components/PoDetail/QcComponent.tsx
  6. +8
    -5
      src/components/PoDetail/QcStockInModalVer2.tsx
  7. +4
    -2
      src/i18n/zh/purchaseOrder.json

+ 66
- 0
src/components/CollapsibleCard/CollapsibleCard.tsx View File

@@ -0,0 +1,66 @@
import React, { useState } from "react";
import {
Card,
CardHeader,
CardContent,
IconButton,
Collapse,
Checkbox,
Box,
} from "@mui/material";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import ExpandLessIcon from "@mui/icons-material/ExpandLess";

interface CollapsibleCardProps {
title: string;
children: React.ReactNode;
defaultOpen?: boolean;
showFilter?: boolean;
filterText?: string;
}

const CollapsibleCard: React.FC<CollapsibleCardProps> = ({
title,
children,
defaultOpen = true,
showFilter = false,
filterText = "Optional Filter",
}) => {

const [filter, setFilter] = useState(false);

const onFilterChange = (bool : boolean) => {
setFilter(bool);
setOpen(true);
}
const [open, setOpen] = useState(defaultOpen);

return (
<Card>
<CardHeader
title={title}
action={
<Box display="flex" justifyContent="flex-end" alignItems="center" gap={1}>
{showFilter && (
<>
<Checkbox
sx={{textAlign: "left"}}
checked={filter}
onChange={(e) => {onFilterChange(e.target.checked);}}
// disabled={!isEmpty(pickOrder.consoCode)}
/> <span style={{paddingRight: 20}}>{filterText}</span>
</>)}
<IconButton onClick={() => setOpen((v) => !v)}>
{open ? <ExpandLessIcon /> : <ExpandMoreIcon />}
</IconButton>
</Box>
}
/>
<Collapse in={open}>
<CardContent>{children}</CardContent>
</Collapse>
</Card>
);
};

export default CollapsibleCard;

+ 1
- 0
src/components/CollapsibleCard/index.ts View File

@@ -0,0 +1 @@
export { default } from "./CollapsibleCard";

+ 0
- 42
src/components/DashboardPage/CollapsibleCard.tsx View File

@@ -1,42 +0,0 @@
import React, { useState } from "react";
import {
Card,
CardHeader,
CardContent,
IconButton,
Collapse,
} from "@mui/material";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import ExpandLessIcon from "@mui/icons-material/ExpandLess";

interface CollapsibleCardProps {
title: string;
children: React.ReactNode;
defaultOpen?: boolean;
}

const CollapsibleCard: React.FC<CollapsibleCardProps> = ({
title,
children,
defaultOpen = true,
}) => {
const [open, setOpen] = useState(defaultOpen);

return (
<Card>
<CardHeader
title={title}
action={
<IconButton onClick={() => setOpen((v) => !v)}>
{open ? <ExpandLessIcon /> : <ExpandMoreIcon />}
</IconButton>
}
/>
<Collapse in={open}>
<CardContent>{children}</CardContent>
</Collapse>
</Card>
);
};

export default CollapsibleCard;

+ 8
- 2
src/components/DashboardPage/DashboardPage.tsx View File

@@ -13,7 +13,7 @@ import PendingStorageChart from "./chart/PendingStorageChart";
import ApplicationCompletionChart from "./chart/ApplicationCompletionChart"; import ApplicationCompletionChart from "./chart/ApplicationCompletionChart";
import OrderCompletionChart from "./chart/OrderCompletionChart"; import OrderCompletionChart from "./chart/OrderCompletionChart";
import DashboardBox from "./Dashboardbox"; import DashboardBox from "./Dashboardbox";
import CollapsibleCard from "./CollapsibleCard";
import CollapsibleCard from "../CollapsibleCard";
// import SupervisorQcApproval, { IQCItems } from "./QC/SupervisorQcApproval"; // import SupervisorQcApproval, { IQCItems } from "./QC/SupervisorQcApproval";
import { EscalationResult } from "@/app/api/escalation"; import { EscalationResult } from "@/app/api/escalation";
import EscalationLogTable from "./escalation/EscalationLogTable"; import EscalationLogTable from "./escalation/EscalationLogTable";
@@ -28,6 +28,12 @@ const DashboardPage: React.FC<Props> = ({
}) => { }) => {
const { t } = useTranslation("dashboard"); const { t } = useTranslation("dashboard");
const router = useRouter(); const router = useRouter();
const [escLog, setEscLog] = useState<EscalationResult[]>([])
useEffect(() => {
setEscLog(escalationLogs);
}, [escalationLogs])


return ( return (
<ThemeProvider theme={theme}> <ThemeProvider theme={theme}>
@@ -35,7 +41,7 @@ const DashboardPage: React.FC<Props> = ({
<Grid item xs={12}> <Grid item xs={12}>
<CollapsibleCard title={t("My Escalation List")}> <CollapsibleCard title={t("My Escalation List")}>
<CardContent> <CardContent>
<EscalationLogTable items={escalationLogs || []}/>
<EscalationLogTable items={escLog}/>
</CardContent> </CardContent>
</CollapsibleCard> </CollapsibleCard>
</Grid> </Grid>


+ 74
- 23
src/components/PoDetail/QcComponent.tsx View File

@@ -20,7 +20,7 @@ import {
Tooltip, Tooltip,
Typography, Typography,
} from "@mui/material"; } from "@mui/material";
import { useFormContext, Controller, FieldPath, useFieldArray } from "react-hook-form";
import { useFormContext, Controller, FieldPath } from "react-hook-form";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import StyledDataGrid from "../StyledDataGrid"; import StyledDataGrid from "../StyledDataGrid";
import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useState } from "react"; import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useState } from "react";
@@ -51,11 +51,12 @@ import QcDataGrid from "./QCDatagrid";
import StockInFormVer2 from "./StockInFormVer2"; import StockInFormVer2 from "./StockInFormVer2";
import { dummyEscalationHistory, dummyQCData } from "./dummyQcTemplate"; import { dummyEscalationHistory, dummyQCData } from "./dummyQcTemplate";
import { ModalFormInput } from "@/app/api/po/actions"; import { ModalFormInput } from "@/app/api/po/actions";
import { escape } from "lodash";
import { escape, min } from "lodash";
import { PanoramaSharp } from "@mui/icons-material"; import { PanoramaSharp } from "@mui/icons-material";
import EscalationLogTable from "../DashboardPage/escalation/EscalationLogTable"; import EscalationLogTable from "../DashboardPage/escalation/EscalationLogTable";
import { EscalationResult } from "@/app/api/escalation"; import { EscalationResult } from "@/app/api/escalation";
import { EscalationCombo } from "@/app/api/user"; import { EscalationCombo } from "@/app/api/user";
import CollapsibleCard from "../CollapsibleCard/CollapsibleCard";


interface Props { interface Props {
itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] } & { escResult?: EscalationResult[] }; itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] } & { escResult?: EscalationResult[] };
@@ -135,16 +136,20 @@ const QcComponent: React.FC<Props> = ({ qc, itemDetail, disabled = false, escala
const accQty = watch("acceptQty"); const accQty = watch("acceptQty");
const validateForm = useCallback(() => { const validateForm = useCallback(() => {
if (qcDecision == 1) { if (qcDecision == 1) {
if (accQty > itemDetail.acceptedQty){
if (isNaN(accQty) || accQty === undefined || accQty === null || typeof(accQty) != "number") {
setError("acceptQty", { message: t("value must be a number") });
} else
if (!Number.isInteger(accQty)) {
setError("acceptQty", { message: t("value must be integer") });
}
if (accQty > itemDetail.acceptedQty) {
setError("acceptQty", { message: `${t("acceptQty must not greater than")} ${ setError("acceptQty", { message: `${t("acceptQty must not greater than")} ${
itemDetail.acceptedQty}` }); itemDetail.acceptedQty}` });
}
if (accQty < 1){
} else
if (accQty < 1) {
setError("acceptQty", { message: t("minimal value is 1") }); setError("acceptQty", { message: t("minimal value is 1") });
}
if (isNaN(accQty)){
setError("acceptQty", { message: t("value must be a number") });
}
} else
console.log("%c Validated accQty:", "color:yellow", accQty);
} }
},[setError, qcDecision, accQty, itemDetail]) },[setError, qcDecision, accQty, itemDetail])


@@ -343,7 +348,6 @@ const QcComponent: React.FC<Props> = ({ qc, itemDetail, disabled = false, escala
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
onMouseDown={(e) => e.stopPropagation()} onMouseDown={(e) => e.stopPropagation()}
onKeyDown={(e) => e.stopPropagation()} onKeyDown={(e) => e.stopPropagation()}
inputProps={{ min: 0 }}
sx={{ width: '100%' }} sx={{ width: '100%' }}
/> />
); );
@@ -490,7 +494,7 @@ const QcComponent: React.FC<Props> = ({ qc, itemDetail, disabled = false, escala
</Grid> */} </Grid> */}
<Grid item xs={12}> <Grid item xs={12}>
<EscalationLogTable type="qc" items={itemDetail.escResult || []}/> <EscalationLogTable type="qc" items={itemDetail.escResult || []}/>
{/* <Collapse in={true}> */}
<CollapsibleCard title={t("QC Record")}>
<StyledDataGrid <StyledDataGrid
columns={qcColumns} columns={qcColumns}
rows={qcResult} rows={qcResult}
@@ -499,7 +503,7 @@ const QcComponent: React.FC<Props> = ({ qc, itemDetail, disabled = false, escala
autoHeight autoHeight
sortModel={[]} sortModel={[]}
/> />
{/* </Collapse> */}
</CollapsibleCard>
{/* <StyledDataGrid {/* <StyledDataGrid
rows={escalationHistory} rows={escalationHistory}
columns={columns} columns={columns}
@@ -531,37 +535,84 @@ const QcComponent: React.FC<Props> = ({ qc, itemDetail, disabled = false, escala
// value={field.value?.toString() || "true"} // value={field.value?.toString() || "true"}
onChange={(e) => { onChange={(e) => {
const value = e.target.value.toString();// === 'true'; const value = e.target.value.toString();// === 'true';
if (value != "1" && Boolean(errors.acceptQty)) {
// if (!value && Boolean(errors.acceptQty)) {
setValue("acceptQty", itemDetail.acceptedQty ?? 0);

const input = document.getElementById('accQty') as HTMLInputElement;
console.log("%c Error", "color:pink", errors.acceptQty);
if (input) { // Selected Reject in new flow with Error
if (value == "1") { // Selected Accept
input.value = Number(accQty).toString();
} else {
if (Boolean(errors.acceptQty)) {
setValue("acceptQty", 0);
}
input.value = '0';
}
} }
// setValue("acceptQty", itemDetail.acceptedQty ?? 0);
// clearErrors("acceptQty");
// }
field.onChange(value); field.onChange(value);
}} }}
> >
<FormControlLabel disabled={disabled} <FormControlLabel disabled={disabled}
value="1" control={<Radio />} label="接受來貨" /> value="1" control={<Radio />} label="接受來貨" />
{(itemDetail.status == "escalated" || (disabled && accQty != itemDetail.acceptedQty && qcDecision == 1)) && ( //TODO Improve
{(itemDetail.status == "escalated"|| (disabled && accQty != itemDetail.acceptedQty && qcDecision == 1)) && ( //TODO Improve
<Box sx={{mr:2}}> <Box sx={{mr:2}}>
<TextField <TextField
type="number"
// type="number"
id="accQty"
label={t("acceptQty")} label={t("acceptQty")}
sx={{ width: '150px' }} sx={{ width: '150px' }}
value={(qcDecision == 1)? Number(accQty) : 0 }
// value={Number(accQty)}
defaultValue={Number(accQty)}
// defaultValue={(qcDecision == 1)? Number(accQty) : 0}
// value={(qcDecision == 1)? Number(accQty) : undefined }
// value={qcAccept? accQty : 0 } // value={qcAccept? accQty : 0 }
disabled={qcDecision != 1 || disabled} disabled={qcDecision != 1 || disabled}
// disabled={!qcAccept || disabled} // disabled={!qcAccept || disabled}
{...register("acceptQty", {
required: "acceptQty required!",
})}
onBlur={(e) => {
const value = e.target.value;
const input = document.getElementById('accQty') as HTMLInputElement;
input.value = Number(value).toString()
setValue(`acceptQty`, Number(value));
}}
onInput={(e: React.ChangeEvent<HTMLInputElement>) => {
const input = e.target.value;

const numReg = /^[0-9]+$/
let r = '';
if (!numReg.test(input)) {
const result = input.replace(/\D/g, "");
r = (result === '' ? result : Number(result)).toString();
} else {
r = Number(input).toString()
}
e.target.value = r;
}}
inputProps={{ min: 1, max:itemDetail.acceptedQty }}
// onChange={(e) => {
// const inputValue = e.target.value;
// if (inputValue === '' || /^[0-9]*$/.test(inputValue)) {
// setValue("acceptQty", Number(inputValue === '' ? null : parseInt(inputValue, 10)));
// }
// }}
// {...register("acceptQty", {
// required: "acceptQty required!",
// })}
error={Boolean(errors.acceptQty)} error={Boolean(errors.acceptQty)}
helperText={errors.acceptQty?.message} helperText={errors.acceptQty?.message}
/>
/>
<TextField <TextField
type="number" type="number"
label={t("rejectQty")} label={t("rejectQty")}
sx={{ width: '150px' }} sx={{ width: '150px' }}
value={itemDetail.acceptedQty - accQty}
value={
(!Boolean(errors.acceptQty)) ?
(qcDecision == 1 ? itemDetail.acceptedQty - accQty : itemDetail.acceptedQty)
: ""
}
error={Boolean(errors.acceptQty)}
disabled={true} disabled={true}
/> />
</Box>)} </Box>)}


+ 8
- 5
src/components/PoDetail/QcStockInModalVer2.tsx View File

@@ -218,7 +218,7 @@ const [qcItems, setQcItems] = useState(dummyQCData)
// Get QC data from the shared form context // Get QC data from the shared form context
const qcAccept = data.qcDecision == 1; const qcAccept = data.qcDecision == 1;
// const qcAccept = data.qcAccept; // const qcAccept = data.qcAccept;
const acceptQty = Number(data.acceptQty);
let acceptQty = Number(data.acceptQty);
const qcResults = data.qcResult || dummyQCData; // qcItems; const qcResults = data.qcResult || dummyQCData; // qcItems;
// const qcResults = data.qcResult as PurchaseQcResult[]; // qcItems; // const qcResults = data.qcResult as PurchaseQcResult[]; // qcItems;
// const qcResults = viewOnly? data.qcResult as PurchaseQcResult[] : qcItems; // const qcResults = viewOnly? data.qcResult as PurchaseQcResult[] : qcItems;
@@ -241,12 +241,15 @@ const [qcItems, setQcItems] = useState(dummyQCData)
// if (qcAccept === undefined) { // if (qcAccept === undefined) {
validationErrors.push(t("QC decision is required")); validationErrors.push(t("QC decision is required"));
} }
// Check if accept quantity is valid // Check if accept quantity is valid
if (acceptQty === undefined || acceptQty <= 0) {
validationErrors.push("Accept quantity must be greater than 0");
if (data.qcDecision == 2) {
acceptQty = 0;
} else {
if (acceptQty === undefined || acceptQty <= 0) {
validationErrors.push("Accept quantity must be greater than 0");
}
} }

// Check if dates are input // Check if dates are input
if (data.productionDate === undefined || data.productionDate == null) { if (data.productionDate === undefined || data.productionDate == null) {
alert("請輸入生產日期!"); alert("請輸入生產日期!");


+ 4
- 2
src/i18n/zh/purchaseOrder.json View File

@@ -78,7 +78,7 @@
"status": "狀態", "status": "狀態",
"acceptedQty must not greater than": "接受數量不得大於", "acceptedQty must not greater than": "接受數量不得大於",
"minimal value is 1": "最小值為1", "minimal value is 1": "最小值為1",
"value must be a number": "值必須是數字",
"value must be a number": "請輸入數字",
"qc Check": "質量控制檢查", "qc Check": "質量控制檢查",
"Please select QC": "請選擇質量控制", "Please select QC": "請選擇質量控制",
"failQty": "失敗數量", "failQty": "失敗數量",
@@ -142,5 +142,7 @@
"Select All": "選擇全部採購單", "Select All": "選擇全部採購單",
"View Selected": "查看已選擇採購單", "View Selected": "查看已選擇採購單",
"No Option": "沒有選項", "No Option": "沒有選項",
"receivedTotal": "已來貨總數"
"receivedTotal": "已來貨總數",
"QC Record": "品檢記錄",
"value must be integer": "請輸入整數"
} }

Loading…
Cancel
Save