FPSMS-frontend
Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 
 
 

292 wiersze
7.5 KiB

  1. import {
  2. Autocomplete,
  3. Box,
  4. Button,
  5. Card,
  6. CardContent,
  7. Grid,
  8. Modal,
  9. ModalProps,
  10. Stack,
  11. SxProps,
  12. TextField,
  13. Typography,
  14. } from "@mui/material";
  15. import {
  16. CameraDevice,
  17. Html5Qrcode,
  18. Html5QrcodeCameraScanConfig,
  19. Html5QrcodeFullConfig,
  20. Html5QrcodeResult,
  21. Html5QrcodeScanner,
  22. Html5QrcodeScannerState,
  23. QrcodeErrorCallback,
  24. QrcodeSuccessCallback,
  25. } from "html5-qrcode";
  26. import { Html5QrcodeError } from "html5-qrcode/esm/core";
  27. import { Html5QrcodeScannerConfig } from "html5-qrcode/esm/html5-qrcode-scanner";
  28. import React, {
  29. RefObject,
  30. useCallback,
  31. useEffect,
  32. useMemo,
  33. useRef,
  34. useState,
  35. } from "react";
  36. import { useTranslation } from "react-i18next";
  37. import QrCodeScannerIcon from "@mui/icons-material/QrCodeScanner";
  38. import StopCircleOutlinedIcon from "@mui/icons-material/StopCircleOutlined";
  39. import CloseOutlinedIcon from "@mui/icons-material/CloseOutlined";
  40. import { QrCodeInfo } from "@/app/api/qrcode";
  41. const scannerSx: React.CSSProperties = {
  42. position: "absolute",
  43. // top: "50%",
  44. // left: "50%",
  45. // transform: "translate(-50%, -50%)",
  46. width: "90%",
  47. maxHeight: "10%",
  48. maxWidth: 1400,
  49. };
  50. type QrCodeScannerProps = {
  51. cameras: CameraDevice[];
  52. title?: string;
  53. contents?: string[];
  54. onScanSuccess: (qrCodeInfo: QrCodeInfo) => void;
  55. onScanError?: (error: string) => void;
  56. isOpen: boolean;
  57. onClose: () => void;
  58. };
  59. const QrCodeScanner: React.FC<QrCodeScannerProps> = ({
  60. title,
  61. contents,
  62. onScanSuccess,
  63. onScanError,
  64. isOpen,
  65. onClose,
  66. }) => {
  67. const { t } = useTranslation();
  68. const [isScanned, setIsScanned] = useState<boolean>(false);
  69. const [scanner, setScanner] = useState<Html5Qrcode | null>(null);
  70. const [cameraList, setCameraList] = useState<CameraDevice[]>([]);
  71. const [selectedCameraId, setSelectedCameraId] = useState<string | null>(null);
  72. const stringList = [
  73. "ABC: abc",
  74. "123:123",
  75. "ABC: abc",
  76. "123:123",
  77. "ABC: abc",
  78. "123:123",
  79. ];
  80. const scannerConfig: Html5QrcodeFullConfig = {
  81. verbose: false,
  82. };
  83. const cameraConfig: Html5QrcodeCameraScanConfig = {
  84. fps: 10,
  85. qrbox: { width: 250, height: 250 },
  86. // aspectRatio: cardRef.current ? (cardRef.current.offsetWidth / cardRef.current.offsetHeight) : 1.78,
  87. aspectRatio: (window.innerWidth / window.innerHeight) * 1.5, // can be better
  88. };
  89. // MediaTrackConstraintSet
  90. const mediaTrackConstraintSet = {
  91. facingMode: "environment",
  92. };
  93. const handleScanStart = useCallback(() => {
  94. if (scanner && selectedCameraId) {
  95. if (scanner.getState() === Html5QrcodeScannerState.SCANNING) {
  96. console.log("first");
  97. scanner.stop();
  98. }
  99. scanner.start(
  100. selectedCameraId,
  101. cameraConfig,
  102. handleScanSuccess,
  103. handleScanError
  104. );
  105. }
  106. }, [selectedCameraId, scanner]);
  107. const handleCameraList = useCallback(async () => {
  108. const cameras = await Html5Qrcode.getCameras();
  109. setCameraList(cameras);
  110. if (cameras.length > 0) {
  111. handleCameraChange(cameras[cameras.length - 1].id);
  112. }
  113. }, []);
  114. const handleCameraChange = useCallback((id: string) => {
  115. setSelectedCameraId(id);
  116. }, []);
  117. const switchScanStatus = useCallback(() => {
  118. if (scanner) {
  119. console.log(isScanned);
  120. switch (isScanned) {
  121. case true:
  122. setIsScanned(false);
  123. scanner.resume();
  124. break;
  125. case false:
  126. setIsScanned(true);
  127. scanner.pause(true);
  128. break;
  129. }
  130. }
  131. }, [scanner, isScanned]);
  132. const handleScanSuccess = useCallback<QrcodeSuccessCallback>(
  133. (decodedText, result) => {
  134. if (scanner) {
  135. console.log(`Decoded text: ${decodedText}`);
  136. const parseData: QrCodeInfo = JSON.parse(decodedText);
  137. console.log(parseData);
  138. // Handle the decoded text as needed
  139. switchScanStatus();
  140. onScanSuccess(parseData);
  141. }
  142. },
  143. [scanner, onScanSuccess]
  144. );
  145. const handleScanError = useCallback<QrcodeErrorCallback>(
  146. (errorMessage, error) => {
  147. // console.log(`Error: ${errorMessage}`);
  148. if (onScanError) {
  149. onScanError(errorMessage);
  150. }
  151. },
  152. [scanner, onScanError]
  153. );
  154. const handleScanCloseButton = useCallback(async () => {
  155. if (scanner) {
  156. console.log("Cleaning up scanner...");
  157. await scanner.stop();
  158. scanner.clear();
  159. onClose();
  160. }
  161. }, [scanner]);
  162. // close modal without using Cancel Button
  163. const handleScanClose = useCallback(async () => {
  164. if (scanner && !isOpen) {
  165. handleScanCloseButton();
  166. }
  167. }, [scanner, isOpen, handleScanCloseButton]);
  168. // -------------------------------------------------------//
  169. useEffect(() => {
  170. setScanner(new Html5Qrcode("qr-reader", scannerConfig));
  171. handleCameraList();
  172. }, []);
  173. useEffect(() => {
  174. handleScanStart();
  175. }, [scanner, selectedCameraId]);
  176. useEffect(() => {
  177. handleScanClose();
  178. }, [isOpen]);
  179. return (
  180. <>
  181. <Stack spacing={2}>
  182. {title && (
  183. <Typography
  184. variant="overline"
  185. display="block"
  186. marginBlockEnd={1}
  187. paddingLeft={2}
  188. >
  189. {"Title"}
  190. </Typography>
  191. )}
  192. <Grid
  193. container
  194. alignItems="center"
  195. justifyContent="center"
  196. rowSpacing={2}
  197. columns={{ xs: 6, sm: 12 }}
  198. >
  199. <Grid item xs={12}>
  200. <div
  201. style={{
  202. textAlign: "center",
  203. margin: "auto",
  204. justifyContent: "center",
  205. }}
  206. id="qr-reader"
  207. hidden={isScanned}
  208. />
  209. </Grid>
  210. {/* {cameraList.length > 0 && <Grid item xs={6} >
  211. <Autocomplete
  212. disableClearable
  213. noOptionsText={t("No Options")}
  214. options={cameraList}
  215. value={cameraList.find((camera) => camera.id === selectedCameraId)}
  216. onChange={((event, value) => {
  217. setSelectedCameraId(value.id)
  218. })}
  219. renderInput={(params) => (
  220. <TextField
  221. {...params}
  222. variant="outlined"
  223. label={t("Selected Camera")}
  224. />
  225. )}
  226. />
  227. </Grid>} */}
  228. {contents &&
  229. contents.map((string) => {
  230. return (
  231. <Grid item xs={8}>
  232. {string}
  233. </Grid>
  234. );
  235. })}
  236. </Grid>
  237. <Stack
  238. direction="row"
  239. justifyContent={"flex-end"}
  240. spacing={2}
  241. sx={{ margin: 2 }}
  242. >
  243. <Button
  244. size="small"
  245. onClick={switchScanStatus}
  246. variant="contained"
  247. // sx={{ margin: 2 }}
  248. startIcon={
  249. isScanned ? <QrCodeScannerIcon /> : <StopCircleOutlinedIcon />
  250. }
  251. >
  252. {isScanned ? t("Start Scanning") : t("Stop Scanning")}
  253. </Button>
  254. <Button
  255. size="small"
  256. onClick={handleScanCloseButton}
  257. variant="outlined"
  258. // color="error"
  259. // sx={{ margin: 2 }}
  260. startIcon={<CloseOutlinedIcon />}
  261. >
  262. {t("Cancel")}
  263. </Button>
  264. </Stack>
  265. </Stack>
  266. </>
  267. );
  268. };
  269. export default QrCodeScanner;