@@ -4,6 +4,9 @@ This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next | |||||
It is recommended to use the same node and npm versions for development. An easy way to do so would be to use `nvm` ([Linux/MacOS](https://github.com/nvm-sh/nvm), [Windows](https://github.com/coreybutler/nvm-windows)). | It is recommended to use the same node and npm versions for development. An easy way to do so would be to use `nvm` ([Linux/MacOS](https://github.com/nvm-sh/nvm), [Windows](https://github.com/coreybutler/nvm-windows)). | ||||
## Version | |||||
nvm: 1.1.12 | |||||
After installing `nvm`, run: | After installing `nvm`, run: | ||||
```bash | ```bash | ||||
@@ -29,4 +32,12 @@ This project uses the following libraries: | |||||
- [NextJS](https://nextjs.org/docs) | - [NextJS](https://nextjs.org/docs) | ||||
- [Next-Auth](https://next-auth.js.org/getting-started/example) | - [Next-Auth](https://next-auth.js.org/getting-started/example) | ||||
- [Material UI](https://mui.com/material-ui/getting-started/) | - [Material UI](https://mui.com/material-ui/getting-started/) | ||||
- [i18next](https://www.i18next.com/overview/getting-started) | |||||
- [i18next](https://www.i18next.com/overview/getting-started) | |||||
## Qrcode Testing | |||||
https://stackoverflow.com/questions/16835421/how-to-allow-chrome-to-access-my-camera-on-localhost | |||||
Local Qrcode testing require setting tweak | |||||
Steps: | |||||
1. Navigate to chrome://flags/#unsafely-treat-insecure-origin-as-secure in Chrome. | |||||
2. Find and enable the Insecure origins treated as secure section (see below). | |||||
3. Add any addresses you want to ignore the secure origin policy for. Remember to include the port number too (if required). |
@@ -6,8 +6,9 @@ import Box from "@mui/material/Box"; | |||||
import { NAVIGATION_CONTENT_WIDTH } from "@/config/uiConfig"; | import { NAVIGATION_CONTENT_WIDTH } from "@/config/uiConfig"; | ||||
import Stack from "@mui/material/Stack"; | import Stack from "@mui/material/Stack"; | ||||
import Breadcrumb from "@/components/Breadcrumb"; | import Breadcrumb from "@/components/Breadcrumb"; | ||||
import {AxiosProvider} from "@/app/(main)/axios/AxiosProvider"; | |||||
import {SetupAxiosInterceptors} from "@/app/(main)/axios/axiosInstance"; | |||||
import { AxiosProvider } from "@/app/(main)/axios/AxiosProvider"; | |||||
import { SetupAxiosInterceptors } from "@/app/(main)/axios/axiosInstance"; | |||||
import { CameraProvider } from "@/components/Cameras/CameraProvider"; | |||||
export default async function MainLayout({ | export default async function MainLayout({ | ||||
children, | children, | ||||
@@ -20,30 +21,32 @@ export default async function MainLayout({ | |||||
redirect("/login"); | redirect("/login"); | ||||
} | } | ||||
if(session){ | |||||
SetupAxiosInterceptors(session?.accessToken); | |||||
if (session) { | |||||
SetupAxiosInterceptors(session?.accessToken); | |||||
} | } | ||||
return ( | return ( | ||||
<CameraProvider> | |||||
<AxiosProvider> | <AxiosProvider> | ||||
<> | |||||
<AppBar | |||||
profileName={session.user.name!} | |||||
avatarImageSrc={session.user.image || undefined} | |||||
/> | |||||
<Box | |||||
component="main" | |||||
sx={{ | |||||
marginInlineStart: { xs: 0, xl: NAVIGATION_CONTENT_WIDTH }, | |||||
padding: { xs: "1rem", sm: "1.5rem", lg: "3rem" }, | |||||
}} | |||||
> | |||||
<Stack spacing={2}> | |||||
<Breadcrumb /> | |||||
{children} | |||||
</Stack> | |||||
</Box> | |||||
</> | |||||
<> | |||||
<AppBar | |||||
profileName={session.user.name!} | |||||
avatarImageSrc={session.user.image || undefined} | |||||
/> | |||||
<Box | |||||
component="main" | |||||
sx={{ | |||||
marginInlineStart: { xs: 0, xl: NAVIGATION_CONTENT_WIDTH }, | |||||
padding: { xs: "1rem", sm: "1.5rem", lg: "3rem" }, | |||||
}} | |||||
> | |||||
<Stack spacing={2}> | |||||
<Breadcrumb /> | |||||
{children} | |||||
</Stack> | |||||
</Box> | |||||
</> | |||||
</AxiosProvider> | </AxiosProvider> | ||||
</CameraProvider> | |||||
); | ); | ||||
} | } |
@@ -5,6 +5,7 @@ import { revalidateTag } from "next/cache"; | |||||
import { cache } from "react"; | import { cache } from "react"; | ||||
import { PoResult, StockInLine } from "."; | import { PoResult, StockInLine } from "."; | ||||
import { serverFetchJson } from "@/app/utils/fetchUtil"; | import { serverFetchJson } from "@/app/utils/fetchUtil"; | ||||
import { QcItemResult } from "../settings/qcItem"; | |||||
// import { BASE_API_URL } from "@/config/api"; | // import { BASE_API_URL } from "@/config/api"; | ||||
export interface PostStockInLiineResponse<T> { | export interface PostStockInLiineResponse<T> { | ||||
@@ -69,6 +70,22 @@ 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 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", | ||||
@@ -5,6 +5,10 @@ import { cache } from "react"; | |||||
import { serverFetchJson } from "@/app/utils/fetchUtil"; | import { serverFetchJson } from "@/app/utils/fetchUtil"; | ||||
import { QcItemWithChecks } from "."; | import { QcItemWithChecks } from "."; | ||||
export interface QcResult { | |||||
} | |||||
export const fetchQcItemCheck = cache(async (itemId?: number) => { | export const fetchQcItemCheck = cache(async (itemId?: number) => { | ||||
var url = `${BASE_API_URL}/qcCheck` | var url = `${BASE_API_URL}/qcCheck` | ||||
if (itemId) url +=`/${itemId}` | if (itemId) url +=`/${itemId}` | ||||
@@ -12,4 +16,10 @@ export const fetchQcItemCheck = cache(async (itemId?: number) => { | |||||
next: { tags: ["qc"] }, | next: { tags: ["qc"] }, | ||||
}); | }); | ||||
}); | }); | ||||
export const fetchQcResult = cache(async (id: number) => { | |||||
return serverFetchJson<any[]>(`${BASE_API_URL}/qcResult/${id}`, { | |||||
next: { tags: ["qc"] }, | |||||
}); | |||||
}); |
@@ -8,7 +8,7 @@ export interface Uom { | |||||
code: string | code: string | ||||
name: string | name: string | ||||
unit1: string | unit1: string | ||||
unit1Qty: number | |||||
unit1Qty: number | |||||
unit2?: string | unit2?: string | ||||
unit2Qty: number | unit2Qty: number | ||||
unit3?: string | unit3?: string | ||||
@@ -17,4 +17,4 @@ export interface Uom { | |||||
unit4Qty: number | unit4Qty: number | ||||
sizeInGram: number | sizeInGram: number | ||||
gramPerSmallestUnit: number | gramPerSmallestUnit: number | ||||
} | |||||
} |
@@ -6,5 +6,4 @@ export const downloadFile = (blobData: Uint8Array, filename: string) => { | |||||
link.href = url; | link.href = url; | ||||
link.setAttribute("download", filename); | link.setAttribute("download", filename); | ||||
link.click(); | link.click(); | ||||
} | |||||
} |
@@ -0,0 +1,26 @@ | |||||
"use client"; | |||||
import { CameraDevice, Html5Qrcode } from "html5-qrcode"; | |||||
import React, { createContext, useContext, useEffect, useState } from "react"; | |||||
export const CameraContext = createContext<CameraDevice[]>([]); | |||||
export const CameraProvider: React.FC<{ children: React.ReactNode }> = ({ | |||||
children, | |||||
}) => { | |||||
const [cameras, setCameras] = useState<CameraDevice[]>([]); | |||||
useEffect(() => { | |||||
const fetchCameras = async () => { | |||||
const res = await Html5Qrcode.getCameras(); | |||||
if (res) { | |||||
setCameras(res); | |||||
} | |||||
}; | |||||
fetchCameras(); | |||||
}, []); | |||||
return ( | |||||
<CameraContext.Provider value={cameras}>{children}</CameraContext.Provider> | |||||
); | |||||
}; |
@@ -30,7 +30,7 @@ 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 { GridColDef, GridRowModel, useGridApiRef } from "@mui/x-data-grid"; | ||||
import { testFetch } from "@/app/api/po/actions"; | import { testFetch } from "@/app/api/po/actions"; | ||||
import { useCallback, useEffect, useMemo, useState } from "react"; | |||||
import { useCallback, useContext, useEffect, useMemo, useState } from "react"; | |||||
import { FormProvider, useForm } from "react-hook-form"; | import { 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"; | ||||
@@ -42,6 +42,10 @@ import { QcItemWithChecks } from "@/app/api/qc"; | |||||
import { useSearchParams } from "next/navigation"; | import { useSearchParams } from "next/navigation"; | ||||
import { WarehouseResult } from "@/app/api/warehouse"; | import { WarehouseResult } from "@/app/api/warehouse"; | ||||
import { calculateWeight, returnWeightUnit } from "@/app/utils/formatUtil"; | import { calculateWeight, returnWeightUnit } from "@/app/utils/formatUtil"; | ||||
import QrCodeScanner from "../QrCodeScanner"; | |||||
import { CameraDevice, Html5Qrcode } from "html5-qrcode"; | |||||
import { CameraContext } from "../Cameras/CameraProvider"; | |||||
import StyledDataGrid from "../StyledDataGrid"; | |||||
type Props = { | type Props = { | ||||
po: PoResult; | po: PoResult; | ||||
@@ -54,9 +58,10 @@ type EntryError = | |||||
[field in keyof StockInLine]?: string; | [field in keyof StockInLine]?: string; | ||||
} | } | ||||
| undefined; | | undefined; | ||||
// type PolRow = TableRow<Partial<StockInLine>, EntryError>; | // type PolRow = TableRow<Partial<StockInLine>, EntryError>; | ||||
const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | ||||
const cameras = useContext(CameraContext); | |||||
console.log(cameras); | |||||
const { t } = useTranslation(); | const { t } = useTranslation(); | ||||
const apiRef = useGridApiRef(); | const apiRef = useGridApiRef(); | ||||
const [rows, setRows] = useState<PurchaseOrderLine[]>(po.pol || []); | const [rows, setRows] = useState<PurchaseOrderLine[]>(po.pol || []); | ||||
@@ -77,8 +82,16 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||||
setCurrStatus("pending".toUpperCase()); | setCurrStatus("pending".toUpperCase()); | ||||
} | } | ||||
}, [processedQty]); | }, [processedQty]); | ||||
const totalWeight = useMemo(() => calculateWeight(row.qty, row.uom), []); | |||||
const weightUnit = useMemo(() => returnWeightUnit(row.uom), []); | |||||
const totalWeight = useMemo( | |||||
() => calculateWeight(row.qty, row.uom), | |||||
[calculateWeight] | |||||
); | |||||
const weightUnit = useMemo( | |||||
() => returnWeightUnit(row.uom), | |||||
[returnWeightUnit] | |||||
); | |||||
return ( | return ( | ||||
<> | <> | ||||
<TableRow sx={{ "& > *": { borderBottom: "unset" }, color: "black" }}> | <TableRow sx={{ "& > *": { borderBottom: "unset" }, color: "black" }}> | ||||
@@ -133,6 +146,27 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||||
); | ); | ||||
} | } | ||||
const [isOpenScanner, setOpenScanner] = useState(false); | |||||
const onOpenScanner = useCallback(() => { | |||||
setOpenScanner(true); | |||||
}, []); | |||||
const onCloseScanner = useCallback(() => { | |||||
setOpenScanner(false); | |||||
}, []); | |||||
const handleScanSuccess = useCallback((result: string) => { | |||||
console.log(result); | |||||
}, []); | |||||
const [tabIndex, setTabIndex] = useState(0); | |||||
const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>( | |||||
(_e, newValue) => { | |||||
setTabIndex(newValue); | |||||
}, | |||||
[] | |||||
); | |||||
return ( | return ( | ||||
<> | <> | ||||
<Stack | <Stack | ||||
@@ -140,12 +174,37 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||||
// component="form" | // component="form" | ||||
// onSubmit={formProps.handleSubmit(onSubmit, onSubmitError)} | // onSubmit={formProps.handleSubmit(onSubmit, onSubmitError)} | ||||
> | > | ||||
<Grid> | |||||
<Typography mb={2} variant="h4"> | |||||
{po.code} | |||||
</Typography> | |||||
<Grid container xs={12} justifyContent="space-between"> | |||||
<Grid item> | |||||
<Typography mb={2} variant="h4"> | |||||
{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> | ||||
<Grid> | |||||
<Grid container xs={12}> | |||||
<Tabs | |||||
value={tabIndex} | |||||
onChange={handleTabChange} | |||||
variant="scrollable" | |||||
> | |||||
<Tab label={t("General")} iconPosition="end" /> | |||||
<Tab label={t("Bind Storage")} iconPosition="end" /> | |||||
</Tabs> | |||||
</Grid> | |||||
{/* tab 1 */} | |||||
<Grid sx={{ display: tabIndex === 0 ? "block" : "none" }}> | |||||
<TableContainer component={Paper}> | <TableContainer component={Paper}> | ||||
<Table aria-label="collapsible table"> | <Table aria-label="collapsible table"> | ||||
<TableHead> | <TableHead> | ||||
@@ -172,6 +231,12 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||||
</Table> | </Table> | ||||
</TableContainer> | </TableContainer> | ||||
</Grid> | </Grid> | ||||
{/* tab 2 */} | |||||
<Grid sx={{ display: tabIndex === 1 ? "block" : "none" }}> | |||||
{/* <StyledDataGrid | |||||
/> */} | |||||
</Grid> | |||||
</Stack> | </Stack> | ||||
</> | </> | ||||
); | ); | ||||
@@ -33,22 +33,30 @@ 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, testFetch } from "@/app/api/po/actions"; | |||||
import { | |||||
createStockInLine, | |||||
PurchaseQcResult, | |||||
} from "@/app/api/po/actions"; | |||||
import { useSearchParams } from "next/navigation"; | import { useSearchParams } from "next/navigation"; | ||||
import { returnWeightUnit, calculateWeight, stockInLineStatusMap } from "@/app/utils/formatUtil"; | |||||
import { | |||||
returnWeightUnit, | |||||
calculateWeight, | |||||
stockInLineStatusMap, | |||||
} from "@/app/utils/formatUtil"; | |||||
import PoQcStockInModal from "./PoQcStockInModal"; | import 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 LooksTwoIcon from '@mui/icons-material/LooksTwo'; | |||||
import Looks3Icon from '@mui/icons-material/Looks3'; | |||||
import LooksOneIcon from "@mui/icons-material/LooksOne"; | |||||
import LooksTwoIcon from "@mui/icons-material/LooksTwo"; | |||||
import Looks3Icon from "@mui/icons-material/Looks3"; | |||||
import axiosInstance from "@/app/(main)/axios/axiosInstance"; | import axiosInstance from "@/app/(main)/axios/axiosInstance"; | ||||
// import axios, { AxiosRequestConfig } from "axios"; | // import axios, { AxiosRequestConfig } from "axios"; | ||||
import { NEXT_PUBLIC_API_URL } from "@/config/api"; | |||||
import qs from 'qs'; | |||||
import QrCodeIcon from '@mui/icons-material/QrCode'; | |||||
import { BASE_API_URL, NEXT_PUBLIC_API_URL } from "@/config/api"; | |||||
import qs from "qs"; | |||||
import QrCodeIcon from "@mui/icons-material/QrCode"; | |||||
import { downloadFile } from "@/app/utils/commonUtil"; | import { 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"; | |||||
interface ResultWithId { | interface ResultWithId { | ||||
id: number; | id: number; | ||||
} | } | ||||
@@ -98,7 +106,7 @@ function PoInputGrid({ | |||||
stockInLine, | stockInLine, | ||||
warehouse, | warehouse, | ||||
}: Props) { | }: Props) { | ||||
console.log(itemDetail) | |||||
console.log(itemDetail); | |||||
const { t } = useTranslation("home"); | const { t } = useTranslation("home"); | ||||
const apiRef = useGridApiRef(); | const apiRef = useGridApiRef(); | ||||
const [rowModesModel, setRowModesModel] = useState<GridRowModesModel>({}); | const [rowModesModel, setRowModesModel] = useState<GridRowModesModel>({}); | ||||
@@ -108,7 +116,7 @@ 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>(); | |||||
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); | ||||
@@ -122,9 +130,7 @@ function PoInputGrid({ | |||||
}); | }); | ||||
useEffect(() => { | useEffect(() => { | ||||
const completedList = entries.filter( | |||||
(e) => e.status === "completed" | |||||
); | |||||
const completedList = entries.filter((e) => e.status === "completed"); | |||||
const processedQty = completedList.reduce( | const processedQty = completedList.reduce( | ||||
(acc, curr) => acc + (curr.acceptedQty || 0), | (acc, curr) => acc + (curr.acceptedQty || 0), | ||||
0 | 0 | ||||
@@ -150,6 +156,7 @@ function PoInputGrid({ | |||||
console.log(params); | console.log(params); | ||||
const oldId = params.row.id; | const oldId = params.row.id; | ||||
console.log(oldId); | console.log(oldId); | ||||
console.log(params.row); | |||||
const postData = { | const postData = { | ||||
itemId: params.row.itemId, | itemId: params.row.itemId, | ||||
itemNo: params.row.itemNo, | itemNo: params.row.itemNo, | ||||
@@ -167,17 +174,21 @@ function PoInputGrid({ | |||||
// openStartModal(); | // openStartModal(); | ||||
}, 200); | }, 200); | ||||
}, | }, | ||||
[] | |||||
[createStockInLine] | |||||
); | ); | ||||
const fetchQcDefaultValue = useCallback(async () => { | const fetchQcDefaultValue = useCallback(async () => { | ||||
const authHeader = axiosInstance.defaults.headers['Authorization']; | |||||
if (!authHeader) { | |||||
return; // Exit the function if the token is not set | |||||
} | |||||
console.log(authHeader) | |||||
const res = await axiosInstance.get<QcItemWithChecks[]>(`${NEXT_PUBLIC_API_URL}/qcResult/${itemDetail.id}`) | |||||
console.log(res) | |||||
}, [axiosInstance]) | |||||
// const authHeader = axiosInstance.defaults.headers['Authorization']; | |||||
// if (!authHeader) { | |||||
// return; // Exit the function if the token is not set | |||||
// } | |||||
// console.log(authHeader) | |||||
// console.log(NEXT_PUBLIC_API_URL) | |||||
// const res = await axiosInstance.get<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 handleQC = useCallback( | const handleQC = useCallback( | ||||
(id: GridRowId, params: any) => async () => { | (id: GridRowId, params: any) => async () => { | ||||
@@ -185,15 +196,20 @@ function PoInputGrid({ | |||||
...prev, | ...prev, | ||||
[id]: { mode: GridRowModes.View }, | [id]: { mode: GridRowModes.View }, | ||||
})); | })); | ||||
setModalInfo(params.row); | |||||
// await fetchQcDefaultValue() | |||||
const qcResult = await fetchQcDefaultValue(); | |||||
console.log(qcResult) | |||||
setModalInfo({ | |||||
...params.row, | |||||
qcResult: qcResult | |||||
}); | |||||
// set default values | |||||
setTimeout(() => { | setTimeout(() => { | ||||
// open qc modal | // open qc modal | ||||
console.log("delayed"); | console.log("delayed"); | ||||
openQcModal(); | openQcModal(); | ||||
}, 200); | }, 200); | ||||
}, | }, | ||||
[] | |||||
[fetchQcDefaultValue] | |||||
); | ); | ||||
const handleEscalation = useCallback( | const handleEscalation = useCallback( | ||||
(id: GridRowId, params: any) => () => { | (id: GridRowId, params: any) => () => { | ||||
@@ -246,15 +262,19 @@ function PoInputGrid({ | |||||
[] | [] | ||||
); | ); | ||||
const printQrcode = useCallback(async (row: any) => { | |||||
console.log(row.id) | |||||
const postData = {stockInLineIds: [row.id]} | |||||
const response = await fetchPoQrcode(postData) | |||||
if (response) { | |||||
console.log(response) | |||||
downloadFile(new Uint8Array(response.blobValue), response.filename!!) | |||||
} | |||||
}, [fetchPoQrcode, downloadFile]) | |||||
const printQrcode = useCallback( | |||||
async (row: any) => { | |||||
console.log(row.id); | |||||
const postData = { stockInLineIds: [row.id] }; | |||||
// const postData = { stockInLineIds: [42,43,44] }; | |||||
const response = await fetchPoQrcode(postData); | |||||
if (response) { | |||||
console.log(response); | |||||
downloadFile(new Uint8Array(response.blobValue), response.filename!!); | |||||
} | |||||
}, | |||||
[fetchPoQrcode, downloadFile] | |||||
); | |||||
const handleQrCode = useCallback( | const handleQrCode = useCallback( | ||||
(id: GridRowId, params: any) => () => { | (id: GridRowId, params: any) => () => { | ||||
@@ -269,7 +289,7 @@ function PoInputGrid({ | |||||
// return the record with its status as pending | // return the record with its status as pending | ||||
// update layout | // update layout | ||||
console.log("delayed"); | console.log("delayed"); | ||||
printQrcode(params.row) | |||||
printQrcode(params.row); | |||||
}, 200); | }, 200); | ||||
}, | }, | ||||
[] | [] | ||||
@@ -334,7 +354,10 @@ function PoInputGrid({ | |||||
headerName: "weight", | headerName: "weight", | ||||
flex: 0.5, | flex: 0.5, | ||||
renderCell: (params) => { | renderCell: (params) => { | ||||
const weight = calculateWeight(params.row.acceptedQty, params.row.uom); | |||||
const weight = calculateWeight( | |||||
params.row.acceptedQty, | |||||
params.row.uom | |||||
); | |||||
const weightUnit = returnWeightUnit(params.row.uom); | const weightUnit = returnWeightUnit(params.row.uom); | ||||
return `${weight} ${weightUnit}`; | return `${weight} ${weightUnit}`; | ||||
}, | }, | ||||
@@ -375,9 +398,7 @@ function PoInputGrid({ | |||||
color: "primary.main", | color: "primary.main", | ||||
// marginRight: 1, | // marginRight: 1, | ||||
}} | }} | ||||
disabled={ | |||||
stockInLineStatusMap[status] < 1 | |||||
} | |||||
disabled={stockInLineStatusMap[status] < 1} | |||||
// set _isNew to false after posting | // set _isNew to false after posting | ||||
// or check status | // or check status | ||||
onClick={handleQC(params.row.id, params)} | onClick={handleQC(params.row.id, params)} | ||||
@@ -463,7 +484,7 @@ function PoInputGrid({ | |||||
); | ); | ||||
const addRow = useCallback(() => { | const addRow = useCallback(() => { | ||||
console.log(itemDetail) | |||||
console.log(itemDetail); | |||||
const newEntry = { | const newEntry = { | ||||
id: Date.now(), | id: Date.now(), | ||||
_isNew: true, | _isNew: true, | ||||
@@ -1,6 +1,6 @@ | |||||
"use client"; | "use client"; | ||||
import { ModalFormInput, PurchaseQCInput, StockInInput, StockInLineEntry, updateStockInLine } from "@/app/api/po/actions"; | |||||
import { ModalFormInput, PurchaseQCInput, PurchaseQcResult, StockInInput, StockInLineEntry, updateStockInLine } from "@/app/api/po/actions"; | |||||
import { Box, Button, Modal, ModalProps, Stack } from "@mui/material"; | import { 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"; | ||||
@@ -18,7 +18,7 @@ import { stockInLineStatusMap } from "@/app/utils/formatUtil"; | |||||
interface CommonProps extends Omit<ModalProps, "children"> { | interface CommonProps extends Omit<ModalProps, "children"> { | ||||
setEntries: Dispatch<SetStateAction<StockInLineRow[]>> | setEntries: Dispatch<SetStateAction<StockInLineRow[]>> | ||||
itemDetail: StockInLine; | |||||
itemDetail: StockInLine & {qcResult?: PurchaseQcResult[]}; | |||||
qc?: QcItemWithChecks[]; | qc?: QcItemWithChecks[]; | ||||
warehouse?: any[]; | warehouse?: any[]; | ||||
type: "qc" | "stockIn" | "escalation" | "putaway" | type: "qc" | "stockIn" | "escalation" | "putaway" | ||||
@@ -58,7 +58,7 @@ const style = { | |||||
const PoQcStockInModal: React.FC<Props> = ({ | const PoQcStockInModal: React.FC<Props> = ({ | ||||
type, | type, | ||||
setEntries, | setEntries, | ||||
open, | |||||
open, | |||||
onClose, | onClose, | ||||
itemDetail, | itemDetail, | ||||
qc, | qc, | ||||
@@ -70,8 +70,19 @@ const PoQcStockInModal: React.FC<Props> = ({ | |||||
const params = useSearchParams() | const params = useSearchParams() | ||||
console.log(params.get("id")) | console.log(params.get("id")) | ||||
const [defaultValues, setDefaultValues] = useState({}); | 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 formProps = useForm<ModalFormInput>({ | const formProps = useForm<ModalFormInput>({ | ||||
defaultValues: defaultValues ? defaultValues : {}, | |||||
defaultValues: defaultValue | |||||
}); | }); | ||||
const errors = formProps.formState.errors; | const errors = formProps.formState.errors; | ||||
const closeHandler = useCallback<NonNullable<ModalProps["onClose"]>>( | const closeHandler = useCallback<NonNullable<ModalProps["onClose"]>>( | ||||
@@ -155,17 +166,22 @@ const PoQcStockInModal: React.FC<Props> = ({ | |||||
const renderSubmitButton = useMemo((): Boolean => { | const renderSubmitButton = useMemo((): Boolean => { | ||||
if (itemDetail) { | if (itemDetail) { | ||||
const status = itemDetail.status | const status = itemDetail.status | ||||
console.log(status) | |||||
switch (type) { | switch (type) { | ||||
case "qc": | case "qc": | ||||
return stockInLineStatusMap[status] === 1 | return stockInLineStatusMap[status] === 1 | ||||
case "putaway": | case "putaway": | ||||
return stockInLineStatusMap[status] === 7 | return stockInLineStatusMap[status] === 7 | ||||
case "stockIn": | |||||
return stockInLineStatusMap[status] === 6 | |||||
default: | default: | ||||
return false; // Handle unexpected type | return false; // Handle unexpected type | ||||
} | } | ||||
} else return false | } else return false | ||||
}, [type, itemDetail]) | }, [type, itemDetail]) | ||||
renderSubmitButton | |||||
useEffect(() => { | |||||
console.log(renderSubmitButton) | |||||
}, [renderSubmitButton]) | |||||
return ( | return ( | ||||
<> | <> | ||||
<Modal open={open} onClose={closeHandler}> | <Modal open={open} onClose={closeHandler}> | ||||
@@ -19,6 +19,7 @@ const scannerSx: React.CSSProperties = { | |||||
}; | }; | ||||
type QrCodeScannerProps = { | type QrCodeScannerProps = { | ||||
cameras: CameraDevice[] | |||||
title?: string, | title?: string, | ||||
contents?: string[], | contents?: string[], | ||||
onScanSuccess: (result: string) => void, | onScanSuccess: (result: string) => void, | ||||
@@ -79,7 +80,7 @@ const QrCodeScanner: React.FC<QrCodeScannerProps> = ({ | |||||
const cameras = await Html5Qrcode.getCameras() | const cameras = await Html5Qrcode.getCameras() | ||||
setCameraList(cameras) | setCameraList(cameras) | ||||
if (cameras.length > 0) { | if (cameras.length > 0) { | ||||
handleCameraChange(cameras[0].id) | |||||
handleCameraChange(cameras[cameras.length-1].id) | |||||
} | } | ||||
}, []) | }, []) | ||||
@@ -106,6 +107,8 @@ const QrCodeScanner: React.FC<QrCodeScannerProps> = ({ | |||||
const handleScanSuccess = useCallback<QrcodeSuccessCallback>((decodedText, result) => { | const handleScanSuccess = useCallback<QrcodeSuccessCallback>((decodedText, result) => { | ||||
if (scanner) { | if (scanner) { | ||||
console.log(`Decoded text: ${decodedText}`); | console.log(`Decoded text: ${decodedText}`); | ||||
const parseData = JSON.parse(decodedText) | |||||
console.log(parseData) | |||||
// Handle the decoded text as needed | // Handle the decoded text as needed | ||||
switchScanStatus() | switchScanStatus() | ||||
onScanSuccess(decodedText) | onScanSuccess(decodedText) | ||||
@@ -113,20 +116,18 @@ const QrCodeScanner: React.FC<QrCodeScannerProps> = ({ | |||||
}, [scanner, onScanSuccess]) | }, [scanner, onScanSuccess]) | ||||
const handleScanError = useCallback<QrcodeErrorCallback>((errorMessage, error) => { | const handleScanError = useCallback<QrcodeErrorCallback>((errorMessage, error) => { | ||||
console.log(`Error: ${errorMessage}`); | |||||
// console.log(`Error: ${errorMessage}`); | |||||
if (onScanError) { | if (onScanError) { | ||||
onScanError(errorMessage) | onScanError(errorMessage) | ||||
} | } | ||||
}, [scanner, onScanError]) | }, [scanner, onScanError]) | ||||
const handleScanCloseButton = useCallback(async () => { | const handleScanCloseButton = useCallback(async () => { | ||||
if (scanner) { | if (scanner) { | ||||
console.log("Cleaning up scanner..."); | console.log("Cleaning up scanner..."); | ||||
await scanner.stop() | await scanner.stop() | ||||
await scanner.clear() | |||||
scanner.clear() | |||||
onClose() | onClose() | ||||
} | } | ||||
}, [scanner]) | }, [scanner]) | ||||
@@ -166,7 +167,7 @@ const QrCodeScanner: React.FC<QrCodeScannerProps> = ({ | |||||
<Grid item xs={12}> | <Grid item xs={12}> | ||||
<div style={{ textAlign: "center", margin: "auto", justifyContent: "center" }} id="qr-reader" hidden={isScanned} /> | <div style={{ textAlign: "center", margin: "auto", justifyContent: "center" }} id="qr-reader" hidden={isScanned} /> | ||||
</Grid> | </Grid> | ||||
{cameraList.length > 0 && <Grid item xs={6} > | |||||
{/* {cameraList.length > 0 && <Grid item xs={6} > | |||||
<Autocomplete | <Autocomplete | ||||
disableClearable | disableClearable | ||||
noOptionsText={t("No Options")} | noOptionsText={t("No Options")} | ||||
@@ -183,7 +184,7 @@ const QrCodeScanner: React.FC<QrCodeScannerProps> = ({ | |||||
/> | /> | ||||
)} | )} | ||||
/> | /> | ||||
</Grid>} | |||||
</Grid>} */} | |||||
{ | { | ||||
contents && contents.map((string) => { | contents && contents.map((string) => { | ||||
return <Grid item xs={8}>{string}</Grid> | return <Grid item xs={8}>{string}</Grid> | ||||
@@ -1,65 +1,86 @@ | |||||
import { Button, CardContent, Card, Modal, SxProps, ModalProps, Box, CardActions } from "@mui/material"; | |||||
import { | |||||
Button, | |||||
CardContent, | |||||
Card, | |||||
Modal, | |||||
SxProps, | |||||
ModalProps, | |||||
Box, | |||||
CardActions, | |||||
} from "@mui/material"; | |||||
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"; | |||||
const modalSx: SxProps = { | const modalSx: SxProps = { | ||||
position: "absolute", | |||||
top: "50%", | |||||
left: "50%", | |||||
transform: "translate(-50%, -50%)", | |||||
width: { xs: "calc(100% - 2rem)", sm: "90%" }, | |||||
maxHeight: "90%", | |||||
maxWidth: 1400, | |||||
position: "absolute", | |||||
top: "50%", | |||||
left: "50%", | |||||
transform: "translate(-50%, -50%)", | |||||
width: { xs: "calc(100% - 2rem)", sm: "90%" }, | |||||
maxHeight: "90%", | |||||
maxWidth: 1400, | |||||
}; | }; | ||||
type QrCodeScannerModalProps = { | type QrCodeScannerModalProps = { | ||||
title?: string, | |||||
contents?: string[], | |||||
isOpen: boolean, | |||||
onClose: () => void, | |||||
onScanSuccess: (result: string) => void, | |||||
onScanError?: (error: string) => void | |||||
} | |||||
cameras: CameraDevice[]; | |||||
title?: string; | |||||
contents?: string[]; | |||||
isOpen: boolean; | |||||
onClose: () => void; | |||||
onScanSuccess: (result: string) => void; | |||||
onScanError?: (error: string) => void; | |||||
}; | |||||
const QrCodeScannerModal: React.FC<QrCodeScannerModalProps> = ({ | const QrCodeScannerModal: React.FC<QrCodeScannerModalProps> = ({ | ||||
title, | |||||
contents, | |||||
isOpen, | |||||
onClose, | |||||
onScanSuccess, | |||||
onScanError, | |||||
cameras, | |||||
title, | |||||
contents, | |||||
isOpen, | |||||
onClose, | |||||
onScanSuccess, | |||||
onScanError, | |||||
}) => { | }) => { | ||||
const [modalOpen, setModalOpen] = useState(isOpen); // pass to qr code scanner | |||||
useEffect(() => { | |||||
setModalOpen(isOpen); | |||||
}, [isOpen]); | |||||
const [modalOpen, setModalOpen] = useState(isOpen) // pass to qr code scanner | |||||
useEffect(() => { | |||||
setModalOpen(isOpen) | |||||
}, [isOpen]) | |||||
const onModalClose = useCallback<NonNullable<ModalProps["onClose"]>>( | |||||
(_, reason) => { | |||||
if (reason !== "backdropClick") { | |||||
setModalOpen(false); | |||||
} | |||||
}, | |||||
[] | |||||
); | |||||
const onModalClose = useCallback<NonNullable<ModalProps["onClose"]>>( | |||||
(_, reason) => { | |||||
if (reason !== "backdropClick") { | |||||
setModalOpen(false) | |||||
return ( | |||||
<Modal open={isOpen} onClose={onModalClose}> | |||||
<Card sx={modalSx}> | |||||
<CardContent sx={{ overflow: "auto", maxHeight: "90vh" }}> | |||||
<Box | |||||
sx={ | |||||
{ | |||||
// marginInline: -1, | |||||
// marginBlock: 1, | |||||
} | |||||
} | } | ||||
}, | |||||
[], | |||||
); | |||||
return ( | |||||
<Modal open={isOpen} onClose={onModalClose}> | |||||
<Card sx={modalSx}> | |||||
<CardContent sx={{ overflow: "auto", maxHeight: "90vh" }} > | |||||
<Box | |||||
sx={{ | |||||
// marginInline: -1, | |||||
// marginBlock: 1, | |||||
}} | |||||
> | |||||
<QrCodeScanner title={title} contents={contents} onScanSuccess={onScanSuccess} onScanError={onScanError} isOpen={modalOpen} onClose={onClose} /> | |||||
</Box> | |||||
</CardContent> | |||||
</Card> | |||||
</Modal> | |||||
) | |||||
} | |||||
> | |||||
<QrCodeScanner | |||||
cameras={cameras} | |||||
title={title} | |||||
contents={contents} | |||||
onScanSuccess={onScanSuccess} | |||||
onScanError={onScanError} | |||||
isOpen={modalOpen} | |||||
onClose={onClose} | |||||
/> | |||||
</Box> | |||||
</CardContent> | |||||
</Card> | |||||
</Modal> | |||||
); | |||||
}; | |||||
export default QrCodeScannerModal | |||||
export default QrCodeScannerModal; |