Merge pull request 'mobile-apk-and-web-qrscan' (#159) from mobile-apk-and-web-qrscan into main

Reviewed-on: Pozomind/pozo-retail-app#159
This commit is contained in:
karthikalakshmi 2026-02-20 15:36:27 +05:30
commit 8d19d901b9
9 changed files with 565 additions and 61 deletions

View File

@ -81,6 +81,7 @@ import { Global_PromoCodeOffers } from '../../../../Features/Offer/Offer.js';
import { validateOffers } from '../BSItemCards/ValidateOffer.jsx';
import { useSaleswiseOfferWatcher } from '../BSItemCards/SalesWiseOffer.jsx';
import ExpireConfirmModal from '../BookingFunctionality/ExpireConfirmModal.jsx';
import BarCodeScan from '../../../../Services/BarCodeScan.jsx';
export default function BSC1Search(props) {
const dispatch = useDispatch();
@ -4567,10 +4568,15 @@ export default function BSC1Search(props) {
</Form.Item>
</Form>
{isMobile && screenWidth <= 768 && (
<BiBarcodeReader
className="BSC1Search-barcodeicon"
onClick={() => handleQrcode()}
// onClick={() => OnfocusTrue()}
// <BiBarcodeReader
// className="BSC1Search-barcodeicon"
// onClick={() => handleQrcode()}
// // onClick={() => OnfocusTrue()}
// />
<BarCodeScan
onScan={(value) => {
ProductNameOnChange(value, true);
}}
/>
)}
@ -4821,7 +4827,7 @@ export default function BSC1Search(props) {
</div>
</div>
</DefaultModal>
{isMobile && screenWidth <= 768 && (
{/* {isMobile && screenWidth <= 768 && (
<DefaultModal
title="Barcode Reader"
width={800}
@ -4839,7 +4845,7 @@ export default function BSC1Search(props) {
/>
)}
</DefaultModal>
)}
)} */}
</>
);
}

View File

@ -19,6 +19,8 @@ import './Verfiedproduct.scss';
import { useSelector } from 'react-redux';
import { ApplicationPreferences } from '../../../../../Features/BrachLogin/BranchLogin.js';
import { changeOfferAppliedProductsForMembership } from '../../../../../Features/Offer/Offernew/BookingOffernew.js';
import BarCodeScan from '../../../../../Services/BarCodeScan.jsx';
import { isMobile } from 'react-device-detect';
const VerfiedProducts = ({ drawerOpen = false, setDrawerOpen = () => {} }) => {
@ -303,10 +305,16 @@ const VerfiedProducts = ({ drawerOpen = false, setDrawerOpen = () => {} }) => {
>
Clear
</button>
<button onClick={() => setShowScanner((s) => !s)}>
{!isMobile ?
(<button onClick={() => setShowScanner((s) => !s)}>
{showScanner ? 'Stop Camera' : 'Scan with Camera'}
</button>
</button>)
: (<BarCodeScan
onScan={(value) => {
setScanValue(value);
fetchProductsByOrderId(value);
}}
/>)}
</div>
{message && <div className="SelfBooking-message">{message}</div>}

View File

@ -32,6 +32,7 @@ import BarcodeScanner from './BarcodeScanner';
import { isMobile } from 'react-device-detect';
import { BiBarcodeReader } from 'react-icons/bi';
import TooltipWrapper from '../../../../Components/Tooltip/Tooltip';
import BarCodeScan from '../../../../Services/BarCodeScan.jsx';
const BSNavBarComboSearch = (props) => {
@ -1197,28 +1198,28 @@ const BSNavBarComboSearch = (props) => {
: [];
// });
};
const stopScannerFromParent = () => {
if (scannerRef.current) {
scannerRef.current.stopScanner(); // Call stopScanner from the child component
}
};
// const stopScannerFromParent = () => {
// if (scannerRef.current) {
// scannerRef.current.stopScanner(); // Call stopScanner from the child component
// }
// };
const handleQrcode = () => {
setOpenScanner((prev) => !prev);
if (!openScanner && scannerRef.current) {
scannerRef.current.reinitializeCamera(); // Call the function to reinitialize the camera
}
};
// const handleQrcode = () => {
// setOpenScanner((prev) => !prev);
// if (!openScanner && scannerRef.current) {
// scannerRef.current.reinitializeCamera(); // Call the function to reinitialize the camera
// }
// };
const handleQrData = (decodedText, err) => {
onChangeprodFunction(decodedText);
setOpenScanner(false); // Close the modal after scanning
stopScannerFromParent();
if (err && err?.error) {
setMessageData(err?.error);
setMessageType('error');
}
};
// const handleQrData = (decodedText, err) => {
// onChangeprodFunction(decodedText);
// setOpenScanner(false); // Close the modal after scanning
// stopScannerFromParent();
// if (err && err?.error) {
// setMessageData(err?.error);
// setMessageType('error');
// }
// };
return (
<div className="L">
<Messages
@ -1289,10 +1290,14 @@ const BSNavBarComboSearch = (props) => {
/>
</div>
{isMobile && screenWidth <= 768 ? (
<BiBarcodeReader
className="search-icon"
onClick={() => handleQrcode()}
/>
// <BiBarcodeReader
// className="search-icon"
// onClick={() => handleQrcode()}
// />
<BarCodeScan
onScan={(value) => {
onChangeprodFunction(value);
}} />
) : (
<IoSearchOutline className="search-icon" />
)}
@ -1395,7 +1400,7 @@ const BSNavBarComboSearch = (props) => {
</div>
</Modal>
)}
{isMobile && screenWidth <= 768 && (
{/* {isMobile && screenWidth <= 768 && (
<DefaultModal
title="Barcode Reader"
width={800}
@ -1413,7 +1418,7 @@ const BSNavBarComboSearch = (props) => {
/>
)}
</DefaultModal>
)}
)} */}
</div>
);
};

View File

@ -37,6 +37,7 @@ import { BiBarcodeReader } from 'react-icons/bi';
import { ApplicationPreferences } from '../../../../Features/BrachLogin/BranchLogin.js';
import { useDateStore } from '../../../../Features/BookingScreen/BookingData/DateStore.js';
import { Messages } from '../../../../../ownLib/my-ui-lib.js';
import BarCodeScan from '../../../../Services/BarCodeScan.jsx';
const BSNavBarSearch = (props) => {
const searchInputRef = useRef(null);
@ -362,28 +363,28 @@ const BSNavBarSearch = (props) => {
await dispatch(getCardDataWithoutSub(data));
};
const stopScannerFromParent = () => {
if (scannerRef?.current) {
scannerRef?.current?.stopScanner(); // Call stopScanner from the child component
}
};
// const stopScannerFromParent = () => {
// if (scannerRef?.current) {
// scannerRef?.current?.stopScanner(); // Call stopScanner from the child component
// }
// };
const handleQrcode = () => {
setOpenScanner((prev) => !prev);
if (!openScanner && scannerRef?.current) {
scannerRef?.current?.reinitializeCamera(); // Call the function to reinitialize the camera
}
};
// const handleQrcode = () => {
// setOpenScanner((prev) => !prev);
// if (!openScanner && scannerRef?.current) {
// scannerRef?.current?.reinitializeCamera(); // Call the function to reinitialize the camera
// }
// };
const handleQrData = async (value, err) => {
if (err && err?.error) {
setMessageData(err?.error);
setMessageType('error');
}
await searchdata(value);
setOpenScanner(false);
stopScannerFromParent();
};
// const handleQrData = async (value, err) => {
// if (err && err?.error) {
// setMessageData(err?.error);
// setMessageType('error');
// }
// await searchdata(value);
// setOpenScanner(false);
// stopScannerFromParent();
// };
const onComplete = useCallback(() => {
setMessageData(null);
@ -421,9 +422,14 @@ const BSNavBarSearch = (props) => {
disabled={props.OrderType === 'Failed' ? true : false}
/>
{isMobile && screenWidth <= 768 ? (
<BiBarcodeReader
className="search-icon"
onClick={() => handleQrcode()}
// <BiBarcodeReader
// className="search-icon"
// onClick={() => handleQrcode()}
// />
<BarCodeScan
onScan={(value) => {
searchdata(value);
}}
/>
) : (
<IoSearchOutline className="search-icon" />
@ -435,7 +441,7 @@ const BSNavBarSearch = (props) => {
</div>} */}
</div>
</div>
{isMobile && screenWidth <= 768 && (
{/* {isMobile && screenWidth <= 768 && (
<DefaultModal
title="Barcode Reader"
width={800}
@ -454,7 +460,7 @@ const BSNavBarSearch = (props) => {
/>
)}
</DefaultModal>
)}
)} */}
</div>
);
};

View File

@ -19,6 +19,8 @@ import Search from "../../../../Components/Forms/Search";
import VoiceToTextItemCard from "../../../VoiceToText/VoiceToTextItemCard";
import { ApplicationPreferences } from "../../../../Features/BrachLogin/BranchLogin";
import { useDateStore } from "../../../../Features/BookingScreen/BookingData/DateStore";
import BarCodeScan from "../../../../Services/BarCodeScan";
import { isMobile } from "react-device-detect";
const BSNavbarSearchStandard = ({ inputRef }) => {
const dispatch = useDispatch();
@ -147,6 +149,9 @@ const BSNavbarSearchStandard = ({ inputRef }) => {
const handleSearch = (e) => {
dispatch(changeSearchedData(e?.target?.value));
};
const handleScan = (e) => {
dispatch(changeSearchedData(e));
};
const HandleVoiceSearch = () => {
setVoiceSearch(!voiceSearch);
};
@ -159,6 +164,13 @@ const BSNavbarSearchStandard = ({ inputRef }) => {
ref={inputRef}
value={ProdName}
/>
{isMobile && screenWidth <= 768 && (
<BarCodeScan
onScan={(value) => {
handleScan(value);
}}
/>
)}
</>
);
};

View File

@ -52,6 +52,7 @@ import { PrintStyleFunction } from '../paymentpdfPage/PrintStyleFunction.js';
import { PurchaseOrderMobilePrint } from './PurchsasePrintStyle/PurchaseOrderMobilePrint.js';
import { isMobile } from 'react-device-detect';
import { CiEdit } from 'react-icons/ci';
import BarCodeScan from '../../Services/BarCodeScan.jsx';
const subDirectory = import.meta.env.BASE_URL;
const stylesMap = {
@ -1628,6 +1629,7 @@ const PurchaseOrder = () => {
}}
></AutoComplete>
</FloatLabel>
{!isMobile && (
<Tooltip title={'BarCode/QrCode'} placement={'top'}>
<BsUpcScan
strokeWidth={0.5}
@ -1641,6 +1643,16 @@ const PurchaseOrder = () => {
onClick={handleScanSearch}
/>
</Tooltip>
)}
{isMobile && (
<div className='IsmoBileBarcode'>
<BarCodeScan
onScan={(value) => {
debouncedScannerSearch(value);
}}
/>
</div>
)}
</div>
</Form.Item>
<Tooltip

View File

@ -76,6 +76,8 @@ import AddAllProductsButton from './AddAllProductGrid.jsx';
import SettingsIconWithModal from './SettingsIconWithModal.jsx';
import VirtualizedTable from './VirtualizedTable.jsx';
import { useDebounce } from './UseDebounce.jsx';
import { isMobile } from 'react-device-detect';
import BarCodeScan from '../../Services/BarCodeScan.jsx';
const subDirectory = import.meta.env.BASE_URL;
const EditableContext = React.createContext(null);
@ -3813,6 +3815,28 @@ const StockForm = ({ formType }) => {
style={{ width: '250px' }}
></AutoComplete>
</FloatLabel>
{/* <Tooltip title={'BarCode/QrCode'} placement={'top'}>
<BsUpcScan
strokeWidth={0.5}
color="#000"
style={{
fontSize: '16px',
color: scanner ? '#52c41a' : '#888',
fontWeight: '600',
}}
className="product-scan-Icon"
onClick={handleScanSearch}
/>
</Tooltip> */}
{isMobile ? (
<div className="IsmoBileBarcode">
<BarCodeScan
onScan={(value) => {
debouncedScannerSearch(value);
}}
/>
</div>
) : (
<Tooltip title={'BarCode/QrCode'} placement={'top'}>
<BsUpcScan
strokeWidth={0.5}
@ -3826,6 +3850,7 @@ const StockForm = ({ formType }) => {
onClick={handleScanSearch}
/>
</Tooltip>
)}
</div>
<Tooltip

View File

@ -16,6 +16,8 @@ import { getCompBranchData } from '../../Features/BrachLogin/BranchLogin.js';
import { changeBreadCrumb } from '../../Features/AppPage/CenterPage.js';
import { useLocation, useNavigate } from 'react-router-dom';
import { postRequestStock } from '../../Features/RequestStock/RequestStock.js';
import BarCodeScan from '../../Services/BarCodeScan.jsx';
import { isMobile } from 'react-device-detect';
const subDirectory = import.meta.env.BASE_URL;
function debounce(func, wait) {
@ -516,13 +518,22 @@ const CreateTransfer = () => {
disabled={isProcessing}
/>
</FloatLabel>
{isMobile ? (
<div className="IsmoBileBarcode">
<BarCodeScan
onScan={(value) => {
debouncedScannerSearch(value);
}}
/>
</div>
) : (
<Tooltip title="BarCode / QR Code">
<BsUpcScan
className={`product-scan-icon ${scanner ? 'active' : ''}`}
onClick={handleScannerToggle}
/>
</Tooltip>
)}
</div>
</div>
<div>

View File

@ -0,0 +1,419 @@
// import React, { useRef, useState, useEffect } from "react";
// import { BarcodeScanner } from "@capacitor-mlkit/barcode-scanning";
// import { BsUpcScan } from "react-icons/bs";
// import { BrowserMultiFormatReader } from "@zxing/library";
// import { Capacitor } from "@capacitor/core";
// import { DefaultModal } from "../Components/Modal/DefaultModal";
// export default function BarCodeScan({ onScan }) {
// const isMobileApp = Capacitor.getPlatform() !== "web";
// const [scanning, setScanning] = useState(false);
// const [modalOpen, setModalOpen] = useState(false);
// const videoRef = useRef(null);
// const codeReaderRef = useRef(null);
// const streamRef = useRef(null);
// const startScan = async () => {
// if (scanning) return;
// setScanning(true);
// if (isMobileApp) {
// // Mobile: MLKit scanner
// try {
// const perm = await BarcodeScanner.requestPermissions();
// if (perm.camera !== "granted") {
// alert("Camera permission not granted");
// setScanning(false);
// return;
// }
// const { barcodes } = await BarcodeScanner.scan();
// if (barcodes && barcodes.length > 0) {
// onScan(barcodes[0].rawValue);
// }
// } catch (err) {
// alert("Error scanning barcode: " + err.message);
// } finally {
// setScanning(false);
// }
// } else {
// // Web: open modal and start camera
// setModalOpen(true);
// setTimeout(() => startWebScan(), 50); // wait for modal DOM
// }
// };
// const startWebScan = async () => {
// try {
// codeReaderRef.current = new BrowserMultiFormatReader();
// streamRef.current = await navigator.mediaDevices.getUserMedia({
// video: { facingMode: "environment" },
// });
// if (videoRef.current) {
// videoRef.current.srcObject = streamRef.current;
// videoRef.current.play();
// }
// codeReaderRef.current.decodeFromVideoDevice(
// null,
// videoRef.current,
// (result) => {
// if (result) {
// onScan(result.getText());
// stopWebScan();
// setModalOpen(false);
// }
// }
// );
// } catch (err) {
// alert("Error accessing camera: " + err.message);
// setScanning(false);
// setModalOpen(false);
// }
// };
// const stopWebScan = () => {
// if (streamRef.current) {
// streamRef.current.getTracks().forEach((track) => track.stop());
// streamRef.current = null;
// }
// if (codeReaderRef.current) {
// codeReaderRef.current.reset();
// codeReaderRef.current = null;
// }
// setScanning(false);
// };
// const closeModal = () => {
// stopWebScan();
// setModalOpen(false);
// };
// // Stop camera if component unmounts
// useEffect(() => () => stopWebScan(), []);
// return (
// <div style={{ display: "flex", justifyContent: "center", padding: 20 }}>
// {!scanning && (
// <BsUpcScan
// onClick={startScan}
// style={{ fontSize: 32, color: "#007bff", cursor: "pointer" }}
// />
// )}
// <DefaultModal
// title="Barcode Reader"
// width={500}
// open={modalOpen}
// footer={false}
// handleCancel={() => {
// closeModal();
// }}
// >
// <div
// style={{
// display: "flex",
// flexDirection: "column",
// alignItems: "center",
// justifyContent: "center",
// }}
// >
// <video
// ref={videoRef}
// style={{
// width: "100%",
// maxWidth: 400,
// borderRadius: 8,
// border: "2px solid #000",
// }}
// />
// <button
// onClick={closeModal}
// style={{
// marginTop: 20,
// padding: "10px 20px",
// fontSize: 16,
// borderRadius: 8,
// border: "none",
// cursor: "pointer",
// }}
// >
// Close
// </button>
// </div>
// </DefaultModal>
// </div>
// );
// }
import React, { useRef, useState, useEffect } from "react";
import { BarcodeScanner } from "@capacitor-mlkit/barcode-scanning";
import { BsUpcScan } from "react-icons/bs";
import { Capacitor } from "@capacitor/core";
import { DefaultModal } from "../Components/Modal/DefaultModal";
import QrScanner from "qr-scanner/qr-scanner.min.js";
import {
BrowserMultiFormatReader,
BarcodeFormat,
} from "@zxing/library";
QrScanner.WORKER_PATH = new URL(
"qr-scanner/qr-scanner-worker.min.js",
import.meta.url
).toString();
export default function BarCodeScan({ onScan }) {
const isMobileApp = Capacitor.getPlatform() !== "web";
const [scanning, setScanning] = useState(false);
const [modalOpen, setModalOpen] = useState(false);
const videoRef = useRef(null);
const qrScannerRef = useRef(null);
const barcodeReaderRef = useRef(null);
// -------------------------
// START SCAN
// -------------------------
const startScan = async () => {
if (scanning) return;
setScanning(true);
if (isMobileApp) {
try {
const perm = await BarcodeScanner.requestPermissions();
if (perm.camera !== "granted") {
alert("Camera permission not granted");
setScanning(false);
return;
}
const { barcodes } = await BarcodeScanner.scan();
if (barcodes?.length > 0) {
onScan(barcodes[0].rawValue);
}
} catch (err) {
console.error(err);
} finally {
setScanning(false);
}
} else {
setModalOpen(true);
}
};
// -------------------------
// START WEB SCAN (HYBRID)
// -------------------------
const startWebScan = async () => {
if (!videoRef.current) return;
// Start QR scanner (fast)
qrScannerRef.current = new QrScanner(
videoRef.current,
(result) => {
stopWebScan();
onScan(result.data);
setModalOpen(false);
},
{
maxScansPerSecond: 25,
}
);
await qrScannerRef.current.start();
// Start Barcode scanner (ZXing)
barcodeReaderRef.current = new BrowserMultiFormatReader(undefined, {
possibleFormats: [
BarcodeFormat.CODE_128,
BarcodeFormat.EAN_13,
BarcodeFormat.UPC_A,
BarcodeFormat.UPC_E,
],
});
barcodeReaderRef.current.timeBetweenDecodingAttempts = 50;
barcodeReaderRef.current.decodeFromVideoDevice(
null,
videoRef.current,
(result) => {
if (result) {
stopWebScan();
onScan(result.getText());
setModalOpen(false);
}
}
);
};
const stopWebScan = () => {
if (qrScannerRef.current) {
qrScannerRef.current.stop();
qrScannerRef.current.destroy();
qrScannerRef.current = null;
}
if (barcodeReaderRef.current) {
barcodeReaderRef.current.reset();
barcodeReaderRef.current = null;
}
setScanning(false);
};
const closeModal = () => {
stopWebScan();
setModalOpen(false);
};
useEffect(() => {
if (modalOpen) {
startWebScan();
}
}, [modalOpen]);
useEffect(() => {
return () => stopWebScan();
}, []);
return (
<div style={{ display: "flex", justifyContent: "center", padding: 20 }}>
{!scanning && (
<BsUpcScan
onClick={startScan}
style={{
fontSize: 32,
color: "#007bff",
cursor: "pointer",
}}
/>
)}
{/* <DefaultModal
title="QR / Barcode Reader"
width={500}
open={modalOpen}
footer={false}
handleCancel={closeModal}
>
<div style={{ display: "flex", justifyContent: "center" }}>
<video
ref={videoRef}
style={{
width: "100%",
maxWidth: 400,
borderRadius: 8,
}}
/>
</div>
<button
onClick={closeModal}
style={{
marginTop: 20,
padding: "10px 20px",
fontSize: 16,
borderRadius: 8,
border: "none",
cursor: "pointer",
}}
>
Close
</button>
</DefaultModal> */}
<DefaultModal
title="QR / Barcode Reader"
width={500}
open={modalOpen}
footer={false}
handleCancel={closeModal}
>
<div
style={{
position: "relative",
width: "100%",
maxWidth: 400,
margin: "auto",
}}
>
{/* VIDEO */}
<video
ref={videoRef}
style={{
width: "100%",
borderRadius: 8,
}}
/>
{/* DARK OVERLAY */}
<div
style={{
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: "100%",
background: "rgba(0,0,0,0.5)",
borderRadius: 8,
}}
/>
{/* SCAN BOX */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
width: "70%",
height: "60%",
transform: "translate(-50%, -50%)",
border: "3px solid #00ff00",
borderRadius: 12,
boxShadow: "0 0 0 2000px rgba(0,0,0,0.4)",
}}
/>
{/* SCAN LINE */}
<div
style={{
position: "absolute",
left: "15%",
width: "70%",
height: 2,
background: "red",
animation: "scanAnimation 2s infinite linear",
}}
/>
</div>
<button
onClick={closeModal}
style={{
marginTop: 20,
padding: "10px 20px",
fontSize: 16,
borderRadius: 8,
border: "none",
cursor: "pointer",
}}
>
Close
</button>
{/* Animation */}
<style>
{`
@keyframes scanAnimation {
0% { top: 25%; }
50% { top: 70%; }
100% { top: 25%; }
}
`}
</style>
</DefaultModal>
</div>
);
}