Dashboard

Conductor - Tap Card Ticket Issuance
Conductor Interface: This interface is specifically for issuing tickets using tap cards. Have passengers tap their cards or enter the NFC UID manually.
Ask passenger to tap their card on the NFC reader or enter UID manually
No card detected
Recent Tickets

No tickets issued yet

let ticketCounter = 1; let issuedTickets = JSON.parse(localStorage.getItem('issuedTickets') || '[]'); let currentTapCard = null; let tapCards = JSON.parse(localStorage.getItem('tapCards') || '[]'); // Initialize some sample tap cards if none exist if (tapCards.length === 0) { tapCards = [ { nfc_uid: 'NFC001', card_number: 'TC20260109001', card_type: 'student', balance: 25.50, holder_name: 'John Student', status: 'active' }, { nfc_uid: 'NFC002', card_number: 'TC20260109002', card_type: 'senior', balance: 15.75, holder_name: 'Mary Senior', status: 'active' }, { nfc_uid: 'NFC003', card_number: 'TC20260109003', card_type: 'adult', balance: 50.00, holder_name: 'Bob Adult', status: 'active' } ]; localStorage.setItem('tapCards', JSON.stringify(tapCards)); } document.addEventListener('DOMContentLoaded', function() { updateRecentTicketsList(); updateFareCalculation(); document.getElementById('conductorTicketForm').addEventListener('submit', function(e) { e.preventDefault(); issueTicket(); }); // Auto-validate when NFC UID is entered document.getElementById('nfcUid').addEventListener('input', function() { if (this.value.length >= 6) { setTimeout(() => validateTapCard(), 500); } }); }); function validateTapCard() { const nfcUid = document.getElementById('nfcUid').value.trim(); if (!nfcUid) { showAlert('error', 'Please enter NFC UID or tap a card'); return; } // Find tap card in localStorage const tapCard = tapCards.find(card => card.nfc_uid === nfcUid || card.card_number === nfcUid); if (tapCard && tapCard.status === 'active') { currentTapCard = tapCard; displayCardDetails(tapCard); updateFareCalculation(); showAlert('success', 'Tap card validated successfully!'); } else { currentTapCard = null; document.getElementById('cardStatus').innerHTML = 'Invalid Card'; document.getElementById('cardDetails').style.display = 'none'; document.getElementById('balanceCheck').style.display = 'none'; showAlert('error', 'Invalid or inactive tap card'); } } function displayCardDetails(card) { document.getElementById('cardStatus').innerHTML = 'Valid Card'; document.getElementById('cardNumber').textContent = card.card_number; document.getElementById('cardType').textContent = card.card_type.charAt(0).toUpperCase() + card.card_type.slice(1); document.getElementById('cardHolder').textContent = card.holder_name; document.getElementById('cardBalance').textContent = '$' + card.balance.toFixed(2); document.getElementById('cardStatusText').textContent = card.status.charAt(0).toUpperCase() + card.status.slice(1); // Set passenger category based on card type const categoryMap = { 'student': 'Student (50% off)', 'senior': 'Senior (30% off)', 'disabled': 'Disabled (50% off)', 'adult': 'Adult' }; document.getElementById('passengerCategoryDisplay').value = categoryMap[card.card_type] || 'Adult'; document.getElementById('passengerCategory').value = card.card_type; document.getElementById('passengerName').value = card.holder_name; // Calculate discount let discountRate = 0; switch(card.card_type) { case 'student': case 'disabled': discountRate = 50; break; case 'senior': discountRate = 30; break; } document.getElementById('cardDiscount').textContent = discountRate + '%'; document.getElementById('cardDetails').style.display = 'block'; } function updateFareCalculation() { const ticketType = document.getElementById('ticketType').value; const passengerCategory = document.getElementById('passengerCategory').value; const routeSelect = document.getElementById('routeSelect'); const selectedOption = routeSelect.options[routeSelect.selectedIndex]; if (!selectedOption.value) { document.getElementById('baseFare').value = ''; document.getElementById('discount').value = ''; document.getElementById('finalAmount').value = ''; document.getElementById('balanceCheck').style.display = 'none'; return; } let baseFare = parseFloat(selectedOption.dataset.fare); // Apply ticket type multipliers switch(ticketType) { case 'return': baseFare *= 1.8; break; case 'daily': baseFare *= 3; break; case 'weekly': baseFare *= 18; break; case 'monthly': baseFare *= 65; break; } // Apply passenger category discounts let discountRate = 0; switch(passengerCategory) { case 'student': case 'disabled': discountRate = 0.5; break; case 'senior': discountRate = 0.3; break; } const discountAmount = baseFare * discountRate; const finalAmount = baseFare - discountAmount; document.getElementById('baseFare').value = '$' + baseFare.toFixed(2); document.getElementById('discount').value = discountRate > 0 ? '-$' + discountAmount.toFixed(2) + ' (' + (discountRate * 100) + '%)' : '$0.00'; document.getElementById('finalAmount').value = '$' + finalAmount.toFixed(2); // Show balance check if card is validated if (currentTapCard) { document.getElementById('currentBalance').textContent = '$' + currentTapCard.balance.toFixed(2); document.getElementById('ticketCost').textContent = '$' + finalAmount.toFixed(2); const remainingBalance = currentTapCard.balance - finalAmount; document.getElementById('remainingBalance').textContent = '$' + remainingBalance.toFixed(2); document.getElementById('remainingBalance').className = remainingBalance >= 0 ? 'h5 text-success' : 'h5 text-danger'; document.getElementById('balanceCheck').style.display = 'block'; } } function issueTicket() { if (!currentTapCard) { showAlert('error', 'Please validate a tap card first'); return; } const routeSelect = document.getElementById('routeSelect'); const vehicleSelect = document.getElementById('vehicleSelect'); if (!routeSelect.value) { showAlert('error', 'Please select a route'); return; } const finalAmountText = document.getElementById('finalAmount').value; if (!finalAmountText) { showAlert('error', 'Please ensure fare calculation is complete'); return; } const finalAmount = parseFloat(finalAmountText.replace('$', '')); // Check if card has sufficient balance if (currentTapCard.balance < finalAmount) { showAlert('error', 'Insufficient balance on tap card. Current balance: $' + currentTapCard.balance.toFixed(2)); return; } const ticketData = { ticketNumber: 'TK' + Date.now() + ticketCounter.toString().padStart(3, '0'), ticketType: document.getElementById('ticketType').value, passengerCategory: document.getElementById('passengerCategory').value, route: routeSelect.options[routeSelect.selectedIndex].text, vehicle: vehicleSelect.value ? vehicleSelect.options[vehicleSelect.selectedIndex].text : 'Not specified', baseFare: document.getElementById('baseFare').value, discount: document.getElementById('discount').value, finalAmount: '$' + finalAmount.toFixed(2), paymentMethod: 'tap_card', passengerName: currentTapCard.holder_name, cardNumber: currentTapCard.card_number, nfcUid: currentTapCard.nfc_uid, issuedAt: new Date().toLocaleString(), status: 'valid', qrCode: 'QR' + Date.now() }; // Deduct amount from card balance currentTapCard.balance -= finalAmount; // Update tap card in localStorage const cardIndex = tapCards.findIndex(card => card.nfc_uid === currentTapCard.nfc_uid); if (cardIndex !== -1) { tapCards[cardIndex] = currentTapCard; localStorage.setItem('tapCards', JSON.stringify(tapCards)); } // Add ticket to issued tickets issuedTickets.unshift(ticketData); ticketCounter++; localStorage.setItem('issuedTickets', JSON.stringify(issuedTickets)); updateRecentTicketsList(); resetForm(); showAlert('success', 'Ticket ' + ticketData.ticketNumber + ' issued successfully! Remaining balance: $' + currentTapCard.balance.toFixed(2)); } function resetForm() { document.getElementById('nfcUid').value = ''; document.getElementById('ticketType').value = 'single'; document.getElementById('routeSelect').value = ''; document.getElementById('vehicleSelect').value = ''; document.getElementById('passengerCategoryDisplay').value = 'Adult'; document.getElementById('passengerCategory').value = 'adult'; document.getElementById('passengerName').value = ''; document.getElementById('cardStatus').innerHTML = 'No card detected'; document.getElementById('cardDetails').style.display = 'none'; document.getElementById('balanceCheck').style.display = 'none'; currentTapCard = null; updateFareCalculation(); } function updateRecentTicketsList() { const container = document.getElementById('recentTickets'); if (issuedTickets.length === 0) { container.innerHTML = '

No tickets issued yet

'; return; } let html = ''; issuedTickets.slice(0, 10).forEach(ticket => { const paymentBadge = ticket.paymentMethod === 'tap_card' ? 'bg-primary' : 'bg-success'; const paymentText = ticket.paymentMethod === 'tap_card' ? 'Tap Card' : ticket.paymentMethod; html += `
${ticket.ticketNumber}
${ticket.route}
${ticket.finalAmount} ${ticket.cardNumber ? '
' + ticket.cardNumber + '' : ''}
${paymentText}
${new Date(ticket.issuedAt).toLocaleTimeString()}
`; }); container.innerHTML = html; } function refreshTickets() { updateRecentTicketsList(); showAlert('success', 'Tickets refreshed!'); } function showAlert(type, message) { const alertDiv = document.createElement('div'); alertDiv.className = `alert alert-${type === 'success' ? 'success' : 'danger'} alert-dismissible fade show position-fixed`; alertDiv.style.top = '20px'; alertDiv.style.right = '20px'; alertDiv.style.zIndex = '9999'; alertDiv.innerHTML = ` ${message} `; document.body.appendChild(alertDiv); setTimeout(() => { if (alertDiv.parentNode) { alertDiv.parentNode.removeChild(alertDiv); } }, 5000); } document.getElementById('finalAmount').value = ''; return; } let baseFare = parseFloat(selectedOption.dataset.fare); // Apply ticket type multipliers switch(ticketType) { case 'return': baseFare *= 1.8; break; case 'daily': baseFare *= 3; break; case 'weekly': baseFare *= 18; break; case 'monthly': baseFare *= 65; break; } // Apply passenger category discounts let discountRate = 0; switch(passengerCategory) { case 'student': case 'disabled': discountRate = 0.5; break; case 'senior': discountRate = 0.3; break; } const discountAmount = baseFare * discountRate; const finalAmount = baseFare - discountAmount; document.getElementById('baseFare').value = '$' + baseFare.toFixed(2); document.getElementById('discount').value = discountRate > 0 ? '-$' + discountAmount.toFixed(2) + ' (' + (discountRate * 100) + '%)' : '$0.00'; document.getElementById('finalAmount').value = '$' + finalAmount.toFixed(2); } function issueTicket() { const routeSelect = document.getElementById('routeSelect'); const vehicleSelect = document.getElementById('vehicleSelect'); if (!routeSelect.value) { showAlert('error', 'Please select a route'); return; } if (!document.getElementById('finalAmount').value) { showAlert('error', 'Please ensure fare calculation is complete'); return; } const ticketData = { ticketNumber: 'TK' + Date.now() + ticketCounter.toString().padStart(3, '0'), ticketType: document.getElementById('ticketType').value, passengerCategory: document.getElementById('passengerCategory').value, route: routeSelect.options[routeSelect.selectedIndex].text, vehicle: vehicleSelect.value ? vehicleSelect.options[vehicleSelect.selectedIndex].text : 'Not specified', baseFare: document.getElementById('baseFare').value, discount: document.getElementById('discount').value, finalAmount: document.getElementById('finalAmount').value, paymentMethod: document.getElementById('paymentMethod').value, passengerName: document.getElementById('passengerName').value || 'Anonymous', issuedAt: new Date().toLocaleString(), status: 'valid', qrCode: 'QR' + Date.now() }; issuedTickets.unshift(ticketData); ticketCounter++; localStorage.setItem('issuedTickets', JSON.stringify(issuedTickets)); updateRecentTicketsList(); resetForm(); showAlert('success', 'Ticket ' + ticketData.ticketNumber + ' issued successfully!'); } function resetForm() { document.getElementById('ticketType').value = 'single'; document.getElementById('passengerCategory').value = 'adult'; document.getElementById('routeSelect').value = ''; document.getElementById('vehicleSelect').value = ''; document.getElementById('paymentMethod').value = 'cash'; document.getElementById('passengerName').value = ''; updateFareCalculation(); } function updateRecentTicketsList() { const container = document.getElementById('recentTickets'); if (issuedTickets.length === 0) { container.innerHTML = '

No tickets issued yet

'; return; } let html = ''; issuedTickets.slice(0, 10).forEach(ticket => { html += `
${ticket.ticketNumber}
${ticket.route}
${ticket.finalAmount}
${ticket.paymentMethod}
${new Date(ticket.issuedAt).toLocaleTimeString()}
`; }); container.innerHTML = html; } function refreshTickets() { updateRecentTicketsList(); showAlert('success', 'Tickets refreshed!'); } function showAlert(type, message) { const alertDiv = document.createElement('div'); alertDiv.className = `alert alert-${type === 'success' ? 'success' : 'danger'} alert-dismissible fade show position-fixed`; alertDiv.style.top = '20px'; alertDiv.style.right = '20px'; alertDiv.style.zIndex = '9999'; alertDiv.innerHTML = ` ${message} `; document.body.appendChild(alertDiv); setTimeout(() => { if (alertDiv.parentNode) { alertDiv.parentNode.removeChild(alertDiv); } }, 5000); }