import { useState, useRef, useEffect, KeyboardEvent } from 'react'; import { Input } from '@/Components/ui/input'; import { Label } from '@/Components/ui/label'; import { Switch } from '@/Components/ui/switch'; import { RefreshCcw, Scan, Zap } from 'lucide-react'; import { cn } from '@/lib/utils'; interface ScannerInputProps { onScan: (code: string, mode: 'continuous' | 'single') => void; className?: string; placeholder?: string; } export default function ScannerInput({ onScan, className, placeholder = "點擊此處並掃描..." }: ScannerInputProps) { const [code, setCode] = useState(''); const [isContinuous, setIsContinuous] = useState(true); const [lastAction, setLastAction] = useState<{ message: string; type: 'success' | 'info' | 'error'; time: number } | null>(null); const [isFlashing, setIsFlashing] = useState(false); const inputRef = useRef(null); // Focus input on mount useEffect(() => { if (inputRef.current) { inputRef.current.focus(); } }, []); // Audio context for beep sound const playBeep = (type: 'success' | 'error' = 'success') => { try { const AudioContext = window.AudioContext || (window as any).webkitAudioContext; if (!AudioContext) return; const ctx = new AudioContext(); const oscillator = ctx.createOscillator(); const gainNode = ctx.createGain(); oscillator.connect(gainNode); gainNode.connect(ctx.destination); if (type === 'success') { oscillator.type = 'sine'; oscillator.frequency.setValueAtTime(880, ctx.currentTime); // A5 oscillator.frequency.exponentialRampToValueAtTime(440, ctx.currentTime + 0.1); gainNode.gain.setValueAtTime(0.1, ctx.currentTime); gainNode.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.1); oscillator.start(); oscillator.stop(ctx.currentTime + 0.1); } else { oscillator.type = 'sawtooth'; oscillator.frequency.setValueAtTime(110, ctx.currentTime); // Low buzz gainNode.gain.setValueAtTime(0.2, ctx.currentTime); gainNode.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.2); oscillator.start(); oscillator.stop(ctx.currentTime + 0.2); } } catch (e) { console.error('Audio playback failed', e); } }; const handleKeyDown = (e: KeyboardEvent) => { if (e.key === 'Enter') { e.preventDefault(); if (code.trim()) { handleScanSubmit(code.trim()); } } }; const handleScanSubmit = (scannedCode: string) => { // Trigger parent callback onScan(scannedCode, isContinuous ? 'continuous' : 'single'); // UI Feedback playBeep('success'); setIsFlashing(true); setTimeout(() => setIsFlashing(false), 300); // Show last action tip setLastAction({ message: `已掃描: ${scannedCode}`, type: 'success', time: Date.now() }); // Clear input and focus setCode(''); }; // Public method to set last action message from parent (if needed for more context like product name) // For now we just use internal state return (
{/* Background flashy effect */}
{/* Left: Input Area */}
setCode(e.target.value)} onKeyDown={handleKeyDown} placeholder={placeholder} className="pl-10 h-12 text-lg font-mono border-gray-300 focus:border-primary focus:ring-primary/20" /> {/* Continuous Mode Badge */}
{isContinuous && (
連續模式
)}
{/* Right: Controls & Status */}
{/* Last Action Display */}
{lastAction && (Date.now() - lastAction.time < 5000) ? (
{lastAction.message} {isContinuous && 自動加總 (+1)}
) : ( 等待掃描... )}
{/* Toggle */}
提示:開啟連續模式時,掃描相同商品會自動將數量 +1;關閉則會視為新批號輸入。
); }