function sanitizeHTML(str) { var temp = document.createElement('div'); temp.textContent = str; return temp.innerHTML; } // Firebase Configuration const firebaseConfig = { apiKey: "AIzaSyDO0aES6rW38p1NPJp26ylEkZf0x0Rp3JI", authDomain: "bus-manage-437a8.firebaseapp.com", projectId: "bus-manage-437a8", storageBucket: "bus-manage-437a8.firebasestorage.app", messagingSenderId: "827423480903", appId: "1:827423480903:web:7a514468dce182c7fb6562", measurementId: "G-WEN616B6JV" }; // Initialize Firebase firebase.initializeApp(firebaseConfig); const auth = firebase.auth(); const db = firebase.firestore(); // Global State let currentUser = null; let selectedBus = null; let selectedSeats = []; let busesSnapshot = null; // Constants const SEAT_LAYOUT = { regularRows: 10, leftSeatsPerRow: 2, rightSeatsPerRow: 3, backRowSeats: 6, totalSeats: 56 }; // Initialize App auth.onAuthStateChanged(async (user) => { if (user) { currentUser = user; await loadUserProfile(); showApp(); } else { showAuth(); } }); // Auth Functions function showAuth() { document.getElementById('authContainer').classList.remove('hidden'); document.getElementById('appContainer').classList.add('hidden'); } function showApp() { document.getElementById('authContainer').classList.add('hidden'); document.getElementById('appContainer').classList.remove('hidden'); document.getElementById('userDisplayName').textContent = sanitizeHTML(currentUser.displayName || currentUser.email); } function showLogin() { document.getElementById('loginForm').classList.remove('hidden'); document.getElementById('signupForm').classList.add('hidden'); } function showSignup() { document.getElementById('loginForm').classList.add('hidden'); document.getElementById('signupForm').classList.remove('hidden'); } async function handleLogin() { const email = document.getElementById('loginEmail').value.trim(); const password = document.getElementById('loginPassword').value; if (!email || !password) { // Strong password check const strongPwd = /^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[^A-Za-z0-9]).{8,}$/; if (!strongPwd.test(password)) { showNotification('Password must be 8+ chars with uppercase, lowercase, digit, symbol.', 'error'); return; } showNotification('Please fill in all fields', 'error'); return; } showLoading(true); try { await auth.signInWithEmailAndPassword(email, password); showNotification('Login successful!', 'success'); } catch (error) { showNotification(error.message, 'error'); } finally { showLoading(false); } } async function handleSignup() { const name = document.getElementById('signupName').value.trim(); const email = document.getElementById('signupEmail').value.trim(); const password = document.getElementById('signupPassword').value; const role = document.getElementById('signupRole').value; if (!name || !email || !password) { // Strong password check const strongPwd = /^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[^A-Za-z0-9]).{8,}$/; if (!strongPwd.test(password)) { showNotification('Password must be 8+ chars with uppercase, lowercase, digit, symbol.', 'error'); return; } showNotification('Please fill in all fields', 'error'); return; } if (password.length < 6) { showNotification('Password must be at least 6 characters', 'error'); return; } showLoading(true); try { const userCredential = await auth.createUserWithEmailAndPassword(email, password); await userCredential.user.updateProfile({ displayName: name }); // Create user document await db.collection('users').doc(userCredential.user.uid).set({ displayName: name, email: email, role: role, createdAt: firebase.firestore.FieldValue.serverTimestamp() }); showNotification('Account created successfully!', 'success'); } catch (error) { showNotification(error.message, 'error'); } finally { showLoading(false); } } async function handleLogout() { try { await auth.signOut(); showNotification('Logged out successfully', 'success'); resetAppState(); } catch (error) { showNotification(error.message, 'error'); } } // User Profile async function loadUserProfile() { try { const userDoc = await db.collection('users').doc(currentUser.uid).get(); if (userDoc.exists) { const userData = userDoc.data(); if (userData.role === 'admin') { showAdminDashboard(); } else { showBusList(); } } else { showBusList(); } } catch (error) { console.error('Error loading user profile:', error); showBusList(); } } // Navigation function showAdminDashboard() { hideAllViews(); document.getElementById('adminDashboard').classList.remove('hidden'); loadAdminDashboard(); } function showBusList() { hideAllViews(); document.getElementById('busSelection').classList.remove('hidden'); loadBuses(); } function showSeatSelection(bus) { hideAllViews(); selectedBus = bus; selectedSeats = []; document.getElementById('seatSelection').classList.remove('hidden'); document.getElementById('selectedBusName').textContent = bus.busName; document.getElementById('selectedBusInfo').textContent = `Departure: ${formatDateTime(bus.departureDateTime)} | Destination: ${bus.destinationName}`; loadSeatMap(); } function showBookingForm() { hideAllViews(); document.getElementById('bookingForm').classList.remove('hidden'); generatePassengerForms(); updateFinalSummary(); } function showConfirmation(bookingData) { hideAllViews(); document.getElementById('bookingConfirmation').classList.remove('hidden'); displayConfirmation(bookingData); } function hideAllViews() { document.getElementById('adminDashboard').classList.add('hidden'); document.getElementById('busSelection').classList.add('hidden'); document.getElementById('seatSelection').classList.add('hidden'); document.getElementById('bookingForm').classList.add('hidden'); document.getElementById('bookingConfirmation').classList.add('hidden'); } function backToBusList() { selectedBus = null; selectedSeats = []; showBusList(); } function backToSeatSelection() { showSeatSelection(selectedBus); } // Admin Dashboard async function loadAdminDashboard() { showLoading(true); try { // Load statistics const busesSnapshot = await db.collection('buses').get(); const bookingsSnapshot = await db.collection('bookings').where('status', '==', 'confirmed').get(); const totalBuses = busesSnapshot.size; const totalBookings = bookingsSnapshot.size; let totalSeats = 0; let bookedSeats = 0; busesSnapshot.forEach(doc => { totalSeats += SEAT_LAYOUT.totalSeats; }); bookingsSnapshot.forEach(doc => { bookedSeats += doc.data().seatNumbers.length; }); document.getElementById('totalBuses').textContent = totalBuses; document.getElementById('totalBookings').textContent = totalBookings; document.getElementById('availableSeats').textContent = totalSeats - bookedSeats; document.getElementById('totalRevenue').textContent = '-'; // Load buses for admin loadAdminBuses(); // Load report bus select loadReportBusSelect(); } catch (error) { console.error('Error loading admin dashboard:', error); showNotification('Error loading dashboard', 'error'); } finally { showLoading(false); } } async function loadAdminBuses() { const container = document.getElementById('adminBusList'); container.innerHTML = '

Loading buses...

'; try { const snapshot = await db.collection('buses').orderBy('departureDateTime', 'asc').get(); if (snapshot.empty) { container.innerHTML = '

No buses added yet. Click "Add New Bus" to get started.

'; return; } container.innerHTML = ''; for (const doc of snapshot.docs) { const bus = { id: doc.id, ...doc.data() }; const bookedSeats = await getBookedSeatsCount(bus.id); const availableSeats = SEAT_LAYOUT.totalSeats - bookedSeats; const card = document.createElement('div'); card.className = 'bus-card'; card.innerHTML = `

${bus.busName}

${bus.destinationName}

${availableSeats > 0 ? 'Available' : 'Full'}
📅 Departure: ${formatDateTime(bus.departureDateTime)}
👤 Driver: ${bus.driverName || 'N/A'}
💺 Available: ${availableSeats} / ${SEAT_LAYOUT.totalSeats}
`; container.appendChild(card); } } catch (error) { console.error('Error loading admin buses:', error); container.innerHTML = '

Error loading buses

'; } } async function loadReportBusSelect() { const select = document.getElementById('reportBusSelect'); select.innerHTML = ''; try { const snapshot = await db.collection('buses').orderBy('departureDateTime', 'asc').get(); snapshot.forEach(doc => { const bus = doc.data(); const option = document.createElement('option'); option.value = doc.id; option.textContent = `${bus.busName} - ${formatDateTime(bus.departureDateTime)}`; select.appendChild(option); }); } catch (error) { console.error('Error loading bus select:', error); } } // Bus Management async function loadBuses() { const container = document.getElementById('busList'); container.innerHTML = '

Loading available buses...

'; try { const now = new Date(); const snapshot = await db.collection('buses') .orderBy('departureDateTime', 'asc') .get(); if (snapshot.empty) { container.innerHTML = '

No buses available at the moment. Please check back later.

'; return; } container.innerHTML = ''; for (const doc of snapshot.docs) { const bus = { id: doc.id, ...doc.data() }; const departureDate = new Date(bus.departureDateTime); if (departureDate < now) continue; // Skip past buses const bookedSeats = await getBookedSeatsCount(bus.id); const availableSeats = SEAT_LAYOUT.totalSeats - bookedSeats; const card = document.createElement('div'); card.className = 'bus-card'; card.onclick = () => availableSeats > 0 && showSeatSelection(bus); card.style.cursor = availableSeats > 0 ? 'pointer' : 'not-allowed'; card.style.opacity = availableSeats > 0 ? '1' : '0.6'; card.innerHTML = `

${bus.busName}

${bus.destinationName}

${availableSeats > 0 ? 'Available' : 'Full'}
📅 Departure: ${formatDateTime(bus.departureDateTime)}
🕌 Destination: ${bus.destinationName}
💺 Seats Available: ${availableSeats} / ${SEAT_LAYOUT.totalSeats}
`; container.appendChild(card); } } catch (error) { console.error('Error loading buses:', error); container.innerHTML = '

Error loading buses. Please try again.

'; } } function showAddBusModal() { document.getElementById('addBusModal').classList.remove('hidden'); // Set minimum date to today const now = new Date(); const dateStr = now.toISOString().slice(0, 16); document.getElementById('departureDateTime').min = dateStr; } function closeAddBusModal() { document.getElementById('addBusModal').classList.add('hidden'); document.getElementById('busName').value = ''; document.getElementById('driverName').value = ''; document.getElementById('departureDateTime').value = ''; } async function addNewBus() { const busName = document.getElementById('busName').value.trim(); const driverName = document.getElementById('driverName').value.trim(); const departureDateTime = document.getElementById('departureDateTime').value; const destination = document.getElementById('destination').value.trim(); if (!busName || !departureDateTime || !destination) { showNotification('Please fill in all required fields', 'error'); return; } showLoading(true); try { await db.collection('buses').add({ busName, driverName, departureDateTime, destinationName: destination, totalSeats: SEAT_LAYOUT.totalSeats, createdBy: currentUser.uid, createdAt: firebase.firestore.FieldValue.serverTimestamp() }); showNotification('Bus added successfully!', 'success'); closeAddBusModal(); loadAdminDashboard(); } catch (error) { console.error('Error adding bus:', error); showNotification('Error adding bus. Please try again.', 'error'); } finally { showLoading(false); } } // Seat Management async function loadSeatMap() { const container = document.getElementById('seatMap'); container.innerHTML = '

Loading seat layout...

'; try { const bookedSeats = await getBookedSeats(selectedBus.id); container.innerHTML = ''; let seatNumber = 1; // Regular rows (10 rows with 2+3 layout) for (let row = 0; row < SEAT_LAYOUT.regularRows; row++) { const rowDiv = document.createElement('div'); rowDiv.className = 'seat-row'; // Left side (2 seats) const leftGroup = document.createElement('div'); leftGroup.className = 'seat-group'; for (let i = 0; i < SEAT_LAYOUT.leftSeatsPerRow; i++) { leftGroup.appendChild(createSeat(seatNumber++, bookedSeats)); } rowDiv.appendChild(leftGroup); // Aisle const aisle = document.createElement('div'); aisle.className = 'aisle'; rowDiv.appendChild(aisle); // Right side (3 seats) const rightGroup = document.createElement('div'); rightGroup.className = 'seat-group'; for (let i = 0; i < SEAT_LAYOUT.rightSeatsPerRow; i++) { rightGroup.appendChild(createSeat(seatNumber++, bookedSeats)); } rowDiv.appendChild(rightGroup); container.appendChild(rowDiv); } // Back row (6 seats) const backRow = document.createElement('div'); backRow.className = 'back-row'; for (let i = 0; i < SEAT_LAYOUT.backRowSeats; i++) { backRow.appendChild(createSeat(seatNumber++, bookedSeats)); } container.appendChild(backRow); updateSeatSummary(); } catch (error) { console.error('Error loading seat map:', error); container.innerHTML = '

Error loading seat layout

'; } } function createSeat(number, bookedSeats) { const seat = document.createElement('div'); seat.className = 'seat'; seat.textContent = number; seat.dataset.seatNumber = number; if (bookedSeats.includes(number)) { seat.classList.add('booked'); } else { seat.classList.add('available'); seat.onclick = () => toggleSeat(number); } return seat; } function toggleSeat(seatNumber) { const seatElement = document.querySelector(`[data-seat-number="${seatNumber}"]`); if (selectedSeats.includes(seatNumber)) { selectedSeats = selectedSeats.filter(s => s !== seatNumber); seatElement.classList.remove('selected'); seatElement.classList.add('available'); } else { selectedSeats.push(seatNumber); seatElement.classList.remove('available'); seatElement.classList.add('selected'); } selectedSeats.sort((a, b) => a - b); updateSeatSummary(); } function updateSeatSummary() { const display = document.getElementById('selectedSeatsDisplay'); const count = document.getElementById('totalSeatsSelected'); const button = document.getElementById('proceedBtn'); if (selectedSeats.length > 0) { display.textContent = selectedSeats.join(', '); count.textContent = selectedSeats.length; button.disabled = false; } else { display.textContent = 'None'; count.textContent = '0'; button.disabled = true; } } function proceedToBooking() { if (selectedSeats.length === 0) { showNotification('Please select at least one seat', 'warning'); return; } showBookingForm(); } // Booking Functions function generatePassengerForms() { const container = document.getElementById('passengerForms'); container.innerHTML = ''; selectedSeats.forEach((seatNumber, index) => { const form = document.createElement('div'); form.className = 'passenger-form'; form.innerHTML = `

Passenger ${index + 1} - Seat ${seatNumber}

`; container.appendChild(form); }); } function updateFinalSummary() { document.getElementById('finalSeatsDisplay').textContent = selectedSeats.join(', '); document.getElementById('finalPassengerCount').textContent = selectedSeats.length; } async function confirmBooking() { // Collect passenger data const passengers = []; let isValid = true; selectedSeats.forEach(seatNumber => { const name = document.querySelector(`.passenger-name[data-seat="${seatNumber}"]`).value.trim(); const phone = document.querySelector(`.passenger-phone[data-seat="${seatNumber}"]`).value.trim(); const email = document.querySelector(`.passenger-email[data-seat="${seatNumber}"]`).value.trim(); if (!name || !phone) { isValid = false; return; } passengers.push({ seatNumber, name, phone, email: email || '' }); }); if (!isValid) { showNotification('Please fill in all required passenger details', 'error'); return; } // Get payment method const paymentMethod = document.querySelector('input[name="payment"]:checked').value; showLoading(true); try { // Use Firestore transaction to prevent double booking const bookingData = await db.runTransaction(async (transaction) => { // Check if seats are still available const bookingsRef = db.collection('bookings') .where('busId', '==', selectedBus.id) .where('status', '==', 'confirmed'); const existingBookings = await transaction.get(bookingsRef); const bookedSeatNumbers = []; existingBookings.forEach(doc => { bookedSeatNumbers.push(...doc.data().seatNumbers); }); // Check for conflicts const conflicts = selectedSeats.filter(seat => bookedSeatNumbers.includes(seat)); if (conflicts.length > 0) { throw new Error(`Seats ${conflicts.join(', ')} are no longer available. Please select different seats.`); } // Create booking const bookingRef = db.collection('bookings').doc(); const bookingData = { bookingId: bookingRef.id, busId: selectedBus.id, busName: selectedBus.busName, departureDateTime: selectedBus.departureDateTime, seatNumbers: selectedSeats, passengers: passengers, paymentMethod: paymentMethod, bookedBy: currentUser.uid, bookedByName: currentUser.displayName || currentUser.email, status: 'confirmed', bookedAt: firebase.firestore.FieldValue.serverTimestamp() }; transaction.set(bookingRef, bookingData); return bookingData; }); showNotification('Booking confirmed successfully!', 'success'); showConfirmation(bookingData); } catch (error) { console.error('Error confirming booking:', error); showNotification(error.message || 'Error confirming booking. Please try again.', 'error'); // Reload seat map to show updated availability backToSeatSelection(); } finally { showLoading(false); } } function displayConfirmation(bookingData) { const container = document.getElementById('confirmationDetails'); container.innerHTML = `

Booking ID: ${bookingData.bookingId}

Bus: ${bookingData.busName}

Departure: ${formatDateTime(bookingData.departureDateTime)}

Seats: ${bookingData.seatNumbers.join(', ')}

Passengers: ${bookingData.passengers.length}

Payment Method: ${bookingData.paymentMethod}


${bookingData.passengers.map((p, i) => `
Seat ${p.seatNumber}: ${p.name} - ${p.phone}
`).join('')}
`; } function printBooking() { window.print(); } // Admin Functions async function viewBusBookings(busId) { showLoading(true); try { const bookingsSnapshot = await db.collection('bookings') .where('busId', '==', busId) .where('status', '==', 'confirmed') .get(); const container = document.getElementById('bookingsTableContainer'); if (bookingsSnapshot.empty) { container.innerHTML = '

No bookings for this bus yet.

'; } else { let html = ''; html += ''; html += ''; bookingsSnapshot.forEach(doc => { const booking = doc.data(); html += ''; html += ``; html += ``; html += ``; html += ``; html += ``; html += ``; html += ''; }); html += '
Booking IDSeatsPassengersPaymentBooked ByDate
${booking.bookingId.substring(0, 8)}...${booking.seatNumbers.join(', ')}${booking.passengers.map(p => p.name).join(', ')}${booking.paymentMethod}${booking.bookedByName}${booking.bookedAt ? new Date(booking.bookedAt.toDate()).toLocaleDateString() : 'N/A'}
'; container.innerHTML = html; } document.getElementById('viewBookingsModal').classList.remove('hidden'); } catch (error) { console.error('Error loading bookings:', error); showNotification('Error loading bookings', 'error'); } finally { showLoading(false); } } function closeViewBookingsModal() { document.getElementById('viewBookingsModal').classList.add('hidden'); } async function viewBusSeatLayout(busId) { showLoading(true); try { const busDoc = await db.collection('buses').doc(busId).get(); if (busDoc.exists) { const bus = { id: busDoc.id, ...busDoc.data() }; showSeatSelection(bus); } } catch (error) { console.error('Error loading bus:', error); showNotification('Error loading bus details', 'error'); } finally { showLoading(false); } } // Reports async function generatePassengerReport() { const busId = document.getElementById('reportBusSelect').value; if (!busId) { showNotification('Please select a bus', 'warning'); return; } showLoading(true); try { const busDoc = await db.collection('buses').doc(busId).get(); const bookingsSnapshot = await db.collection('bookings') .where('busId', '==', busId) .where('status', '==', 'confirmed') .get(); const bus = busDoc.data(); const output = document.getElementById('reportOutput'); let html = `

${bus.busName} - Passenger List

`; html += `

Departure: ${formatDateTime(bus.departureDateTime)}

`; if (bookingsSnapshot.empty) { html += '

No passengers booked yet.

'; } else { html += ''; html += ''; html += ''; const allPassengers = []; bookingsSnapshot.forEach(doc => { const booking = doc.data(); booking.passengers.forEach(p => { allPassengers.push({ ...p, paymentMethod: booking.paymentMethod }); }); }); allPassengers.sort((a, b) => a.seatNumber - b.seatNumber); allPassengers.forEach(p => { html += ''; html += ``; html += ``; html += ``; html += ``; html += ``; html += ''; }); html += '
SeatNamePhoneEmailPayment
${p.seatNumber}${p.name}${p.phone}${p.email || '-'}${p.paymentMethod}
'; } output.innerHTML = html; } catch (error) { console.error('Error generating report:', error); showNotification('Error generating report', 'error'); } finally { showLoading(false); } } async function generatePaymentReport() { const busId = document.getElementById('reportBusSelect').value; if (!busId) { showNotification('Please select a bus', 'warning'); return; } showLoading(true); try { const busDoc = await db.collection('buses').doc(busId).get(); const bookingsSnapshot = await db.collection('bookings') .where('busId', '==', busId) .where('status', '==', 'confirmed') .get(); const bus = busDoc.data(); const output = document.getElementById('reportOutput'); let cashCount = 0; let upiCount = 0; let cashSeats = 0; let upiSeats = 0; bookingsSnapshot.forEach(doc => { const booking = doc.data(); if (booking.paymentMethod === 'Cash') { cashCount++; cashSeats += booking.seatNumbers.length; } else { upiCount++; upiSeats += booking.seatNumbers.length; } }); let html = `

${bus.busName} - Payment Summary

`; html += `

Departure: ${formatDateTime(bus.departureDateTime)}

`; html += ''; html += ''; html += ''; html += ''; html += ''; html += ``; html += ``; html += ''; html += ''; html += ''; html += ``; html += ``; html += ''; html += ''; html += ''; html += ``; html += ``; html += ''; html += '
Payment MethodBookingsSeats
Cash${cashCount}${cashSeats}
UPI${upiCount}${upiSeats}
Total${cashCount + upiCount}${cashSeats + upiSeats}
'; output.innerHTML = html; } catch (error) { console.error('Error generating report:', error); showNotification('Error generating report', 'error'); } finally { showLoading(false); } } async function exportToCSV() { const busId = document.getElementById('reportBusSelect').value; if (!busId) { showNotification('Please select a bus', 'warning'); return; } showLoading(true); try { const busDoc = await db.collection('buses').doc(busId).get(); const bookingsSnapshot = await db.collection('bookings') .where('busId', '==', busId) .where('status', '==', 'confirmed') .get(); const bus = busDoc.data(); let csv = 'Seat,Name,Phone,Email,Payment Method,Booked By,Booking Date\n'; bookingsSnapshot.forEach(doc => { const booking = doc.data(); booking.passengers.forEach(p => { csv += `${p.seatNumber},`; csv += `"${p.name}",`; csv += `${p.phone},`; csv += `${p.email || '-'},`; csv += `${booking.paymentMethod},`; csv += `"${booking.bookedByName}",`; csv += `${booking.bookedAt ? new Date(booking.bookedAt.toDate()).toLocaleDateString() : 'N/A'}\n`; }); }); // Download CSV const blob = new Blob([csv], { type: 'text/csv' }); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `${bus.busName.replace(/\s+/g, '_')}_passengers.csv`; document.body.appendChild(a); a.click(); document.body.removeChild(a); window.URL.revokeObjectURL(url); showNotification('CSV exported successfully!', 'success'); } catch (error) { console.error('Error exporting CSV:', error); showNotification('Error exporting CSV', 'error'); } finally { showLoading(false); } } // Helper Functions async function getBookedSeats(busId) { const bookingsSnapshot = await db.collection('bookings') .where('busId', '==', busId) .where('status', '==', 'confirmed') .get(); const bookedSeats = []; bookingsSnapshot.forEach(doc => { bookedSeats.push(...doc.data().seatNumbers); }); return bookedSeats; } async function getBookedSeatsCount(busId) { const bookedSeats = await getBookedSeats(busId); return bookedSeats.length; } function formatDateTime(dateTimeString) { const date = new Date(dateTimeString); return date.toLocaleString('en-IN', { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }); } function showLoading(show) { const overlay = document.getElementById('loadingOverlay'); if (show) { overlay.classList.remove('hidden'); } else { overlay.classList.add('hidden'); } } function showNotification(message, type = 'info') { const toast = document.getElementById('notificationToast'); toast.textContent = message; toast.className = `notification-toast ${type}`; toast.classList.remove('hidden'); setTimeout(() => { toast.classList.add('hidden'); }, 4000); } function resetAppState() { currentUser = null; selectedBus = null; selectedSeats = []; hideAllViews(); } // Initialize on load document.addEventListener('DOMContentLoaded', () => { console.log('Pilgrimage Bus Booking System Initialized'); });