@@ -12,6 +12,7 @@ export interface PostStockInLiineResponse<T> { | |||||
id: number | null; | id: number | null; | ||||
name: string; | name: string; | ||||
code: string; | code: string; | ||||
type?: string | |||||
message: string | null; | message: string | null; | ||||
errorPosition: string | keyof T; | errorPosition: string | keyof T; | ||||
entity: StockInLine | StockInLine[] | 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) => { | export const createStockInLine = async (data: StockInLineEntry) => { | ||||
const stockInLine = await serverFetchJson<PostStockInLiineResponse<StockInLineEntry>>(`${BASE_API_URL}/stockInLine/create`, { | const stockInLine = await serverFetchJson<PostStockInLiineResponse<StockInLineEntry>>(`${BASE_API_URL}/stockInLine/create`, { | ||||
method: "POST", | method: "POST", | ||||
@@ -104,4 +95,13 @@ export const updateStockInLine = async (data: StockInLineEntry & ModalFormInput) | |||||
return stockInLine | 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 | lotNo: string | ||||
poCode: string | poCode: string | ||||
uom: Uom | uom: Uom | ||||
defaultWarehouseId: number // id for now | |||||
} | } | ||||
export const fetchPoList = cache(async () => { | 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, | GridValidRowModel, | ||||
useGridApiRef, | useGridApiRef, | ||||
} from "@mui/x-data-grid"; | } 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 SaveIcon from "@mui/icons-material/Save"; | ||||
import DeleteIcon from "@mui/icons-material/Delete"; | import DeleteIcon from "@mui/icons-material/Delete"; | ||||
import CancelIcon from "@mui/icons-material/Cancel"; | import CancelIcon from "@mui/icons-material/Cancel"; | ||||
import { Add } from "@mui/icons-material"; | import { Add } from "@mui/icons-material"; | ||||
import { Box, Button, Typography } from "@mui/material"; | import { Box, Button, Typography } from "@mui/material"; | ||||
import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
import { GridApiCommunity } from "@mui/x-data-grid/internals"; | |||||
import { GridApiCommunity, GridSlotsComponentsProps } from "@mui/x-data-grid/internals"; | |||||
interface ResultWithId { | interface ResultWithId { | ||||
id: string | number; | id: string | number; | ||||
@@ -67,6 +67,7 @@ export interface InputDataGridProps<T, V, E> { | |||||
_formKey: keyof T; | _formKey: keyof T; | ||||
columns: GridColDef[]; | columns: GridColDef[]; | ||||
validateRow: (newRow: GridRowModel<TableRow<V, E>>) => E; | validateRow: (newRow: GridRowModel<TableRow<V, E>>) => E; | ||||
needAdd?: Boolean | |||||
}; | }; | ||||
export interface SelectionInputDataGridProps<T, V, E> { // thinking how do | 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; | _formKey: keyof T; | ||||
columns: GridColDef[]; | columns: GridColDef[]; | ||||
validateRow: (newRow: GridRowModel<TableRow<V, E>>) => E; | validateRow: (newRow: GridRowModel<TableRow<V, E>>) => E; | ||||
needAdd?: Boolean | |||||
} | } | ||||
export type Props<T, V, E> = InputDataGridProps<T, V, E> | SelectionInputDataGridProps<T, V, E> | 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 | // E == error | ||||
function InputDataGrid<T, V, E>({ | function InputDataGrid<T, V, E>({ | ||||
apiRef, | apiRef, | ||||
checkboxSelection, | |||||
checkboxSelection = false, | |||||
_formKey, | _formKey, | ||||
columns, | columns, | ||||
validateRow, | validateRow, | ||||
needAdd, | |||||
}: Props<T, V, E>) { | }: Props<T, V, E>) { | ||||
const { | const { | ||||
t, | t, | ||||
@@ -113,6 +116,7 @@ function InputDataGrid<T, V, E>({ | |||||
[] | [] | ||||
); | ); | ||||
const list: TableRow<V, E>[] = getValues(formKey); | const list: TableRow<V, E>[] = getValues(formKey); | ||||
// console.log(list) | |||||
const [rows, setRows] = useState<TableRow<V, E>[]>(() => { | const [rows, setRows] = useState<TableRow<V, E>[]>(() => { | ||||
const list: TableRow<V, E>[] = getValues(formKey); | const list: TableRow<V, E>[] = getValues(formKey); | ||||
return list && list.length > 0 ? list : []; | return list && list.length > 0 ? list : []; | ||||
@@ -348,9 +352,13 @@ function InputDataGrid<T, V, E>({ | |||||
footer: FooterToolbar, | footer: FooterToolbar, | ||||
noRowsOverlay: NoRowsOverlay, | noRowsOverlay: NoRowsOverlay, | ||||
} : undefined} | } : undefined} | ||||
slotProps={!checkboxSelection ? { | |||||
slotProps={!checkboxSelection && Boolean(needAdd) ? { | |||||
footer: { child: footer }, | footer: { child: footer }, | ||||
} : undefined} | |||||
}: undefined | |||||
// slotProps={renderFooter ? { | |||||
// footer: { child: footer }, | |||||
// }: undefined | |||||
} | |||||
/> | /> | ||||
) | ) | ||||
} | } | ||||
@@ -28,9 +28,26 @@ import { | |||||
} from "@mui/material"; | } from "@mui/material"; | ||||
import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
// import InputDataGrid, { TableRow } from "../InputDataGrid/InputDataGrid"; | // 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 { FormProvider, useForm } from "react-hook-form"; | ||||
import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown"; | import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown"; | ||||
import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp"; | import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp"; | ||||
@@ -46,6 +63,13 @@ import QrCodeScanner from "../QrCodeScanner"; | |||||
import { CameraDevice, Html5Qrcode } from "html5-qrcode"; | import { CameraDevice, Html5Qrcode } from "html5-qrcode"; | ||||
import { CameraContext } from "../Cameras/CameraProvider"; | import { CameraContext } from "../Cameras/CameraProvider"; | ||||
import StyledDataGrid from "../StyledDataGrid"; | 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 = { | type Props = { | ||||
po: PoResult; | po: PoResult; | ||||
@@ -72,7 +96,6 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||||
const [open, setOpen] = useState(false); | const [open, setOpen] = useState(false); | ||||
const [processedQty, setProcessedQty] = useState(row.processed); | const [processedQty, setProcessedQty] = useState(row.processed); | ||||
const [currStatus, setCurrStatus] = useState(row.status); | const [currStatus, setCurrStatus] = useState(row.status); | ||||
useEffect(() => { | useEffect(() => { | ||||
if (processedQty === row.qty) { | if (processedQty === row.qty) { | ||||
setCurrStatus("completed".toUpperCase()); | 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 [isOpenScanner, setOpenScanner] = useState(false); | ||||
const onOpenScanner = useCallback(() => { | const onOpenScanner = useCallback(() => { | ||||
@@ -155,17 +185,33 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||||
setOpenScanner(false); | 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 ( | return ( | ||||
<> | <> | ||||
@@ -180,30 +226,34 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||||
{po.code} | {po.code} | ||||
</Typography> | </Typography> | ||||
</Grid> | </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> | ||||
<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 | <Tabs | ||||
value={tabIndex} | value={tabIndex} | ||||
onChange={handleTabChange} | onChange={handleTabChange} | ||||
variant="scrollable" | variant="scrollable" | ||||
> | > | ||||
<Tab label={t("General")} iconPosition="end" /> | <Tab label={t("General")} iconPosition="end" /> | ||||
<Tab label={t("Bind Storage")} iconPosition="end" /> | |||||
{/* <Tab label={t("Bind Storage")} iconPosition="end" /> */} | |||||
</Tabs> | </Tabs> | ||||
</Grid> | </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" }}> | <Grid sx={{ display: tabIndex === 0 ? "block" : "none" }}> | ||||
<TableContainer component={Paper}> | <TableContainer component={Paper}> | ||||
<Table aria-label="collapsible table"> | <Table aria-label="collapsible table"> | ||||
@@ -238,6 +288,18 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||||
/> */} | /> */} | ||||
</Grid> | </Grid> | ||||
</Stack> | </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 PoDetailWrapper: React.FC<Props> & SubComponents = async ({ id }) => { | ||||
const [ | |||||
poWithStockInLine, | |||||
warehouse, | |||||
qc, | |||||
] = await Promise.all([ | |||||
const [poWithStockInLine, warehouse, qc] = await Promise.all([ | |||||
fetchPoWithStockInLines(id), | fetchPoWithStockInLines(id), | ||||
fetchWarehouseList(), | fetchWarehouseList(), | ||||
fetchQcItemCheck() | |||||
]) | |||||
fetchQcItemCheck(), | |||||
]); | |||||
// const poWithStockInLine = await fetchPoWithStockInLines(id) | // 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; | PoDetailWrapper.Loading = PoDetailLoading; | ||||
@@ -33,17 +33,14 @@ import ShoppingCartIcon from "@mui/icons-material/ShoppingCart"; | |||||
import { QcItemWithChecks } from "src/app/api/qc"; | import { QcItemWithChecks } from "src/app/api/qc"; | ||||
import PlayArrowIcon from "@mui/icons-material/PlayArrow"; | import PlayArrowIcon from "@mui/icons-material/PlayArrow"; | ||||
import { PurchaseOrderLine, StockInLine } from "@/app/api/po"; | 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 { useSearchParams } from "next/navigation"; | ||||
import { | import { | ||||
returnWeightUnit, | returnWeightUnit, | ||||
calculateWeight, | calculateWeight, | ||||
stockInLineStatusMap, | stockInLineStatusMap, | ||||
} from "@/app/utils/formatUtil"; | } from "@/app/utils/formatUtil"; | ||||
import PoQcStockInModal from "./PoQcStockInModal"; | |||||
// import PoQcStockInModal from "./PoQcStockInModal"; | |||||
import NotificationImportantIcon from "@mui/icons-material/NotificationImportant"; | import NotificationImportantIcon from "@mui/icons-material/NotificationImportant"; | ||||
import { WarehouseResult } from "@/app/api/warehouse"; | import { WarehouseResult } from "@/app/api/warehouse"; | ||||
import LooksOneIcon from "@mui/icons-material/LooksOne"; | 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 { downloadFile } from "@/app/utils/commonUtil"; | ||||
import { fetchPoQrcode } from "@/app/api/pdf/actions"; | import { fetchPoQrcode } from "@/app/api/pdf/actions"; | ||||
import { fetchQcResult } from "@/app/api/qc/actions"; | import { fetchQcResult } from "@/app/api/qc/actions"; | ||||
import PoQcStockInModal from "./PoQcStockInModal"; | |||||
interface ResultWithId { | interface ResultWithId { | ||||
id: number; | id: number; | ||||
} | } | ||||
@@ -116,7 +115,9 @@ function PoInputGrid({ | |||||
); | ); | ||||
console.log(stockInLine); | console.log(stockInLine); | ||||
const [entries, setEntries] = useState<StockInLineRow[]>(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 [qcOpen, setQcOpen] = useState(false); | ||||
const [escalOpen, setEscalOpen] = useState(false); | const [escalOpen, setEscalOpen] = useState(false); | ||||
const [stockInOpen, setStockInOpen] = useState(false); | const [stockInOpen, setStockInOpen] = useState(false); | ||||
@@ -176,19 +177,9 @@ function PoInputGrid({ | |||||
}, | }, | ||||
[createStockInLine] | [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( | const handleQC = useCallback( | ||||
(id: GridRowId, params: any) => async () => { | (id: GridRowId, params: any) => async () => { | ||||
@@ -196,11 +187,12 @@ function PoInputGrid({ | |||||
...prev, | ...prev, | ||||
[id]: { mode: GridRowModes.View }, | [id]: { mode: GridRowModes.View }, | ||||
})); | })); | ||||
const qcResult = await fetchQcDefaultValue(); | |||||
console.log(qcResult) | |||||
const qcResult = await fetchQcDefaultValue(id); | |||||
console.log(params.row); | |||||
console.log(qcResult); | |||||
setModalInfo({ | setModalInfo({ | ||||
...params.row, | ...params.row, | ||||
qcResult: qcResult | |||||
qcResult: qcResult, | |||||
}); | }); | ||||
// set default values | // set default values | ||||
setTimeout(() => { | setTimeout(() => { | ||||
@@ -414,7 +406,7 @@ function PoInputGrid({ | |||||
}} | }} | ||||
disabled={ | disabled={ | ||||
stockInLineStatusMap[status] <= 0 || | stockInLineStatusMap[status] <= 0 || | ||||
stockInLineStatusMap[status] >= 5 | |||||
stockInLineStatusMap[status] >= 4 | |||||
} | } | ||||
// set _isNew to false after posting | // set _isNew to false after posting | ||||
// or check status | // or check status | ||||
@@ -429,7 +421,7 @@ function PoInputGrid({ | |||||
color: "primary.main", | color: "primary.main", | ||||
// marginRight: 1, | // marginRight: 1, | ||||
}} | }} | ||||
disabled={stockInLineStatusMap[status] !== 6} | |||||
disabled={stockInLineStatusMap[status] <= 2 || stockInLineStatusMap[status] >= 7} | |||||
// set _isNew to false after posting | // set _isNew to false after posting | ||||
// or check status | // or check status | ||||
onClick={handleStockIn(params.row.id, params)} | onClick={handleStockIn(params.row.id, params)} | ||||
@@ -563,13 +555,6 @@ function PoInputGrid({ | |||||
[apiRef] | [apiRef] | ||||
); | ); | ||||
// useEffect(() => { | |||||
// const total = entries.reduce( | |||||
// (acc, curr) => acc + (curr.acceptedQty || 0), | |||||
// 0 | |||||
// ); | |||||
// setDefaultQty(itemDetail.qty - total); | |||||
// }, [entries]); | |||||
const footer = ( | const footer = ( | ||||
<Box display="flex" gap={2} alignItems="center"> | <Box display="flex" gap={2} alignItems="center"> | ||||
<Button | <Button | ||||
@@ -584,6 +569,9 @@ function PoInputGrid({ | |||||
</Button> | </Button> | ||||
</Box> | </Box> | ||||
); | ); | ||||
useEffect(() => { | |||||
console.log(modalInfo); | |||||
}, [modalInfo]); | |||||
return ( | return ( | ||||
<> | <> | ||||
<StyledDataGrid | <StyledDataGrid | ||||
@@ -631,46 +619,58 @@ function PoInputGrid({ | |||||
footer: { child: footer }, | 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"; | "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 { 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 { FormProvider, SubmitHandler, useForm } from "react-hook-form"; | ||||
import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
import QcForm from "./QcForm"; | import QcForm from "./QcForm"; | ||||
import { QcItemWithChecks } from "@/app/api/qc"; | 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 { StockInLine } from "@/app/api/po"; | ||||
import { useSearchParams } from "next/navigation"; | import { useSearchParams } from "next/navigation"; | ||||
import { StockInLineRow } from "./PoInputGrid"; | import { StockInLineRow } from "./PoInputGrid"; | ||||
@@ -15,35 +29,38 @@ import EscalationForm from "./EscalationForm"; | |||||
import StockInForm from "./StockInForm"; | import StockInForm from "./StockInForm"; | ||||
import PutawayForm from "./PutawayForm"; | import PutawayForm from "./PutawayForm"; | ||||
import { stockInLineStatusMap } from "@/app/utils/formatUtil"; | 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"> { | 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[]; | qc?: QcItemWithChecks[]; | ||||
warehouse?: any[]; | warehouse?: any[]; | ||||
type: "qc" | "stockIn" | "escalation" | "putaway" | |||||
type: "qc" | "stockIn" | "escalation" | "putaway"; | |||||
} | } | ||||
interface QcProps extends CommonProps { | interface QcProps extends CommonProps { | ||||
qc: QcItemWithChecks[]; | qc: QcItemWithChecks[]; | ||||
type: "qc" | |||||
} | |||||
type: "qc"; | |||||
} | |||||
interface StockInProps extends CommonProps { | interface StockInProps extends CommonProps { | ||||
// naming | // naming | ||||
type: "stockIn" | |||||
type: "stockIn"; | |||||
} | } | ||||
interface PutawayProps extends CommonProps { | interface PutawayProps extends CommonProps { | ||||
// naming | |||||
// warehouse: any[]; | |||||
warehouse: any[]; | warehouse: any[]; | ||||
type: "putaway" | |||||
type: "putaway"; | |||||
} | } | ||||
interface EscalationProps extends CommonProps { | interface EscalationProps extends CommonProps { | ||||
// naming | // naming | ||||
type: "escalation" | |||||
type: "escalation"; | |||||
} | } | ||||
type Props = QcProps | StockInProps | EscalationProps | PutawayProps; | |||||
type Props = QcProps | StockInProps | PutawayProps | EscalationProps; | |||||
const style = { | const style = { | ||||
position: "absolute", | position: "absolute", | ||||
top: "50%", | top: "50%", | ||||
@@ -55,35 +72,29 @@ const style = { | |||||
pb: 10, | pb: 10, | ||||
width: { xs: "80%", sm: "80%", md: "80%" }, | width: { xs: "80%", sm: "80%", md: "80%" }, | ||||
}; | }; | ||||
const PoQcStockInModal: React.FC<Props> = ({ | const PoQcStockInModal: React.FC<Props> = ({ | ||||
type, | type, | ||||
setEntries, | setEntries, | ||||
open, | open, | ||||
onClose, | |||||
itemDetail, | |||||
onClose, | |||||
itemDetail, | |||||
setItemDetail, | |||||
qc, | qc, | ||||
warehouse, | warehouse, | ||||
}) => { | |||||
console.log(itemDetail) | |||||
}) => { | |||||
const [serverError, setServerError] = useState(""); | const [serverError, setServerError] = useState(""); | ||||
const { t } = useTranslation(); | 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>({ | const formProps = useForm<ModalFormInput>({ | ||||
defaultValues: defaultValue | |||||
defaultValues: { | |||||
...itemDetail, | |||||
}, | |||||
}); | }); | ||||
// console.log(formProps); | |||||
const errors = formProps.formState.errors; | const errors = formProps.formState.errors; | ||||
const closeHandler = useCallback<NonNullable<ModalProps["onClose"]>>( | const closeHandler = useCallback<NonNullable<ModalProps["onClose"]>>( | ||||
(...args) => { | (...args) => { | ||||
@@ -94,27 +105,52 @@ const PoQcStockInModal: React.FC<Props> = ({ | |||||
); | ); | ||||
useEffect(() => { | 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 & {}>>( | const onSubmit = useCallback<SubmitHandler<ModalFormInput & {}>>( | ||||
async (data, event) => { | async (data, event) => { | ||||
let hasErrors = false; | let hasErrors = false; | ||||
console.log(errors); | console.log(errors); | ||||
console.log(data); | console.log(data); | ||||
console.log(itemDetail); | console.log(itemDetail); | ||||
console.log(data.receiptDate) | |||||
console.log(fix0IndexedDate(data.receiptDate)) | |||||
try { | try { | ||||
// add checking | // add checking | ||||
// const qty = data.sampleRate | // const qty = data.sampleRate | ||||
//////////////////////// modify this mess later ////////////////////// | //////////////////////// 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) { | 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) { | 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 = { | const args = { | ||||
id: itemDetail.id, | id: itemDetail.id, | ||||
@@ -123,36 +159,43 @@ const PoQcStockInModal: React.FC<Props> = ({ | |||||
itemId: itemDetail.itemId, | itemId: itemDetail.itemId, | ||||
...data, | ...data, | ||||
productionDate: productionDate, | productionDate: productionDate, | ||||
expiryDate: expiryDate, | |||||
receiptDate: receiptDate, | |||||
} as StockInLineEntry & ModalFormInput; | } as StockInLineEntry & ModalFormInput; | ||||
////////////////////////////////////////////////////////////////////// | ////////////////////////////////////////////////////////////////////// | ||||
console.log(args) | |||||
console.log(args); | |||||
// return | // return | ||||
if (hasErrors) { | if (hasErrors) { | ||||
setServerError(t("An error has occurred. Please try again later.")); | setServerError(t("An error has occurred. Please try again later.")); | ||||
return false; | return false; | ||||
} | } | ||||
const res = await updateStockInLine(args) | |||||
const res = await updateStockInLine(args); | |||||
if (Boolean(res.id)) { | if (Boolean(res.id)) { | ||||
// update entries | // 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 | // add loading | ||||
closeHandler({}, "backdropClick") | |||||
setItemDetail(undefined) | |||||
closeHandler({}, "backdropClick"); | |||||
} | } | ||||
console.log(res) | |||||
console.log(res); | |||||
// if (res) | // if (res) | ||||
} catch (e) { | } catch (e) { | ||||
// server error | // server error | ||||
@@ -162,39 +205,49 @@ const PoQcStockInModal: React.FC<Props> = ({ | |||||
}, | }, | ||||
[t, itemDetail] | [t, itemDetail] | ||||
); | ); | ||||
const renderSubmitButton = useMemo((): Boolean => { | const renderSubmitButton = useMemo((): Boolean => { | ||||
if (itemDetail) { | if (itemDetail) { | ||||
const status = itemDetail.status | |||||
console.log(status) | |||||
const status = itemDetail.status; | |||||
console.log(status); | |||||
switch (type) { | switch (type) { | ||||
case "qc": | 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": | case "stockIn": | ||||
return stockInLineStatusMap[status] === 6 | |||||
return stockInLineStatusMap[status] >= 3 && stockInLineStatusMap[status] <= 6; | |||||
case "putaway": | |||||
return stockInLineStatusMap[status] === 7; | |||||
default: | default: | ||||
return false; // Handle unexpected type | 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 ( | return ( | ||||
<> | <> | ||||
<Modal open={open} onClose={closeHandler}> | |||||
<FormProvider {...formProps}> | |||||
<FormProvider {...formProps}> | |||||
<Modal open={open} onClose={closeHandler}> | |||||
<Box | <Box | ||||
sx={style} | sx={style} | ||||
component="form" | component="form" | ||||
onSubmit={formProps.handleSubmit(onSubmit)} | 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 ? ( | {renderSubmitButton ? ( | ||||
<Stack direction="row" justifyContent="flex-end" gap={1}> | <Stack direction="row" justifyContent="flex-end" gap={1}> | ||||
<Button | <Button | ||||
@@ -207,11 +260,10 @@ const PoQcStockInModal: React.FC<Props> = ({ | |||||
{t("submit")} | {t("submit")} | ||||
</Button> | </Button> | ||||
</Stack> | </Stack> | ||||
) : undefined | |||||
} | |||||
) : undefined} | |||||
</Box> | </Box> | ||||
</FormProvider> | |||||
</Modal> | |||||
</Modal> | |||||
</FormProvider> | |||||
</> | </> | ||||
); | ); | ||||
}; | }; | ||||
@@ -4,10 +4,13 @@ import { PurchaseQcResult, PutawayInput } from "@/app/api/po/actions"; | |||||
import { | import { | ||||
Autocomplete, | Autocomplete, | ||||
Box, | Box, | ||||
Button, | |||||
Card, | Card, | ||||
CardContent, | CardContent, | ||||
FormControl, | FormControl, | ||||
Grid, | Grid, | ||||
Modal, | |||||
ModalProps, | |||||
Stack, | Stack, | ||||
TextField, | TextField, | ||||
Tooltip, | Tooltip, | ||||
@@ -35,8 +38,10 @@ import { GridEditInputCell } from "@mui/x-data-grid"; | |||||
import { StockInLine } from "@/app/api/po"; | import { StockInLine } from "@/app/api/po"; | ||||
import { WarehouseResult } from "@/app/api/warehouse"; | import { WarehouseResult } from "@/app/api/warehouse"; | ||||
import { stockInLineStatusMap } from "@/app/utils/formatUtil"; | import { stockInLineStatusMap } from "@/app/utils/formatUtil"; | ||||
import { QRCodeSVG } from 'qrcode.react'; | |||||
import { QRCodeSVG } from "qrcode.react"; | |||||
import { QrCode } from "../QrCode"; | import { QrCode } from "../QrCode"; | ||||
import ReactQrCodeScanner, { ScannerConfig } from "../ReactQrCodeScanner/ReactQrCodeScanner"; | |||||
import { QrCodeInfo } from "@/app/api/qrcode"; | |||||
interface Props { | interface Props { | ||||
itemDetail: StockInLine; | itemDetail: StockInLine; | ||||
@@ -51,6 +56,18 @@ type EntryError = | |||||
// type PoQcRow = TableRow<Partial<PurchaseQcResult>, 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 PutawayForm: React.FC<Props> = ({ itemDetail, warehouse }) => { | ||||
const { t } = useTranslation(); | const { t } = useTranslation(); | ||||
const apiRef = useGridApiRef(); | 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(() => { | useEffect(() => { | ||||
setValue("status", "completed") | |||||
}, []) | |||||
setValue("status", "completed"); | |||||
}, []); | |||||
return ( | return ( | ||||
<Grid container justifyContent="flex-start" alignItems="flex-start"> | <Grid container justifyContent="flex-start" alignItems="flex-start"> | ||||
<Grid item xs={12}> | <Grid item xs={12}> | ||||
@@ -190,7 +245,7 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse }) => { | |||||
disabled | disabled | ||||
/> | /> | ||||
</Grid> | </Grid> | ||||
<Grid item xs={12}> | |||||
<Grid item xs={6}> | |||||
<TextField | <TextField | ||||
label={t("expiryDate")} | label={t("expiryDate")} | ||||
fullWidth | fullWidth | ||||
@@ -199,6 +254,21 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse }) => { | |||||
/> | /> | ||||
</Grid> | </Grid> | ||||
<Grid item xs={6}> | <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 | <TextField | ||||
label={t("acceptedQty")} | label={t("acceptedQty")} | ||||
fullWidth | fullWidth | ||||
@@ -213,7 +283,10 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse }) => { | |||||
helperText={errors.acceptedQty?.message} | helperText={errors.acceptedQty?.message} | ||||
/> | /> | ||||
</Grid> | </Grid> | ||||
<Grid item xs={6}> | |||||
<Grid item xs={1}> | |||||
<Button onClick={onOpenScanner}>bind</Button> | |||||
</Grid> | |||||
<Grid item xs={5.5}> | |||||
<FormControl fullWidth> | <FormControl fullWidth> | ||||
<Autocomplete | <Autocomplete | ||||
noOptionsText={t("No Warehouse")} | noOptionsText={t("No Warehouse")} | ||||
@@ -227,27 +300,29 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse }) => { | |||||
/> | /> | ||||
</FormControl> | </FormControl> | ||||
</Grid> | </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> | ||||
<Grid | |||||
{/* <Grid | |||||
container | container | ||||
justifyContent="flex-start" | justifyContent="flex-start" | ||||
alignItems="flex-start" | alignItems="flex-start" | ||||
spacing={2} | spacing={2} | ||||
sx={{ mt: 0.5 }} | 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> | </Grid> | ||||
); | ); | ||||
}; | }; | ||||
@@ -31,14 +31,14 @@ import QcSelect from "./QcSelect"; | |||||
import { GridEditInputCell } from "@mui/x-data-grid"; | import { GridEditInputCell } from "@mui/x-data-grid"; | ||||
import { StockInLine } from "@/app/api/po"; | import { StockInLine } from "@/app/api/po"; | ||||
import { stockInLineStatusMap } from "@/app/utils/formatUtil"; | 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 { QcItemWithChecks } from "@/app/api/qc"; | ||||
import axios from "@/app/(main)/axios/axiosInstance"; | import axios from "@/app/(main)/axios/axiosInstance"; | ||||
import { NEXT_PUBLIC_API_URL } from "@/config/api"; | import { NEXT_PUBLIC_API_URL } from "@/config/api"; | ||||
import axiosInstance from "@/app/(main)/axios/axiosInstance"; | import axiosInstance from "@/app/(main)/axios/axiosInstance"; | ||||
interface Props { | interface Props { | ||||
itemDetail: StockInLine; | |||||
itemDetail: StockInLine | |||||
qc: QcItemWithChecks[]; | qc: QcItemWithChecks[]; | ||||
} | } | ||||
type EntryError = | type EntryError = | ||||
@@ -65,24 +65,8 @@ const QcForm: React.FC<Props> = ({ qc, itemDetail }) => { | |||||
clearErrors, | clearErrors, | ||||
} = useFormContext<PurchaseQCInput>(); | } = useFormContext<PurchaseQCInput>(); | ||||
console.log(itemDetail); | 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 [recordQty, setRecordQty] = useState(0); | ||||
const columns = useMemo<GridColDef[]>( | const columns = useMemo<GridColDef[]>( | ||||
() => [ | () => [ | ||||
@@ -203,7 +187,7 @@ const QcForm: React.FC<Props> = ({ qc, itemDetail }) => { | |||||
spacing={2} | spacing={2} | ||||
sx={{ mt: 0.5 }} | sx={{ mt: 0.5 }} | ||||
> | > | ||||
<Grid item xs={6}> | |||||
<Grid item xs={12} lg={6}> | |||||
<TextField | <TextField | ||||
label={t("accepted Qty")} | label={t("accepted Qty")} | ||||
fullWidth | fullWidth | ||||
@@ -217,7 +201,7 @@ const QcForm: React.FC<Props> = ({ qc, itemDetail }) => { | |||||
helperText={errors.acceptedQty?.message} | helperText={errors.acceptedQty?.message} | ||||
/> | /> | ||||
</Grid> | </Grid> | ||||
<Grid item xs={6}> | |||||
<Grid item xs={12} lg={6}> | |||||
<TextField | <TextField | ||||
label={t("Total record qty")} | label={t("Total record qty")} | ||||
fullWidth | fullWidth | ||||
@@ -230,7 +214,7 @@ const QcForm: React.FC<Props> = ({ qc, itemDetail }) => { | |||||
// helperText={errors.sampleRate?.message} | // helperText={errors.sampleRate?.message} | ||||
/> | /> | ||||
</Grid> | </Grid> | ||||
<Grid item xs={4}> | |||||
<Grid item xs={12} lg={6}> | |||||
<TextField | <TextField | ||||
label={t("sampleRate")} | label={t("sampleRate")} | ||||
fullWidth | fullWidth | ||||
@@ -242,7 +226,7 @@ const QcForm: React.FC<Props> = ({ qc, itemDetail }) => { | |||||
helperText={errors.sampleRate?.message} | helperText={errors.sampleRate?.message} | ||||
/> | /> | ||||
</Grid> | </Grid> | ||||
<Grid item xs={4}> | |||||
<Grid item xs={12} lg={6}> | |||||
<TextField | <TextField | ||||
label={t("sampleWeight")} | label={t("sampleWeight")} | ||||
fullWidth | fullWidth | ||||
@@ -253,7 +237,7 @@ const QcForm: React.FC<Props> = ({ qc, itemDetail }) => { | |||||
helperText={errors.sampleWeight?.message} | helperText={errors.sampleWeight?.message} | ||||
/> | /> | ||||
</Grid> | </Grid> | ||||
<Grid item xs={4}> | |||||
<Grid item xs={12} lg={6}> | |||||
<TextField | <TextField | ||||
label={t("totalWeight")} | label={t("totalWeight")} | ||||
fullWidth | fullWidth | ||||
@@ -279,6 +263,7 @@ const QcForm: React.FC<Props> = ({ qc, itemDetail }) => { | |||||
_formKey={"qcResult"} | _formKey={"qcResult"} | ||||
columns={columns} | columns={columns} | ||||
validateRow={validation} | validateRow={validation} | ||||
needAdd={itemDetail.status === "qc" || itemDetail.status === "pending"} | |||||
/> | /> | ||||
</Grid> | </Grid> | ||||
</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 { Html5QrcodeError } from "html5-qrcode/esm/core"; | ||||
import { Html5QrcodeScannerConfig } from "html5-qrcode/esm/html5-qrcode-scanner"; | 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 { 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 = { | 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 = { | 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> = ({ | 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 | <Autocomplete | ||||
disableClearable | disableClearable | ||||
noOptionsText={t("No Options")} | noOptionsText={t("No Options")} | ||||
@@ -185,36 +246,46 @@ const QrCodeScanner: React.FC<QrCodeScannerProps> = ({ | |||||
)} | )} | ||||
/> | /> | ||||
</Grid>} */} | </Grid>} */} | ||||
{ | |||||
contents && contents.map((string) => { | |||||
return <Grid item xs={8}>{string}</Grid> | |||||
}) | |||||
} | |||||
{contents && | |||||
contents.map((string) => { | |||||
return ( | |||||
<Grid item xs={8}> | |||||
{string} | |||||
</Grid> | </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 QrCodeScanner from "./QrCodeScanner"; | ||||
import { useCallback, useEffect, useRef, useState } from "react"; | import { useCallback, useEffect, useRef, useState } from "react"; | ||||
import { CameraDevice } from "html5-qrcode"; | import { CameraDevice } from "html5-qrcode"; | ||||
import { QrCodeInfo } from "@/app/api/qrcode"; | |||||
const modalSx: SxProps = { | const modalSx: SxProps = { | ||||
position: "absolute", | position: "absolute", | ||||
@@ -28,7 +29,7 @@ type QrCodeScannerModalProps = { | |||||
contents?: string[]; | contents?: string[]; | ||||
isOpen: boolean; | isOpen: boolean; | ||||
onClose: () => void; | onClose: () => void; | ||||
onScanSuccess: (result: string) => void; | |||||
onScanSuccess: (qrCodeInfo: QrCodeInfo) => void; | |||||
onScanError?: (error: string) => 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; |