fix: 바코드 스캔 개선 + POP 에러 3건 수정

- 바코드 모달: 카메라 자동 실행 제거 (버튼 클릭 시 실행)
- 바코드 모달: 수동 입력 필드 추가 (외장 스캐너/직접 입력)
- 바코드 모달: facingMode fallback (후면→전면 카메라)
- usePopSettings: pop_settings 테이블 없을 때 400 에러 무시
- RecentActivity: key 중복 에러 수정 (인덱스 추가)
This commit is contained in:
SeongHyun Kim
2026-04-06 10:12:50 +09:00
parent aba2de7ee3
commit b844eb8eb8
3 changed files with 61 additions and 10 deletions

View File

@@ -36,18 +36,22 @@ export const BarcodeScanModal: React.FC<BarcodeScanModalProps> = ({
}) => {
const [isScanning, setIsScanning] = useState(false);
const [scannedCode, setScannedCode] = useState<string>("");
const [manualInput, setManualInput] = useState<string>("");
const [error, setError] = useState<string>("");
const [hasPermission, setHasPermission] = useState<boolean | null>(null);
const webcamRef = useRef<Webcam>(null);
const codeReaderRef = useRef<BrowserMultiFormatReader | null>(null);
const scanIntervalRef = useRef<NodeJS.Timeout | null>(null);
const manualInputRef = useRef<HTMLInputElement>(null);
// 바코드 리더 초기화 + 모달 열릴 때 상태 리셋
useEffect(() => {
if (open) {
setScannedCode("");
setManualInput("");
setError("");
setIsScanning(false);
setHasPermission(null);
codeReaderRef.current = new BrowserMultiFormatReader();
}
@@ -73,10 +77,15 @@ export const BarcodeScanModal: React.FC<BarcodeScanModalProps> = ({
}
try {
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
// 후면 카메라 먼저 시도, 실패하면 전면 카메라 fallback
let stream: MediaStream;
try {
stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: "environment" } });
} catch {
stream = await navigator.mediaDevices.getUserMedia({ video: true });
}
setHasPermission(true);
stream.getTracks().forEach((track) => track.stop());
toast.success("카메라 권한이 허용되었습니다.");
} catch (err: any) {
setHasPermission(false);
@@ -154,12 +163,14 @@ export const BarcodeScanModal: React.FC<BarcodeScanModalProps> = ({
}
};
// 수동 확인 버튼
// 수동 확인 버튼 (스캔 결과 또는 직접 입력)
const handleConfirm = () => {
if (scannedCode) {
onScanSuccess(scannedCode);
const code = scannedCode || manualInput.trim();
if (code) {
onScanSuccess(code); // 호출 측에서 검색 필드를 덮어쓰기
onOpenChange(false);
} else {
toast.error("스캔된 바코드가 없습니다.");
toast.error("바코드를 스캔하거나 직접 입력해주세요.");
}
};
@@ -254,7 +265,10 @@ export const BarcodeScanModal: React.FC<BarcodeScanModalProps> = ({
audio={false}
screenshotFormat="image/jpeg"
videoConstraints={{
facingMode: "environment",
facingMode: { ideal: "environment" },
}}
onUserMediaError={() => {
// environment 카메라 실패 시 자동 fallback (Webcam 내부 처리)
}}
className="h-full w-full object-cover"
/>
@@ -285,6 +299,41 @@ export const BarcodeScanModal: React.FC<BarcodeScanModalProps> = ({
</div>
)}
{/* 수동 입력 (카메라 사용 불가 시 또는 외장 스캐너 사용 시) */}
<div className="rounded-md border border-border bg-muted/30 p-3 space-y-2">
<p className="text-xs font-medium text-muted-foreground"> </p>
<div className="flex gap-2">
<input
ref={manualInputRef}
type="text"
value={manualInput}
onChange={(e) => setManualInput(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter" && manualInput.trim()) {
e.preventDefault();
onScanSuccess(manualInput.trim());
onOpenChange(false);
}
}}
placeholder="바코드/QR 번호 입력 후 Enter"
className="flex-1 h-11 rounded-lg border border-border px-3 text-sm focus:outline-none focus:ring-2 focus:ring-primary"
autoFocus={hasPermission === false}
/>
<Button
onClick={() => {
if (manualInput.trim()) {
onScanSuccess(manualInput.trim());
onOpenChange(false);
}
}}
disabled={!manualInput.trim()}
className="h-11 px-4"
>
</Button>
</div>
</div>
{/* 바코드 포맷 정보 */}
<div className="rounded-md border border-border bg-muted/50 p-3">
<div className="flex items-start gap-2">