Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Barcode Scanner App</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://unpkg.com/feather-icons"></script> | |
| <style> | |
| .scan-animation { | |
| animation: pulse 2s infinite; | |
| } | |
| @keyframes pulse { | |
| 0% { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.4); } | |
| 70% { box-shadow: 0 0 0 10px rgba(59, 130, 246, 0); } | |
| 100% { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0); } | |
| } | |
| .toast { | |
| transition: all 0.3s ease; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-100 min-h-screen"> | |
| <div class="container mx-auto px-4 py-8 max-w-md"> | |
| <header class="mb-8 text-center"> | |
| <h1 class="text-3xl font-bold text-indigo-600 mb-2">UniScan X</h1> | |
| <p class="text-gray-600">Advanced barcode scanning solution</p> | |
| </header> | |
| <!-- Scanner Section --> | |
| <div class="bg-white rounded-xl shadow-md p-6 mb-6"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h2 class="text-xl font-semibold text-gray-800">Scanner</h2> | |
| <button id="scanBtn" class="bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded-lg flex items-center transition"> | |
| <i data-feather="maximize" class="mr-2"></i> Start Scan | |
| </button> | |
| </div> | |
| <div class="text-center"> | |
| <div id="scannerPlaceholder" class="mx-auto w-full h-48 bg-gray-200 rounded-lg flex items-center justify-center scan-animation"> | |
| <div class="text-center"> | |
| <i data-feather="camera" class="w-12 h-12 mx-auto text-gray-400 mb-2"></i> | |
| <p class="text-gray-500">Ready to scan</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Watchlist Section --> | |
| <div class="bg-white rounded-xl shadow-md p-6 mb-6"> | |
| <h2 class="text-xl font-semibold text-gray-800 mb-4">Special Watchlist</h2> | |
| <textarea id="watchlistInput" rows="3" class="w-full p-3 border border-gray-300 rounded-lg mb-3 focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500" placeholder="Enter barcodes to watch (one per line)"></textarea> | |
| </div> | |
| <!-- Scan History --> | |
| <div class="bg-white rounded-xl shadow-md p-6"> | |
| <h2 class="text-xl font-semibold text-gray-800 mb-4">Recent Scans</h2> | |
| <div id="scanHistory" class="space-y-2"> | |
| <p class="text-gray-500 text-center py-4">No scans yet</p> | |
| </div> | |
| </div> | |
| <!-- Toast Notification --> | |
| <div id="toast" class="fixed bottom-4 right-4 bg-gray-800 text-white px-4 py-3 rounded-lg shadow-lg hidden toast"> | |
| <div class="flex items-center"> | |
| <span id="toastMessage"></span> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // State Management | |
| const state = { | |
| scanHistory: [], | |
| watchlist: [], | |
| maxHistorySize: 10, | |
| isScanning: false | |
| }; | |
| // DOM Elements | |
| const dom = { | |
| scanBtn: document.getElementById('scanBtn'), | |
| scannerPlaceholder: document.getElementById('scannerPlaceholder'), | |
| watchlistInput: document.getElementById('watchlistInput'), | |
| scanHistory: document.getElementById('scanHistory'), | |
| toast: document.getElementById('toast'), | |
| toastMessage: document.getElementById('toastMessage') | |
| }; | |
| // Initialize Feather Icons | |
| feather.replace(); | |
| // Event Listeners | |
| dom.scanBtn.addEventListener('click', startScanProcess); | |
| dom.watchlistInput.addEventListener('change', updateWatchlist); | |
| // Scanner Functionality | |
| function startScanProcess() { | |
| if (state.isScanning) return; | |
| state.isScanning = true; | |
| dom.scanBtn.disabled = true; | |
| dom.scannerPlaceholder.innerHTML = ` | |
| <div class="text-center"> | |
| <div class="border-4 border-indigo-400 rounded-lg w-64 h-32 mb-4"></div> | |
| <p class="text-gray-700">Scanning...</p> | |
| </div> | |
| `; | |
| // Simulate scanning (in real app, this would use device camera) | |
| setTimeout(() => { | |
| const barcode = generateRandomBarcode(); | |
| handleScannedBarcode(barcode); | |
| state.isScanning = false; | |
| dom.scanBtn.disabled = false; | |
| resetScannerUI(); | |
| }, 2000); | |
| } | |
| function resetScannerUI() { | |
| dom.scannerPlaceholder.innerHTML = ` | |
| <div class="text-center"> | |
| <i data-feather="camera" class="w-12 h-12 mx-auto text-gray-400 mb-2"></i> | |
| <p class="text-gray-500">Ready to scan</p> | |
| </div> | |
| `; | |
| feather.replace(); | |
| } | |
| // Barcode Processing | |
| function generateRandomBarcode() { | |
| return 'BC' + Math.floor(100000 + Math.random() * 900000); | |
| } | |
| function handleScannedBarcode(barcode) { | |
| if (isBarcodeInHistory(barcode)) { | |
| showToast('This barcode has already been scanned!', 'warning'); | |
| return; | |
| } | |
| addBarcodeToHistory(barcode); | |
| updateHistoryDisplay(); | |
| if (isBarcodeInWatchlist(barcode)) { | |
| showToast('⚠️ Special barcode detected!', 'warning'); | |
| } else { | |
| sendBarcodeToServer(barcode); | |
| } | |
| } | |
| function isBarcodeInHistory(barcode) { | |
| return state.scanHistory.includes(barcode); | |
| } | |
| function isBarcodeInWatchlist(barcode) { | |
| return state.watchlist.includes(barcode); | |
| } | |
| function addBarcodeToHistory(barcode) { | |
| if (state.scanHistory.length >= state.maxHistorySize) { | |
| state.scanHistory.pop(); | |
| } | |
| state.scanHistory.unshift(barcode); | |
| } | |
| // Watchlist Management | |
| function updateWatchlist() { | |
| const text = dom.watchlistInput.value; | |
| state.watchlist = text.split('\n') | |
| .map(item => item.trim()) | |
| .filter(item => item !== ''); | |
| } | |
| // History Display | |
| function updateHistoryDisplay() { | |
| if (state.scanHistory.length === 0) { | |
| dom.scanHistory.innerHTML = '<p class="text-gray-500 text-center py-4">No scans yet</p>'; | |
| return; | |
| } | |
| dom.scanHistory.innerHTML = state.scanHistory.map(barcode => ` | |
| <div class="flex justify-between items-center p-2 bg-gray-50 rounded-lg"> | |
| <span class="font-mono">${barcode}</span> | |
| <span class="text-xs text-gray-500">Just now</span> | |
| </div> | |
| `).join(''); | |
| } | |
| // Server Communication | |
| function sendBarcodeToServer(barcode) { | |
| fetch('https://api.example.com/scans', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ barcode }) | |
| }) | |
| .then(response => { | |
| if (!response.ok) throw new Error('Server error'); | |
| showToast(`Barcode "${barcode}" saved successfully`, 'success'); | |
| }) | |
| .catch(error => { | |
| console.error('Error:', error); | |
| showToast('Failed to save barcode', 'error'); | |
| }); | |
| } | |
| // Toast Notifications | |
| function showToast(message, type = 'info') { | |
| dom.toastMessage.textContent = message; | |
| const colors = { | |
| info: 'bg-gray-800', | |
| success: 'bg-green-600', | |
| warning: 'bg-yellow-600', | |
| error: 'bg-red-600' | |
| }; | |
| dom.toast.className = `fixed bottom-4 right-4 text-white px-4 py-3 rounded-lg shadow-lg toast ${colors[type]}`; | |
| dom.toast.classList.remove('hidden'); | |
| setTimeout(() => { | |
| dom.toast.classList.add('hidden'); | |
| }, 3000); | |
| } | |
| </script> | |
| </body> | |
| </html> |