| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>QR Check-In/Check-Out System</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <script src="https://cdn.jsdelivr.net/npm/qrcode@1.5.1/build/qrcode.min.js"></script> |
| <script src="https://cdn.jsdelivr.net/npm/jsqr@1.4.0/dist/jsQR.min.js"></script> |
| <style> |
| .video-container { |
| position: relative; |
| width: 100%; |
| max-width: 500px; |
| margin: 0 auto; |
| } |
| .scan-region-highlight { |
| position: absolute; |
| top: 50%; |
| left: 50%; |
| transform: translate(-50%, -50%); |
| width: 70%; |
| height: 70%; |
| max-width: 300px; |
| max-height: 300px; |
| border: 3px dashed rgba(255, 255, 255, 0.5); |
| border-radius: 10px; |
| box-sizing: border-box; |
| } |
| .hidden { |
| display: none; |
| } |
| </style> |
| </head> |
| <body class="bg-gray-100 min-h-screen"> |
| <div class="container mx-auto px-4 py-8"> |
| <div class="max-w-md mx-auto bg-white rounded-xl shadow-md overflow-hidden"> |
| |
| <div class="bg-blue-600 py-4 px-6"> |
| <h1 class="text-2xl font-bold text-white text-center">QR Check-In/Out System</h1> |
| </div> |
| |
| |
| <div class="p-6"> |
| |
| <div class="flex border-b border-gray-200 mb-6"> |
| <button id="checkInTab" class="flex-1 py-2 px-4 font-medium text-blue-600 border-b-2 border-blue-600">Check In</button> |
| <button id="checkOutTab" class="flex-1 py-2 px-4 font-medium text-gray-500">Check Out</button> |
| <button id="scanTab" class="flex-1 py-2 px-4 font-medium text-gray-500">Scan QR</button> |
| </div> |
| |
| |
| <div id="checkInSection" class="space-y-4"> |
| <div class="form-group"> |
| <label class="block text-gray-700 text-sm font-bold mb-2" for="name"> |
| Full Name |
| </label> |
| <input class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" id="name" type="text" placeholder="Enter your name"> |
| </div> |
| <div class="form-group"> |
| <label class="block text-gray-700 text-sm font-bold mb-2" for="purpose"> |
| Purpose |
| </label> |
| <select class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" id="purpose"> |
| <option value="">Select purpose</option> |
| <option value="Meeting">Meeting</option> |
| <option value="Delivery">Delivery</option> |
| <option value="Visit">Visit</option> |
| <option value="Other">Other</option> |
| </select> |
| </div> |
| <div class="form-group"> |
| <label class="block text-gray-700 text-sm font-bold mb-2" for="host"> |
| Host/Department |
| </label> |
| <input class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" id="host" type="text" placeholder="Who are you visiting?"> |
| </div> |
| <button id="generateCheckInQR" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-md transition duration-200"> |
| Generate Check-In QR |
| </button> |
| <div id="checkInQRCode" class="hidden p-4 border border-gray-200 rounded-md flex flex-col items-center"> |
| <div id="qrCodeCheckIn" class="mb-4"></div> |
| <button id="downloadCheckInQR" class="bg-green-600 hover:bg-green-700 text-white font-bold py-2 px-4 rounded-md transition duration-200"> |
| Download QR Code |
| </button> |
| </div> |
| </div> |
| |
| |
| <div id="checkOutSection" class="space-y-4 hidden"> |
| <div class="form-group"> |
| <label class="block text-gray-700 text-sm font-bold mb-2" for="checkOutId"> |
| Check-In ID |
| </label> |
| <input class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" id="checkOutId" type="text" placeholder="Enter your check-in ID"> |
| </div> |
| <button id="generateCheckOutQR" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-md transition duration-200"> |
| Generate Check-Out QR |
| </button> |
| <div id="checkOutQRCode" class="hidden p-4 border border-gray-200 rounded-md flex flex-col items-center"> |
| <div id="qrCodeCheckOut" class="mb-4"></div> |
| <button id="downloadCheckOutQR" class="bg-green-600 hover:bg-green-700 text-white font-bold py-2 px-4 rounded-md transition duration-200"> |
| Download QR Code |
| </button> |
| </div> |
| </div> |
| |
| |
| <div id="scanSection" class="space-y-4 hidden"> |
| <div class="video-container"> |
| <video id="scanner" width="100%" muted playsinline></video> |
| <div class="scan-region-highlight"></div> |
| </div> |
| <div id="scanResult" class="text-center p-4 bg-gray-100 rounded-md hidden"> |
| <h3 class="font-bold mb-2">Scan Result:</h3> |
| <p id="resultText" class="text-gray-800"></p> |
| <div id="actionButtons" class="mt-4 hidden"> |
| <button id="checkInAction" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-md mr-2 transition duration-200"> |
| Check In |
| </button> |
| <button id="checkOutAction" class="bg-red-600 hover:bg-red-700 text-white font-bold py-2 px-4 rounded-md transition duration-200"> |
| Check Out |
| </button> |
| </div> |
| </div> |
| <button id="startScan" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-md transition duration-200"> |
| Start Scanner |
| </button> |
| <button id="stopScan" class="w-full bg-red-600 hover:bg-red-700 text-white font-bold py-2 px-4 rounded-md transition duration-200 hidden"> |
| Stop Scanner |
| </button> |
| </div> |
| |
| |
| <div class="mt-8 border-t border-gray-200 pt-6"> |
| <h2 class="text-xl font-bold text-gray-800 mb-4">Recent Activity</h2> |
| <div id="activityLog" class="space-y-2 max-h-60 overflow-y-auto"> |
| |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| <script> |
| |
| document.getElementById('checkInTab').addEventListener('click', () => { |
| showSection('checkInSection'); |
| setActiveTab('checkInTab'); |
| }); |
| |
| document.getElementById('checkOutTab').addEventListener('click', () => { |
| showSection('checkOutSection'); |
| setActiveTab('checkOutTab'); |
| }); |
| |
| document.getElementById('scanTab').addEventListener('click', () => { |
| showSection('scanSection'); |
| setActiveTab('scanTab'); |
| }); |
| |
| function showSection(sectionId) { |
| document.getElementById('checkInSection').classList.add('hidden'); |
| document.getElementById('checkOutSection').classList.add('hidden'); |
| document.getElementById('scanSection').classList.add('hidden'); |
| document.getElementById(sectionId).classList.remove('hidden'); |
| } |
| |
| function setActiveTab(tabId) { |
| document.getElementById('checkInTab').classList.remove('text-blue-600', 'border-blue-600'); |
| document.getElementById('checkInTab').classList.add('text-gray-500'); |
| document.getElementById('checkOutTab').classList.remove('text-blue-600', 'border-blue-600'); |
| document.getElementById('checkOutTab').classList.add('text-gray-500'); |
| document.getElementById('scanTab').classList.remove('text-blue-600', 'border-blue-600'); |
| document.getElementById('scanTab').classList.add('text-gray-500'); |
| |
| document.getElementById(tabId).classList.remove('text-gray-500'); |
| document.getElementById(tabId).classList.add('text-blue-600', 'border-blue-600'); |
| } |
| |
| |
| document.getElementById('generateCheckInQR').addEventListener('click', () => { |
| const name = document.getElementById('name').value; |
| const purpose = document.getElementById('purpose').value; |
| const host = document.getElementById('host').value; |
| |
| if (!name || !purpose || !host) { |
| alert('Please fill in all fields'); |
| return; |
| } |
| |
| const checkInData = { |
| type: 'checkIn', |
| name: name, |
| purpose: purpose, |
| host: host, |
| timestamp: new Date().toISOString(), |
| id: generateId() |
| }; |
| |
| const qrData = JSON.stringify(checkInData); |
| |
| QRCode.toCanvas(document.getElementById('qrCodeCheckIn'), qrData, { |
| width: 200, |
| margin: 2, |
| color: { |
| dark: '#000000', |
| light: '#ffffff' |
| } |
| }, (error) => { |
| if (error) console.error(error); |
| document.getElementById('checkInQRCode').classList.remove('hidden'); |
| |
| |
| localStorage.setItem(checkInData.id, qrData); |
| |
| |
| addActivityLog(`Checked in: ${name} visiting ${host} for ${purpose}`); |
| }); |
| }); |
| |
| |
| document.getElementById('generateCheckOutQR').addEventListener('click', () => { |
| const checkOutId = document.getElementById('checkOutId').value; |
| |
| if (!checkOutId) { |
| alert('Please enter your check-in ID'); |
| return; |
| } |
| |
| |
| const checkInData = localStorage.getItem(checkOutId); |
| |
| if (!checkInData) { |
| alert('No check-in record found with this ID'); |
| return; |
| } |
| |
| const checkOutData = { |
| ...JSON.parse(checkInData), |
| type: 'checkOut', |
| checkOutTimestamp: new Date().toISOString() |
| }; |
| |
| const qrData = JSON.stringify(checkOutData); |
| |
| QRCode.toCanvas(document.getElementById('qrCodeCheckOut'), qrData, { |
| width: 200, |
| margin: 2, |
| color: { |
| dark: '#000000', |
| light: '#ffffff' |
| } |
| }, (error) => { |
| if (error) console.error(error); |
| document.getElementById('checkOutQRCode').classList.remove('hidden'); |
| |
| |
| localStorage.setItem(checkOutId, qrData); |
| |
| |
| const data = JSON.parse(checkInData); |
| addActivityLog(`Checked out: ${data.name} from visiting ${data.host}`); |
| }); |
| }); |
| |
| |
| document.getElementById('downloadCheckInQR').addEventListener('click', () => { |
| const canvas = document.querySelector('#qrCodeCheckIn canvas'); |
| const link = document.createElement('a'); |
| link.download = 'check-in-qr.png'; |
| link.href = canvas.toDataURL('image/png'); |
| link.click(); |
| }); |
| |
| document.getElementById('downloadCheckOutQR').addEventListener('click', () => { |
| const canvas = document.querySelector('#qrCodeCheckOut canvas'); |
| const link = document.createElement('a'); |
| link.download = 'check-out-qr.png'; |
| link.href = canvas.toDataURL('image/png'); |
| link.click(); |
| }); |
| |
| |
| let scannerInterval; |
| const video = document.getElementById('scanner'); |
| const startScanBtn = document.getElementById('startScan'); |
| const stopScanBtn = document.getElementById('stopScan'); |
| const scanResult = document.getElementById('scanResult'); |
| const resultText = document.getElementById('resultText'); |
| const actionButtons = document.getElementById('actionButtons'); |
| const checkInAction = document.getElementById('checkInAction'); |
| const checkOutAction = document.getElementById('checkOutAction'); |
| |
| let currentScanData = null; |
| |
| startScanBtn.addEventListener('click', async () => { |
| try { |
| const stream = await navigator.mediaDevices.getUserMedia({ |
| video: { |
| facingMode: 'environment', |
| width: { ideal: 1280 }, |
| height: { ideal: 720 } |
| } |
| }); |
| video.srcObject = stream; |
| video.play(); |
| |
| startScanBtn.classList.add('hidden'); |
| stopScanBtn.classList.remove('hidden'); |
| scanResult.classList.add('hidden'); |
| |
| scannerInterval = setInterval(scanQR, 100); |
| } catch (err) { |
| console.error('Error accessing camera:', err); |
| alert('Could not access the camera. Please ensure you have granted camera permissions.'); |
| } |
| }); |
| |
| stopScanBtn.addEventListener('click', () => { |
| clearInterval(scannerInterval); |
| const stream = video.srcObject; |
| if (stream) { |
| stream.getTracks().forEach(track => track.stop()); |
| } |
| video.srcObject = null; |
| |
| startScanBtn.classList.remove('hidden'); |
| stopScanBtn.classList.add('hidden'); |
| scanResult.classList.add('hidden'); |
| }); |
| |
| function scanQR() { |
| const canvas = document.createElement('canvas'); |
| canvas.width = video.videoWidth; |
| canvas.height = video.videoHeight; |
| const ctx = canvas.getContext('2d'); |
| ctx.drawImage(video, 0, 0, canvas.width, canvas.height); |
| |
| const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); |
| const code = jsQR(imageData.data, imageData.width, imageData.height, { |
| inversionAttempts: 'dontInvert', |
| }); |
| |
| if (code) { |
| try { |
| const data = JSON.parse(code.data); |
| currentScanData = data; |
| |
| if (data.type === 'checkIn') { |
| resultText.textContent = `${data.name} is checking in to visit ${data.host} for ${data.purpose}`; |
| checkInAction.classList.add('hidden'); |
| checkOutAction.classList.remove('hidden'); |
| } else if (data.type === 'checkOut') { |
| resultText.textContent = `${data.name} is checking out from visiting ${data.host}`; |
| checkInAction.classList.add('hidden'); |
| checkOutAction.classList.remove('hidden'); |
| } |
| |
| scanResult.classList.remove('hidden'); |
| actionButtons.classList.remove('hidden'); |
| |
| |
| clearInterval(scannerInterval); |
| } catch (e) { |
| resultText.textContent = 'Invalid QR Code'; |
| scanResult.classList.remove('hidden'); |
| actionButtons.classList.add('hidden'); |
| } |
| } |
| } |
| |
| checkInAction.addEventListener('click', () => { |
| if (currentScanData) { |
| addActivityLog(`Checked in via scanner: ${currentScanData.name} visiting ${currentScanData.host}`); |
| alert(`Successfully checked in ${currentScanData.name}`); |
| scanResult.classList.add('hidden'); |
| currentScanData = null; |
| |
| |
| scannerInterval = setInterval(scanQR, 100); |
| } |
| }); |
| |
| checkOutAction.addEventListener('click', () => { |
| if (currentScanData) { |
| addActivityLog(`Checked out via scanner: ${currentScanData.name} from visiting ${currentScanData.host}`); |
| alert(`Successfully checked out ${currentScanData.name}`); |
| scanResult.classList.add('hidden'); |
| currentScanData = null; |
| |
| |
| scannerInterval = setInterval(scanQR, 100); |
| } |
| }); |
| |
| |
| function generateId() { |
| return 'id-' + Math.random().toString(36).substr(2, 9); |
| } |
| |
| function addActivityLog(message) { |
| const logContainer = document.getElementById('activityLog'); |
| const logItem = document.createElement('div'); |
| logItem.className = 'p-3 bg-gray-50 rounded-md border border-gray-200'; |
| logItem.innerHTML = ` |
| <p class="text-sm text-gray-800">${message}</p> |
| <p class="text-xs text-gray-500 mt-1">${new Date().toLocaleString()}</p> |
| `; |
| logContainer.insertBefore(logItem, logContainer.firstChild); |
| |
| |
| if (logContainer.children.length > 10) { |
| logContainer.removeChild(logContainer.lastChild); |
| } |
| } |
| |
| |
| showSection('checkInSection'); |
| setActiveTab('checkInTab'); |
| </script> |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=ivanhoang/check-in" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
| </html> |