"use client"; import React, { useState, useRef, useEffect } from "react"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { toast } from "sonner"; import { Camera, CameraOff, CheckCircle2, AlertCircle, Scan } from "lucide-react"; import Webcam from "react-webcam"; import { BrowserMultiFormatReader, NotFoundException } from "@zxing/library"; export interface BarcodeScanModalProps { open: boolean; onOpenChange: (open: boolean) => void; targetField?: string; barcodeFormat?: "all" | "1d" | "2d"; autoSubmit?: boolean; onScanSuccess: (barcode: string) => void; userId?: string; } export const BarcodeScanModal: React.FC = ({ open, onOpenChange, targetField, barcodeFormat = "all", autoSubmit = false, onScanSuccess, userId = "guest", }) => { const [isScanning, setIsScanning] = useState(false); const [scannedCode, setScannedCode] = useState(""); const [error, setError] = useState(""); const [hasPermission, setHasPermission] = useState(null); const webcamRef = useRef(null); const codeReaderRef = useRef(null); const scanIntervalRef = useRef(null); // 바코드 리더 초기화 + 모달 열릴 때 상태 리셋 useEffect(() => { if (open) { setScannedCode(""); setError(""); setIsScanning(false); codeReaderRef.current = new BrowserMultiFormatReader(); } return () => { stopScanning(); if (codeReaderRef.current) { codeReaderRef.current.reset(); } }; }, [open]); // 카메라 권한 요청 const requestCameraPermission = async () => { // navigator.mediaDevices 지원 확인 if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) { setHasPermission(false); setError( "이 브라우저는 카메라 접근을 지원하지 않거나, 보안 컨텍스트(HTTPS 또는 localhost)가 아닙니다. " + "현재 프로토콜: " + window.location.protocol ); toast.error("카메라 접근이 불가능합니다."); return; } try { const stream = await navigator.mediaDevices.getUserMedia({ video: true }); setHasPermission(true); stream.getTracks().forEach((track) => track.stop()); toast.success("카메라 권한이 허용되었습니다."); } catch (err: any) { setHasPermission(false); if (err.name === "NotAllowedError") { setError("카메라 접근이 거부되었습니다. 브라우저 설정에서 카메라 권한을 허용해주세요."); toast.error("카메라 권한이 거부되었습니다."); } else if (err.name === "NotFoundError") { setError("카메라를 찾을 수 없습니다. 카메라가 연결되어 있는지 확인해주세요."); toast.error("카메라를 찾을 수 없습니다."); } else if (err.name === "NotReadableError") { setError("카메라가 이미 다른 애플리케이션에서 사용 중입니다."); toast.error("카메라가 사용 중입니다."); } else if (err.name === "NotSupportedError") { setError("보안 컨텍스트(HTTPS 또는 localhost)가 아니어서 카메라를 사용할 수 없습니다."); toast.error("HTTPS 환경이 필요합니다."); } else { setError(`카메라 접근 오류: ${err.name} - ${err.message}`); toast.error("카메라 접근 중 오류가 발생했습니다."); } } }; // 스캔 시작 const startScanning = () => { setIsScanning(true); setError(""); setScannedCode(""); scanIntervalRef.current = setInterval(() => { scanBarcode(); }, 500); }; // 스캔 중지 const stopScanning = () => { setIsScanning(false); if (scanIntervalRef.current) { clearInterval(scanIntervalRef.current); scanIntervalRef.current = null; } }; // 바코드 스캔 const scanBarcode = async () => { if (!webcamRef.current || !codeReaderRef.current) return; try { const imageSrc = webcamRef.current.getScreenshot(); if (!imageSrc) return; const img = new Image(); img.src = imageSrc; await new Promise((resolve) => { img.onload = resolve; }); const result = await codeReaderRef.current.decodeFromImageElement(img); if (result) { const barcode = result.getText(); setScannedCode(barcode); stopScanning(); toast.success(`바코드 스캔 완료: ${barcode}`); if (autoSubmit) { onScanSuccess(barcode); } } } catch (err) { if (!(err instanceof NotFoundException)) { // NotFoundException은 정상 (바코드 미인식) } } }; // 수동 확인 버튼 const handleConfirm = () => { if (scannedCode) { onScanSuccess(scannedCode); } else { toast.error("스캔된 바코드가 없습니다."); } }; return ( 바코드 스캔 카메라로 바코드를 스캔합니다. {targetField && ` (대상 필드: ${targetField})`}
{/* 카메라 권한 요청 대기 중 */} {hasPermission === null && (

카메라 권한이 필요합니다

바코드를 스캔하려면 카메라 접근 권한을 허용해주세요.

권한 요청 안내:

  • 아래 버튼을 클릭하면 브라우저에서 권한 요청 팝업이 표시됩니다
  • 팝업에서 "허용" 버튼을 클릭해주세요
  • 권한은 언제든지 브라우저 설정에서 변경할 수 있습니다
)} {/* 카메라 권한 거부됨 */} {hasPermission === false && (

카메라 접근 권한이 필요합니다

{error}

권한 허용 방법:

  1. 브라우저 주소창 왼쪽의 자물쇠 아이콘을 클릭하세요
  2. "카메라" 항목을 찾아 "허용"으로 변경하세요
  3. 페이지를 새로고침하거나 다시 스캔을 시도하세요
)} {/* 웹캠 뷰 */} {hasPermission && (
{/* 스캔 가이드 오버레이 */} {isScanning && (
스캔 중...
)} {/* 스캔 완료 오버레이 */} {scannedCode && (

스캔 완료!

{scannedCode}

)}
)} {/* 바코드 포맷 정보 */}

지원 포맷

{barcodeFormat === "all" && "1D/2D 바코드 모두 지원 (Code 128, QR Code 등)"} {barcodeFormat === "1d" && "1D 바코드 (Code 128, Code 39, EAN-13, UPC-A)"} {barcodeFormat === "2d" && "2D 바코드 (QR Code, Data Matrix)"}

{/* 에러 메시지 */} {error && (

{error}

)}
{!isScanning && !scannedCode && hasPermission && ( )} {isScanning && ( )} {scannedCode && ( )} {scannedCode && !autoSubmit && ( )}
); };