diff --git a/src/app/(main)/settings/qcCategory/edit/page.tsx b/src/app/(main)/settings/qcCategory/edit/page.tsx new file mode 100644 index 0000000..cadcd7a --- /dev/null +++ b/src/app/(main)/settings/qcCategory/edit/page.tsx @@ -0,0 +1,53 @@ +import { Metadata } from "next"; +import { getServerI18n, I18nProvider } from "@/i18n"; +import Typography from "@mui/material/Typography"; +import { fetchQcCategoryDetails, preloadQcCategory } from "@/app/api/settings/qcCategory"; +import QcCategorySave from "@/components/QcCategorySave"; +import { isArray } from "lodash"; +import { notFound } from "next/navigation"; +import { ServerFetchError } from "@/app/utils/fetchUtil"; + +export const metadata: Metadata = { + title: "Qc Category", +}; + +interface Props { + searchParams: { [key: string]: string | string[] | undefined }; +} + +const qcCategory: React.FC = async ({ searchParams }) => { + const { t } = await getServerI18n("qcCategory"); + + const id = searchParams["id"]; + + if (!id || isArray(id)) { + notFound(); + } + + try { + console.log("first"); + await fetchQcCategoryDetails(id); + console.log("firsts"); + } catch (e) { + if ( + e instanceof ServerFetchError && + (e.response?.status === 404 || e.response?.status === 400) + ) { + console.log(e); + notFound(); + } + } + + return ( + <> + + {t("Edit Qc Category")} + + + + + + ); +}; + +export default qcCategory; diff --git a/src/app/api/settings/qcCategory/actions.ts b/src/app/api/settings/qcCategory/actions.ts index e564628..0e3462d 100644 --- a/src/app/api/settings/qcCategory/actions.ts +++ b/src/app/api/settings/qcCategory/actions.ts @@ -2,16 +2,55 @@ import { serverFetchJson } from "@/app/utils/fetchUtil"; import { BASE_API_URL } from "@/config/api"; +import { revalidatePath, revalidateTag } from "next/cache"; +import { QcCategoryResult } from "."; export interface CreateQcCategoryInputs { code: string; name: string; } -export const saveQcCategory = async (data: CreateQcCategoryInputs) => { - return serverFetchJson(`${BASE_API_URL}/qcCategories/save`, { +export const saveQcCategory = async (data: SaveQcCategoryInputs) => { + return serverFetchJson(`${BASE_API_URL}/qcCategories/save`, { method: "POST", body: JSON.stringify(data), headers: { "Content-Type": "application/json" }, }); }; + +export interface SaveQcItemsMapping { + id: number; + order: number; +} + +export interface SaveQcCategoryInputs { + id?: number; + code: string; + name: string; + description?: string; + qcItems: SaveQcItemsMapping[]; +} + +export interface SaveQcCategoryResponse { + id?: number; + code: string; + name: string; + description?: string; + errors: Record; + // qcItems: SaveQcItemsMapping[]; +} + +export const deleteQcCategory = async (id: number) => { + const response = await serverFetchJson( + `${BASE_API_URL}/qcCategories/${id}`, + { + method: "DELETE", + headers: { "Content-Type": "application/json" }, + }, + ); + + revalidateTag("qcCategories"); + revalidatePath("/(main)/settings/qcCategory"); + + return response; +}; diff --git a/src/app/api/settings/qcCategory/index.ts b/src/app/api/settings/qcCategory/index.ts index b96bb9c..9e38697 100644 --- a/src/app/api/settings/qcCategory/index.ts +++ b/src/app/api/settings/qcCategory/index.ts @@ -2,11 +2,13 @@ import { serverFetchJson } from "@/app/utils/fetchUtil"; import { BASE_API_URL } from "@/config/api"; import { cache } from "react"; import "server-only"; +import { SaveQcCategoryInputs } from "./actions"; export interface QcCategoryResult { id: number; code: string; name: string; + description?: string; } export interface QcCategoryCombo { @@ -25,6 +27,16 @@ export const fetchQcCategories = cache(async () => { }); }); +export const fetchQcCategoryDetails = cache(async (qcCategoryId: string) => { + return serverFetchJson( + `${BASE_API_URL}/qcCategories/details/${qcCategoryId}`, + { + next: { tags: [`qcCategoryDetails_${qcCategoryId}`] }, + }, + ); +}); + + export const fetchQcCategoryCombo = cache(async () => { return serverFetchJson(`${BASE_API_URL}/qcCategories/combo`, { next: { tags: ["qcCategoryCombo"] }, diff --git a/src/app/api/stockIn/index.ts b/src/app/api/stockIn/index.ts index 17dda00..2cafaca 100644 --- a/src/app/api/stockIn/index.ts +++ b/src/app/api/stockIn/index.ts @@ -1,5 +1,5 @@ import { cache } from "react"; -import "server-only"; +import "client-only"; // import { serverFetchJson } from "@/app/utils/fetchUtil"; // import { BASE_API_URL } from "@/config/api"; import { serverFetchJson } from "../../utils/fetchUtil"; @@ -14,6 +14,7 @@ export enum StockInStatus { RECEIVED = "received", APPROVED = "escalated", REJECTED = "rejected", + ESCALATED = "escalated", COMPLETED = "completed", PARTIALLY_COMPLETED = "partially_completed", } diff --git a/src/components/PoDetail/PoDetail.tsx b/src/components/PoDetail/PoDetail.tsx index 867f04d..c06ff86 100644 --- a/src/components/PoDetail/PoDetail.tsx +++ b/src/components/PoDetail/PoDetail.tsx @@ -63,7 +63,6 @@ import { useRouter, useSearchParams, usePathname } from "next/navigation"; import { WarehouseResult } from "@/app/api/warehouse"; import { calculateWeight, dateStringToDayjs, dayjsToDateString, OUTPUT_DATE_FORMAT, outputDateStringToInputDateString, returnWeightUnit } from "@/app/utils/formatUtil"; import { CameraContext } from "../Cameras/CameraProvider"; -import PoQcStockInModal from "./PoQcStockInModal"; import QrModal from "./QrModal"; import { PlayArrow } from "@mui/icons-material"; import DoneIcon from "@mui/icons-material/Done"; @@ -480,6 +479,8 @@ const PoDetail: React.FC = ({ po, warehouse, printerCombo }) => { // const [focusField, setFocusField] = useState(); const purchaseToStockRatio = (row.stockUom.purchaseRatioN ?? 1) / (row.stockUom.purchaseRatioD ?? 1) * (row.stockUom.stockRatioD ?? 1) / (row.stockUom.stockRatioN ?? 1) + const receivedTotal = decimalFormatter.format(row.stockInLine.filter((sil) => sil.purchaseOrderLineId === row.id).reduce((acc, cur) => acc + (cur.acceptedQty ?? 0),0) * purchaseToStockRatio); + const highlightColor = (Number(receivedTotal.replace(/,/g, '')) <= 0) ? "red" : "inherit"; return ( <> = ({ po, warehouse, printerCombo }) => { {integerFormatter.format(row.processed)} {row.uom?.udfudesc} {/* {decimalFormatter.format(row.stockUom.stockQty)} */} - {decimalFormatter.format(row.stockInLine.filter((sil) => sil.purchaseOrderLineId === row.id).reduce((acc, cur) => acc + (cur.acceptedQty ?? 0),0) * purchaseToStockRatio)} - {row.stockUom.stockUomDesc} + {receivedTotal} + {row.stockUom.stockUomDesc} {/* {decimalFormatter.format(totalWeight)} {weightUnit} */} {/* {weightUnit} */} {/* {decimalFormatter.format(row.price)} */} {/* {row.expiryDate} */} - {t(`${currStatus.toLowerCase()}`)} + {t(`${row.status.toLowerCase()}`)} + {/* {t(`${currStatus.toLowerCase()}`)} */} {/* {integerFormatter.format(row.receivedQty)} */} = ({ itemDetail, warehouse=[], disabled, setR return [ { value: 1, - label: t("W001 - 憶兆 3樓A倉"), + label: t("W201 - 2F-A,B室"), group: "default", }, ...filteredWarehouse.map((w) => ({ diff --git a/src/components/PutAwayScan/PutAwayModal.tsx b/src/components/PutAwayScan/PutAwayModal.tsx index 7ad89cc..331fa2b 100644 --- a/src/components/PutAwayScan/PutAwayModal.tsx +++ b/src/components/PutAwayScan/PutAwayModal.tsx @@ -36,6 +36,7 @@ import { QrCodeScanner } from "../QrCodeScannerProvider/QrCodeScannerProvider"; import { msg } from "../Swal/CustomAlerts"; import { PutAwayRecord } from "."; import FgStockInForm from "../StockIn/FgStockInForm"; +import Swal from "sweetalert2"; interface Props extends Omit { @@ -153,18 +154,22 @@ const PutAwayModal: React.FC = ({ open, onClose, warehouse, stockInLineId scanner.startScan(); console.log("%c Scanning started ", "color:cyan"); }; - useEffect(() => { if (warehouseId > 0) { // Scanned Warehouse if (scanner.isScanning) { setIsOpenScanner(false); setVerified(true); - msg("貨倉掃瞄成功!") + msg("貨倉掃瞄成功!"); scanner.resetScan(); console.log("%c Scanner reset", "color:cyan"); } } }, [warehouseId]) + + const warehouseDisplay = useMemo(() => { + const wh = warehouse.find((w) => w.id == warehouseId) ?? warehouse.find((w) => w.id == 1); + return <>{wh?.name}
[{wh?.code}]; + }, [warehouse, warehouseId, verified]); // useEffect(() => { // Restart scanner for changing warehouse // if (warehouseId > 0) { @@ -379,20 +384,21 @@ const PutAwayModal: React.FC = ({ open, onClose, warehouse, stockInLineId - {warehouseId > 0 ? `${warehouse.find((w) => w.id == warehouseId)?.name}` - : `${warehouse.find((w) => w.id == 1)?.name} (建議)`} + {warehouseDisplay} {verified ? "" : `(建議)`} - + = ({ open, onClose, warehouse, stockInLineId borderColor: "black", }, "& .MuiInputLabel-root": { - fontSize: "30px", + fontSize: 30, top: "-5px", color: "black", }, diff --git a/src/components/PutAwayScan/PutAwayScan.tsx b/src/components/PutAwayScan/PutAwayScan.tsx index 4fda256..1e26d42 100644 --- a/src/components/PutAwayScan/PutAwayScan.tsx +++ b/src/components/PutAwayScan/PutAwayScan.tsx @@ -30,7 +30,7 @@ type Props = { warehouse : WarehouseResult[]; }; -type ScanStatusType = "pending" | "rescan"; +type ScanStatusType = "pending" | "scanning" | "retry"; const PutAwayScan: React.FC = ({ warehouse }) => { const { t } = useTranslation("putAway"); @@ -54,7 +54,7 @@ const PutAwayScan: React.FC = ({ warehouse }) => { const resetScan = (error : string = "") => { if (error !== "") { console.log("%c Scan failed, error: ", "color:red", error); - setScanDisplay("rescan"); + setScanDisplay("retry"); } else { console.log("%c Scan reset", "color:red"); } @@ -111,6 +111,31 @@ const PutAwayScan: React.FC = ({ warehouse }) => { } }, [scanner.result]); + // Get Scanner State + useEffect(() => { + if (scanner.state) { + // + } + }, [scanner.state]); + + const displayText = useMemo(() => { + switch (scanner.state) { + case "pending": + return t("Pending scan"); + case "scanning": + return t("Scanning"); + case "retry": + return t("Rescan"); + default: + return t("Pending scan"); + } + // if (scanDisplay == "pending") { + // return t("Pending scan"); + // } else if (scanDisplay == "retry") { + // return t("Rescan"); + // } + }, [scanner.state]); + return (<> = ({ warehouse }) => { textAlign: 'center',}} > - {scanDisplay == "pending" ? t("Pending scan") : t("Rescan")} + {displayText} diff --git a/src/components/Qc/QcComponent.tsx b/src/components/Qc/QcComponent.tsx index f20a606..041aab4 100644 --- a/src/components/Qc/QcComponent.tsx +++ b/src/components/Qc/QcComponent.tsx @@ -203,13 +203,13 @@ const QcComponent: React.FC = ({ itemDetail, disabled = false }) => { // Set initial value for acceptQty - useEffect(() => { - if (itemDetail?.demandQty > 0) { //!== undefined) { - setValue("acceptQty", itemDetail.demandQty); // TODO: THIS NEED TO UPDATE TO NOT USE DEMAND QTY - } else { - setValue("acceptQty", itemDetail?.acceptedQty); - } - }, [itemDetail?.demandQty, itemDetail?.acceptedQty, setValue]); + // useEffect(() => { + // if (itemDetail?.demandQty > 0) { //!== undefined) { + // setValue("acceptQty", itemDetail.demandQty); // TODO: THIS NEED TO UPDATE TO NOT USE DEMAND QTY + // } else { + // setValue("acceptQty", itemDetail?.acceptedQty); + // } + // }, [itemDetail?.demandQty, itemDetail?.acceptedQty, setValue]); // Fetch Qc Data useEffect(() => { @@ -532,6 +532,8 @@ const QcComponent: React.FC = ({ itemDetail, disabled = false }) => { if (input) { // Selected Reject in new flow with Error if (value == "1") { // Selected Accept input.value = Number(accQty).toString(); + } else if (value == "3") { + input.value = ""; } else { if (Boolean(errors.acceptQty)) { setValue("acceptQty", 0); @@ -599,7 +601,7 @@ const QcComponent: React.FC = ({ itemDetail, disabled = false }) => { label={t("rejectQty")} sx={{ width: '150px' }} value={ - (!Boolean(errors.acceptQty) && qcDecision !== undefined) ? + (!Boolean(errors.acceptQty) && qcDecision !== undefined && qcDecision != 3) ? (qcDecision == 1 ? itemDetail.acceptedQty - accQty : itemDetail.acceptedQty) : "" } diff --git a/src/components/Qc/QcStockInModal.tsx b/src/components/Qc/QcStockInModal.tsx index 2edd7a6..8c39710 100644 --- a/src/components/Qc/QcStockInModal.tsx +++ b/src/components/Qc/QcStockInModal.tsx @@ -35,7 +35,7 @@ import { GridRowModesModel } from "@mui/x-data-grid"; import { isEmpty } from "lodash"; import { EscalationCombo } from "@/app/api/user"; import { truncateSync } from "fs"; -import { ModalFormInput, StockInLineInput, StockInLine } from "@/app/api/stockIn"; +import { ModalFormInput, StockInLineInput, StockInLine, StockInStatus } from "@/app/api/stockIn"; import { StockInLineEntry, updateStockInLine, printQrCodeForSil, PrintQrCodeForSilRequest } from "@/app/api/stockIn/actions"; import { fetchStockInLineInfo } from "@/app/api/stockIn/actions"; import FgStockInForm from "../StockIn/FgStockInForm"; @@ -83,7 +83,8 @@ const QcStockInModal: React.FC = ({ } = useTranslation("purchaseOrder"); const [stockInLineInfo, setStockInLineInfo] = useState(); - const [isLoading, setIsLoading] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const [isSubmitting, setIsSubmitting] = useState(false); // const [skipQc, setSkipQc] = useState(false); // const [viewOnly, setViewOnly] = useState(false); @@ -120,6 +121,7 @@ const QcStockInModal: React.FC = ({ // Fetch info if id is input useEffect(() => { setIsLoading(true); + setIsSubmitting(false); if (inputDetail && open) { console.log("%c Opened Modal with input:", "color:yellow", inputDetail); if (inputDetail.id) { @@ -157,7 +159,7 @@ const QcStockInModal: React.FC = ({ expiryDate: d.expiryDate ? arrayToDateString(d.expiryDate, "input") : undefined, receiptDate: d.receiptDate ? arrayToDateString(d.receiptDate, "input") : dayjs().add(0, "month").format(INPUT_DATE_FORMAT), - acceptQty: d.demandQty?? d.acceptedQty, + acceptQty: d.status != StockInStatus.REJECTED ? (d.demandQty?? d.acceptedQty) : 0, // escResult: (d.escResult && d.escResult?.length > 0) ? d.escResult : [], // qcResult: (d.qcResult && d.qcResult?.length > 0) ? d.qcResult : [],//[...dummyQCData], warehouseId: d.defaultWarehouseId ?? 1, @@ -195,7 +197,7 @@ const QcStockInModal: React.FC = ({ const showPutaway = useMemo(() => { if (stockInLineInfo) { const status = stockInLineInfo.status; - return status !== "pending" && status !== "escalated" && status !== "rejected"; + return status !== StockInStatus.PENDING && status !== StockInStatus.ESCALATED && status !== StockInStatus.REJECTED; } return false; }, [stockInLineInfo]); @@ -205,10 +207,10 @@ const QcStockInModal: React.FC = ({ if (stockInLineInfo) { if (stockInLineInfo.status) { const status = stockInLineInfo.status; - const isViewOnly = status.toLowerCase() == "completed" - || status.toLowerCase() == "partially_completed" // TODO update DB - || status.toLowerCase() == "rejected" - || (status.toLowerCase() == "escalated" && session?.id != stockInLineInfo.handlerId) + const isViewOnly = status.toLowerCase() == StockInStatus.COMPLETED + || status.toLowerCase() == StockInStatus.PARTIALLY_COMPLETED // TODO update DB + || status.toLowerCase() == StockInStatus.REJECTED + || (status.toLowerCase() == StockInStatus.ESCALATED && session?.id != stockInLineInfo.handlerId) if (showPutaway) { setTabIndex(1); } else { setTabIndex(0); } return isViewOnly; } @@ -311,7 +313,7 @@ const QcStockInModal: React.FC = ({ alert("請輸入到期日!"); return; } - if (!qcResults.every((qc) => qc.qcPassed) && qcAccept && stockInLineInfo?.status != "escalated") { //TODO: fix it please! + if (!qcResults.every((qc) => qc.qcPassed) && qcAccept && stockInLineInfo?.status != StockInStatus.ESCALATED) { //TODO: fix it please! validationErrors.push("有不合格檢查項目,無法收貨!"); // submitDialogWithWarning(() => postStockInLineWithQc(qcData), t, {title:"有不合格檢查項目,確認接受收貨?", // confirmButtonText: t("confirm putaway"), html: ""}); @@ -321,7 +323,7 @@ const QcStockInModal: React.FC = ({ // Check if all QC items have results const itemsWithoutResult = qcResults.filter(item => item.qcPassed === undefined); - if (itemsWithoutResult.length > 0 && stockInLineInfo?.status != "escalated") { //TODO: fix it please! + if (itemsWithoutResult.length > 0 && stockInLineInfo?.status != StockInStatus.ESCALATED) { //TODO: fix it please! validationErrors.push(`${t("QC items without result")}`); // validationErrors.push(`${t("QC items without result")}: ${itemsWithoutResult.map(item => item.code).join(', ')}`); } @@ -368,9 +370,12 @@ const QcStockInModal: React.FC = ({ handlerId : data.escalationLog?.handlerId, } console.log("Escalation Data for submission", escalationLog); + + setIsSubmitting(true); //TODO improve await postStockInLine({...qcData, escalationLog}); } else { + setIsSubmitting(true); //TODO improve await postStockInLine(qcData); } @@ -383,6 +388,7 @@ const QcStockInModal: React.FC = ({ } else { closeHandler({}, "backdropClick"); } + setIsSubmitting(false); msg("已更新來貨狀態"); return ; @@ -487,8 +493,6 @@ const QcStockInModal: React.FC = ({ // }, [pafRowSelectionModel, printQty, selectedPrinter]); }, [stockInLineInfo?.id, pafRowSelectionModel, printQty, selectedPrinter]); - const acceptQty = formProps.watch("acceptedQty") - // const checkQcIsPassed = useCallback((qcItems: PurchaseQcResult[]) => { // const isPassed = qcItems.every((qc) => qc.qcPassed); // console.log(isPassed) @@ -585,8 +589,9 @@ const QcStockInModal: React.FC = ({ color="primary" sx={{ mt: 1 }} onClick={formProps.handleSubmit(onSubmitQc, onSubmitErrorQc)} + disabled={isSubmitting || isLoading} > - {skipQc ? t("confirm") : t("confirm qc result")} + {isSubmitting ? (t("submitting")) : (skipQc ? t("confirm") : t("confirm qc result"))} )} diff --git a/src/components/QcCategorySave/QcCategoryDetails.tsx b/src/components/QcCategorySave/QcCategoryDetails.tsx new file mode 100644 index 0000000..896c088 --- /dev/null +++ b/src/components/QcCategorySave/QcCategoryDetails.tsx @@ -0,0 +1,63 @@ +import { SaveQcCategoryInputs } from "@/app/api/settings/qcCategory/actions"; +import { + Box, + Card, + CardContent, + Grid, + Stack, + TextField, + Typography, +} from "@mui/material"; +import { useFormContext } from "react-hook-form"; +import { useTranslation } from "react-i18next"; + +const QcCategoryDetails = () => { + const { t } = useTranslation("qcCategory"); + const { register } = useFormContext(); + + return ( + + + + {/* + {t("Qc Item Details")} + */} + + + + + + + + + + + + + + + ); +}; + +export default QcCategoryDetails; diff --git a/src/components/QcCategorySave/QcCategorySave.tsx b/src/components/QcCategorySave/QcCategorySave.tsx new file mode 100644 index 0000000..a2c67c1 --- /dev/null +++ b/src/components/QcCategorySave/QcCategorySave.tsx @@ -0,0 +1,121 @@ +"use client"; + +import { + deleteQcCategory, + saveQcCategory, + SaveQcCategoryInputs, +} from "@/app/api/settings/qcCategory/actions"; +import { Button, Stack } from "@mui/material"; +import { useCallback } from "react"; +import { FormProvider, SubmitHandler, useForm } from "react-hook-form"; +import { + deleteDialog, + errorDialogWithContent, + submitDialog, + successDialog, +} from "../Swal/CustomAlerts"; +import { useTranslation } from "react-i18next"; +import { useRouter } from "next/navigation"; +import QcCategoryDetails from "./QcCategoryDetails"; +import { Check, Close, Delete } from "@mui/icons-material"; + +interface Props { + defaultInputs?: SaveQcCategoryInputs; +} + +const QcCategorySave: React.FC = ({ defaultInputs }) => { + const { t } = useTranslation("qcCategory"); + const router = useRouter(); + + const formProps = useForm({ + defaultValues: { + ...defaultInputs, + }, + }); + + const handleSubmit = useCallback(async (data: SaveQcCategoryInputs) => { + const response = await saveQcCategory(data); + + const errors = response.errors; + if (errors) { + let errorContents = ""; + for (const [key, value] of Object.entries(errors)) { + formProps.setError(key as keyof SaveQcCategoryInputs, { + type: "custom", + message: value, + }); + errorContents = errorContents + t(value) + "
"; + } + + errorDialogWithContent(t("Submit Error"), errorContents, t); + } else { + await successDialog(t("Submit Success"), t, () => + router.push("/settings/qcCategory"), + ); + } + }, []); + + const onSubmit = useCallback>( + async (data) => { + await submitDialog(() => handleSubmit(data), t); + }, + [], + ); + + const handleCancel = () => { + router.replace("/settings/qcCategory"); + }; + + const handleDelete = () => { + deleteDialog(async () => { + await deleteQcCategory(formProps.getValues("id")!); + + await successDialog(t("Delete Success"), t, () => + router.replace("/settings/qcCategory"), + ); + }, t); + }; + + return ( + <> + + + + + {defaultInputs?.id && ( + + )} + + + + + + + ); +}; + +export default QcCategorySave; diff --git a/src/components/QcCategorySave/QcCategorySaveLoading.tsx b/src/components/QcCategorySave/QcCategorySaveLoading.tsx new file mode 100644 index 0000000..01c9782 --- /dev/null +++ b/src/components/QcCategorySave/QcCategorySaveLoading.tsx @@ -0,0 +1,40 @@ +import Card from "@mui/material/Card"; +import CardContent from "@mui/material/CardContent"; +import Skeleton from "@mui/material/Skeleton"; +import Stack from "@mui/material/Stack"; +import React from "react"; + +// Can make this nicer +export const QcItemSaveLoading: React.FC = () => { + return ( + <> + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default QcItemSaveLoading; diff --git a/src/components/QcCategorySave/QcCategorySaveWrapper.tsx b/src/components/QcCategorySave/QcCategorySaveWrapper.tsx new file mode 100644 index 0000000..8b76876 --- /dev/null +++ b/src/components/QcCategorySave/QcCategorySaveWrapper.tsx @@ -0,0 +1,24 @@ +import React from "react"; +import QcCategorySaveLoading from "./QcCategorySaveLoading"; +import QcCategorySave from "./QcCategorySave"; +import { fetchQcCategoryDetails } from "@/app/api/settings/qcCategory"; + +interface SubComponents { + Loading: typeof QcCategorySaveLoading; +} + +type SaveQcCategoryProps = { + id?: string; +}; + +type Props = SaveQcCategoryProps; + +const QcCategorySaveWrapper: React.FC & SubComponents = async (props) => { + const qcCategory = props.id ? await fetchQcCategoryDetails(props.id) : undefined; + + return ; +}; + +QcCategorySaveWrapper.Loading = QcCategorySaveLoading; + +export default QcCategorySaveWrapper; diff --git a/src/components/QcCategorySave/index.ts b/src/components/QcCategorySave/index.ts new file mode 100644 index 0000000..55fba21 --- /dev/null +++ b/src/components/QcCategorySave/index.ts @@ -0,0 +1 @@ +export { default } from "./QcCategorySaveWrapper"; diff --git a/src/components/QcCategorySearch/QcCategorySearch.tsx b/src/components/QcCategorySearch/QcCategorySearch.tsx index f6d91b5..f00fcd3 100644 --- a/src/components/QcCategorySearch/QcCategorySearch.tsx +++ b/src/components/QcCategorySearch/QcCategorySearch.tsx @@ -6,6 +6,10 @@ import { useTranslation } from "react-i18next"; import SearchResults, { Column } from "../SearchResults"; import EditNote from "@mui/icons-material/EditNote"; import { QcCategoryResult } from "@/app/api/settings/qcCategory"; +import { deleteDialog, successDialog } from "../Swal/CustomAlerts"; +import { deleteQcCategory } from "@/app/api/settings/qcCategory/actions"; +import Delete from "@mui/icons-material/Delete"; +import { usePathname, useRouter } from "next/navigation"; interface Props { qcCategories: QcCategoryResult[]; @@ -15,7 +19,9 @@ type SearchQuery = Partial>; type SearchParamNames = keyof SearchQuery; const QcCategorySearch: React.FC = ({ qcCategories }) => { - const { t } = useTranslation("qcCategories"); + const { t } = useTranslation("qcCategory"); + const router = useRouter(); + const pathname = usePathname(); // If qcCategory searching is done on the server-side, then no need for this. const [filteredQcCategories, setFilteredQcCategories] = @@ -34,8 +40,21 @@ const QcCategorySearch: React.FC = ({ qcCategories }) => { }, [qcCategories]); const onQcCategoryClick = useCallback((qcCategory: QcCategoryResult) => { - console.log(qcCategory); + router.push(`${pathname}/edit?id=${qcCategory.id}`); + }, [router]); + + const handleDelete = useCallback((qcCategory: QcCategoryResult) => { + deleteDialog(async () => { + qcCategories = await deleteQcCategory(qcCategory.id); + setFilteredQcCategories(qcCategories); + + await successDialog(t("Delete Success"), t); + }, t); }, []); + + const columnWidthSx = (width = "10%") => { + return { width: width, whiteSpace: "nowrap" }; + }; const columns = useMemo[]>( () => [ @@ -44,9 +63,19 @@ const QcCategorySearch: React.FC = ({ qcCategories }) => { label: t("Details"), onClick: onQcCategoryClick, buttonIcon: , + sx: columnWidthSx("5%"), + }, + { name: "code", label: t("Code"), sx: columnWidthSx("15%"), }, + { name: "name", label: t("Name"), sx: columnWidthSx("30%"), }, + // { name: "description", label: t("Description"), sx: columnWidthSx("50%"), }, + { + name: "id", + label: t("Delete"), + onClick: handleDelete, + buttonIcon: , + buttonColor: "error", + sx: columnWidthSx("5%"), }, - { name: "code", label: t("Code") }, - { name: "name", label: t("Name") }, ], [t, onQcCategoryClick], ); diff --git a/src/components/QcItemSave/QcItemDetails.tsx b/src/components/QcItemSave/QcItemDetails.tsx index fd51e06..7f0f298 100644 --- a/src/components/QcItemSave/QcItemDetails.tsx +++ b/src/components/QcItemSave/QcItemDetails.tsx @@ -12,16 +12,16 @@ import { useFormContext } from "react-hook-form"; import { useTranslation } from "react-i18next"; const QcItemDetails = () => { - const { t } = useTranslation(); + const { t } = useTranslation("qcItem"); const { register } = useFormContext(); return ( - + {/* {t("Qc Item Details")} - + */} = ({ defaultInputs }) => { ); const handleCancel = () => { - router.replace("/qcItem"); + router.replace("/settings/qcItem"); }; const handleDelete = () => { diff --git a/src/components/QcItemSearch/QcItemSearch.tsx b/src/components/QcItemSearch/QcItemSearch.tsx index 3256b6c..e357bd6 100644 --- a/src/components/QcItemSearch/QcItemSearch.tsx +++ b/src/components/QcItemSearch/QcItemSearch.tsx @@ -61,16 +61,22 @@ const QcItemSearch: React.FC = ({ qcItems }) => { }, t); }, []); + const columnWidthSx = (width = "10%") => { + return { width: width, whiteSpace: "nowrap" }; + }; + const columns = useMemo[]>( () => [ { name: "id", label: t("Details"), + sx: columnWidthSx("150px"), onClick: onQcItemClick, buttonIcon: , }, - { name: "code", label: t("Code") }, - { name: "name", label: t("Name") }, + { name: "code", label: t("Code"), sx: columnWidthSx() }, + { name: "name", label: t("Name"), sx: columnWidthSx() }, + { name: "description", label: t("Description") }, { name: "id", label: t("Delete"), diff --git a/src/components/QrCodeScannerProvider/QrCodeScannerProvider.tsx b/src/components/QrCodeScannerProvider/QrCodeScannerProvider.tsx index 270010a..b3aed36 100644 --- a/src/components/QrCodeScannerProvider/QrCodeScannerProvider.tsx +++ b/src/components/QrCodeScannerProvider/QrCodeScannerProvider.tsx @@ -16,6 +16,8 @@ export interface QrCodeScanner { stopScan: () => void; resetScan: () => void; result: QrCodeInfo | undefined; + state: "scanning" | "pending" | "retry"; + error: string | undefined; } interface QrCodeScannerProviderProps { @@ -35,6 +37,8 @@ const QrCodeScannerProvider: React.FC = ({ const [leftCurlyBraceCount, setLeftCurlyBraceCount] = useState(0); const [rightCurlyBraceCount, setRightCurlyBraceCount] = useState(0); const [scanResult, setScanResult] = useState() + const [scanState, setScanState] = useState<"scanning" | "pending" | "retry">("pending"); + const [scanError, setScanError] = useState() // TODO return scan error message const resetScannerInput = useCallback(() => { setKeys(() => []); @@ -51,6 +55,8 @@ const QrCodeScannerProvider: React.FC = ({ if (error.length > 0) { console.log("%c Error:", "color:red", error); + console.log("%c key:", "color:red", keys); + setScanState("retry"); } }, []); @@ -127,10 +133,15 @@ const QrCodeScannerProvider: React.FC = ({ // Update Qr Code Scanner Values useEffect(() => { if (rightCurlyBraceCount > leftCurlyBraceCount || leftCurlyBraceCount > 1) { // Prevent multiple scan + setScanState("retry"); + setScanError("Too many scans at once"); resetQrCodeScanner("Too many scans at once"); } else { if (leftCurlyBraceCount == 1 && keys.length == 1) - { console.log("%c Scan detected, waiting for inputs...", "color:cyan"); } + { + setScanState("scanning"); + console.log("%c Scan detected, waiting for inputs...", "color:cyan"); + } if ( leftCurlyBraceCount !== 0 && rightCurlyBraceCount !== 0 && @@ -138,6 +149,7 @@ const QrCodeScannerProvider: React.FC = ({ ) { const startBrace = keys.indexOf("{"); const endBrace = keys.lastIndexOf("}"); + setScanState("pending"); setQrCodeScannerValues((value) => [ ...value, keys.join("").substring(startBrace, endBrace + 1), @@ -201,6 +213,8 @@ const QrCodeScannerProvider: React.FC = ({ stopScan: endQrCodeScanner, resetScan: resetQrCodeScanner, result: scanResult, + state: scanState, + error: scanError, }} > {children} diff --git a/src/components/StockIn/FgStockInForm.tsx b/src/components/StockIn/FgStockInForm.tsx index 467f3e6..af46970 100644 --- a/src/components/StockIn/FgStockInForm.tsx +++ b/src/components/StockIn/FgStockInForm.tsx @@ -67,7 +67,7 @@ const textfieldSx = { transform: "translate(14px, 1.2rem) scale(1)", "&.MuiInputLabel-shrink": { fontSize: 24, - transform: "translate(14px, -9px) scale(1)", + transform: "translate(14px, -0.5rem) scale(1)", }, // [theme.breakpoints.down("sm")]: { // fontSize: "1rem", diff --git a/src/components/StockIn/StockInForm.tsx b/src/components/StockIn/StockInForm.tsx index e48c0d9..8b88692 100644 --- a/src/components/StockIn/StockInForm.tsx +++ b/src/components/StockIn/StockInForm.tsx @@ -60,7 +60,7 @@ const textfieldSx = { transform: "translate(14px, 1.2rem) scale(1)", "&.MuiInputLabel-shrink": { fontSize: 24, - transform: "translate(14px, -9px) scale(1)", + transform: "translate(14px, -0.5rem) scale(1)", }, // [theme.breakpoints.down("sm")]: { // fontSize: "1rem", diff --git a/src/i18n/zh/common.json b/src/i18n/zh/common.json index 16d5b4b..f38e4c2 100644 --- a/src/i18n/zh/common.json +++ b/src/i18n/zh/common.json @@ -50,8 +50,10 @@ "Delivery Order":"送貨訂單", "Detail Scheduling":"詳細排程", "Customer":"客戶", - "QC Check Item":"QC檢查項目", - "QC Category":"QC分類", + "qcItem":"品檢項目", + "QC Check Item":"QC品檢項目", + "QC Category":"QC品檢模板", + "qcCategory":"品檢模板", "QC Check Template":"QC檢查模板", "Mail":"郵件", "Import Testing":"匯入測試", diff --git a/src/i18n/zh/purchaseOrder.json b/src/i18n/zh/purchaseOrder.json index 6dd7d9c..5320af3 100644 --- a/src/i18n/zh/purchaseOrder.json +++ b/src/i18n/zh/purchaseOrder.json @@ -165,5 +165,6 @@ "Production Date must be earlier than Expiry Date": "生產日期必須早於到期日", "confirm expiry date": "確認到期日", "Invalid Date": "無效日期", - "Missing QC Template, please contact administrator": "找不到品檢模板,請聯絡管理員" + "Missing QC Template, please contact administrator": "找不到品檢模板,請聯絡管理員", + "submitting": "提交中..." } diff --git a/src/i18n/zh/putAway.json b/src/i18n/zh/putAway.json index 90eb045..50abb3e 100644 --- a/src/i18n/zh/putAway.json +++ b/src/i18n/zh/putAway.json @@ -20,5 +20,6 @@ "lotNo": "貨品批號", "poCode": "採購訂單編號", "itemCode": "貨品編號", - "uom": "單位" + "uom": "單位", + "Scanning": "掃瞄中,請稍後..." } diff --git a/src/i18n/zh/qcCategory.json b/src/i18n/zh/qcCategory.json index fbd0481..b608a76 100644 --- a/src/i18n/zh/qcCategory.json +++ b/src/i18n/zh/qcCategory.json @@ -1,9 +1,20 @@ { - "Qc Category": "QC 類別", + "Qc Category": "品檢模板", "Qc Category List": "QC 類別列表", "Qc Category Name": "QC 類別名稱", "Qc Category Description": "QC 類別描述", "Qc Category Status": "QC 類別狀態", "Qc Category Created At": "QC 類別創建時間", - "Qc Category Updated At": "QC 類別更新時間" + "Qc Category Updated At": "QC 類別更新時間", + "Name": "名稱", + "Code": "編號", + "Description": "描述", + "Details": "詳情", + "Delete": "刪除", + "Qc Item": "QC 項目", + "Create Qc Category": "新增品檢模板", + "Edit Qc Item": "編輯品檢項目", + "Qc Item Details": "品檢項目詳情", + "Cancel": "取消", + "Submit": "儲存" } \ No newline at end of file diff --git a/src/i18n/zh/qcItem.json b/src/i18n/zh/qcItem.json index 4ffc45c..129c9b8 100644 --- a/src/i18n/zh/qcItem.json +++ b/src/i18n/zh/qcItem.json @@ -1,8 +1,13 @@ { - "Name": "名稱", - "Code": "代碼", - "Details": "詳細資料", - "Delete": "刪除", - "Qc Item": "QC 項目", - "Create Qc Item": "新增 QC 項目" -} \ No newline at end of file + "Name": "名稱", + "Code": "編號", + "Description": "描述", + "Details": "詳情", + "Delete": "刪除", + "Qc Item": "QC 項目", + "Create Qc Item": "新增 QC 項目", + "Edit Qc Item": "編輯品檢項目", + "Qc Item Details": "品檢項目詳情", + "Cancel": "取消", + "Submit": "儲存" +}