ZUPCO
Transport Management
Dashboard
Dispatch
Ticketing
Live Tracking
Vehicles
Drivers
Routes
Tap Cards
Reports
Wialon Token
Dashboard
Refresh
Admin
Settings
Logout
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.
Tap Card / NFC UID
Validate
Ask passenger to tap their card on the NFC reader or enter UID manually
Card Status
No card detected
Card Number:
-
Card Type:
-
Holder:
-
Balance:
$0.00
Discount:
0%
Status:
-
Ticket Type
Single Trip
Return Trip
Daily Pass
Weekly Pass
Monthly Pass
Passenger Category (Auto-detected from card)
Route
Select Route
Harare - Bulawayo ($15.00)
Harare - Mutare ($12.00)
Bulawayo - Victoria Falls ($18.00)
Harare - Gweru ($10.00)
Vehicle
Select Vehicle
Eco-Driving (19762003)
INSB Scania Truck (20271277)
Temperature Vehicle (20309976)
Base Fare
Discount
Final Amount
Payment Method
Passenger Name (From Card)
Current Balance:
$0.00
Ticket Cost:
$0.00
Remaining Balance:
$0.00
Issue Ticket
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); }