From 71210e0ceab97bad9a4d13a00212e4db4c9ff8cb Mon Sep 17 00:00:00 2001 From: "MSI\\derek" Date: Wed, 28 May 2025 14:24:05 +0800 Subject: [PATCH] update po --- README.md | 13 +- src/app/(main)/layout.tsx | 47 +++---- src/app/api/po/actions.ts | 17 +++ src/app/api/qc/actions.ts | 12 +- src/app/api/settings/uom/index.ts | 4 +- src/app/utils/commonUtil.ts | 3 +- src/components/Cameras/CameraProvider.tsx | 26 ++++ src/components/PoDetail/PoDetail.tsx | 83 ++++++++++-- src/components/PoDetail/PoInputGrid.tsx | 101 ++++++++------ src/components/PoDetail/PoQcStockInModal.tsx | 26 +++- src/components/PoDetail/PoStockInModal.tsx | 0 .../QrCodeScanner/QrCodeScanner.tsx | 15 ++- .../QrCodeScanner/QrCodeScannerModal.tsx | 123 ++++++++++-------- 13 files changed, 330 insertions(+), 140 deletions(-) create mode 100644 src/components/Cameras/CameraProvider.tsx delete mode 100644 src/components/PoDetail/PoStockInModal.tsx diff --git a/README.md b/README.md index d8bffd4..581e288 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,9 @@ This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next It is recommended to use the same node and npm versions for development. An easy way to do so would be to use `nvm` ([Linux/MacOS](https://github.com/nvm-sh/nvm), [Windows](https://github.com/coreybutler/nvm-windows)). +## Version +nvm: 1.1.12 + After installing `nvm`, run: ```bash @@ -29,4 +32,12 @@ This project uses the following libraries: - [NextJS](https://nextjs.org/docs) - [Next-Auth](https://next-auth.js.org/getting-started/example) - [Material UI](https://mui.com/material-ui/getting-started/) -- [i18next](https://www.i18next.com/overview/getting-started) \ No newline at end of file +- [i18next](https://www.i18next.com/overview/getting-started) + +## Qrcode Testing +https://stackoverflow.com/questions/16835421/how-to-allow-chrome-to-access-my-camera-on-localhost +Local Qrcode testing require setting tweak +Steps: +1. Navigate to chrome://flags/#unsafely-treat-insecure-origin-as-secure in Chrome. +2. Find and enable the Insecure origins treated as secure section (see below). +3. Add any addresses you want to ignore the secure origin policy for. Remember to include the port number too (if required). \ No newline at end of file diff --git a/src/app/(main)/layout.tsx b/src/app/(main)/layout.tsx index fc8be81..bac4553 100644 --- a/src/app/(main)/layout.tsx +++ b/src/app/(main)/layout.tsx @@ -6,8 +6,9 @@ import Box from "@mui/material/Box"; import { NAVIGATION_CONTENT_WIDTH } from "@/config/uiConfig"; import Stack from "@mui/material/Stack"; import Breadcrumb from "@/components/Breadcrumb"; -import {AxiosProvider} from "@/app/(main)/axios/AxiosProvider"; -import {SetupAxiosInterceptors} from "@/app/(main)/axios/axiosInstance"; +import { AxiosProvider } from "@/app/(main)/axios/AxiosProvider"; +import { SetupAxiosInterceptors } from "@/app/(main)/axios/axiosInstance"; +import { CameraProvider } from "@/components/Cameras/CameraProvider"; export default async function MainLayout({ children, @@ -20,30 +21,32 @@ export default async function MainLayout({ redirect("/login"); } - if(session){ - SetupAxiosInterceptors(session?.accessToken); + if (session) { + SetupAxiosInterceptors(session?.accessToken); } return ( + - <> - - - - - {children} - - - + <> + + + + + {children} + + + + ); } diff --git a/src/app/api/po/actions.ts b/src/app/api/po/actions.ts index 9ad00c2..a21e333 100644 --- a/src/app/api/po/actions.ts +++ b/src/app/api/po/actions.ts @@ -5,6 +5,7 @@ import { revalidateTag } from "next/cache"; import { cache } from "react"; import { PoResult, StockInLine } from "."; import { serverFetchJson } from "@/app/utils/fetchUtil"; +import { QcItemResult } from "../settings/qcItem"; // import { BASE_API_URL } from "@/config/api"; export interface PostStockInLiineResponse { @@ -69,6 +70,22 @@ export const testFetch = cache(async (id: number) => { }); }); +export const testFetch2 = cache(async (id: number) => { + return serverFetchJson(`${BASE_API_URL}/qcResult/${id}`, { + next: { tags: ["test"] }, + }); +}); + +export const test3 = cache(async (id: number) => { + var endpoint = `${BASE_API_URL}/qcResult/${id}` + // if (startDate.length > 0) endpoint += `&startDate=${startDate}` + // if (endDate.length > 0) endpoint += `&endDate=${endDate}` + // if (teamId > 0 ) endpoint += `&teamId=${teamId}` + return serverFetchJson(endpoint, { + next: { tags: ["test"] }, + }); +}) + export const createStockInLine = async (data: StockInLineEntry) => { const stockInLine = await serverFetchJson>(`${BASE_API_URL}/stockInLine/create`, { method: "POST", diff --git a/src/app/api/qc/actions.ts b/src/app/api/qc/actions.ts index ee03960..08bb4c1 100644 --- a/src/app/api/qc/actions.ts +++ b/src/app/api/qc/actions.ts @@ -5,6 +5,10 @@ import { cache } from "react"; import { serverFetchJson } from "@/app/utils/fetchUtil"; import { QcItemWithChecks } from "."; +export interface QcResult { + +} + export const fetchQcItemCheck = cache(async (itemId?: number) => { var url = `${BASE_API_URL}/qcCheck` if (itemId) url +=`/${itemId}` @@ -12,4 +16,10 @@ export const fetchQcItemCheck = cache(async (itemId?: number) => { next: { tags: ["qc"] }, }); }); - \ No newline at end of file + + +export const fetchQcResult = cache(async (id: number) => { + return serverFetchJson(`${BASE_API_URL}/qcResult/${id}`, { + next: { tags: ["qc"] }, + }); +}); \ No newline at end of file diff --git a/src/app/api/settings/uom/index.ts b/src/app/api/settings/uom/index.ts index 67e91a4..685b012 100644 --- a/src/app/api/settings/uom/index.ts +++ b/src/app/api/settings/uom/index.ts @@ -8,7 +8,7 @@ export interface Uom { code: string name: string unit1: string - unit1Qty: number + unit1Qty: number unit2?: string unit2Qty: number unit3?: string @@ -17,4 +17,4 @@ export interface Uom { unit4Qty: number sizeInGram: number gramPerSmallestUnit: number -} +} \ No newline at end of file diff --git a/src/app/utils/commonUtil.ts b/src/app/utils/commonUtil.ts index c0465a1..c73ace6 100644 --- a/src/app/utils/commonUtil.ts +++ b/src/app/utils/commonUtil.ts @@ -6,5 +6,4 @@ export const downloadFile = (blobData: Uint8Array, filename: string) => { link.href = url; link.setAttribute("download", filename); link.click(); - } - \ No newline at end of file + } \ No newline at end of file diff --git a/src/components/Cameras/CameraProvider.tsx b/src/components/Cameras/CameraProvider.tsx new file mode 100644 index 0000000..7859c72 --- /dev/null +++ b/src/components/Cameras/CameraProvider.tsx @@ -0,0 +1,26 @@ +"use client"; +import { CameraDevice, Html5Qrcode } from "html5-qrcode"; +import React, { createContext, useContext, useEffect, useState } from "react"; + +export const CameraContext = createContext([]); + +export const CameraProvider: React.FC<{ children: React.ReactNode }> = ({ + children, +}) => { + const [cameras, setCameras] = useState([]); + + useEffect(() => { + const fetchCameras = async () => { + const res = await Html5Qrcode.getCameras(); + if (res) { + setCameras(res); + } + }; + + fetchCameras(); + }, []); + + return ( + {children} + ); +}; diff --git a/src/components/PoDetail/PoDetail.tsx b/src/components/PoDetail/PoDetail.tsx index 6696086..363c99d 100644 --- a/src/components/PoDetail/PoDetail.tsx +++ b/src/components/PoDetail/PoDetail.tsx @@ -30,7 +30,7 @@ import { useTranslation } from "react-i18next"; // import InputDataGrid, { TableRow } from "../InputDataGrid/InputDataGrid"; import { GridColDef, GridRowModel, useGridApiRef } from "@mui/x-data-grid"; import { testFetch } from "@/app/api/po/actions"; -import { useCallback, useEffect, useMemo, useState } from "react"; +import { useCallback, useContext, useEffect, useMemo, useState } from "react"; import { FormProvider, useForm } from "react-hook-form"; import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown"; import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp"; @@ -42,6 +42,10 @@ import { QcItemWithChecks } from "@/app/api/qc"; import { useSearchParams } from "next/navigation"; import { WarehouseResult } from "@/app/api/warehouse"; import { calculateWeight, returnWeightUnit } from "@/app/utils/formatUtil"; +import QrCodeScanner from "../QrCodeScanner"; +import { CameraDevice, Html5Qrcode } from "html5-qrcode"; +import { CameraContext } from "../Cameras/CameraProvider"; +import StyledDataGrid from "../StyledDataGrid"; type Props = { po: PoResult; @@ -54,9 +58,10 @@ type EntryError = [field in keyof StockInLine]?: string; } | undefined; - // type PolRow = TableRow, EntryError>; const PoDetail: React.FC = ({ po, qc, warehouse }) => { + const cameras = useContext(CameraContext); + console.log(cameras); const { t } = useTranslation(); const apiRef = useGridApiRef(); const [rows, setRows] = useState(po.pol || []); @@ -77,8 +82,16 @@ const PoDetail: React.FC = ({ po, qc, warehouse }) => { setCurrStatus("pending".toUpperCase()); } }, [processedQty]); - const totalWeight = useMemo(() => calculateWeight(row.qty, row.uom), []); - const weightUnit = useMemo(() => returnWeightUnit(row.uom), []); + + const totalWeight = useMemo( + () => calculateWeight(row.qty, row.uom), + [calculateWeight] + ); + const weightUnit = useMemo( + () => returnWeightUnit(row.uom), + [returnWeightUnit] + ); + return ( <> *": { borderBottom: "unset" }, color: "black" }}> @@ -133,6 +146,27 @@ const PoDetail: React.FC = ({ po, qc, warehouse }) => { ); } + const [isOpenScanner, setOpenScanner] = useState(false); + const onOpenScanner = useCallback(() => { + setOpenScanner(true); + }, []); + + const onCloseScanner = useCallback(() => { + setOpenScanner(false); + }, []); + + const handleScanSuccess = useCallback((result: string) => { + console.log(result); + }, []); + + const [tabIndex, setTabIndex] = useState(0); + const handleTabChange = useCallback>( + (_e, newValue) => { + setTabIndex(newValue); + }, + [] + ); + return ( <> = ({ po, qc, warehouse }) => { // component="form" // onSubmit={formProps.handleSubmit(onSubmit, onSubmitError)} > - - - {po.code} - + + + + {po.code} + + + + {/* go to scanner */} + {/* scan item */} + {/* putaway model form with defaultValues */} + + + - + + + + + + + {/* tab 1 */} + @@ -172,6 +231,12 @@ const PoDetail: React.FC = ({ po, qc, warehouse }) => {
+ {/* tab 2 */} + + {/* */} +
); diff --git a/src/components/PoDetail/PoInputGrid.tsx b/src/components/PoDetail/PoInputGrid.tsx index f27c363..f2ae97e 100644 --- a/src/components/PoDetail/PoInputGrid.tsx +++ b/src/components/PoDetail/PoInputGrid.tsx @@ -33,22 +33,30 @@ import ShoppingCartIcon from "@mui/icons-material/ShoppingCart"; import { QcItemWithChecks } from "src/app/api/qc"; import PlayArrowIcon from "@mui/icons-material/PlayArrow"; import { PurchaseOrderLine, StockInLine } from "@/app/api/po"; -import { createStockInLine, testFetch } from "@/app/api/po/actions"; +import { + createStockInLine, + PurchaseQcResult, +} from "@/app/api/po/actions"; import { useSearchParams } from "next/navigation"; -import { returnWeightUnit, calculateWeight, stockInLineStatusMap } from "@/app/utils/formatUtil"; +import { + returnWeightUnit, + calculateWeight, + stockInLineStatusMap, +} from "@/app/utils/formatUtil"; import PoQcStockInModal from "./PoQcStockInModal"; import NotificationImportantIcon from "@mui/icons-material/NotificationImportant"; import { WarehouseResult } from "@/app/api/warehouse"; -import LooksOneIcon from '@mui/icons-material/LooksOne'; -import LooksTwoIcon from '@mui/icons-material/LooksTwo'; -import Looks3Icon from '@mui/icons-material/Looks3'; +import LooksOneIcon from "@mui/icons-material/LooksOne"; +import LooksTwoIcon from "@mui/icons-material/LooksTwo"; +import Looks3Icon from "@mui/icons-material/Looks3"; import axiosInstance from "@/app/(main)/axios/axiosInstance"; // import axios, { AxiosRequestConfig } from "axios"; -import { NEXT_PUBLIC_API_URL } from "@/config/api"; -import qs from 'qs'; -import QrCodeIcon from '@mui/icons-material/QrCode'; +import { BASE_API_URL, NEXT_PUBLIC_API_URL } from "@/config/api"; +import qs from "qs"; +import QrCodeIcon from "@mui/icons-material/QrCode"; import { downloadFile } from "@/app/utils/commonUtil"; import { fetchPoQrcode } from "@/app/api/pdf/actions"; +import { fetchQcResult } from "@/app/api/qc/actions"; interface ResultWithId { id: number; } @@ -98,7 +106,7 @@ function PoInputGrid({ stockInLine, warehouse, }: Props) { - console.log(itemDetail) + console.log(itemDetail); const { t } = useTranslation("home"); const apiRef = useGridApiRef(); const [rowModesModel, setRowModesModel] = useState({}); @@ -108,7 +116,7 @@ function PoInputGrid({ ); console.log(stockInLine); const [entries, setEntries] = useState(stockInLine || []); - const [modalInfo, setModalInfo] = useState(); + const [modalInfo, setModalInfo] = useState(); const [qcOpen, setQcOpen] = useState(false); const [escalOpen, setEscalOpen] = useState(false); const [stockInOpen, setStockInOpen] = useState(false); @@ -122,9 +130,7 @@ function PoInputGrid({ }); useEffect(() => { - const completedList = entries.filter( - (e) => e.status === "completed" - ); + const completedList = entries.filter((e) => e.status === "completed"); const processedQty = completedList.reduce( (acc, curr) => acc + (curr.acceptedQty || 0), 0 @@ -150,6 +156,7 @@ function PoInputGrid({ console.log(params); const oldId = params.row.id; console.log(oldId); + console.log(params.row); const postData = { itemId: params.row.itemId, itemNo: params.row.itemNo, @@ -167,17 +174,21 @@ function PoInputGrid({ // openStartModal(); }, 200); }, - [] + [createStockInLine] ); const fetchQcDefaultValue = useCallback(async () => { - const authHeader = axiosInstance.defaults.headers['Authorization']; - if (!authHeader) { - return; // Exit the function if the token is not set - } - console.log(authHeader) - const res = await axiosInstance.get(`${NEXT_PUBLIC_API_URL}/qcResult/${itemDetail.id}`) - console.log(res) - }, [axiosInstance]) + // const authHeader = axiosInstance.defaults.headers['Authorization']; + // if (!authHeader) { + // return; // Exit the function if the token is not set + // } + // console.log(authHeader) + // console.log(NEXT_PUBLIC_API_URL) + // const res = await axiosInstance.get(`${NEXT_PUBLIC_API_URL}/qcResult/${itemDetail.id}`) + // const res = await testFetch2(itemDetail.id) + const res = await fetchQcResult(itemDetail.id); + console.log(res); + return res + }, [axiosInstance]); const handleQC = useCallback( (id: GridRowId, params: any) => async () => { @@ -185,15 +196,20 @@ function PoInputGrid({ ...prev, [id]: { mode: GridRowModes.View }, })); - setModalInfo(params.row); - // await fetchQcDefaultValue() + const qcResult = await fetchQcDefaultValue(); + console.log(qcResult) + setModalInfo({ + ...params.row, + qcResult: qcResult + }); + // set default values setTimeout(() => { // open qc modal console.log("delayed"); openQcModal(); }, 200); }, - [] + [fetchQcDefaultValue] ); const handleEscalation = useCallback( (id: GridRowId, params: any) => () => { @@ -246,15 +262,19 @@ function PoInputGrid({ [] ); - const printQrcode = useCallback(async (row: any) => { - console.log(row.id) - const postData = {stockInLineIds: [row.id]} - const response = await fetchPoQrcode(postData) - if (response) { - console.log(response) - downloadFile(new Uint8Array(response.blobValue), response.filename!!) - } - }, [fetchPoQrcode, downloadFile]) + const printQrcode = useCallback( + async (row: any) => { + console.log(row.id); + const postData = { stockInLineIds: [row.id] }; + // const postData = { stockInLineIds: [42,43,44] }; + const response = await fetchPoQrcode(postData); + if (response) { + console.log(response); + downloadFile(new Uint8Array(response.blobValue), response.filename!!); + } + }, + [fetchPoQrcode, downloadFile] + ); const handleQrCode = useCallback( (id: GridRowId, params: any) => () => { @@ -269,7 +289,7 @@ function PoInputGrid({ // return the record with its status as pending // update layout console.log("delayed"); - printQrcode(params.row) + printQrcode(params.row); }, 200); }, [] @@ -334,7 +354,10 @@ function PoInputGrid({ headerName: "weight", flex: 0.5, renderCell: (params) => { - const weight = calculateWeight(params.row.acceptedQty, params.row.uom); + const weight = calculateWeight( + params.row.acceptedQty, + params.row.uom + ); const weightUnit = returnWeightUnit(params.row.uom); return `${weight} ${weightUnit}`; }, @@ -375,9 +398,7 @@ function PoInputGrid({ color: "primary.main", // marginRight: 1, }} - disabled={ - stockInLineStatusMap[status] < 1 - } + disabled={stockInLineStatusMap[status] < 1} // set _isNew to false after posting // or check status onClick={handleQC(params.row.id, params)} @@ -463,7 +484,7 @@ function PoInputGrid({ ); const addRow = useCallback(() => { - console.log(itemDetail) + console.log(itemDetail); const newEntry = { id: Date.now(), _isNew: true, diff --git a/src/components/PoDetail/PoQcStockInModal.tsx b/src/components/PoDetail/PoQcStockInModal.tsx index 16fb7fe..2ffca6a 100644 --- a/src/components/PoDetail/PoQcStockInModal.tsx +++ b/src/components/PoDetail/PoQcStockInModal.tsx @@ -1,6 +1,6 @@ "use client"; -import { ModalFormInput, PurchaseQCInput, StockInInput, StockInLineEntry, updateStockInLine } from "@/app/api/po/actions"; +import { ModalFormInput, PurchaseQCInput, PurchaseQcResult, StockInInput, StockInLineEntry, updateStockInLine } from "@/app/api/po/actions"; import { Box, Button, Modal, ModalProps, Stack } from "@mui/material"; import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useState } from "react"; import { FormProvider, SubmitHandler, useForm } from "react-hook-form"; @@ -18,7 +18,7 @@ import { stockInLineStatusMap } from "@/app/utils/formatUtil"; interface CommonProps extends Omit { setEntries: Dispatch> - itemDetail: StockInLine; + itemDetail: StockInLine & {qcResult?: PurchaseQcResult[]}; qc?: QcItemWithChecks[]; warehouse?: any[]; type: "qc" | "stockIn" | "escalation" | "putaway" @@ -58,7 +58,7 @@ const style = { const PoQcStockInModal: React.FC = ({ type, setEntries, - open, + open, onClose, itemDetail, qc, @@ -70,8 +70,19 @@ const PoQcStockInModal: React.FC = ({ const params = useSearchParams() console.log(params.get("id")) const [defaultValues, setDefaultValues] = useState({}); + const defaultValue = useMemo(() => { + // switch (type) { + // case "qc": + // return {qcResult: itemDetail.qcResult} + // } + // return {} + return {...itemDetail} + }, []) + // const formProps = useForm({ + // defaultValues: defaultValues ? defaultValues : {}, + // }); const formProps = useForm({ - defaultValues: defaultValues ? defaultValues : {}, + defaultValues: defaultValue }); const errors = formProps.formState.errors; const closeHandler = useCallback>( @@ -155,17 +166,22 @@ const PoQcStockInModal: React.FC = ({ const renderSubmitButton = useMemo((): Boolean => { if (itemDetail) { const status = itemDetail.status + console.log(status) switch (type) { case "qc": return stockInLineStatusMap[status] === 1 case "putaway": return stockInLineStatusMap[status] === 7 + case "stockIn": + return stockInLineStatusMap[status] === 6 default: return false; // Handle unexpected type } } else return false }, [type, itemDetail]) - renderSubmitButton + useEffect(() => { + console.log(renderSubmitButton) + }, [renderSubmitButton]) return ( <> diff --git a/src/components/PoDetail/PoStockInModal.tsx b/src/components/PoDetail/PoStockInModal.tsx deleted file mode 100644 index e69de29..0000000 diff --git a/src/components/QrCodeScanner/QrCodeScanner.tsx b/src/components/QrCodeScanner/QrCodeScanner.tsx index c41b7d1..fc7184d 100644 --- a/src/components/QrCodeScanner/QrCodeScanner.tsx +++ b/src/components/QrCodeScanner/QrCodeScanner.tsx @@ -19,6 +19,7 @@ const scannerSx: React.CSSProperties = { }; type QrCodeScannerProps = { + cameras: CameraDevice[] title?: string, contents?: string[], onScanSuccess: (result: string) => void, @@ -79,7 +80,7 @@ const QrCodeScanner: React.FC = ({ const cameras = await Html5Qrcode.getCameras() setCameraList(cameras) if (cameras.length > 0) { - handleCameraChange(cameras[0].id) + handleCameraChange(cameras[cameras.length-1].id) } }, []) @@ -106,6 +107,8 @@ const QrCodeScanner: React.FC = ({ const handleScanSuccess = useCallback((decodedText, result) => { if (scanner) { console.log(`Decoded text: ${decodedText}`); + const parseData = JSON.parse(decodedText) + console.log(parseData) // Handle the decoded text as needed switchScanStatus() onScanSuccess(decodedText) @@ -113,20 +116,18 @@ const QrCodeScanner: React.FC = ({ }, [scanner, onScanSuccess]) const handleScanError = useCallback((errorMessage, error) => { - console.log(`Error: ${errorMessage}`); + // console.log(`Error: ${errorMessage}`); if (onScanError) { onScanError(errorMessage) } }, [scanner, onScanError]) - - const handleScanCloseButton = useCallback(async () => { if (scanner) { console.log("Cleaning up scanner..."); await scanner.stop() - await scanner.clear() + scanner.clear() onClose() } }, [scanner]) @@ -166,7 +167,7 @@ const QrCodeScanner: React.FC = ({