| @@ -12,6 +12,7 @@ export interface PostStockInLiineResponse<T> { | |||
| id: number | null; | |||
| name: string; | |||
| code: string; | |||
| type?: string | |||
| message: string | null; | |||
| errorPosition: string | keyof T; | |||
| entity: StockInLine | StockInLine[] | |||
| @@ -70,22 +71,12 @@ export const testFetch = cache(async (id: number) => { | |||
| }); | |||
| }); | |||
| export const testFetch2 = cache(async (id: number) => { | |||
| return serverFetchJson<any[]>(`${BASE_API_URL}/qcResult/${id}`, { | |||
| next: { tags: ["test"] }, | |||
| export const fetchStockInLineInfo = cache(async (stockInLineId: number) => { | |||
| return serverFetchJson<StockInLine>(`${BASE_API_URL}/stockInLine/${stockInLineId}`, { | |||
| next: { tags: ["stockInLine"] }, | |||
| }); | |||
| }); | |||
| 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<any[]>(endpoint, { | |||
| next: { tags: ["test"] }, | |||
| }); | |||
| }) | |||
| export const createStockInLine = async (data: StockInLineEntry) => { | |||
| const stockInLine = await serverFetchJson<PostStockInLiineResponse<StockInLineEntry>>(`${BASE_API_URL}/stockInLine/create`, { | |||
| method: "POST", | |||
| @@ -104,4 +95,13 @@ export const updateStockInLine = async (data: StockInLineEntry & ModalFormInput) | |||
| return stockInLine | |||
| } | |||
| export const checkPolAndCompletePo = async (poId: number) => { | |||
| const po = await serverFetchJson<PostStockInLiineResponse<PoResult>>(`${BASE_API_URL}/po//check/${poId}`, { | |||
| method: "POST", | |||
| body: JSON.stringify({ poId }), | |||
| headers: { "Content-Type": "application/json" }, | |||
| }); | |||
| return po | |||
| } | |||
| @@ -48,6 +48,7 @@ export interface StockInLine { | |||
| lotNo: string | |||
| poCode: string | |||
| uom: Uom | |||
| defaultWarehouseId: number // id for now | |||
| } | |||
| export const fetchPoList = cache(async () => { | |||
| @@ -0,0 +1,11 @@ | |||
| import { cache } from "react"; | |||
| import "server-only"; | |||
| import { serverFetchJson } from "@/app/utils/fetchUtil"; | |||
| import { BASE_API_URL } from "@/config/api"; | |||
| export interface QrCodeInfo { | |||
| stockInLineId?: number; | |||
| itemId: number | |||
| warehouseId?: number | |||
| lotNo?: string | |||
| } | |||
| @@ -26,14 +26,14 @@ import { | |||
| GridValidRowModel, | |||
| useGridApiRef, | |||
| } from "@mui/x-data-grid"; | |||
| import { useFormContext } from "react-hook-form"; | |||
| import { set, useFormContext } from "react-hook-form"; | |||
| import SaveIcon from "@mui/icons-material/Save"; | |||
| import DeleteIcon from "@mui/icons-material/Delete"; | |||
| import CancelIcon from "@mui/icons-material/Cancel"; | |||
| import { Add } from "@mui/icons-material"; | |||
| import { Box, Button, Typography } from "@mui/material"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import { GridApiCommunity } from "@mui/x-data-grid/internals"; | |||
| import { GridApiCommunity, GridSlotsComponentsProps } from "@mui/x-data-grid/internals"; | |||
| interface ResultWithId { | |||
| id: string | number; | |||
| @@ -67,6 +67,7 @@ export interface InputDataGridProps<T, V, E> { | |||
| _formKey: keyof T; | |||
| columns: GridColDef[]; | |||
| validateRow: (newRow: GridRowModel<TableRow<V, E>>) => E; | |||
| needAdd?: Boolean | |||
| }; | |||
| export interface SelectionInputDataGridProps<T, V, E> { // thinking how do | |||
| @@ -75,6 +76,7 @@ export interface SelectionInputDataGridProps<T, V, E> { // thinking how do | |||
| _formKey: keyof T; | |||
| columns: GridColDef[]; | |||
| validateRow: (newRow: GridRowModel<TableRow<V, E>>) => E; | |||
| needAdd?: Boolean | |||
| } | |||
| export type Props<T, V, E> = InputDataGridProps<T, V, E> | SelectionInputDataGridProps<T, V, E> | |||
| @@ -94,10 +96,11 @@ export class ProcessRowUpdateError<T, E> extends Error { | |||
| // E == error | |||
| function InputDataGrid<T, V, E>({ | |||
| apiRef, | |||
| checkboxSelection, | |||
| checkboxSelection = false, | |||
| _formKey, | |||
| columns, | |||
| validateRow, | |||
| needAdd, | |||
| }: Props<T, V, E>) { | |||
| const { | |||
| t, | |||
| @@ -113,6 +116,7 @@ function InputDataGrid<T, V, E>({ | |||
| [] | |||
| ); | |||
| const list: TableRow<V, E>[] = getValues(formKey); | |||
| // console.log(list) | |||
| const [rows, setRows] = useState<TableRow<V, E>[]>(() => { | |||
| const list: TableRow<V, E>[] = getValues(formKey); | |||
| return list && list.length > 0 ? list : []; | |||
| @@ -348,9 +352,13 @@ function InputDataGrid<T, V, E>({ | |||
| footer: FooterToolbar, | |||
| noRowsOverlay: NoRowsOverlay, | |||
| } : undefined} | |||
| slotProps={!checkboxSelection ? { | |||
| slotProps={!checkboxSelection && Boolean(needAdd) ? { | |||
| footer: { child: footer }, | |||
| } : undefined} | |||
| }: undefined | |||
| // slotProps={renderFooter ? { | |||
| // footer: { child: footer }, | |||
| // }: undefined | |||
| } | |||
| /> | |||
| ) | |||
| } | |||
| @@ -28,9 +28,26 @@ import { | |||
| } from "@mui/material"; | |||
| 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, useContext, useEffect, useMemo, useState } from "react"; | |||
| import { | |||
| GridColDef, | |||
| GridRowId, | |||
| GridRowModel, | |||
| useGridApiRef, | |||
| } from "@mui/x-data-grid"; | |||
| import { | |||
| checkPolAndCompletePo, | |||
| fetchStockInLineInfo, | |||
| PurchaseQcResult, | |||
| testFetch, | |||
| } from "@/app/api/po/actions"; | |||
| import { | |||
| use, | |||
| 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"; | |||
| @@ -46,6 +63,13 @@ import QrCodeScanner from "../QrCodeScanner"; | |||
| import { CameraDevice, Html5Qrcode } from "html5-qrcode"; | |||
| import { CameraContext } from "../Cameras/CameraProvider"; | |||
| import StyledDataGrid from "../StyledDataGrid"; | |||
| import { QrCodeInfo } from "@/app/api/qrcode"; | |||
| import { fetchQcResult } from "@/app/api/qc/actions"; | |||
| import PoQcStockInModal from "./PoQcStockInModal"; | |||
| import ReactQrCodeScannerModal, { | |||
| ScannerConfig, | |||
| } from "../ReactQrCodeScanner/ReactQrCodeScanner"; | |||
| import QrModal from "./QrModal"; | |||
| type Props = { | |||
| po: PoResult; | |||
| @@ -72,7 +96,6 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||
| const [open, setOpen] = useState(false); | |||
| const [processedQty, setProcessedQty] = useState(row.processed); | |||
| const [currStatus, setCurrStatus] = useState(row.status); | |||
| useEffect(() => { | |||
| if (processedQty === row.qty) { | |||
| setCurrStatus("completed".toUpperCase()); | |||
| @@ -145,6 +168,13 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||
| </> | |||
| ); | |||
| } | |||
| const [tabIndex, setTabIndex] = useState(0); | |||
| const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>( | |||
| (_e, newValue) => { | |||
| setTabIndex(newValue); | |||
| }, | |||
| [] | |||
| ); | |||
| const [isOpenScanner, setOpenScanner] = useState(false); | |||
| const onOpenScanner = useCallback(() => { | |||
| @@ -155,17 +185,33 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||
| setOpenScanner(false); | |||
| }, []); | |||
| const handleScanSuccess = useCallback((result: string) => { | |||
| console.log(result); | |||
| const [itemInfo, setItemInfo] = useState< | |||
| StockInLine & { warehouseId?: number } | |||
| >(); | |||
| const [putAwayOpen, setPutAwayOpen] = useState(false); | |||
| // const [scannedInfo, setScannedInfo] = useState<QrCodeInfo>({} as QrCodeInfo); | |||
| const closePutAwayModal = useCallback(() => { | |||
| setPutAwayOpen(false); | |||
| setItemInfo(undefined); | |||
| }, []); | |||
| const openPutAwayModal = useCallback(() => { | |||
| setPutAwayOpen(true); | |||
| }, []); | |||
| const [tabIndex, setTabIndex] = useState(0); | |||
| const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>( | |||
| (_e, newValue) => { | |||
| setTabIndex(newValue); | |||
| }, | |||
| [] | |||
| ); | |||
| const handleComplete = useCallback(async () => { | |||
| const res = await checkPolAndCompletePo(po.id) | |||
| if (res.type === "completed") { | |||
| // toast.success(res.message) | |||
| console.log(res) | |||
| return | |||
| } | |||
| if (res.type === "receiving") { | |||
| // toast.error(res.message) | |||
| console.log(res) | |||
| return | |||
| } | |||
| }, [checkPolAndCompletePo]); | |||
| return ( | |||
| <> | |||
| @@ -180,30 +226,34 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||
| {po.code} | |||
| </Typography> | |||
| </Grid> | |||
| <Grid item> | |||
| {/* go to scanner */} | |||
| {/* scan item */} | |||
| {/* putaway model form with defaultValues */} | |||
| <QrCodeScanner | |||
| cameras={cameras} | |||
| isOpen={isOpenScanner} | |||
| onClose={onCloseScanner} | |||
| onScanSuccess={handleScanSuccess} | |||
| /> | |||
| <Button onClick={onOpenScanner}>bind</Button> | |||
| </Grid> | |||
| </Grid> | |||
| <Grid container xs={12}> | |||
| {/* <Grid container xs={12} justifyContent="space-between"> | |||
| <Button onClick={handleComplete}>Complete</Button> | |||
| </Grid> */} | |||
| <Grid container xs={12} justifyContent="space-between"> | |||
| <Grid item xs={8}> | |||
| <Tabs | |||
| value={tabIndex} | |||
| onChange={handleTabChange} | |||
| variant="scrollable" | |||
| > | |||
| <Tab label={t("General")} iconPosition="end" /> | |||
| <Tab label={t("Bind Storage")} iconPosition="end" /> | |||
| {/* <Tab label={t("Bind Storage")} iconPosition="end" /> */} | |||
| </Tabs> | |||
| </Grid> | |||
| {/* tab 1 */} | |||
| {/* <Grid item xs={4}> */} | |||
| {/* scanner */} | |||
| {/* </Grid> */} | |||
| <Grid item xs={4} display="flex" justifyContent="end" alignItems="end"> | |||
| <QrModal | |||
| open={isOpenScanner} | |||
| onClose={onCloseScanner} | |||
| warehouse={warehouse} | |||
| /> | |||
| <Button onClick={onOpenScanner}>bind</Button> | |||
| </Grid> | |||
| </Grid> | |||
| {/* tab 1 */} | |||
| <Grid sx={{ display: tabIndex === 0 ? "block" : "none" }}> | |||
| <TableContainer component={Paper}> | |||
| <Table aria-label="collapsible table"> | |||
| @@ -238,6 +288,18 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||
| /> */} | |||
| </Grid> | |||
| </Stack> | |||
| {itemInfo !== undefined && ( | |||
| <> | |||
| <PoQcStockInModal | |||
| type={"putaway"} | |||
| open={putAwayOpen} | |||
| warehouse={warehouse} | |||
| setItemDetail={setItemInfo} | |||
| onClose={closePutAwayModal} | |||
| itemDetail={itemInfo} | |||
| /> | |||
| </> | |||
| )} | |||
| </> | |||
| ); | |||
| }; | |||
| @@ -20,23 +20,14 @@ type Props = { | |||
| }; | |||
| const PoDetailWrapper: React.FC<Props> & SubComponents = async ({ id }) => { | |||
| const [ | |||
| poWithStockInLine, | |||
| warehouse, | |||
| qc, | |||
| ] = await Promise.all([ | |||
| const [poWithStockInLine, warehouse, qc] = await Promise.all([ | |||
| fetchPoWithStockInLines(id), | |||
| fetchWarehouseList(), | |||
| fetchQcItemCheck() | |||
| ]) | |||
| fetchQcItemCheck(), | |||
| ]); | |||
| // const poWithStockInLine = await fetchPoWithStockInLines(id) | |||
| console.log(poWithStockInLine) | |||
| console.log(warehouse) | |||
| console.log(qc) | |||
| return <PoDetail po={poWithStockInLine} qc={qc} warehouse={warehouse}/>; | |||
| return <PoDetail po={poWithStockInLine} qc={qc} warehouse={warehouse} />; | |||
| }; | |||
| PoDetailWrapper.Loading = PoDetailLoading; | |||
| @@ -33,17 +33,14 @@ 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, | |||
| PurchaseQcResult, | |||
| } 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 PoQcStockInModal from "./PoQcStockInModal"; | |||
| // import PoQcStockInModal from "./PoQcStockInModal"; | |||
| import NotificationImportantIcon from "@mui/icons-material/NotificationImportant"; | |||
| import { WarehouseResult } from "@/app/api/warehouse"; | |||
| import LooksOneIcon from "@mui/icons-material/LooksOne"; | |||
| @@ -57,6 +54,8 @@ 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"; | |||
| import PoQcStockInModal from "./PoQcStockInModal"; | |||
| interface ResultWithId { | |||
| id: number; | |||
| } | |||
| @@ -116,7 +115,9 @@ function PoInputGrid({ | |||
| ); | |||
| console.log(stockInLine); | |||
| const [entries, setEntries] = useState<StockInLineRow[]>(stockInLine || []); | |||
| const [modalInfo, setModalInfo] = useState<StockInLine & {qcResult?: PurchaseQcResult[]}>(); | |||
| const [modalInfo, setModalInfo] = useState< | |||
| StockInLine & { qcResult?: PurchaseQcResult[] } | |||
| >(); | |||
| const [qcOpen, setQcOpen] = useState(false); | |||
| const [escalOpen, setEscalOpen] = useState(false); | |||
| const [stockInOpen, setStockInOpen] = useState(false); | |||
| @@ -176,19 +177,9 @@ function PoInputGrid({ | |||
| }, | |||
| [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) | |||
| // console.log(NEXT_PUBLIC_API_URL) | |||
| // const res = await axiosInstance.get<QcItemWithChecks[]>(`${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 fetchQcDefaultValue = useCallback(async (stockInLineId: GridRowId) => { | |||
| return await fetchQcResult(stockInLineId as number); | |||
| }, []); | |||
| const handleQC = useCallback( | |||
| (id: GridRowId, params: any) => async () => { | |||
| @@ -196,11 +187,12 @@ function PoInputGrid({ | |||
| ...prev, | |||
| [id]: { mode: GridRowModes.View }, | |||
| })); | |||
| const qcResult = await fetchQcDefaultValue(); | |||
| console.log(qcResult) | |||
| const qcResult = await fetchQcDefaultValue(id); | |||
| console.log(params.row); | |||
| console.log(qcResult); | |||
| setModalInfo({ | |||
| ...params.row, | |||
| qcResult: qcResult | |||
| qcResult: qcResult, | |||
| }); | |||
| // set default values | |||
| setTimeout(() => { | |||
| @@ -414,7 +406,7 @@ function PoInputGrid({ | |||
| }} | |||
| disabled={ | |||
| stockInLineStatusMap[status] <= 0 || | |||
| stockInLineStatusMap[status] >= 5 | |||
| stockInLineStatusMap[status] >= 4 | |||
| } | |||
| // set _isNew to false after posting | |||
| // or check status | |||
| @@ -429,7 +421,7 @@ function PoInputGrid({ | |||
| color: "primary.main", | |||
| // marginRight: 1, | |||
| }} | |||
| disabled={stockInLineStatusMap[status] !== 6} | |||
| disabled={stockInLineStatusMap[status] <= 2 || stockInLineStatusMap[status] >= 7} | |||
| // set _isNew to false after posting | |||
| // or check status | |||
| onClick={handleStockIn(params.row.id, params)} | |||
| @@ -563,13 +555,6 @@ function PoInputGrid({ | |||
| [apiRef] | |||
| ); | |||
| // useEffect(() => { | |||
| // const total = entries.reduce( | |||
| // (acc, curr) => acc + (curr.acceptedQty || 0), | |||
| // 0 | |||
| // ); | |||
| // setDefaultQty(itemDetail.qty - total); | |||
| // }, [entries]); | |||
| const footer = ( | |||
| <Box display="flex" gap={2} alignItems="center"> | |||
| <Button | |||
| @@ -584,6 +569,9 @@ function PoInputGrid({ | |||
| </Button> | |||
| </Box> | |||
| ); | |||
| useEffect(() => { | |||
| console.log(modalInfo); | |||
| }, [modalInfo]); | |||
| return ( | |||
| <> | |||
| <StyledDataGrid | |||
| @@ -631,46 +619,58 @@ function PoInputGrid({ | |||
| footer: { child: footer }, | |||
| }} | |||
| /> | |||
| <> | |||
| <PoQcStockInModal | |||
| type={"qc"} | |||
| setEntries={setEntries} | |||
| qc={qc} | |||
| open={qcOpen} | |||
| onClose={closeQcModal} | |||
| itemDetail={modalInfo!!} | |||
| /> | |||
| </> | |||
| <> | |||
| <PoQcStockInModal | |||
| type={"escalation"} | |||
| setEntries={setEntries} | |||
| // qc={qc} | |||
| open={escalOpen} | |||
| onClose={closeEscalationModal} | |||
| itemDetail={modalInfo!!} | |||
| /> | |||
| </> | |||
| <> | |||
| <PoQcStockInModal | |||
| type={"stockIn"} | |||
| setEntries={setEntries} | |||
| // qc={qc} | |||
| open={stockInOpen} | |||
| onClose={closeStockInModal} | |||
| itemDetail={modalInfo!!} | |||
| /> | |||
| </> | |||
| <> | |||
| <PoQcStockInModal | |||
| type={"putaway"} | |||
| setEntries={setEntries} | |||
| open={putAwayOpen} | |||
| warehouse={warehouse} | |||
| onClose={closePutAwayModal} | |||
| itemDetail={modalInfo!!} | |||
| /> | |||
| </> | |||
| {modalInfo !== undefined && ( | |||
| <> | |||
| <PoQcStockInModal | |||
| type={"qc"} | |||
| setEntries={setEntries} | |||
| setItemDetail={setModalInfo} | |||
| qc={qc} | |||
| open={qcOpen} | |||
| onClose={closeQcModal} | |||
| itemDetail={modalInfo} | |||
| /> | |||
| </> | |||
| )} | |||
| {modalInfo !== undefined && ( | |||
| <> | |||
| <PoQcStockInModal | |||
| type={"escalation"} | |||
| setEntries={setEntries} | |||
| setItemDetail={setModalInfo} | |||
| // qc={qc} | |||
| open={escalOpen} | |||
| onClose={closeEscalationModal} | |||
| itemDetail={modalInfo} | |||
| /> | |||
| </> | |||
| )} | |||
| {modalInfo !== undefined && ( | |||
| <> | |||
| <PoQcStockInModal | |||
| type={"stockIn"} | |||
| setEntries={setEntries} | |||
| // qc={qc} | |||
| setItemDetail={setModalInfo} | |||
| open={stockInOpen} | |||
| onClose={closeStockInModal} | |||
| itemDetail={modalInfo} | |||
| /> | |||
| </> | |||
| )} | |||
| {modalInfo !== undefined && ( | |||
| <> | |||
| <PoQcStockInModal | |||
| type={"putaway"} | |||
| setEntries={setEntries} | |||
| setItemDetail={setModalInfo} | |||
| open={putAwayOpen} | |||
| warehouse={warehouse} | |||
| onClose={closePutAwayModal} | |||
| itemDetail={modalInfo} | |||
| /> | |||
| </> | |||
| )} | |||
| </> | |||
| ); | |||
| } | |||
| @@ -1,13 +1,27 @@ | |||
| "use client"; | |||
| import { ModalFormInput, PurchaseQCInput, PurchaseQcResult, 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 { | |||
| Dispatch, | |||
| SetStateAction, | |||
| useCallback, | |||
| useEffect, | |||
| useMemo, | |||
| useState, | |||
| } from "react"; | |||
| import { FormProvider, SubmitHandler, useForm } from "react-hook-form"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import QcForm from "./QcForm"; | |||
| import { QcItemWithChecks } from "@/app/api/qc"; | |||
| import { Check, CurrencyYuanRounded } from "@mui/icons-material"; | |||
| import { Check, CurrencyYuanRounded, TtyTwoTone } from "@mui/icons-material"; | |||
| import { StockInLine } from "@/app/api/po"; | |||
| import { useSearchParams } from "next/navigation"; | |||
| import { StockInLineRow } from "./PoInputGrid"; | |||
| @@ -15,35 +29,38 @@ import EscalationForm from "./EscalationForm"; | |||
| import StockInForm from "./StockInForm"; | |||
| import PutawayForm from "./PutawayForm"; | |||
| import { stockInLineStatusMap } from "@/app/utils/formatUtil"; | |||
| import dayjs from "dayjs"; | |||
| import arraySupport from "dayjs/plugin/arraySupport"; | |||
| dayjs.extend(arraySupport) | |||
| interface CommonProps extends Omit<ModalProps, "children"> { | |||
| setEntries: Dispatch<SetStateAction<StockInLineRow[]>> | |||
| itemDetail: StockInLine & {qcResult?: PurchaseQcResult[]}; | |||
| setEntries?: Dispatch<SetStateAction<StockInLineRow[]>>; | |||
| itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] }; | |||
| setItemDetail: Dispatch<SetStateAction<(StockInLine & { | |||
| warehouseId?: number; | |||
| }) | undefined>> | |||
| qc?: QcItemWithChecks[]; | |||
| warehouse?: any[]; | |||
| type: "qc" | "stockIn" | "escalation" | "putaway" | |||
| type: "qc" | "stockIn" | "escalation" | "putaway"; | |||
| } | |||
| interface QcProps extends CommonProps { | |||
| qc: QcItemWithChecks[]; | |||
| type: "qc" | |||
| } | |||
| type: "qc"; | |||
| } | |||
| interface StockInProps extends CommonProps { | |||
| // naming | |||
| type: "stockIn" | |||
| type: "stockIn"; | |||
| } | |||
| interface PutawayProps extends CommonProps { | |||
| // naming | |||
| // warehouse: any[]; | |||
| warehouse: any[]; | |||
| type: "putaway" | |||
| type: "putaway"; | |||
| } | |||
| interface EscalationProps extends CommonProps { | |||
| // naming | |||
| type: "escalation" | |||
| type: "escalation"; | |||
| } | |||
| type Props = QcProps | StockInProps | EscalationProps | PutawayProps; | |||
| type Props = QcProps | StockInProps | PutawayProps | EscalationProps; | |||
| const style = { | |||
| position: "absolute", | |||
| top: "50%", | |||
| @@ -55,35 +72,29 @@ const style = { | |||
| pb: 10, | |||
| width: { xs: "80%", sm: "80%", md: "80%" }, | |||
| }; | |||
| const PoQcStockInModal: React.FC<Props> = ({ | |||
| type, | |||
| setEntries, | |||
| open, | |||
| onClose, | |||
| itemDetail, | |||
| onClose, | |||
| itemDetail, | |||
| setItemDetail, | |||
| qc, | |||
| warehouse, | |||
| }) => { | |||
| console.log(itemDetail) | |||
| }) => { | |||
| const [serverError, setServerError] = useState(""); | |||
| const { t } = useTranslation(); | |||
| 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<ModalFormInput>({ | |||
| // defaultValues: defaultValues ? defaultValues : {}, | |||
| // }); | |||
| const params = useSearchParams(); | |||
| console.log(params.get("id")); | |||
| console.log(itemDetail); | |||
| console.log(itemDetail.qcResult); | |||
| const formProps = useForm<ModalFormInput>({ | |||
| defaultValues: defaultValue | |||
| defaultValues: { | |||
| ...itemDetail, | |||
| }, | |||
| }); | |||
| // console.log(formProps); | |||
| const errors = formProps.formState.errors; | |||
| const closeHandler = useCallback<NonNullable<ModalProps["onClose"]>>( | |||
| (...args) => { | |||
| @@ -94,27 +105,52 @@ const PoQcStockInModal: React.FC<Props> = ({ | |||
| ); | |||
| useEffect(() => { | |||
| setDefaultValues({}); | |||
| }, []); | |||
| // setDefaultValues({...itemDetail}); | |||
| if (!itemDetail) { | |||
| console.log(itemDetail); | |||
| } | |||
| }, [itemDetail]); | |||
| const fix0IndexedDate = useCallback((date: string | number[] | undefined) => { | |||
| if (Array.isArray(date)) { | |||
| console.log(date) | |||
| return dayjs([date[0], date[1] - 1, date[2]]).format("YYYY-MM-DD") | |||
| } | |||
| return date | |||
| }, []) | |||
| const onSubmit = useCallback<SubmitHandler<ModalFormInput & {}>>( | |||
| async (data, event) => { | |||
| let hasErrors = false; | |||
| console.log(errors); | |||
| console.log(data); | |||
| console.log(itemDetail); | |||
| console.log(data.receiptDate) | |||
| console.log(fix0IndexedDate(data.receiptDate)) | |||
| try { | |||
| // add checking | |||
| // const qty = data.sampleRate | |||
| //////////////////////// modify this mess later ////////////////////// | |||
| var productionDate = null | |||
| var acceptedQty = null | |||
| var productionDate = null; | |||
| var expiryDate = null; | |||
| var receiptDate = null; | |||
| var acceptedQty = null; | |||
| if (data.productionDate && data.productionDate.length > 0) { | |||
| productionDate = data.productionDate | |||
| productionDate = fix0IndexedDate(data.productionDate); | |||
| } | |||
| if (data.expiryDate && data.expiryDate.length > 0) { | |||
| expiryDate = fix0IndexedDate(data.expiryDate); | |||
| } | |||
| if (data.receiptDate && data.receiptDate.length > 0) { | |||
| receiptDate = fix0IndexedDate(data.receiptDate); | |||
| } | |||
| // if () | |||
| if (data.qcResult) { | |||
| acceptedQty = itemDetail.acceptedQty - data.qcResult.reduce((acc, curr) => acc + curr.failQty, 0) | |||
| acceptedQty = | |||
| itemDetail.acceptedQty - | |||
| data.qcResult.reduce((acc, curr) => acc + curr.failQty, 0); | |||
| } | |||
| const args = { | |||
| id: itemDetail.id, | |||
| @@ -123,36 +159,43 @@ const PoQcStockInModal: React.FC<Props> = ({ | |||
| itemId: itemDetail.itemId, | |||
| ...data, | |||
| productionDate: productionDate, | |||
| expiryDate: expiryDate, | |||
| receiptDate: receiptDate, | |||
| } as StockInLineEntry & ModalFormInput; | |||
| ////////////////////////////////////////////////////////////////////// | |||
| console.log(args) | |||
| console.log(args); | |||
| // return | |||
| if (hasErrors) { | |||
| setServerError(t("An error has occurred. Please try again later.")); | |||
| return false; | |||
| } | |||
| const res = await updateStockInLine(args) | |||
| const res = await updateStockInLine(args); | |||
| if (Boolean(res.id)) { | |||
| // update entries | |||
| const newEntries = res.entity as StockInLine[] | |||
| setEntries((prev) => { | |||
| const updatedEntries = [...prev]; // Create a new array | |||
| newEntries.forEach((item) => { | |||
| const index = updatedEntries.findIndex(p => p.id === item.id); | |||
| if (index !== -1) { | |||
| // Update existing item | |||
| updatedEntries[index] = item; | |||
| } else { | |||
| // Add new item | |||
| updatedEntries.push(item); | |||
| } | |||
| const newEntries = res.entity as StockInLine[]; | |||
| console.log(newEntries) | |||
| if (setEntries) { | |||
| setEntries((prev) => { | |||
| const updatedEntries = [...prev]; // Create a new array | |||
| newEntries.forEach((item) => { | |||
| const index = updatedEntries.findIndex((p) => p.id === item.id); | |||
| if (index !== -1) { | |||
| // Update existing item | |||
| console.log(item) | |||
| updatedEntries[index] = item; | |||
| } else { | |||
| // Add new item | |||
| updatedEntries.push(item); | |||
| } | |||
| }); | |||
| return updatedEntries; // Return the new array | |||
| }); | |||
| return updatedEntries; // Return the new array | |||
| }) | |||
| } | |||
| // add loading | |||
| closeHandler({}, "backdropClick") | |||
| setItemDetail(undefined) | |||
| closeHandler({}, "backdropClick"); | |||
| } | |||
| console.log(res) | |||
| console.log(res); | |||
| // if (res) | |||
| } catch (e) { | |||
| // server error | |||
| @@ -162,39 +205,49 @@ const PoQcStockInModal: React.FC<Props> = ({ | |||
| }, | |||
| [t, itemDetail] | |||
| ); | |||
| const renderSubmitButton = useMemo((): Boolean => { | |||
| if (itemDetail) { | |||
| const status = itemDetail.status | |||
| console.log(status) | |||
| const status = itemDetail.status; | |||
| console.log(status); | |||
| switch (type) { | |||
| case "qc": | |||
| return stockInLineStatusMap[status] === 1 | |||
| case "putaway": | |||
| return stockInLineStatusMap[status] === 7 | |||
| return stockInLineStatusMap[status] >= 1 && stockInLineStatusMap[status] <= 2; | |||
| case "escalation": | |||
| return stockInLineStatusMap[status] === 1 || stockInLineStatusMap[status] >= 3 || stockInLineStatusMap[status] <= 5; | |||
| case "stockIn": | |||
| return stockInLineStatusMap[status] === 6 | |||
| return stockInLineStatusMap[status] >= 3 && stockInLineStatusMap[status] <= 6; | |||
| case "putaway": | |||
| return stockInLineStatusMap[status] === 7; | |||
| default: | |||
| return false; // Handle unexpected type | |||
| } | |||
| } else return false | |||
| }, [type, itemDetail]) | |||
| useEffect(() => { | |||
| console.log(renderSubmitButton) | |||
| }, [renderSubmitButton]) | |||
| } else return false; | |||
| }, [type, itemDetail]); | |||
| // useEffect(() => { | |||
| // console.log(renderSubmitButton) | |||
| // }, [renderSubmitButton]) | |||
| return ( | |||
| <> | |||
| <Modal open={open} onClose={closeHandler}> | |||
| <FormProvider {...formProps}> | |||
| <FormProvider {...formProps}> | |||
| <Modal open={open} onClose={closeHandler}> | |||
| <Box | |||
| sx={style} | |||
| component="form" | |||
| onSubmit={formProps.handleSubmit(onSubmit)} | |||
| > | |||
| {type === "qc" && <QcForm qc={qc} itemDetail={itemDetail} />} | |||
| {type === "stockIn" && <StockInForm itemDetail={itemDetail} />} | |||
| {type === "escalation" && <EscalationForm itemDetail={itemDetail} />} | |||
| {type === "putaway" && <PutawayForm itemDetail={itemDetail} warehouse={warehouse} />} | |||
| {itemDetail !== undefined && type === "qc" && ( | |||
| <QcForm qc={qc!!} itemDetail={itemDetail} /> | |||
| )} | |||
| {itemDetail !== undefined && type === "stockIn" && ( | |||
| <StockInForm itemDetail={itemDetail} /> | |||
| )} | |||
| {itemDetail !== undefined && type === "escalation" && ( | |||
| <EscalationForm itemDetail={itemDetail} /> | |||
| )} | |||
| {itemDetail !== undefined && type === "putaway" && ( | |||
| <PutawayForm itemDetail={itemDetail} warehouse={warehouse!!} /> | |||
| )} | |||
| {renderSubmitButton ? ( | |||
| <Stack direction="row" justifyContent="flex-end" gap={1}> | |||
| <Button | |||
| @@ -207,11 +260,10 @@ const PoQcStockInModal: React.FC<Props> = ({ | |||
| {t("submit")} | |||
| </Button> | |||
| </Stack> | |||
| ) : undefined | |||
| } | |||
| ) : undefined} | |||
| </Box> | |||
| </FormProvider> | |||
| </Modal> | |||
| </Modal> | |||
| </FormProvider> | |||
| </> | |||
| ); | |||
| }; | |||
| @@ -4,10 +4,13 @@ import { PurchaseQcResult, PutawayInput } from "@/app/api/po/actions"; | |||
| import { | |||
| Autocomplete, | |||
| Box, | |||
| Button, | |||
| Card, | |||
| CardContent, | |||
| FormControl, | |||
| Grid, | |||
| Modal, | |||
| ModalProps, | |||
| Stack, | |||
| TextField, | |||
| Tooltip, | |||
| @@ -35,8 +38,10 @@ import { GridEditInputCell } from "@mui/x-data-grid"; | |||
| import { StockInLine } from "@/app/api/po"; | |||
| import { WarehouseResult } from "@/app/api/warehouse"; | |||
| import { stockInLineStatusMap } from "@/app/utils/formatUtil"; | |||
| import { QRCodeSVG } from 'qrcode.react'; | |||
| import { QRCodeSVG } from "qrcode.react"; | |||
| import { QrCode } from "../QrCode"; | |||
| import ReactQrCodeScanner, { ScannerConfig } from "../ReactQrCodeScanner/ReactQrCodeScanner"; | |||
| import { QrCodeInfo } from "@/app/api/qrcode"; | |||
| interface Props { | |||
| itemDetail: StockInLine; | |||
| @@ -51,6 +56,18 @@ type EntryError = | |||
| // type PoQcRow = TableRow<Partial<PurchaseQcResult>, EntryError>; | |||
| const style = { | |||
| position: "absolute", | |||
| top: "50%", | |||
| left: "50%", | |||
| transform: "translate(-50%, -50%)", | |||
| bgcolor: "background.paper", | |||
| pt: 5, | |||
| px: 5, | |||
| pb: 10, | |||
| width: "auto", | |||
| }; | |||
| const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse }) => { | |||
| const { t } = useTranslation(); | |||
| const apiRef = useGridApiRef(); | |||
| @@ -108,18 +125,56 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse }) => { | |||
| [] | |||
| ); | |||
| const qrContent = useMemo(() => ({ | |||
| itemId: itemDetail.itemId, | |||
| lotNo: itemDetail.lotNo, | |||
| // expiryDate: itemDetail.expiryDate, | |||
| // productionDate: itemDetail.productionDate, | |||
| // supplier: itemDetail.supplier, | |||
| // poCode: itemDetail.poCode, | |||
| }),[itemDetail]) | |||
| const qrContent = useMemo( | |||
| () => ({ | |||
| stockInLineId: itemDetail.id, | |||
| itemId: itemDetail.itemId, | |||
| lotNo: itemDetail.lotNo, | |||
| // warehouseId: 1 // for testing | |||
| // expiryDate: itemDetail.expiryDate, | |||
| // productionDate: itemDetail.productionDate, | |||
| // supplier: itemDetail.supplier, | |||
| // poCode: itemDetail.poCode, | |||
| }), | |||
| [itemDetail] | |||
| ); | |||
| const [isOpenScanner, setOpenScanner] = useState(false); | |||
| const closeHandler = useCallback<NonNullable<ModalProps["onClose"]>>( | |||
| (...args) => { | |||
| setOpenScanner(false); | |||
| }, | |||
| [] | |||
| ); | |||
| const onOpenScanner = useCallback(() => { | |||
| setOpenScanner(true); | |||
| }, []); | |||
| const onCloseScanner = useCallback(() => { | |||
| setOpenScanner(false); | |||
| }, []); | |||
| const scannerConfig = useMemo<ScannerConfig>( | |||
| () => ({ | |||
| onUpdate: (err, result) => { | |||
| if (result) { | |||
| const data: QrCodeInfo = JSON.parse(result.getText()); | |||
| console.log(data); | |||
| if (data.warehouseId) { | |||
| setWarehouseId(data.warehouseId); | |||
| onCloseScanner() | |||
| } | |||
| } else return; | |||
| }, | |||
| }), | |||
| [] | |||
| ); | |||
| useEffect(() => { | |||
| setValue("status", "completed") | |||
| }, []) | |||
| setValue("status", "completed"); | |||
| }, []); | |||
| return ( | |||
| <Grid container justifyContent="flex-start" alignItems="flex-start"> | |||
| <Grid item xs={12}> | |||
| @@ -190,7 +245,7 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse }) => { | |||
| disabled | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={12}> | |||
| <Grid item xs={6}> | |||
| <TextField | |||
| label={t("expiryDate")} | |||
| fullWidth | |||
| @@ -199,6 +254,21 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse }) => { | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={6}> | |||
| <FormControl fullWidth> | |||
| <Autocomplete | |||
| noOptionsText={t("No Warehouse")} | |||
| disableClearable | |||
| disabled | |||
| fullWidth | |||
| defaultValue={options.find((o) => o.value === 1)} | |||
| // onChange={onChange} | |||
| getOptionLabel={(option) => option.label} | |||
| options={options} | |||
| renderInput={(params) => <TextField {...params} label="Default Warehouse"/>} | |||
| /> | |||
| </FormControl> | |||
| </Grid> | |||
| <Grid item xs={5.5}> | |||
| <TextField | |||
| label={t("acceptedQty")} | |||
| fullWidth | |||
| @@ -213,7 +283,10 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse }) => { | |||
| helperText={errors.acceptedQty?.message} | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={6}> | |||
| <Grid item xs={1}> | |||
| <Button onClick={onOpenScanner}>bind</Button> | |||
| </Grid> | |||
| <Grid item xs={5.5}> | |||
| <FormControl fullWidth> | |||
| <Autocomplete | |||
| noOptionsText={t("No Warehouse")} | |||
| @@ -227,27 +300,29 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse }) => { | |||
| /> | |||
| </FormControl> | |||
| </Grid> | |||
| <Grid item xs={12} style={{ display: 'flex', justifyContent: 'center' }}> | |||
| <QrCode content={qrContent} sx={{width: 200, height: 200}}/> | |||
| <Grid | |||
| item | |||
| xs={12} | |||
| style={{ display: "flex", justifyContent: "center" }} | |||
| > | |||
| <QrCode content={qrContent} sx={{ width: 200, height: 200 }} /> | |||
| </Grid> | |||
| </Grid> | |||
| <Grid | |||
| {/* <Grid | |||
| container | |||
| justifyContent="flex-start" | |||
| alignItems="flex-start" | |||
| spacing={2} | |||
| sx={{ mt: 0.5 }} | |||
| > | |||
| {/* <Grid item xs={12}> | |||
| <InputDataGrid<PutawayInput, PurchaseQcResult, EntryError> | |||
| apiRef={apiRef} | |||
| checkboxSelection={false} | |||
| _formKey={"qcCheck"} | |||
| columns={columns} | |||
| validateRow={validation} | |||
| /> | |||
| </Grid> */} | |||
| </Grid> | |||
| <Button onClick={onOpenScanner}>bind</Button> | |||
| </Grid> */} | |||
| <Modal open={isOpenScanner} onClose={closeHandler}> | |||
| <Box sx={style}> | |||
| <ReactQrCodeScanner scannerConfig={scannerConfig} /> | |||
| </Box> | |||
| </Modal> | |||
| </Grid> | |||
| ); | |||
| }; | |||
| @@ -31,14 +31,14 @@ import QcSelect from "./QcSelect"; | |||
| import { GridEditInputCell } from "@mui/x-data-grid"; | |||
| import { StockInLine } from "@/app/api/po"; | |||
| import { stockInLineStatusMap } from "@/app/utils/formatUtil"; | |||
| import { fetchQcItemCheck } from "@/app/api/qc/actions"; | |||
| import { fetchQcItemCheck, fetchQcResult } from "@/app/api/qc/actions"; | |||
| import { QcItemWithChecks } from "@/app/api/qc"; | |||
| import axios from "@/app/(main)/axios/axiosInstance"; | |||
| import { NEXT_PUBLIC_API_URL } from "@/config/api"; | |||
| import axiosInstance from "@/app/(main)/axios/axiosInstance"; | |||
| interface Props { | |||
| itemDetail: StockInLine; | |||
| itemDetail: StockInLine | |||
| qc: QcItemWithChecks[]; | |||
| } | |||
| type EntryError = | |||
| @@ -65,24 +65,8 @@ const QcForm: React.FC<Props> = ({ qc, itemDetail }) => { | |||
| clearErrors, | |||
| } = useFormContext<PurchaseQCInput>(); | |||
| console.log(itemDetail); | |||
| console.log(defaultValues); | |||
| // const [qc, setQc] = useState<QcItemWithChecks[]>([]) | |||
| const fetchQcCheck = useCallback(async () => { | |||
| const authHeader = axiosInstance.defaults.headers['Authorization']; | |||
| if (!authHeader) { | |||
| return; // Exit the function if the token is not set | |||
| } | |||
| const params = { | |||
| itemId: itemDetail.itemId | |||
| } | |||
| console.log(params) | |||
| const res = await axios.get<QcItemWithChecks[]>(`${NEXT_PUBLIC_API_URL}/qcCheck`, { params }) | |||
| console.log(res) | |||
| }, [axios]) | |||
| useEffect(() => { | |||
| fetchQcCheck() | |||
| }, [fetchQcCheck]) | |||
| const [recordQty, setRecordQty] = useState(0); | |||
| const columns = useMemo<GridColDef[]>( | |||
| () => [ | |||
| @@ -203,7 +187,7 @@ const QcForm: React.FC<Props> = ({ qc, itemDetail }) => { | |||
| spacing={2} | |||
| sx={{ mt: 0.5 }} | |||
| > | |||
| <Grid item xs={6}> | |||
| <Grid item xs={12} lg={6}> | |||
| <TextField | |||
| label={t("accepted Qty")} | |||
| fullWidth | |||
| @@ -217,7 +201,7 @@ const QcForm: React.FC<Props> = ({ qc, itemDetail }) => { | |||
| helperText={errors.acceptedQty?.message} | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={6}> | |||
| <Grid item xs={12} lg={6}> | |||
| <TextField | |||
| label={t("Total record qty")} | |||
| fullWidth | |||
| @@ -230,7 +214,7 @@ const QcForm: React.FC<Props> = ({ qc, itemDetail }) => { | |||
| // helperText={errors.sampleRate?.message} | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={4}> | |||
| <Grid item xs={12} lg={6}> | |||
| <TextField | |||
| label={t("sampleRate")} | |||
| fullWidth | |||
| @@ -242,7 +226,7 @@ const QcForm: React.FC<Props> = ({ qc, itemDetail }) => { | |||
| helperText={errors.sampleRate?.message} | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={4}> | |||
| <Grid item xs={12} lg={6}> | |||
| <TextField | |||
| label={t("sampleWeight")} | |||
| fullWidth | |||
| @@ -253,7 +237,7 @@ const QcForm: React.FC<Props> = ({ qc, itemDetail }) => { | |||
| helperText={errors.sampleWeight?.message} | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={4}> | |||
| <Grid item xs={12} lg={6}> | |||
| <TextField | |||
| label={t("totalWeight")} | |||
| fullWidth | |||
| @@ -279,6 +263,7 @@ const QcForm: React.FC<Props> = ({ qc, itemDetail }) => { | |||
| _formKey={"qcResult"} | |||
| columns={columns} | |||
| validateRow={validation} | |||
| needAdd={itemDetail.status === "qc" || itemDetail.status === "pending"} | |||
| /> | |||
| </Grid> | |||
| </Grid> | |||
| @@ -0,0 +1,42 @@ | |||
| "use client" | |||
| import { Box, Modal, ModalProps } from "@mui/material" | |||
| import { useCallback } from "react"; | |||
| interface Props extends Omit<ModalProps, "children"> { | |||
| }; | |||
| const style = { | |||
| position: "absolute", | |||
| top: "50%", | |||
| left: "50%", | |||
| transform: "translate(-50%, -50%)", | |||
| bgcolor: "background.paper", | |||
| pt: 5, | |||
| px: 5, | |||
| pb: 10, | |||
| width: { xs: "80%", sm: "80%", md: "80%" }, | |||
| }; | |||
| const QrCodeScanner: React.FC<Props> = ({ | |||
| open, | |||
| }) => { | |||
| const closeHandler = useCallback<NonNullable<ModalProps["onClose"]>>( | |||
| (...args) => { | |||
| // onClose?.(...args); | |||
| // reset(); | |||
| }, | |||
| [] | |||
| ); | |||
| return ( | |||
| <Modal open={open} onClose={closeHandler}> | |||
| <Box sx={style}> | |||
| <input type="file" accept="image/*" capture="environment"> | |||
| </input> | |||
| </Box> | |||
| </Modal>) | |||
| } | |||
| export default QrCodeScanner | |||
| @@ -0,0 +1,167 @@ | |||
| "use client"; | |||
| import { Box, Button, Grid, Modal, ModalProps, Stack } from "@mui/material"; | |||
| import { useCallback, useEffect, useMemo, useState } from "react"; | |||
| import ReactQrCodeScanner, { | |||
| ScannerConfig, | |||
| } from "../ReactQrCodeScanner/ReactQrCodeScanner"; | |||
| import { FormProvider, SubmitHandler, useForm } from "react-hook-form"; | |||
| import { | |||
| fetchStockInLineInfo, | |||
| ModalFormInput, | |||
| StockInLineEntry, | |||
| updateStockInLine, | |||
| } from "@/app/api/po/actions"; | |||
| import PutawayForm from "./PutawayForm"; | |||
| import { StockInLine } from "@/app/api/po"; | |||
| import { WarehouseResult } from "@/app/api/warehouse"; | |||
| import { QrCodeInfo } from "@/app/api/qrcode"; | |||
| import { Check } from "@mui/icons-material"; | |||
| import { useTranslation } from "react-i18next"; | |||
| interface Props extends Omit<ModalProps, "children"> { | |||
| warehouse: WarehouseResult[]; | |||
| } | |||
| const style = { | |||
| position: "absolute", | |||
| top: "50%", | |||
| left: "50%", | |||
| transform: "translate(-50%, -50%)", | |||
| bgcolor: "background.paper", | |||
| pt: 5, | |||
| px: 5, | |||
| pb: 10, | |||
| width: "auto", | |||
| }; | |||
| const QrModal: React.FC<Props> = ({ open, onClose, warehouse }) => { | |||
| const { t } = useTranslation(); | |||
| const [serverError, setServerError] = useState(""); | |||
| const formProps = useForm<ModalFormInput>({ | |||
| defaultValues: { | |||
| // ...itemDetail, | |||
| }, | |||
| }); | |||
| const errors = formProps.formState.errors; | |||
| const closeHandler = useCallback<NonNullable<ModalProps["onClose"]>>( | |||
| (...args) => { | |||
| onClose?.(...args); | |||
| setItemDetail(undefined); | |||
| setStockInLineId(undefined); | |||
| // reset(); | |||
| }, | |||
| [onClose] | |||
| ); | |||
| const [stockInLineId, setStockInLineId] = useState<number>(); | |||
| const scannerConfig = useMemo<ScannerConfig>( | |||
| () => ({ | |||
| onUpdate: (err, result) => { | |||
| if (result) { | |||
| const data: QrCodeInfo = JSON.parse(result.getText()); | |||
| console.log(data); | |||
| if (data.stockInLineId) { | |||
| console.log("still got in"); | |||
| console.log(data.stockInLineId); | |||
| setStockInLineId(data.stockInLineId); | |||
| } | |||
| } else return; | |||
| }, | |||
| }), | |||
| [] | |||
| ); | |||
| const [itemDetail, setItemDetail] = useState<StockInLine>(); | |||
| const fetchStockInLine = useCallback( | |||
| async (stockInLineId: number) => { | |||
| const res = await fetchStockInLineInfo(stockInLineId); | |||
| setItemDetail(res); | |||
| }, | |||
| [fetchStockInLineInfo] | |||
| ); | |||
| useEffect(() => { | |||
| if (stockInLineId) fetchStockInLine(stockInLineId); | |||
| }, [stockInLineId]); | |||
| const onSubmit = useCallback<SubmitHandler<ModalFormInput & {}>>( | |||
| async (data, event) => { | |||
| let hasErrors = false; | |||
| console.log(errors); | |||
| console.log(data); | |||
| console.log(itemDetail); | |||
| try { | |||
| // add checking | |||
| // const qty = data.sampleRate | |||
| //////////////////////// modify this mess later ////////////////////// | |||
| const args = { | |||
| // id: itemDetail.id, | |||
| // purchaseOrderId: parseInt(params.get("id")!!), | |||
| // purchaseOrderLineId: itemDetail.purchaseOrderLineId, | |||
| // itemId: itemDetail.itemId, | |||
| // ...data, | |||
| // productionDate: productionDate, | |||
| } as StockInLineEntry & ModalFormInput; | |||
| ////////////////////////////////////////////////////////////////////// | |||
| console.log(args); | |||
| // return | |||
| if (hasErrors) { | |||
| setServerError(t("An error has occurred. Please try again later.")); | |||
| return false; | |||
| } | |||
| return; | |||
| const res = await updateStockInLine(args); | |||
| if (Boolean(res.id)) { | |||
| // update entries | |||
| console.log(res); | |||
| // add loading | |||
| // closeHandler({}, "backdropClick"); | |||
| } | |||
| console.log(res); | |||
| // if (res) | |||
| } catch (e) { | |||
| // server error | |||
| setServerError(t("An error has occurred. Please try again later.")); | |||
| console.log(e); | |||
| } | |||
| }, | |||
| [t, itemDetail] | |||
| ); | |||
| return ( | |||
| <FormProvider {...formProps}> | |||
| <Modal open={open} onClose={closeHandler}> | |||
| <Box | |||
| sx={style} | |||
| component="form" | |||
| onSubmit={formProps.handleSubmit(onSubmit)} | |||
| > | |||
| <Grid container xs={12}> | |||
| <Grid item xs={12}> | |||
| {itemDetail != undefined ? ( | |||
| <> | |||
| <PutawayForm itemDetail={itemDetail} warehouse={warehouse} /> | |||
| <Stack direction="row" justifyContent="flex-end" gap={1}> | |||
| <Button | |||
| name="submit" | |||
| variant="contained" | |||
| startIcon={<Check />} | |||
| type="submit" | |||
| // disabled={submitDisabled} | |||
| > | |||
| {t("submit")} | |||
| </Button> | |||
| </Stack> | |||
| </> | |||
| ) : ( | |||
| <ReactQrCodeScanner scannerConfig={scannerConfig} /> | |||
| )} | |||
| </Grid> | |||
| </Grid> | |||
| </Box> | |||
| </Modal> | |||
| </FormProvider> | |||
| ); | |||
| }; | |||
| export default QrModal; | |||
| @@ -1,173 +1,234 @@ | |||
| import { Autocomplete, Box, Button, Card, CardContent, Grid, Modal, ModalProps, Stack, SxProps, TextField, Typography } from "@mui/material"; | |||
| import { CameraDevice, Html5Qrcode, Html5QrcodeCameraScanConfig, Html5QrcodeFullConfig, Html5QrcodeResult, Html5QrcodeScanner, Html5QrcodeScannerState, QrcodeErrorCallback, QrcodeSuccessCallback } from "html5-qrcode"; | |||
| import { | |||
| Autocomplete, | |||
| Box, | |||
| Button, | |||
| Card, | |||
| CardContent, | |||
| Grid, | |||
| Modal, | |||
| ModalProps, | |||
| Stack, | |||
| SxProps, | |||
| TextField, | |||
| Typography, | |||
| } from "@mui/material"; | |||
| import { | |||
| CameraDevice, | |||
| Html5Qrcode, | |||
| Html5QrcodeCameraScanConfig, | |||
| Html5QrcodeFullConfig, | |||
| Html5QrcodeResult, | |||
| Html5QrcodeScanner, | |||
| Html5QrcodeScannerState, | |||
| QrcodeErrorCallback, | |||
| QrcodeSuccessCallback, | |||
| } from "html5-qrcode"; | |||
| import { Html5QrcodeError } from "html5-qrcode/esm/core"; | |||
| import { Html5QrcodeScannerConfig } from "html5-qrcode/esm/html5-qrcode-scanner"; | |||
| import React, { RefObject, useCallback, useEffect, useMemo, useRef, useState } from "react"; | |||
| import React, { | |||
| RefObject, | |||
| useCallback, | |||
| useEffect, | |||
| useMemo, | |||
| useRef, | |||
| useState, | |||
| } from "react"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import QrCodeScannerIcon from '@mui/icons-material/QrCodeScanner'; | |||
| import StopCircleOutlinedIcon from '@mui/icons-material/StopCircleOutlined'; | |||
| import CloseOutlinedIcon from '@mui/icons-material/CloseOutlined'; | |||
| import QrCodeScannerIcon from "@mui/icons-material/QrCodeScanner"; | |||
| import StopCircleOutlinedIcon from "@mui/icons-material/StopCircleOutlined"; | |||
| import CloseOutlinedIcon from "@mui/icons-material/CloseOutlined"; | |||
| import { QrCodeInfo } from "@/app/api/qrcode"; | |||
| const scannerSx: React.CSSProperties = { | |||
| position: "absolute", | |||
| // top: "50%", | |||
| // left: "50%", | |||
| // transform: "translate(-50%, -50%)", | |||
| width: "90%", | |||
| maxHeight: "10%", | |||
| maxWidth: 1400, | |||
| position: "absolute", | |||
| // top: "50%", | |||
| // left: "50%", | |||
| // transform: "translate(-50%, -50%)", | |||
| width: "90%", | |||
| maxHeight: "10%", | |||
| maxWidth: 1400, | |||
| }; | |||
| type QrCodeScannerProps = { | |||
| cameras: CameraDevice[] | |||
| title?: string, | |||
| contents?: string[], | |||
| onScanSuccess: (result: string) => void, | |||
| onScanError?: (error: string) => void, | |||
| isOpen: boolean, | |||
| onClose: () => void | |||
| } | |||
| cameras: CameraDevice[]; | |||
| title?: string; | |||
| contents?: string[]; | |||
| onScanSuccess: (qrCodeInfo: QrCodeInfo) => void; | |||
| onScanError?: (error: string) => void; | |||
| isOpen: boolean; | |||
| onClose: () => void; | |||
| }; | |||
| const QrCodeScanner: React.FC<QrCodeScannerProps> = ({ | |||
| title, | |||
| contents, | |||
| onScanSuccess, | |||
| onScanError, | |||
| isOpen, | |||
| onClose | |||
| title, | |||
| contents, | |||
| onScanSuccess, | |||
| onScanError, | |||
| isOpen, | |||
| onClose, | |||
| }) => { | |||
| const { t } = useTranslation() | |||
| const [isScanned, setIsScanned] = useState<boolean>(false) | |||
| const [scanner, setScanner] = useState<Html5Qrcode | null>(null) | |||
| const [cameraList, setCameraList] = useState<CameraDevice[]>([]) | |||
| const [selectedCameraId, setSelectedCameraId] = useState<string | null>(null) | |||
| const stringList = ["ABC: abc", "123:123", "ABC: abc", "123:123", "ABC: abc", "123:123"] | |||
| const scannerConfig: Html5QrcodeFullConfig = { | |||
| verbose: false | |||
| const { t } = useTranslation(); | |||
| const [isScanned, setIsScanned] = useState<boolean>(false); | |||
| const [scanner, setScanner] = useState<Html5Qrcode | null>(null); | |||
| const [cameraList, setCameraList] = useState<CameraDevice[]>([]); | |||
| const [selectedCameraId, setSelectedCameraId] = useState<string | null>(null); | |||
| const stringList = [ | |||
| "ABC: abc", | |||
| "123:123", | |||
| "ABC: abc", | |||
| "123:123", | |||
| "ABC: abc", | |||
| "123:123", | |||
| ]; | |||
| const scannerConfig: Html5QrcodeFullConfig = { | |||
| verbose: false, | |||
| }; | |||
| const cameraConfig: Html5QrcodeCameraScanConfig = { | |||
| fps: 10, | |||
| qrbox: { width: 250, height: 250 }, | |||
| // aspectRatio: cardRef.current ? (cardRef.current.offsetWidth / cardRef.current.offsetHeight) : 1.78, | |||
| aspectRatio: (window.innerWidth / window.innerHeight) * 1.5, // can be better | |||
| }; | |||
| // MediaTrackConstraintSet | |||
| const mediaTrackConstraintSet = { | |||
| facingMode: "environment", | |||
| }; | |||
| const handleScanStart = useCallback(() => { | |||
| if (scanner && selectedCameraId) { | |||
| if (scanner.getState() === Html5QrcodeScannerState.SCANNING) { | |||
| console.log("first"); | |||
| scanner.stop(); | |||
| } | |||
| scanner.start( | |||
| selectedCameraId, | |||
| cameraConfig, | |||
| handleScanSuccess, | |||
| handleScanError | |||
| ); | |||
| } | |||
| }, [selectedCameraId, scanner]); | |||
| const cameraConfig: Html5QrcodeCameraScanConfig = { | |||
| fps: 10, | |||
| qrbox: { width: 250, height: 250 }, | |||
| // aspectRatio: cardRef.current ? (cardRef.current.offsetWidth / cardRef.current.offsetHeight) : 1.78, | |||
| aspectRatio: (window.innerWidth / window.innerHeight) * 1.5 // can be better | |||
| }; | |||
| const handleCameraList = useCallback(async () => { | |||
| const cameras = await Html5Qrcode.getCameras(); | |||
| setCameraList(cameras); | |||
| if (cameras.length > 0) { | |||
| handleCameraChange(cameras[cameras.length - 1].id); | |||
| } | |||
| }, []); | |||
| const handleCameraChange = useCallback((id: string) => { | |||
| setSelectedCameraId(id); | |||
| }, []); | |||
| // MediaTrackConstraintSet | |||
| const mediaTrackConstraintSet = { | |||
| facingMode: "environment" | |||
| const switchScanStatus = useCallback(() => { | |||
| if (scanner) { | |||
| console.log(isScanned); | |||
| switch (isScanned) { | |||
| case true: | |||
| setIsScanned(false); | |||
| scanner.resume(); | |||
| break; | |||
| case false: | |||
| setIsScanned(true); | |||
| scanner.pause(true); | |||
| break; | |||
| } | |||
| } | |||
| }, [scanner, isScanned]); | |||
| const handleScanStart = useCallback(() => { | |||
| if (scanner && selectedCameraId) { | |||
| if (scanner.getState() === Html5QrcodeScannerState.SCANNING) { | |||
| console.log("first") | |||
| scanner.stop() | |||
| } | |||
| const handleScanSuccess = useCallback<QrcodeSuccessCallback>( | |||
| (decodedText, result) => { | |||
| if (scanner) { | |||
| console.log(`Decoded text: ${decodedText}`); | |||
| const parseData: QrCodeInfo = JSON.parse(decodedText); | |||
| console.log(parseData); | |||
| // Handle the decoded text as needed | |||
| switchScanStatus(); | |||
| onScanSuccess(parseData); | |||
| } | |||
| }, | |||
| [scanner, onScanSuccess] | |||
| ); | |||
| scanner.start( | |||
| selectedCameraId, | |||
| cameraConfig, | |||
| handleScanSuccess, | |||
| handleScanError | |||
| ) | |||
| } | |||
| }, [selectedCameraId, scanner]) | |||
| const handleCameraList = useCallback(async () => { | |||
| const cameras = await Html5Qrcode.getCameras() | |||
| setCameraList(cameras) | |||
| if (cameras.length > 0) { | |||
| handleCameraChange(cameras[cameras.length-1].id) | |||
| } | |||
| }, []) | |||
| const handleCameraChange = useCallback((id: string) => { | |||
| setSelectedCameraId(id) | |||
| }, []) | |||
| const switchScanStatus = useCallback(() => { | |||
| if (scanner) { | |||
| console.log(isScanned) | |||
| switch (isScanned) { | |||
| case true: | |||
| setIsScanned(false); | |||
| scanner.resume(); | |||
| break; | |||
| case false: | |||
| setIsScanned(true); | |||
| scanner.pause(true); | |||
| break; | |||
| } | |||
| } | |||
| }, [scanner, isScanned]) | |||
| const handleScanSuccess = useCallback<QrcodeSuccessCallback>((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) | |||
| } | |||
| }, [scanner, onScanSuccess]) | |||
| const handleScanError = useCallback<QrcodeErrorCallback>((errorMessage, error) => { | |||
| // console.log(`Error: ${errorMessage}`); | |||
| if (onScanError) { | |||
| onScanError(errorMessage) | |||
| } | |||
| }, [scanner, onScanError]) | |||
| const handleScanCloseButton = useCallback(async () => { | |||
| if (scanner) { | |||
| console.log("Cleaning up scanner..."); | |||
| await scanner.stop() | |||
| scanner.clear() | |||
| onClose() | |||
| } | |||
| }, [scanner]) | |||
| // close modal without using Cancel Button | |||
| const handleScanClose = useCallback(async () => { | |||
| if (scanner && !isOpen) { | |||
| handleScanCloseButton() | |||
| } | |||
| }, [scanner, isOpen, handleScanCloseButton]) | |||
| // -------------------------------------------------------// | |||
| useEffect(() => { | |||
| setScanner(new Html5Qrcode( | |||
| "qr-reader", | |||
| scannerConfig | |||
| )) | |||
| handleCameraList() | |||
| }, []) | |||
| useEffect(() => { | |||
| handleScanStart() | |||
| }, [scanner, selectedCameraId]); | |||
| useEffect(() => { | |||
| handleScanClose() | |||
| }, [isOpen]) | |||
| return ( | |||
| <> | |||
| <Stack spacing={2}> | |||
| {title && <Typography variant="overline" display="block" marginBlockEnd={1} paddingLeft={2}> | |||
| {"Title"} | |||
| </Typography>} | |||
| <Grid container alignItems="center" justifyContent="center" rowSpacing={2} columns={{ xs: 6, sm: 12 }}> | |||
| <Grid item xs={12}> | |||
| <div style={{ textAlign: "center", margin: "auto", justifyContent: "center" }} id="qr-reader" hidden={isScanned} /> | |||
| </Grid> | |||
| {/* {cameraList.length > 0 && <Grid item xs={6} > | |||
| const handleScanError = useCallback<QrcodeErrorCallback>( | |||
| (errorMessage, error) => { | |||
| // console.log(`Error: ${errorMessage}`); | |||
| if (onScanError) { | |||
| onScanError(errorMessage); | |||
| } | |||
| }, | |||
| [scanner, onScanError] | |||
| ); | |||
| const handleScanCloseButton = useCallback(async () => { | |||
| if (scanner) { | |||
| console.log("Cleaning up scanner..."); | |||
| await scanner.stop(); | |||
| scanner.clear(); | |||
| onClose(); | |||
| } | |||
| }, [scanner]); | |||
| // close modal without using Cancel Button | |||
| const handleScanClose = useCallback(async () => { | |||
| if (scanner && !isOpen) { | |||
| handleScanCloseButton(); | |||
| } | |||
| }, [scanner, isOpen, handleScanCloseButton]); | |||
| // -------------------------------------------------------// | |||
| useEffect(() => { | |||
| setScanner(new Html5Qrcode("qr-reader", scannerConfig)); | |||
| handleCameraList(); | |||
| }, []); | |||
| useEffect(() => { | |||
| handleScanStart(); | |||
| }, [scanner, selectedCameraId]); | |||
| useEffect(() => { | |||
| handleScanClose(); | |||
| }, [isOpen]); | |||
| return ( | |||
| <> | |||
| <Stack spacing={2}> | |||
| {title && ( | |||
| <Typography | |||
| variant="overline" | |||
| display="block" | |||
| marginBlockEnd={1} | |||
| paddingLeft={2} | |||
| > | |||
| {"Title"} | |||
| </Typography> | |||
| )} | |||
| <Grid | |||
| container | |||
| alignItems="center" | |||
| justifyContent="center" | |||
| rowSpacing={2} | |||
| columns={{ xs: 6, sm: 12 }} | |||
| > | |||
| <Grid item xs={12}> | |||
| <div | |||
| style={{ | |||
| textAlign: "center", | |||
| margin: "auto", | |||
| justifyContent: "center", | |||
| }} | |||
| id="qr-reader" | |||
| hidden={isScanned} | |||
| /> | |||
| </Grid> | |||
| {/* {cameraList.length > 0 && <Grid item xs={6} > | |||
| <Autocomplete | |||
| disableClearable | |||
| noOptionsText={t("No Options")} | |||
| @@ -185,36 +246,46 @@ const QrCodeScanner: React.FC<QrCodeScannerProps> = ({ | |||
| )} | |||
| /> | |||
| </Grid>} */} | |||
| { | |||
| contents && contents.map((string) => { | |||
| return <Grid item xs={8}>{string}</Grid> | |||
| }) | |||
| } | |||
| {contents && | |||
| contents.map((string) => { | |||
| return ( | |||
| <Grid item xs={8}> | |||
| {string} | |||
| </Grid> | |||
| <Stack direction="row" justifyContent={"flex-end"} spacing={2} sx={{ margin: 2 }}> | |||
| <Button | |||
| size="small" | |||
| onClick={switchScanStatus} | |||
| variant="contained" | |||
| // sx={{ margin: 2 }} | |||
| startIcon={isScanned ? <QrCodeScannerIcon /> : <StopCircleOutlinedIcon />} | |||
| > | |||
| {isScanned ? t("Start Scanning") : t("Stop Scanning")} | |||
| </Button> | |||
| <Button | |||
| size="small" | |||
| onClick={handleScanCloseButton} | |||
| variant="outlined" | |||
| // color="error" | |||
| // sx={{ margin: 2 }} | |||
| startIcon={<CloseOutlinedIcon />} | |||
| > | |||
| {t("Cancel")} | |||
| </Button> | |||
| </Stack> | |||
| </Stack> | |||
| </> | |||
| ) | |||
| } | |||
| export default QrCodeScanner | |||
| ); | |||
| })} | |||
| </Grid> | |||
| <Stack | |||
| direction="row" | |||
| justifyContent={"flex-end"} | |||
| spacing={2} | |||
| sx={{ margin: 2 }} | |||
| > | |||
| <Button | |||
| size="small" | |||
| onClick={switchScanStatus} | |||
| variant="contained" | |||
| // sx={{ margin: 2 }} | |||
| startIcon={ | |||
| isScanned ? <QrCodeScannerIcon /> : <StopCircleOutlinedIcon /> | |||
| } | |||
| > | |||
| {isScanned ? t("Start Scanning") : t("Stop Scanning")} | |||
| </Button> | |||
| <Button | |||
| size="small" | |||
| onClick={handleScanCloseButton} | |||
| variant="outlined" | |||
| // color="error" | |||
| // sx={{ margin: 2 }} | |||
| startIcon={<CloseOutlinedIcon />} | |||
| > | |||
| {t("Cancel")} | |||
| </Button> | |||
| </Stack> | |||
| </Stack> | |||
| </> | |||
| ); | |||
| }; | |||
| export default QrCodeScanner; | |||
| @@ -11,6 +11,7 @@ import { | |||
| import QrCodeScanner from "./QrCodeScanner"; | |||
| import { useCallback, useEffect, useRef, useState } from "react"; | |||
| import { CameraDevice } from "html5-qrcode"; | |||
| import { QrCodeInfo } from "@/app/api/qrcode"; | |||
| const modalSx: SxProps = { | |||
| position: "absolute", | |||
| @@ -28,7 +29,7 @@ type QrCodeScannerModalProps = { | |||
| contents?: string[]; | |||
| isOpen: boolean; | |||
| onClose: () => void; | |||
| onScanSuccess: (result: string) => void; | |||
| onScanSuccess: (qrCodeInfo: QrCodeInfo) => void; | |||
| onScanError?: (error: string) => void; | |||
| }; | |||
| @@ -0,0 +1,91 @@ | |||
| "use client"; | |||
| import { QrCodeInfo } from "@/app/api/qrcode"; | |||
| import { Box, Button, Modal, ModalProps } from "@mui/material"; | |||
| import { useCallback, useEffect, useMemo, useState } from "react"; | |||
| import BarcodeScanner, { BarcodeStringFormat } from "react-qr-barcode-scanner"; | |||
| import { BarcodeFormat, Result } from "@zxing/library"; | |||
| interface Props { | |||
| scannerConfig: ScannerConfig; | |||
| } | |||
| // interface Props extends Omit<ModalProps, "children"> { | |||
| // scannerConfig: ScannerConfig; | |||
| // } | |||
| const style = { | |||
| position: "absolute", | |||
| top: "50%", | |||
| left: "50%", | |||
| transform: "translate(-50%, -50%)", | |||
| bgcolor: "background.paper", | |||
| pt: 5, | |||
| px: 5, | |||
| pb: 10, | |||
| width: { xs: "80%", sm: "80%", md: "80%" }, | |||
| }; | |||
| export var defaultScannerConfig: ScannerConfig = { | |||
| onUpdate: (err, result) => { | |||
| if (result) { | |||
| const data = JSON.parse(result.getText()) | |||
| console.log(data); | |||
| } else return; | |||
| }, | |||
| width: 500, | |||
| height: 500, | |||
| facingMode: "environment", | |||
| // torch: false | |||
| }; | |||
| export interface ScannerConfig { | |||
| onUpdate: (arg0: unknown, arg1?: Result) => void; | |||
| onError?: (arg0: string | DOMException) => void; | |||
| width?: number | string; | |||
| height?: number | string; | |||
| facingMode?: "environment" | "user"; // default environment | |||
| delay?: number; // Delay between scans in milliseconds. Default is 500ms. | |||
| videoConstraints?: MediaTrackConstraints; // Video constraints to pass to the webcam. If not provided, the default constraints will be used. | |||
| formats?: BarcodeFormat[] | BarcodeStringFormat[]; // Array of barcode formats to decode. If not provided, all formats will be used. A smaller list may improve the speed of the scan. | |||
| stopStream?: boolean | |||
| } | |||
| const ReactQrCodeScanner: React.FC<Props> = ({ | |||
| scannerConfig, | |||
| }) => { | |||
| const [stopStream, setStopStream] = useState(scannerConfig.stopStream || defaultScannerConfig.stopStream || false); | |||
| const [torchEnabled, setTorchEnabled] = useState<boolean>(false); | |||
| const _scannerConfig = useMemo(() => ({ | |||
| ...defaultScannerConfig, | |||
| ...scannerConfig, | |||
| }),[]) | |||
| const SwitchOnOffScanner = useCallback(() => { | |||
| // Stop the QR Reader stream (fixes issue where the browser freezes when closing the modal) and then dismiss the modal one tick later | |||
| setStopStream((prev) => !prev); | |||
| }, []); | |||
| const SwitchOnOffTorch = useCallback(() => { | |||
| setTorchEnabled((prev) => !prev); | |||
| }, []); | |||
| return ( | |||
| <> | |||
| {!stopStream ? ( | |||
| <BarcodeScanner | |||
| stopStream={stopStream} | |||
| torch={torchEnabled} | |||
| {..._scannerConfig} | |||
| /> | |||
| ) : undefined} | |||
| <Button onClick={SwitchOnOffTorch}> | |||
| {torchEnabled ? "off" : "on"} | |||
| </Button> | |||
| <Button onClick={SwitchOnOffScanner}> | |||
| {stopStream ? "start" : "stop"} | |||
| </Button> | |||
| </> | |||
| ); | |||
| }; | |||
| export default ReactQrCodeScanner; | |||