@@ -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; |