diff --git a/admin/travel/coordinatorDashboard.html b/admin/travel/coordinatorDashboard.html new file mode 100644 index 0000000..63a42fb --- /dev/null +++ b/admin/travel/coordinatorDashboard.html @@ -0,0 +1,349 @@ + + + + + + + + + + + + + + + + + + Coordinator Dashboard + + + + + + + + + + + + + +
+ +
+ +
+ + + Logout + + +
+ +
+ +
+ +
+ +
+ +
+ +
+ + + + +
+ + + +
+ +

+ Coordinator Details +

+ +
+ +
+ +
+ + + +
+ +

+ Assigned Bus +

+ +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+
+ + + + +
+ + + + + + +
+
+ + + +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
#NameMobilePickupStop TimeDropLuggageCommentsContactStatusAction
+ +
+ +
+ +
+ +
+ +
+ + + + \ No newline at end of file diff --git a/admin/travel/coordinatorDashboard.js b/admin/travel/coordinatorDashboard.js new file mode 100644 index 0000000..8c5b29f --- /dev/null +++ b/admin/travel/coordinatorDashboard.js @@ -0,0 +1,804 @@ +let allPassengers = []; +let allBuses = []; +let selectedBusIndex = 0; +let qrScanner = null; + +document.addEventListener( + 'DOMContentLoaded', + fetchDashboard +); + +document.addEventListener( + 'DOMContentLoaded', + () => { + + document + .getElementById( + 'passengerSearch' + ) + .addEventListener( + 'input', + applyPassengerFilters + ); + + document + .getElementById( + 'boardingFilter' + ) + .addEventListener( + 'change', + applyPassengerFilters + ); + } +); + +async function fetchDashboard() { + + try { + + const token = + sessionStorage.getItem( + 'coordinatorToken' + ); + + const response = await fetch( + + `${CONFIG.baseUrl}/coordinator/dashboard`, + + { + + headers: { + + Authorization: + `Bearer ${token}`, + }, + } + ); + + if ( + response.status === 401 || + response.status === 403 + ) { + + coordinatorLogout(); + return; + } + + const data = + await response.json(); + + if (!response.ok) { + + throw new Error( + data.message + ); + } + renderCoordinatorInfo( + data.coordinator + ); + allBuses = + data.buses || []; + + renderBusDropdown(); + + if (allBuses.length) { + + if ( + selectedBusIndex >= + allBuses.length + ) { + + selectedBusIndex = 0; + } + + loadBusData( + selectedBusIndex + ); + + document + .getElementById( + 'busSelector' + ) + .value = + selectedBusIndex; + } + } catch (error) { + + alert(error.message); + } +} + +function renderCoordinatorInfo( + coordinator +) { + + document + .getElementById( + 'coordinatorInfo' + ) + .innerHTML = ` + +
+ + + Name + + + + ${coordinator.name || ''} + + +
+ +
+ + + Mobile + + + + ${coordinator.mobno || ''} + + +
+ +
+ + + Card No + + + + ${coordinator.cardno || ''} + + +
+ +
+ + + Center + + + + ${coordinator.center || ''} + + +
+ `; +} + +function renderBusDropdown() { + + const selector = + document.getElementById( + 'busSelector' + ); + + selector.innerHTML = ''; + + allBuses.forEach( + ( + item, + index + ) => { + + const option = + document.createElement( + 'option' + ); + + option.value = + index; + + option.textContent = ` + +${item.bus.bus_name} +- ${item.bus.event_date} + + `; + + selector.appendChild( + option + ); + } + ); + + selector.onchange = + event => { + + selectedBusIndex = + Number( + event.target.value + ); + + loadBusData( + selectedBusIndex + ); + }; +} + +function loadBusData( + index +) { + + const selectedBus = + allBuses[index]; + + if (!selectedBus) { + + return; + } + + renderBusInfo( + selectedBus.bus + ); + + renderBusSummary( + + selectedBus.bus, + + selectedBus.passengers + ); + + allPassengers = + selectedBus.passengers; + + renderPassengers( + allPassengers + ); +} + +function renderBusInfo(bus) { + + const sortedStops = + + (bus.stops || []) + .sort( + (a, b) => + a.stop_order - + b.stop_order + ); + + document + .getElementById( + 'busInfo' + ) + .innerHTML = ` + +
+ + + Bus + + + + ${bus.bus_name || ''} + + +
+ +
+ + + Date + + + + ${bus.event_date || ''} + + +
+ +
+ + + Route + + + + + ${sortedStops + .map( + ( + stop, + index + ) => ` + +
+ + ${stop.stop_name} + + (${stop.timing || '-'}) + +
+ ` + ) + .join('') + } + +
+ +
+ + `; +} + +function renderBusSummary( + bus, + passengers +) { + + const boardedCount = + passengers.filter( + p => p.boarded + ).length; + + const pendingCount = + passengers.filter( + p => !p.boarded + ).length; + + document + .getElementById( + 'busSummary' + ) + .innerHTML = ` + +
+ +
+ + + Total Capacity + + + + ${bus.capacity || 0} + + +
+ +
+ + + Remaining Seats + + + + ${bus.remaining_seats || 0} + + +
+ +
+ + + Boarded + + + + ${boardedCount} + + +
+ +
+ + + Pending + + + + ${pendingCount} + + +
+ +
+ `; +} + +function renderPassengers( + passengers +) { + + const tbody = + document.querySelector( + '#passengerTable tbody' + ); + + tbody.innerHTML = ''; + + if (!passengers.length) { + + tbody.innerHTML = ` + + + + + + No passengers assigned + + + + + `; + + return; + } + + passengers.forEach( + ( + passenger, + index + ) => { + const selectedBus = + allBuses[selectedBusIndex]; + + const stops = + selectedBus?.bus?.stops || []; + + let stopTime = ''; + + if ( + passenger.pickup_point === + 'Research Centre' + ) { + + // RC → Mumbai + stopTime = + stops.find( + stop => + stop.stop_name === + passenger.drop_point + )?.timing || ''; + + } else { + + // Mumbai → RC + stopTime = + stops.find( + stop => + stop.stop_name === + passenger.pickup_point + )?.timing || ''; + } + + const row = + document.createElement( + 'tr' + ); + + row.innerHTML = ` + + + ${index + 1} + + + + ${passenger.name || ''} + + + + ${passenger.mobno || ''} + + + + ${passenger.pickup_point || ''} + + + + ${stopTime || '-'} + + + + ${passenger.drop_point || ''} + + + + ${passenger.luggage || '-' + } + + + + ${passenger.comments || '-' + } + + + + +
+ + + 📞 Call + + + + 💬 WhatsApp + + +
+ + + + + + ${passenger.boarded + ? '🟢 Boarded' + : '🔴 Pending' + } + + + + + + + + +`; + tbody.appendChild(row); + } + ); +} + +function applyPassengerFilters() { + + const search = + document + .getElementById( + 'passengerSearch' + ) + .value + .toLowerCase(); + + const filter = + document + .getElementById( + 'boardingFilter' + ) + .value; + + let filtered = + [...allPassengers]; + + // SEARCH + + filtered = + filtered.filter( + passenger => { + + return ( + + passenger.name + ?.toLowerCase() + .includes(search) + + || + + String( + passenger.mobno || '' + ).includes(search) + + || + + passenger.pickup_point + ?.toLowerCase() + .includes(search) + ); + } + ); + + // FILTER + + if (filter === 'boarded') { + + filtered = + filtered.filter( + p => p.boarded + ); + } + + if (filter === 'pending') { + + filtered = + filtered.filter( + p => !p.boarded + ); + } + + + + renderPassengers( + filtered + ); +} + +async function + toggleBoardingStatus( + passengerId, + boarded + ) { + + try { + + const token = + sessionStorage.getItem( + 'coordinatorToken' + ); + + const response = + await fetch( + + `${CONFIG.baseUrl}/coordinator/boarding-status`, + + { + + method: 'PUT', + + headers: { + + 'Content-Type': + 'application/json', + + Authorization: + `Bearer ${token}`, + }, + + body: JSON.stringify({ + + passenger_id: + passengerId, + + boarded, + }), + } + ); + + const data = + await response.json(); + + if (!response.ok) { + + throw new Error( + data.message + ); + } + + fetchDashboard(); + + } catch (error) { + + alert(error.message); + } +} + +async function + startQrScanner() { + + const qrDiv = + document.getElementById( + 'qr-reader' + ); + + qrDiv.style.display = + 'block'; + + qrScanner = + new Html5Qrcode( + 'qr-reader' + ); + + await qrScanner.start( + + { + facingMode: 'environment', + }, + + { + fps: 10, + qrbox: 250, + }, + + async decodedText => { + + await handleQrScan( + decodedText + ); + } + ); +} + +async function + handleQrScan( + qrValue + ) { + + const passenger = + allPassengers.find(p => + + String( + p.cardno || '' + ) === String(qrValue) + ); + + if (!passenger) { + + alert( + 'Passenger not found' + ); + + return; + } + + if (passenger.boarded) { + + alert( + 'Already boarded' + ); + + return; + } + + await toggleBoardingStatus( + + passenger.passenger_id, + + true + ); + + alert( + `${passenger.name} boarded` + ); + + if (qrScanner) { + + await qrScanner.stop(); + } + + document + .getElementById( + 'qr-reader' + ) + .style.display = 'none'; +} \ No newline at end of file diff --git a/admin/travel/coordinatorLogin.html b/admin/travel/coordinatorLogin.html new file mode 100644 index 0000000..0e36836 --- /dev/null +++ b/admin/travel/coordinatorLogin.html @@ -0,0 +1,108 @@ + + + + + + + + + + + + Coordinator Login + + + + + + + +
+ +
+ +
+ +
+ +

Coordinator Login

+ + + +
+ + + + + +
+ + + +
+ + + +
+ + + + + +
+ +
+ +
+ +
+ + + \ No newline at end of file diff --git a/admin/travel/coordinatorLogin.js b/admin/travel/coordinatorLogin.js new file mode 100644 index 0000000..9e962df --- /dev/null +++ b/admin/travel/coordinatorLogin.js @@ -0,0 +1,151 @@ +document.addEventListener( + 'DOMContentLoaded', + () => { + + document + .getElementById('sendOtpBtn') + .addEventListener( + 'click', + sendOtp + ); + + document + .getElementById('verifyOtpBtn') + .addEventListener( + 'click', + verifyOtp + ); + } +); + +async function sendOtp() { + + try { + + const mobno = + document + .getElementById('mobno') + .value + .trim(); + + if (!mobno) { + alert('Enter mobile number'); + return; + } + + const response = await fetch( + + `${CONFIG.baseUrl}/coordinator/send-otp`, + + { + method: 'POST', + + headers: { + 'Content-Type': + 'application/json', + }, + + body: JSON.stringify({ + mobno, + }), + } + ); + + const data = + await response.json(); + + if (!response.ok) { + throw new Error( + data.message + ); + } + + alert( + 'OTP sent successfully' + ); + + document + .getElementById( + 'otpSection' + ) + .style.display = 'block'; + + } catch (error) { + + alert(error.message); + } +} + +async function verifyOtp() { + + try { + + const mobno = + document + .getElementById('mobno') + .value + .trim(); + + const otp = + document + .getElementById('otp') + .value + .trim(); + + if (!otp) { + alert('Enter OTP'); + return; + } + + const response = await fetch( + + `${CONFIG.baseUrl}/coordinator/verify-otp`, + + { + method: 'POST', + + headers: { + 'Content-Type': + 'application/json', + }, + + body: JSON.stringify({ + mobno, + otp, + }), + } + ); + + const data = + await response.json(); + + if (!response.ok) { + throw new Error( + data.message + ); + } + + // STORE SESSION + + sessionStorage.setItem( + 'coordinatorToken', + data.token + ); + + sessionStorage.setItem( + 'coordinatorUser', + JSON.stringify(data.user) + ); + + alert( + 'Login successful' + ); + + window.location.href = + 'coordinatorDashboard.html'; + + } catch (error) { + + alert(error.message); + } +} \ No newline at end of file diff --git a/admin/travel/fetchUpcomingBookings.html b/admin/travel/fetchUpcomingBookings.html index 0f6f5da..2e7cafa 100644 --- a/admin/travel/fetchUpcomingBookings.html +++ b/admin/travel/fetchUpcomingBookings.html @@ -5,176 +5,190 @@ - - - - Upcoming Travel Bookings - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
- Back -   |   - Home -   |   - Logout -
+ + + + + Upcoming Travel Bookings + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ Back +   |   + Home +   |   + Logout
- -
-
-
-
- -
-

Upcoming Travel Bookings

-
+
+ +
+
+
+
-
- -
- -
- -
- - - - - - - - -
+
+

Upcoming Travel Bookings

+
+ +
+ + + +
+ +
+ + + + + + + +
+
+ +
+ + +
-
- - +
+ + +
+ +
+
+
+
+
+ + -
- -
+
-
-
- -
-
-
+ +
- +
+

Summary

+ +
+ + + + + + + + + + + +
DestinationStatusCount
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
#Travel DateMumukshu NameType (Regular/Full Car)Pickup PointDrop off PointStop TimeBus NameCoordinator?Train/Flight TimeAttending AdhyayanBooking StatusActionMumukshu CommentsTotal PeopleMobile NoAmountPayment StatusPayment DateBooking IdBooked ByAdmin CommentsLuggageTravelling From
-
-

Summary

- -
- - - - - - - - - - - -
DestinationStatusCount
-
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#Travel DateMumukshu NameType (Regular/Full Car)Pickup PointDrop off PointTranin/Flight TimeAttending AdhyayanBooking StatusActionMumukshu CommentsTotal PeopleMobile NoAmountPayment StatusPayment DateBooking IdBooked ByAdmin CommentsLuggageTravelling From
- -
- -
+
+
+
- - -
- -
- - + + + + + + + \ No newline at end of file diff --git a/admin/travel/fetchUpcomingBookings.js b/admin/travel/fetchUpcomingBookings.js index 6b2df3a..44bebd8 100644 --- a/admin/travel/fetchUpcomingBookings.js +++ b/admin/travel/fetchUpcomingBookings.js @@ -24,18 +24,36 @@ const PICKUP_DROP_POINTS = [ document.addEventListener('DOMContentLoaded', async function () { -const today = new Date(); + const today = new Date(); const tomorrow = new Date(); tomorrow.setDate(today.getDate() + 1); document.getElementById('start_date').value = today.toISOString().split('T')[0]; document.getElementById('end_date').value = tomorrow.toISOString().split('T')[0]; + document.getElementById( + 'openBusSummaryModal' + ).addEventListener( + 'click', + openBusSummaryModal + ); + + document.getElementById( + 'closeBusSummaryModal' + ).addEventListener( + 'click', + () => { + + document.getElementById( + 'busSummaryModal' + ).style.display = 'none'; + } + ); statusDropdown = document.getElementById("status"); issueCreditsField = document.getElementById("issueCreditsField"); issueCreditsDropdown = document.getElementById("issueCredits"); - statusDropdown.addEventListener("change", () => { + statusDropdown.addEventListener("change", () => { if (statusDropdown.value === "admin cancelled") { issueCreditsField.style.display = "block"; } else { @@ -45,22 +63,22 @@ const today = new Date(); }); function populatePickupDropDropdowns() { - const pickupSelect = document.getElementById('txnPickup'); - const dropSelect = document.getElementById('txnDrop'); + const pickupSelect = document.getElementById('txnPickup'); + const dropSelect = document.getElementById('txnDrop'); - if (!pickupSelect || !dropSelect) return; + if (!pickupSelect || !dropSelect) return; - PICKUP_DROP_POINTS.forEach(point => { - const opt1 = new Option(point, point); - const opt2 = new Option(point, point); - pickupSelect.add(opt1); - dropSelect.add(opt2); - }); -} + PICKUP_DROP_POINTS.forEach(point => { + const opt1 = new Option(point, point); + const opt2 = new Option(point, point); + pickupSelect.add(opt1); + dropSelect.add(opt2); + }); + } -populatePickupDropDropdowns(); + populatePickupDropDropdowns(); // ✅ Populate Pickup / Drop dropdowns (Transaction Edit Modal) - + const form = document.getElementById('reportForm'); const upcomingTableBody = document.getElementById('upcomingBookings').querySelector('tbody'); @@ -78,7 +96,7 @@ populatePickupDropDropdowns(); // Restore filters and auto-submit sessionStorage.removeItem('filterStatusArray'); -restoreFilters(); + restoreFilters(); if (sessionStorage.getItem('filterStartDate') || sessionStorage.getItem('filterStatus')) { form.dispatchEvent(new Event('submit')); // Auto-fetch data } @@ -93,34 +111,34 @@ restoreFilters(); const dropRC = document.getElementById('dropRC')?.checked; const rawCheckedValues = [...document.querySelectorAll('input[name="status"]:checked')].map(c => c.value); -const normalizedStatuses = []; -const adminCommentFilters = []; + const normalizedStatuses = []; + const adminCommentFilters = []; -rawCheckedValues.forEach(val => { - if (val === 'wrong form cancel') { - adminCommentFilters.push('admin_cancel_wrong_form'); - } else if (val === 'seats full cancel') { - adminCommentFilters.push('admin_cancel_seats_full'); - } else { - normalizedStatuses.push(val); - } -}); + rawCheckedValues.forEach(val => { + if (val === 'wrong form cancel') { + adminCommentFilters.push('admin_cancel_wrong_form'); + } else if (val === 'seats full cancel') { + adminCommentFilters.push('admin_cancel_seats_full'); + } else { + normalizedStatuses.push(val); + } + }); -const searchParams = new URLSearchParams({ start_date: startDate, end_date: endDate }); + const searchParams = new URLSearchParams({ start_date: startDate, end_date: endDate }); -if (normalizedStatuses.length > 0) { - normalizedStatuses.forEach(s => searchParams.append('statuses', s)); -} + if (normalizedStatuses.length > 0) { + normalizedStatuses.forEach(s => searchParams.append('statuses', s)); + } -// 🚨 This was the missing condition: -if (adminCommentFilters.length > 0) { - searchParams.append('statuses', 'admin cancelled'); - adminCommentFilters.forEach(c => searchParams.append('adminComments', c)); -} + // 🚨 This was the missing condition: + if (adminCommentFilters.length > 0) { + searchParams.append('statuses', 'admin cancelled'); + adminCommentFilters.forEach(c => searchParams.append('adminComments', c)); + } -if (pickupRC) searchParams.append('pickupRC', true); -if (dropRC) searchParams.append('dropRC', true); + if (pickupRC) searchParams.append('pickupRC', true); + if (dropRC) searchParams.append('dropRC', true); // Save filters sessionStorage.setItem('filterStartDate', startDate); @@ -143,20 +161,20 @@ if (dropRC) searchParams.append('dropRC', true); const summaryBody = document.getElementById('summaryBooking').querySelector('tbody'); summaryBody.innerHTML = ""; summary.data.forEach((s) => { - let displayStatus = statusLabelMap[s.status] || s.status; + let displayStatus = statusLabelMap[s.status] || s.status; - if (s.status === 'admin cancelled') { - if (s.admin_comments === 'admin_cancel_wrong_form') { - displayStatus = 'Cancelled as wrong form filled'; - } else if (s.admin_comments === 'admin_cancel_seats_full') { - displayStatus = 'Cancelled as all seats are booked'; - } - } + if (s.status === 'admin cancelled') { + if (s.admin_comments === 'admin_cancel_wrong_form') { + displayStatus = 'Cancelled as wrong form filled'; + } else if (s.admin_comments === 'admin_cancel_seats_full') { + displayStatus = 'Cancelled as all seats are booked'; + } + } - const row = document.createElement('tr'); - row.innerHTML = `${s.destination}${displayStatus}${s.count}`; - summaryBody.appendChild(row); -}); + const row = document.createElement('tr'); + row.innerHTML = `${s.destination}${displayStatus}${s.count}`; + summaryBody.appendChild(row); + }); } @@ -181,46 +199,46 @@ if (dropRC) searchParams.append('dropRC', true); document.getElementById("selectedDate").textContent = `For [${formatDate(startDate)} to ${formatDate(endDate)}]`; const normalize = str => - (str || "") - .toLowerCase() - .replace(/[–—]/g, '-') // normalize dash variants - .trim() - .replace(/\s+/g, ' '); - -const mumbaiPoints = new Set([ - 'dadar', 'dadar (swami narayan temple)', 'dadar (swaminarayan temple)', 'amar mahal', - 'airoli', 'borivali', 'vile parle (sahara star)', 'airport terminal 1', 'airport terminal 2', - 'railway station (bandra terminus)', 'railway station (kurla terminus)', 'railway station (ltt - kurla)', - 'railway station (csmt)', 'railway station (mumbai central)', 'mullund', 'mulund', - 'airport t1', 'airport t2', 'other', 'other (enter location in comments)', - 'railway station (ltt - kurla)', 'vile parle (sahara star hotel)', 'full car booking', - 'dadar (pritam hotel)','borivali (indraprasth shopping centre)','dadar (pritam da dhaba)','mulund (sarvoday nagar)', 'railway station (ltt - kurla terminus)' -]); - -travelReport.forEach((b, index) => { -const pickup = normalize(b.pickup_point); -const drop = normalize(b.drop_point); - -let travellingFrom = ""; - -if (pickup === "research centre" && drop !== "research centre") { - travellingFrom = "Research Centre to Mumbai"; -} else if (drop === "research centre" && pickup !== "research centre") { - travellingFrom = "Mumbai to Research Centre"; -} - - - const rowStyle = travellingFrom === "Research Centre to Mumbai" ? 'background-color: #ffff99;' : ""; - - const row = document.createElement('tr'); - const adminComments = b.admin_comments || ""; - const comments = b.comments || ""; - const bookedBy = b.bookedBy || ""; - const arrival_time = b.arrival_time || ""; - - row.setAttribute("style", rowStyle); - - row.innerHTML = ` + (str || "") + .toLowerCase() + .replace(/[–—]/g, '-') // normalize dash variants + .trim() + .replace(/\s+/g, ' '); + + const mumbaiPoints = new Set([ + 'dadar', 'dadar (swami narayan temple)', 'dadar (swaminarayan temple)', 'amar mahal', + 'airoli', 'borivali', 'vile parle (sahara star)', 'airport terminal 1', 'airport terminal 2', + 'railway station (bandra terminus)', 'railway station (kurla terminus)', 'railway station (ltt - kurla)', + 'railway station (csmt)', 'railway station (mumbai central)', 'mullund', 'mulund', + 'airport t1', 'airport t2', 'other', 'other (enter location in comments)', + 'railway station (ltt - kurla)', 'vile parle (sahara star hotel)', 'full car booking', + 'dadar (pritam hotel)', 'borivali (indraprasth shopping centre)', 'dadar (pritam da dhaba)', 'mulund (sarvoday nagar)', 'railway station (ltt - kurla terminus)' + ]); + + travelReport.forEach((b, index) => { + const pickup = normalize(b.pickup_point); + const drop = normalize(b.drop_point); + + let travellingFrom = ""; + + if (pickup === "research centre" && drop !== "research centre") { + travellingFrom = "Research Centre to Mumbai"; + } else if (drop === "research centre" && pickup !== "research centre") { + travellingFrom = "Mumbai to Research Centre"; + } + b.travellingFrom = travellingFrom; + + const rowStyle = travellingFrom === "Research Centre to Mumbai" ? 'background-color: #ffff99;' : ""; + + const row = document.createElement('tr'); + const adminComments = b.admin_comments || ""; + const comments = b.comments || ""; + const bookedBy = b.bookedBy || ""; + const arrival_time = b.arrival_time || ""; + + row.setAttribute("style", rowStyle); + + row.innerHTML = ` ${index + 1} ${b.issuedto} ${b.type} ${b.pickup_point} - ${b.drop_point} - ${formatDateTime(arrival_time)} - ${b.leaving_post_adhyayan == 1 ? 'Yes' : 'No'} - ${ - b.status === 'admin cancelled' && b.admin_comments === 'admin_cancel_wrong_form' - ? 'Cancelled as wrong form filled' - : b.status === 'admin cancelled' && b.admin_comments === 'admin_cancel_seats_full' - ? 'Cancelled as all seats are booked' - : statusLabelMap[b.status] || b.status -} + +${b.drop_point} + + + ${(() => { + + const stops = + (b.stops || []) + .sort( + (a, b) => + a.stop_order - + b.stop_order + ); + + const isRCTOMumbai = + b.pickup_point === + 'Research Centre'; + + if (isRCTOMumbai) { + + return stops.find( + stop => + stop.stop_name === + b.drop_point + )?.timing || '-'; + } + + return stops.find( + stop => + stop.stop_name === + b.pickup_point + )?.timing || '-'; + + })()} + + + + ${b.bus_name || ''} + + + + ${b.coordinator_bookingid === + b.bookingid + ? 'Yes' + : 'No'} + + + + ${formatDateTime(arrival_time)} + + + + ${b.leaving_post_adhyayan == 1 + ? 'Yes' + : 'No'} + + + + ${b.status === 'admin cancelled' && + b.admin_comments === + 'admin_cancel_wrong_form' + + ? 'Cancelled as wrong form filled' + + : b.status === 'admin cancelled' && + b.admin_comments === + 'admin_cancel_seats_full' + + ? 'Cancelled as all seats are booked' + + : statusLabelMap[b.status] || + b.status + } + + + - Update Booking Status - - ${comments} - ${b.total_people} - ${b.mobno} - ${b.amount} + + + Update Booking Status + + + + +${comments} + +${b.total_people} + +${b.mobno} + +${b.amount} ${b.paymentStatus} - ${formatDate(b.paymentDate)} - ${b.bookingid} - ${bookedBy} - ${adminComments} - ${b.luggage} - ${travellingFrom} - `; - upcomingTableBody.appendChild(row); - // Enhance table after rendering +${formatDate(b.paymentDate)} -}); -setTimeout(() => { - enhanceTable('upcomingBookings', 'tableSearch'); -}, 100); +${b.bookingid} + +${bookedBy} + +${adminComments} + +${b.luggage} + +${travellingFrom} `; + + upcomingTableBody.appendChild(row); + // Enhance table after rendering + + }); + setTimeout(() => { + enhanceTable('upcomingBookings', 'tableSearch'); + }, 100); // Restore scroll @@ -281,7 +386,7 @@ setTimeout(() => { console.error('Error fetching bookings:', error); } }); - // ✅ Update Booking Status modal buttons + // ✅ Update Booking Status modal buttons document.getElementById('closeModal')?.addEventListener('click', () => { document.getElementById('updateModal').style.display = 'none'; }); @@ -329,7 +434,7 @@ function openUpdateModal(bookingId) { document.getElementById('updateModal').style.display = 'block'; } -function openTransactionEditModal(bookingId) { +async function openTransactionEditModal(bookingId) { const booking = travelReport.find(b => b.bookingid === bookingId); if (!booking) return; @@ -346,10 +451,102 @@ function openTransactionEditModal(bookingId) { booking.leaving_post_adhyayan != null ? String(booking.leaving_post_adhyayan) : ''; - + await loadAvailableBusRoutes( + booking + ); document.getElementById('transactionModal').style.display = 'block'; } - + +async function loadAvailableBusRoutes( + booking +) { + + try { + + const response = await fetch( + `${CONFIG.basePath}/travel/bus-groups`, + { + headers: { + Authorization: + `Bearer ${sessionStorage.getItem('token')}`, + }, + } + ); + + const data = + await response.json(); + + const dropdown = + document.getElementById( + 'txnBusGroup' + ); + + dropdown.innerHTML = ` + + `; + + (data.data || []).forEach(bus => { + + // SAME DATE ONLY + + if ( + bus.event_date !== booking.date + ) { + return; + } + + const option = + document.createElement( + 'option' + ); + + option.value = bus.id; + option.textContent = + + `${bus.bus_name} | ` + + + `${(bus.stops || []) + .sort( + (a, b) => + a.stop_order - + b.stop_order + ) + .map( + stop => + + `${stop.stop_name} (${stop.timing || '-'})` + ) + .join(' → ') + }`; + + if ( + booking.bus_group_id === + bus.id + ) { + option.selected = true; + } + + dropdown.appendChild(option); + }); + + document.getElementById( + 'txnIsCoordinator' + ).value = + + booking.coordinator_bookingid === + booking.bookingid + + ? 'yes' + : 'no'; + + } catch (error) { + + console.error(error); + } +} + document.getElementById('transactionForm').addEventListener('submit', async (event) => { event.preventDefault(); @@ -361,7 +558,9 @@ document.getElementById('transactionForm').addEventListener('submit', async (eve drop_point: document.getElementById('txnDrop').value, type: document.getElementById('txnType').value, date: document.getElementById('txnTravelDate').value, - leaving_post_adhyayan: document.getElementById('txnLeavingPostAdhyayan').value + leaving_post_adhyayan: document.getElementById('txnLeavingPostAdhyayan').value, + bus_group_id: document.getElementById('txnBusGroup').value, + is_coordinator: document.getElementById('txnIsCoordinator').value, }; // Remove empty values @@ -381,10 +580,274 @@ document.getElementById('transactionForm').addEventListener('submit', async (eve const data = await response.json(); + if (data.capacityExceeded) { + + const increaseCapacity = + confirm( + `Bus capacity is ${data.currentCapacity}.\n\n` + + `Current passengers are ${data.passengerCount}.\n\n` + + `Do you want to increase capacity?` + ); + + if (!increaseCapacity) { + return; + } + + const newCapacity = prompt( + 'Enter new capacity', + Number(data.passengerCount) + 1 + ); + + if (!newCapacity || isNaN(newCapacity) || Number(newCapacity) <= 0) { + return; + } + + // UPDATE BUS CAPACITY + + const capacityResponse = + await fetch( + `${CONFIG.basePath}/travel/bus-group/capacity`, + { + method: 'PUT', + + headers: { + 'Content-Type': + 'application/json', + + Authorization: + `Bearer ${sessionStorage.getItem('token')}`, + }, + + body: JSON.stringify({ + bus_group_id: + payload.bus_group_id, + + capacity: + newCapacity, + }), + } + ); + + const capacityData = + await capacityResponse.json(); + + if (!capacityResponse.ok) { + throw new Error( + capacityData.message + ); + } + + // RESUBMIT ORIGINAL REQUEST + + // RETRY ORIGINAL UPDATE + + const retryResponse = + await fetch( + `${CONFIG.basePath}/travel/bookingupdate`, + { + method: 'PUT', + + headers: { + 'Content-Type': + 'application/json', + + Authorization: + `Bearer ${sessionStorage.getItem('token')}`, + }, + + body: JSON.stringify(payload), + } + ); + + const retryData = + await retryResponse.json(); + + if (!retryResponse.ok) { + throw new Error( + retryData.message + ); + } + + alert( + 'Bus capacity updated and passenger assigned successfully!' + ); + + document.getElementById( + 'transactionModal' + ).style.display = 'none'; + + document + .getElementById('reportForm') + .dispatchEvent( + new Event('submit') + ); + + return; + } if (response.ok) { + + if ( + data.removedFromOldBus && + data.matchingBusAvailable && + !payload.bus_group_id + ) { + + const assignToNewBus = confirm( + 'This booking was removed from its previous bus route.\n\n' + + `Matching bus available: ${data.matchingBus.bus_name}\n\n` + + 'Do you want to assign passenger to this bus?' + ); + + if (assignToNewBus) { + + // Fetch matching bus details + const busDetailsResponse = await fetch( + `${CONFIG.basePath}/travel/bus-group/${data.matchingBus.id}`, + { + headers: { + Authorization: + `Bearer ${sessionStorage.getItem('token')}`, + }, + } + ); + + const busDetails = + await busDetailsResponse.json(); + + if (!busDetailsResponse.ok) { + throw new Error( + busDetails.message + ); + } + + const currentPassengers = + busDetails.passengers.length; + + const busCapacity = + Number(busDetails.bus.capacity); + + const totalAfterAssign = + currentPassengers + 1; + + // Capacity exceeded + if ( + totalAfterAssign > busCapacity + ) { + + const increaseCapacity = + confirm( + `Matching bus capacity is ${busCapacity}.\n\n` + + `Passenger count after reassignment will become ${totalAfterAssign}.\n\n` + + `Do you want to increase capacity?` + ); + + if (!increaseCapacity) { + + alert( + 'Passenger removed from previous bus, but not assigned to the new bus because capacity was not increased.' + ); + + document.getElementById( + 'transactionModal' + ).style.display = 'none'; + + document + .getElementById('reportForm') + .dispatchEvent( + new Event('submit') + ); + + return; + } + + const newCapacity = prompt( + 'Enter new capacity', + totalAfterAssign + ); + + if (!newCapacity) { + return; + } + + // Update capacity + const capacityResponse = + await fetch( + `${CONFIG.basePath}/travel/bus-group/capacity`, + { + method: 'PUT', + + headers: { + 'Content-Type': + 'application/json', + + Authorization: + `Bearer ${sessionStorage.getItem('token')}`, + }, + + body: JSON.stringify({ + bus_group_id: + data.matchingBus.id, + + capacity: newCapacity, + }), + } + ); + + const capacityData = + await capacityResponse.json(); + + if (!capacityResponse.ok) { + throw new Error( + capacityData.message + ); + } + } + + // Assign passenger + const reassignResponse = await fetch( + `${CONFIG.basePath}/travel/bookingupdate`, + { + method: 'PUT', + + headers: { + 'Content-Type': 'application/json', + + Authorization: + `Bearer ${sessionStorage.getItem('token')}`, + }, + + body: JSON.stringify({ + bookingid: payload.bookingid, + bus_group_id: data.matchingBus.id, + + is_coordinator: + payload.is_coordinator, + }), + } + ); + + const reassignData = + await reassignResponse.json(); + + if (!reassignResponse.ok) { + throw new Error( + reassignData.message + ); + } + + alert( + 'Passenger assigned to new bus successfully' + ); + } + } + alert('Transaction updated successfully!'); + document.getElementById('transactionModal').style.display = 'none'; - document.getElementById('reportForm').dispatchEvent(new Event('submit')); + + document.getElementById('reportForm') + .dispatchEvent(new Event('submit')); + } else { alert(`Error: ${data.message}`); } @@ -413,18 +876,18 @@ document.getElementById('updateBookingForm').addEventListener('submit', async fu const statusInput = document.getElementById('status').value; const issueCredits = document.getElementById("issueCredits").value; - + let status = statusInput; let adminComments = document.getElementById('adminComments').value; -// If frontend selected a mapped value, adjust for DB -if (statusInput === 'wrong form cancel') { - status = 'admin cancelled'; - if (!adminComments) adminComments = 'admin_cancel_wrong_form'; -} else if (statusInput === 'seats full cancel') { - status = 'admin cancelled'; - if (!adminComments) adminComments = 'admin_cancel_seats_full'; -} + // If frontend selected a mapped value, adjust for DB + if (statusInput === 'wrong form cancel') { + status = 'admin cancelled'; + if (!adminComments) adminComments = 'admin_cancel_wrong_form'; + } else if (statusInput === 'seats full cancel') { + status = 'admin cancelled'; + if (!adminComments) adminComments = 'admin_cancel_seats_full'; + } try { const response = await fetch(`${CONFIG.basePath}/travel/booking/status`, { @@ -438,6 +901,7 @@ if (statusInput === 'wrong form cancel') { const data = await response.json(); + if (response.ok) { alert(data.message); } else { @@ -493,3 +957,173 @@ function formatDateTime(dateInput) { return `${day}-${month}-${year} ${hours}:${minutes}`; } + + +function openBusSummaryModal() { + + const grouped = {}; + + travelReport.forEach(booking => { + + const route = + booking.travellingFrom || + ( + booking.pickup_point === 'Research Centre' + ? 'Research Centre to Mumbai' + : 'Mumbai to Research Centre' + ); + + const activeStatuses = [ + 'confirmed', + 'proceed for payment', + 'awaiting confirmation', + 'waiting', + ]; + + if ( + !activeStatuses.includes( + booking.status + ) + ) { + return; + } + + const key = [ + booking.date, + route, + booking.bus_name || 'Unassigned' + ].join('|'); + + if (!grouped[key]) { + + grouped[key] = { + date: + booking.date, + + route, + + bus: + booking.bus_name || '', + + capacity: + booking.bus_capacity || '', + + confirmed: 0, + + proceedForPayment: 0, + + total: 0, + + remainingSeats: 0, + }; + } + + if (booking.status === 'confirmed') { + + grouped[key].confirmed++; + } + + else if ( + booking.status === + 'proceed for payment' + ) { + + grouped[key].proceedForPayment++; + } + + grouped[key].total++; + + if (grouped[key].capacity) { + grouped[key].remainingSeats = + Number(grouped[key].capacity) - + grouped[key].total; + } else { + grouped[key].remainingSeats = ''; + } + }); + + const rows = + Object.values(grouped); + + let html = ` + +
+ + + + + + + + + + + + + + + + + + `; + + rows.forEach(row => { + + html += ` + + + + + + + + + + + + + + + + + + + + `; + }); + + html += ` + +
DateRouteBusCapacityConfirmedProceed for PaymentTotalRemaining Seats
+ ${formatDate(row.date)} + + ${row.route} + + ${row.bus} + + ${row.capacity} + + ${row.confirmed} + + ${row.proceedForPayment} + + ${row.total} + + ${row.remainingSeats} +
+
+ `; + + const container = + document.getElementById( + 'busSummaryContainer' + ); + + container.innerHTML = html; + + document.getElementById( + 'busSummaryModal' + ).style.display = 'block'; +} \ No newline at end of file diff --git a/admin/travel/index.html b/admin/travel/index.html index 0f6531a..50bc552 100644 --- a/admin/travel/index.html +++ b/admin/travel/index.html @@ -54,6 +54,12 @@

Travel Management

Fetch Bookings for Driver + + Travel Bus Management + + + Coordinator Login + + + + + + + + + + + + + + + + + + +
+
+
+ Back +   |   + Home +   |   + Logout +
+
+
+ +
+
+ +
+
+ + +
+ +
+ + +
+ + + + + + +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + +
#NameMobilePickupDropCoordinatorAction
+ +
+ +
+
+ +
+
+ + + + + + + + + \ No newline at end of file diff --git a/admin/travel/travelBusDetails.js b/admin/travel/travelBusDetails.js new file mode 100644 index 0000000..a677cbd --- /dev/null +++ b/admin/travel/travelBusDetails.js @@ -0,0 +1,1214 @@ +let bulkPreviewData = null; + +const params = new URLSearchParams(window.location.search); + +const busId = params.get('id'); + +let busData = null; + +document.addEventListener('DOMContentLoaded', async () => { + + fetchBusDetails(); + + document + .getElementById('openAssignPassengerModal') + .addEventListener( + 'click', + openAssignPassengerModal + ); + + document + .getElementById('closeAssignPassengerModal') + .addEventListener( + 'click', + closeAssignPassengerModal + ); + + document + .getElementById('assignSelectedPassengers') + .addEventListener( + 'click', + assignSelectedPassengers + ); + + document + .getElementById('showSameRoute') + ?.addEventListener( + 'change', + () => { + + openAssignPassengerModal(); + } + ); + + document + .getElementById('showOtherRoutes') + ?.addEventListener( + 'change', + () => { + + openAssignPassengerModal(); + } + ); + + document + .getElementById( + 'bulkUploadPassengers' + ) + .addEventListener( + 'click', + () => { + + document + .getElementById( + 'bulkUploadInput' + ) + .click(); + } + ); + + document + .getElementById( + 'bulkUploadInput' + ) + .addEventListener( + 'change', + + async event => { + + const file = + event.target.files[0]; + + if (!file) { + return; + } + + const reader = + new FileReader(); + + reader.onload = + async e => { + + const data = + new Uint8Array( + e.target.result + ); + + const workbook = + XLSX.read( + data, + { + type: 'array', + } + ); + + const sheetName = + workbook.SheetNames[0]; + + const worksheet = + workbook.Sheets[ + sheetName + ]; + + const jsonData = + XLSX.utils.sheet_to_json( + worksheet + ); + + const bookingids = + jsonData + .map( + item => + item[ + 'Booking Id' + ] + ) + .filter(Boolean); + + const coordinatorRow = + jsonData.find( + item => { + + const value = + item[ + 'Coordinator?' + ]; + + return ( + String( + value || '' + ) + .trim() + .toLowerCase() === + 'yes' + ); + } + ); + + const coordinator_bookingid = + coordinatorRow?.[ + 'Booking Id' + ] || null; + + try { + + const response = + await fetch( + + `${CONFIG.basePath}/travel/bus/preview-bulk-upload`, + + { + + method: 'POST', + + headers: { + + 'Content-Type': + 'application/json', + + Authorization: + `Bearer ${sessionStorage.getItem('token')}`, + }, + + body: JSON.stringify({ + + bus_group_id: + busId, + + bookingids, + + coordinator_bookingid, + }), + } + ); + + const result = + await response.json(); + + if ( + !response.ok + ) { + + throw new Error( + result.message + ); + } + + bulkPreviewData = + result; + + renderBulkPreview( + result + ); + + } catch (error) { + + alert( + error.message + ); + } + }; + + reader.readAsArrayBuffer( + file + ); + } + ); + +}); + +async function fetchBusDetails() { + + try { + + const response = await fetch( + `${CONFIG.basePath}/travel/bus-group/${busId}`, + { + headers: { + Authorization: `Bearer ${sessionStorage.getItem('token')}`, + }, + } + ); + + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.message); + } + + busData = data; + + renderBusInfo(); + + renderPassengerTable(); + + } catch (error) { + alert(error.message); + } +} + +function renderBusInfo() { + + const bus = busData.bus; + + const passengerCount = + busData.passengers?.length || 0; + + document.getElementById('busInfoSection').innerHTML = ` +
+

${bus.bus_name}

+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Date + ${bus.event_date || ''} + Capacity + ${bus.capacity || ''} + Passengers + ${passengerCount} +
+ Route Stops + + + + + ${bus.stops + ?.sort( + (a, b) => + a.stop_order - + b.stop_order + ) + .map( + ( + item, + index + ) => ` + + + + + + + + + ` + ) + .join('') + } + +
+ + ${item.stop_name} + + + + ${item.timing || '-'} + +
+ +
+ +
+ `; +} + +function renderPassengerTable() { + + const tbody = + document.querySelector( + '#busPassengerTable tbody' + ); + tbody.innerHTML = ''; + const passengers = + busData.passengers || []; + + if (passengers.length === 0) { + + tbody.innerHTML = ` + + + No passengers available + + + `; + + return; + } + + + + passengers.forEach((passenger, index) => { + + const isCoordinator = + passenger.bookingid === + busData.bus.coordinator_bookingid; + + const row = document.createElement('tr'); + + row.innerHTML = ` + ${index + 1} + + + ${passenger.CardDb?.issuedto || ''} + + + + ${passenger.CardDb?.mobno || ''} + + + + ${passenger.pickup_point || ''} + + + + ${passenger.drop_point || ''} + + + + + ${isCoordinator + ? `Coordinator` + : ` + + ` + } + + + + + + `; + + tbody.appendChild(row); + }); + + attachCoordinatorEvents(); + attachRemovePassengerEvents(); + + setTimeout(() => { + enhanceTable( + 'busPassengerTable', + 'tableSearch' + ); + }, 100); +} + +function attachCoordinatorEvents() { + + document + .querySelectorAll('.setCoordinatorBtn') + .forEach(button => { + + button.addEventListener( + 'click', + async () => { + + try { + + const bookingid = + button.dataset.bookingid; + + const response = await fetch( + `${CONFIG.basePath}/travel/bus-group/coordinator`, + { + method: 'PUT', + + headers: { + 'Content-Type': 'application/json', + + Authorization: + `Bearer ${sessionStorage.getItem('token')}`, + }, + + body: JSON.stringify({ + bus_group_id: busId, + bookingid, + }), + } + ); + + const data = + await response.json(); + + if (!response.ok) { + throw new Error(data.message); + } + + alert( + 'Coordinator assigned successfully' + ); + + fetchBusDetails(); + + } catch (error) { + alert(error.message); + } + } + ); + + }); +} + +async function openAssignPassengerModal() { + + try { + + const response = await fetch( + `${CONFIG.basePath}/travel/available-bookings?event_date=${busData.bus.event_date}&bus_group_id=${busId}`, + { + headers: { + Authorization: + `Bearer ${sessionStorage.getItem('token')}`, + }, + } + ); + + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.message); + } + + renderAvailablePassengers( + data.data || [] + ); + + document.getElementById( + 'assignPassengerModal' + ).style.display = 'block'; + + } catch (error) { + alert(error.message); + } +} + +function closeAssignPassengerModal() { + + document.getElementById( + 'assignPassengerModal' + ).style.display = 'none'; +} + +function renderAvailablePassengers(passengers) { + + const tbody = + document.querySelector( + '#availablePassengerTable tbody' + ); + + tbody.innerHTML = ''; + if (passengers.length === 0) { + + tbody.innerHTML = ` + + + No passengers available + + + `; + + return; + } + + const showSameRoute = + document.getElementById( + 'showSameRoute' + ).checked; + + const showOtherRoutes = + document.getElementById( + 'showOtherRoutes' + ).checked; + + passengers.forEach(passenger => { + + const stops = + busData.bus.stops + ?.sort( + (a, b) => + a.stop_order - + b.stop_order + ) + .map( + item => item.stop_name + ) || []; + + const pickupIndex = + stops.indexOf( + passenger.pickup_point + ); + + const dropIndex = + stops.indexOf( + passenger.drop_point + ); + + const isSameRoute = + + pickupIndex !== -1 && + + dropIndex !== -1 && + + pickupIndex < dropIndex; + + // FILTERING + + if ( + isSameRoute && + !showSameRoute + ) { + return; + } + + if ( + !isSameRoute && + !showOtherRoutes + ) { + return; + } + + const row = + document.createElement('tr'); + + row.innerHTML = ` + + + + + + ${passenger.CardDb?.issuedto || ''} + + + + ${passenger.CardDb?.mobno || ''} + + + + ${passenger.pickup_point || ''} + + + + ${passenger.drop_point || ''} + + + + ${isSameRoute + ? 'Same Route' + : 'Other Route' + } + + + + ${passenger.status || '-'} + + + + ${passenger.total_people || ''} + + `; + + tbody.appendChild(row); + }); +} + +async function assignSelectedPassengers() { + + try { + + const bookingids = []; + + document + .querySelectorAll( + '.assignPassengerCheckbox:checked' + ) + .forEach(checkbox => { + bookingids.push( + checkbox.value + ); + }); + + if (bookingids.length === 0) { + return alert( + 'Please select passengers' + ); + } + + const currentPassengers = + busData.passengers.length; + + const selectedPassengers = + bookingids.length; + + const totalAfterAssign = + currentPassengers + selectedPassengers; + + const busCapacity = + Number(busData.bus.capacity); + + if (totalAfterAssign > busCapacity) { + + const confirmed = confirm( + `Bus capacity is ${busCapacity}.\n\n` + + `After assignment total passengers will become ${totalAfterAssign}.\n\n` + + `Do you want to increase capacity?` + ); + + if (!confirmed) { + return; + } + + const newCapacity = prompt( + 'Enter new bus capacity', + totalAfterAssign + ); + + if (!newCapacity) { + return; + } + + const capacityResponse = + await fetch( + `${CONFIG.basePath}/travel/bus-group/capacity`, + { + method: 'PUT', + + headers: { + 'Content-Type': 'application/json', + + Authorization: + `Bearer ${sessionStorage.getItem('token')}`, + }, + + body: JSON.stringify({ + bus_group_id: busId, + capacity: newCapacity, + }), + } + ); + + const capacityData = + await capacityResponse.json(); + + if (!capacityResponse.ok) { + + throw new Error( + capacityData.message || + 'Failed to update capacity' + ); + } + + busData.bus.capacity = + Number(newCapacity); + } + + const response = await fetch( + `${CONFIG.basePath}/travel/bus-group/assign-passengers`, + { + method: 'POST', + + headers: { + 'Content-Type': 'application/json', + + Authorization: + `Bearer ${sessionStorage.getItem('token')}`, + }, + + body: JSON.stringify({ + bus_group_id: busId, + bookingids, + }), + } + ); + + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.message); + } + + alert( + 'Passengers assigned successfully' + ); + + closeAssignPassengerModal(); + + fetchBusDetails(); + + } catch (error) { + alert(error.message); + } +} + +function attachRemovePassengerEvents() { + + document + .querySelectorAll('.removePassengerBtn') + .forEach(button => { + + button.addEventListener( + 'click', + async () => { + + try { + + const confirmed = confirm( + 'Remove passenger from this bus?' + ); + + if (!confirmed) { + return; + } + + const bookingid = + button.dataset.bookingid; + + const response = await fetch( + `${CONFIG.basePath}/travel/bus-group/passenger/${bookingid}`, + { + method: 'DELETE', + + headers: { + Authorization: + `Bearer ${sessionStorage.getItem('token')}`, + }, + } + ); + + const data = + await response.json(); + + if (!response.ok) { + throw new Error(data.message); + } + + alert(data.message); + + fetchBusDetails(); + + } catch (error) { + alert(error.message); + } + } + ); + }); +} + +async function handleBulkUpload( + event +) { + + try { + + const file = + event.target.files[0]; + + if (!file) { + return; + } + + const reader = + new FileReader(); + + reader.onload = + async e => { + + const data = + new Uint8Array( + e.target.result + ); + + const workbook = + XLSX.read(data, { + type: 'array', + }); + + const sheetName = + workbook.SheetNames[0]; + + const worksheet = + workbook.Sheets[sheetName]; + + const jsonData = + XLSX.utils.sheet_to_json( + worksheet + ); + + if ( + !jsonData.length + ) { + + alert( + 'Excel is empty' + ); + + return; + } + + const bookingids = + jsonData + .map(item => { + + const bookingid = + + item.bookingid || + + item['Booking Id'] || + + item['BookingID'] || + + item['BOOKING ID'] || + + ''; + + return String( + bookingid + ).trim(); + }) + .filter(Boolean); + if ( + !bookingids.length + ) { + + alert( + 'bookingid column missing' + ); + + return; + } + + const coordinatorRow = + jsonData.find(item => { + + const value = + item['Coordinator?']; + + return ( + String(value || '') + .trim() + .toLowerCase() === 'yes' + ); + }); + + + + const coordinator_bookingid = + coordinatorRow?.['Booking Id'] || null; + const response = + await fetch( + + `${CONFIG.basePath}/travel/bus-group/bulk-assign`, + + { + + method: 'POST', + + headers: { + + 'Content-Type': + 'application/json', + + Authorization: + `Bearer ${sessionStorage.getItem('token')}`, + }, + + body: JSON.stringify({ + + bus_group_id: + busId, + + bookingids, + + coordinator_bookingid, + }), + } + ); + + const result = + await response.json(); + + if (!response.ok) { + + throw new Error( + result.message + ); + } + + alert( + result.message + ); + + location.reload(); + }; + + reader.readAsArrayBuffer( + file + ); + + } catch (error) { + + alert(error.message); + } +} + +function renderBulkPreview( + data +) { + + const modal = + document.getElementById( + 'bulkPreviewModal' + ); + + const tbody = + document.querySelector( + '#bulkPreviewTable tbody' + ); + + const summary = + document.getElementById( + 'bulkPreviewSummary' + ); + + tbody.innerHTML = ''; + + summary.innerHTML = ` + + Valid: + ${data.validBookingIds.length} + +   |   + + Already Assigned: + ${data.alreadyAssigned.length} + +   |   + + Wrong Route: + ${data.wrongRoute.length} + +   |   + + Wrong Date: + ${data.wrongDate.length} + +   |   + + Invalid: + ${data.invalidBookingIds.length} + `; + + data.rows.forEach( + item => { + + const row = + document.createElement( + 'tr' + ); + + row.innerHTML = ` + + + + ${item.name || '-'} + + + + ${item.pickup_point || '-'} + + + + ${item.drop_point || '-'} + + + + ${item.status} + + + + ${item.result} + + + + ${item.isCoordinator + ? 'YES' + : '-' + } + +`; + tbody.appendChild( + row + ); + } + ); + + modal.style.display = + 'block'; +} + +document + .getElementById( + 'confirmBulkAssign' + ) + .addEventListener( + 'click', + + async () => { + + if ( + !bulkPreviewData?.validBookingIds?.length + ) { + + alert( + 'No valid bookings available to assign' + ); + + return; + } + + try { + + const response = + await fetch( + + `${CONFIG.basePath}/travel/bus-group/bulk-assign`, + + { + + method: 'POST', + + headers: { + + 'Content-Type': + 'application/json', + + Authorization: + `Bearer ${sessionStorage.getItem('token')}`, + }, + + body: JSON.stringify({ + + bus_group_id: + busId, + + bookingids: + bulkPreviewData.validBookingIds, + + coordinator_bookingid: + bulkPreviewData.coordinator_bookingid, + }), + } + ); + + const result = + await response.json(); + + if ( + !response.ok + ) { + + throw new Error( + result.message + ); + } + + alert( + result.message + ); + + document + .getElementById( + 'bulkPreviewModal' + ) + .style.display = + 'none'; + + fetchBusDetails(); + + } catch (error) { + + alert( + error.message + ); + } + } + ); + +document + .getElementById( + 'closeBulkPreviewModal' + ) + .addEventListener( + 'click', + + () => { + + document + .getElementById( + 'bulkPreviewModal' + ) + .style.display = + 'none'; + } + ); \ No newline at end of file diff --git a/admin/travel/travelBusManagement.html b/admin/travel/travelBusManagement.html new file mode 100644 index 0000000..a946c71 --- /dev/null +++ b/admin/travel/travelBusManagement.html @@ -0,0 +1,593 @@ + + + + + + + + + + Travel Bus Management + + + + + + + + + + + + + + + + + + + + +
+
+
+ Back +   |   + Home +   |   + Logout +
+
+
+ +
+
+
+
+ +
+

Travel Bus Management

+
+ + +
+ + + + + + + + + +
+ + + + + +
+ + + + + + + + + + + + + + + + + + +
#Event DateBus Name + Route Stops + + Timing + CapacityPassenger CountAction
+
+ +
+
+
+
+ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/travel/travelBusManagement.js b/admin/travel/travelBusManagement.js new file mode 100644 index 0000000..d7edac6 --- /dev/null +++ b/admin/travel/travelBusManagement.js @@ -0,0 +1,2438 @@ +const locationOptions = ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +`; +let busGroups = []; +let forceCreateBus = false; +let previewCreateAssign = + true; +let previewUpdateAssign = + true; +let bulkMasterPreview = + null; + +document.addEventListener('DOMContentLoaded', async () => { + + fetchBusGroups(); + + document + .getElementById( + 'confirmBulkMasterImport' + ) + .addEventListener( + 'click', + confirmBulkMasterImport + ); + + document + .getElementById( + 'closeBulkMasterPreviewModal' + ) + .addEventListener( + 'click', + () => { + + document + .getElementById( + 'bulkMasterPreviewModal' + ) + .style.display = + 'none'; + } + ); + // Open create modal + document.getElementById('openCreateBusModal') + .addEventListener('click', () => { + + forceCreateBus = false; + + document.getElementById( + 'createBusModal' + ).style.display = 'block'; + }); + + document + .getElementById( + 'openBulkMasterUpload' + ) + .addEventListener( + 'click', + () => { + + document + .getElementById( + 'bulkMasterUploadInput' + ) + .click(); + } + ); + + document + .getElementById( + 'bulkMasterUploadInput' + ) + .addEventListener( + 'change', + handleBulkMasterUpload + ); + + document.getElementById( + 'stopsContainer' + ).innerHTML = ''; + + addStopField(); + addStopField(); + + // Close create modal + document.getElementById('closeCreateBusModal') + .addEventListener( + 'click', + closeCreateBusModal + ); + + document.getElementById('cancelCreateBus') + .addEventListener( + 'click', + closeCreateBusModal + ); + + // Submit create form + document.getElementById('createBusForm') + .addEventListener( + 'submit', + createBus + ); + + document.getElementById( + 'previewCreateBusBtn' + ).addEventListener( + 'click', + previewCreateBus + ); + + document.getElementById( + 'previewEditBusBtn' + )?.addEventListener( + 'click', + previewEditBus + ); + document.getElementById( + 'confirmUpdateAssignBtn' + )?.addEventListener( + 'click', + () => { + + previewUpdateAssign = + true; + + document.getElementById( + 'editBusForm' + ).requestSubmit(); + } + ); + + document.getElementById( + 'confirmUpdateOnlyBtn' + )?.addEventListener( + 'click', + () => { + + previewUpdateAssign = + false; + + document.getElementById( + 'editBusForm' + ).requestSubmit(); + } + ); + + document.getElementById( + 'closeEditPreviewModal' + )?.addEventListener( + 'click', + () => { + + document.getElementById( + 'editBusPreviewModal' + ).style.display = + 'none'; + } + ); + + document.getElementById( + 'closeCreatePreviewModal' + ).addEventListener( + 'click', + () => { + + document.getElementById( + 'createBusPreviewModal' + ).style.display = + 'none'; + } + ); + + document.getElementById( + 'confirmCreateAssignBtn' + ).addEventListener( + 'click', + () => { + + previewCreateAssign = + true; + + document.getElementById( + 'createBusForm' + ).requestSubmit(); + } + ); + + document.getElementById( + 'confirmCreateOnlyBtn' + ).addEventListener( + 'click', + () => { + + previewCreateAssign = + false; + + document.getElementById( + 'createBusForm' + ).requestSubmit(); + } + ); + // Close edit modal + document.getElementById('closeEditBusModal') + ?.addEventListener( + 'click', + closeEditBusModal + ); + + document.getElementById('cancelEditBus') + ?.addEventListener( + 'click', + closeEditBusModal + ); + + // Submit edit form + document.getElementById('editBusForm') + ?.addEventListener( + 'submit', + updateBus + ); + +}); + +function closeCreateBusModal() { + + forceCreateBus = false; + + document.getElementById( + 'createBusModal' + ).style.display = 'none'; +} + +function closeEditBusModal() { + + forceCreateBus = false; + + document.getElementById( + 'editBusModal' + ).style.display = 'none'; +} + +function addStopField( + value = '', + timing = '' +) { + + const div = + document.createElement('div'); + + div.style.marginBottom = + '10px'; + + div.innerHTML = ` + +
+ + + + + + + + + + + +
+ `; + + const select = + div.querySelector('select'); + + if ( + value && + !Array.from(select.options).some( + option => option.value === value + ) + ) { + + const customOption = + document.createElement('option'); + + customOption.value = value; + + customOption.textContent = value; + + select.appendChild(customOption); + } + + select.value = value; + div.querySelector( + '.bus-stop-time' + ).value = timing; + + document + .getElementById( + 'stopsContainer' + ) + .appendChild(div); +} + +function addEditStopField( + value = '', + timing = '' +) { + + const div = + document.createElement('div'); + + div.style.marginBottom = + '10px'; + + div.innerHTML = ` + +
+ + + + + + + + + + + +
+ `; + + const select = + div.querySelector('select'); + + if ( + value && + !Array.from(select.options).some( + option => option.value === value + ) + ) { + + const customOption = + document.createElement('option'); + + customOption.value = value; + + customOption.textContent = value; + + select.appendChild(customOption); + } + + select.value = value; + div.querySelector( + '.edit-bus-stop-time' + ).value = timing; + + + document + .getElementById( + 'editStopsContainer' + ) + .appendChild(div); +} + +function moveStopUp(button) { + + const current = + button.parentElement.parentElement; + + const previous = + current.previousElementSibling; + + if (previous) { + + current.parentElement.insertBefore( + current, + previous + ); + } +} + +function moveStopDown(button) { + + const current = + button.parentElement.parentElement; + + const next = + current.nextElementSibling; + + if (next) { + + current.parentElement.insertBefore( + next, + current + ); + } +} + +async function fetchBusGroups() { + + try { + + const response = await fetch( + `${CONFIG.basePath}/travel/bus-groups`, + { + headers: { + Authorization: + `Bearer ${sessionStorage.getItem('token')}`, + }, + } + ); + + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.message); + } + + busGroups = data.data || []; + + renderBusTable(); + + } catch (error) { + + forceCreateBus = false; + + alert(error.message); + } +} + +function renderBusTable() { + + const tbody = + document.querySelector( + '#travelBusTable tbody' + ); + + tbody.innerHTML = ''; + + busGroups.forEach((bus, index) => { + + const row = + document.createElement('tr'); + + row.innerHTML = ` + + + ${index + 1} + +

+ + + ✏️ + + + + + + ${bus.event_date || ''} + + + + ${bus.bus_name || ''} + + + + + ${bus.stops + ?.sort( + (a, b) => + a.stop_order - + b.stop_order + ) + .map( + (s, index, arr) => ` +
+ ${s.stop_name} +
+ ` + ) + .join('') + || '' + } + + + + + + ${bus.stops + ?.sort( + (a, b) => + a.stop_order - + b.stop_order + ) + .map( + (s, index, arr) => ` +
+ ${s.timing || '-'} +
+ ` + ) + .join('') + || '' + } + + + + + ${bus.capacity || ''} + + + + ${bus.passengers + ? bus.passengers.length + : 0 + } + + + + + + + + + + + + + + + + + `; + + tbody.appendChild(row); + + row + .querySelector('.editBusBtn') + ?.addEventListener( + 'click', + () => openEditBusModal(bus) + ); + + }); + + setTimeout(() => { + + enhanceTable( + 'travelBusTable', + 'tableSearch' + ); + + }, 100); +} + +function openEditBusModal(bus) { + + document.getElementById( + 'edit_bus_id' + ).value = bus.id; + + document.getElementById( + 'edit_bus_name' + ).value = bus.bus_name || ''; + + document.getElementById( + 'editStopsContainer' + ).innerHTML = ''; + + bus.stops + ?.sort( + (a, b) => + a.stop_order - + b.stop_order + ) + .forEach(stop => { + + addEditStopField( + stop.stop_name, + stop.timing + ); + }); + + document.getElementById( + 'edit_capacity' + ).value = bus.capacity || ''; + + document.getElementById( + 'edit_notes' + ).value = bus.notes || ''; + + document.getElementById( + 'editBusModal' + ).style.display = 'block'; +} + +async function createBus(event) { + + event.preventDefault(); + + try { + + const stops = + Array.from( + document.querySelectorAll( + '#stopsContainer > div' + ) + ).map(div => ({ + + stop_name: + div.querySelector( + '.bus-stop' + ).value, + + timing: + div.querySelector( + '.bus-stop-time' + ).value, + })) + .filter( + item => item.stop_name + ); + + const selectedBookingIds = + Array.from( + + document.querySelectorAll( + '.preview-assign-checkbox:checked' + ) + + ).map( + item => + item.dataset.bookingid + ); + + const payload = { + + event_date: + document.getElementById( + 'event_date' + ).value, + + bus_name: + document.getElementById( + 'bus_name' + ).value, + + stops, + + capacity: + document.getElementById( + 'capacity' + ).value, + + notes: + document.getElementById( + 'notes' + ).value, + + force_create: + forceCreateBus, + + auto_assign: + previewCreateAssign, + + selected_bookingids: + selectedBookingIds, + }; + + const response = await fetch( + `${CONFIG.basePath}/travel/bus-group`, + { + method: 'POST', + + headers: { + 'Content-Type': + 'application/json', + + Authorization: + `Bearer ${sessionStorage.getItem('token')}`, + }, + + body: JSON.stringify(payload), + } + ); + + const data = await response.json(); + + if (data.capacityExceeded) { + + const confirmUpgrade = confirm( + + `${data.matchedPassengers} passengers match this route.\n\n` + + + `Current capacity is ${data.currentCapacity}.\n\n` + + + `Do you want to increase bus capacity?` + ); + + if (!confirmUpgrade) { + return; + } + + document.getElementById( + 'capacity' + ).value = + data.suggestedCapacity; + + document.getElementById( + 'bus_name' + ).focus(); + + forceCreateBus = true; + + alert( + 'Please review/update bus name if needed and click Create Bus again.' + ); + + return; + } + + if (!response.ok) { + throw new Error(data.message); + } + + alert('Bus created successfully'); + + document.getElementById( + 'createBusPreviewModal' + ).style.display = + 'none'; + + forceCreateBus = false; + + closeCreateBusModal(); + + document.getElementById( + 'createBusForm' + ).reset(); + + fetchBusGroups(); + + } catch (error) { + + alert(error.message); + } +} + +async function updateBus(event) { + + event.preventDefault(); + + try { + + const busId = + document.getElementById( + 'edit_bus_id' + ).value; + + const payload = { + + + auto_assign: + previewUpdateAssign, + + remove_invalid: + document.getElementById( + 'removeInvalidPassengers' + )?.checked || false, + + bus_name: + document.getElementById( + 'edit_bus_name' + ).value, + + stops: + Array.from( + document.querySelectorAll( + '#editStopsContainer > div' + ) + ).map(div => ({ + + stop_name: + div.querySelector( + '.edit-bus-stop' + ).value, + + timing: + div.querySelector( + '.edit-bus-stop-time' + ).value, + })) + .filter( + item => item.stop_name + ), + + capacity: + document.getElementById( + 'edit_capacity' + ).value, + + notes: + document.getElementById( + 'edit_notes' + ).value, + }; + + const response = await fetch( + `${CONFIG.basePath}/travel/bus-group/${busId}`, + { + method: 'PUT', + + headers: { + 'Content-Type': + 'application/json', + + Authorization: + `Bearer ${sessionStorage.getItem('token')}`, + }, + + body: JSON.stringify(payload), + } + ); + + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.message); + } + + alert('Bus updated successfully'); + + document.getElementById( + 'editBusPreviewModal' + ).style.display = + 'none'; + + closeEditBusModal(); + + fetchBusGroups(); + + } catch (error) { + + alert(error.message); + } +} + +async function + handleBulkMasterUpload( + event + ) { + + try { + + const file = + event.target.files[0]; + + if (!file) { + return; + } + + const reader = + new FileReader(); + + reader.onload = + async e => { + + const data = + new Uint8Array( + e.target.result + ); + + const workbook = + XLSX.read( + data, + { + type: 'array', + } + ); + + const busesSheet = + workbook.Sheets['Busses']; + + const assignmentsSheet = + workbook.Sheets['Assignments']; + + if ( + !busesSheet || + !assignmentsSheet + ) { + + throw new Error( + 'Excel must contain Buses and Assignments sheets' + ); + } + + const busesData = + XLSX.utils.sheet_to_json( + busesSheet + ); + + const assignmentsData = + XLSX.utils.sheet_to_json( + assignmentsSheet + ); + + const response = + await fetch( + + `${CONFIG.basePath}/travel/bulk-master-preview`, + + { + + method: 'POST', + + headers: { + + 'Content-Type': + 'application/json', + + Authorization: + `Bearer ${sessionStorage.getItem('token')}`, + }, + + body: JSON.stringify({ + + buses: + busesData, + + assignments: + assignmentsData, + }), + } + ); + + const result = + await response.json(); + + if (!response.ok) { + + throw new Error( + result.message + ); + } + + bulkMasterPreview = + result; + + renderBulkMasterPreview( + result + ); + + document + .getElementById( + 'bulkMasterPreviewModal' + ) + .style.display = + 'block'; + }; + + reader.readAsArrayBuffer( + file + ); + + } catch (error) { + + alert(error.message); + } +} + +function + renderBulkMasterPreview( + data + ) { + + const container = + document.getElementById( + 'bulkMasterPreviewContainer' + ); + + const totalBuses = + data.buses.length; + + const totalValid = + data.buses.reduce( + (sum, bus) => + sum + + bus.validPassengers.length, + 0 + ); + + const totalOverflow = + data.buses.reduce( + (sum, bus) => + sum + + bus.overflowPassengers.length, + 0 + ); + + const totalInvalid = + data.buses.reduce( + (sum, bus) => + sum + + bus.invalidPassengers.length, + 0 + ); + + const totalAssigned = + data.buses.reduce( + (sum, bus) => + sum + + bus.alreadyAssigned.length, + 0 + ); + + + const hasDuplicateBuses = + data.buses.some( + bus => bus.duplicateBus + ); + + const totalDuplicateBuses = + data.buses.filter( + bus => bus.duplicateBus + ).length; + + container.innerHTML = ` + +
+ +

+ 🚌 Bulk Bus Import Review +

+ +
+ ${totalBuses} + buses ready for creation +
+ +
+ +
+ +
+
+ ${totalValid} +
+ +
+ Valid Assignments +
+
+ +
+
+ ${totalOverflow} +
+ +
+ Overflow +
+
+ +
+
+ ${totalInvalid} +
+ +
+ Invalid +
+
+ +
+
+ ${totalAssigned} +
+ +
+ Already Assigned +
+
+ +
+ +
+ ${totalDuplicateBuses} +
+ +
+ Duplicate Buses +
+ +
+ +
+ + ${data.buses.map( + (bus, index) => ` + +
+ +
+ +
+ +

+ 🚌 ${bus.bus_name} +

+ +${bus.duplicateBus + ? ` +
+ ⚠️ Bus already exists +
+ ` + : '' + } + +
+ ${bus.stops + ?.map( + s => + + `${s.stop_name} + (${s.timing || '-'})` + ) + .join(' → ')} +
+ + ${bus.routeError + ? ` +
+ ❌ ${bus.routeError} +
+ ` + : '' + } + +
+ +
+ +
+ 👥 Capacity: + ${bus.capacity} +
+ +
+ +
+ +
+ +
+ ✅ ${bus.validPassengers.length} Valid +
+ +
+ ⚠️ ${bus.overflowPassengers.length} Overflow +
+ +
+ ❌ ${bus.invalidPassengers.length} Invalid +
+ +
+ 🔁 ${bus.alreadyAssigned.length} Assigned +
+ +
+ + + +
+ ` + ).join('')} + `; + document.getElementById( + 'bulkDuplicateWarning' + ).style.display = + + hasDuplicateBuses + ? 'block' + : 'none'; + + document.getElementById( + 'bulkUpdateContainer' + ).style.display = + + hasDuplicateBuses + ? 'block' + : 'none'; + + const duplicateCheckbox = + document.getElementById( + 'ignoreDuplicateBuses' + ); + + duplicateCheckbox.onchange = + () => { + + document.getElementById( + 'confirmBulkMasterImport' + ).disabled = + + hasDuplicateBuses && + + !duplicateCheckbox.checked; + }; + + document.getElementById( + 'confirmBulkMasterImport' + ).disabled = + + hasDuplicateBuses; +} + + + +function + toggleBulkPreviewTable( + id + ) { + + const el = + document.getElementById( + id + ); + + el.style.display = + + el.style.display === + 'none' + + ? 'block' + + : 'none'; +} + +async function + confirmBulkMasterImport() { + + try { + + const response = + await fetch( + + `${CONFIG.basePath}/travel/bulk-master-create`, + + { + + method: 'POST', + + headers: { + + 'Content-Type': + 'application/json', + + Authorization: + `Bearer ${sessionStorage.getItem('token')}`, + }, + + body: JSON.stringify({ + + buses: + bulkMasterPreview.buses, + + update_existing: + document.getElementById( + 'updateExistingBuses' + )?.checked || false, + }), + } + ); + + const result = + await response.json(); + + if (!response.ok) { + + throw new Error( + result.message + ); + } + + alert( + result.message + ); + + document + .getElementById( + 'bulkMasterPreviewModal' + ) + .style.display = + 'none'; + + fetchBusGroups(); + + } catch (error) { + + alert(error.message); + } +} + +async function exportBusPassengers( + busGroupId, + busName +) { + + try { + + const response = await fetch( + + `${CONFIG.basePath}/travel/bus-group/${busGroupId}/export`, + + { + headers: { + Authorization: + `Bearer ${sessionStorage.getItem('token')}`, + }, + } + ); + + if (!response.ok) { + + const error = + await response.json(); + + throw new Error( + error.message + ); + } + + const blob = + await response.blob(); + + const url = + window.URL.createObjectURL( + blob + ); + + const a = + document.createElement( + 'a' + ); + + a.href = url; + + a.download = + `${busName}_passengers.xlsx`; + document.body.appendChild( + a + ); + + a.click(); + + a.remove(); + + window.URL.revokeObjectURL( + url + ); + + } catch (error) { + + alert(error.message); + } +} + +async function deleteBus( + busId +) { + + const confirmDelete = + confirm( + 'Delete this bus?' + ); + + if (!confirmDelete) { + return; + } + + try { + + const response = + await fetch( + + `${CONFIG.basePath}/travel/bus-group/${busId}`, + + { + method: 'DELETE', + + headers: { + Authorization: + `Bearer ${sessionStorage.getItem('token')}`, + }, + } + ); + + const data = + await response.json(); + + if (!response.ok) { + + throw new Error( + data.message + ); + } + + alert( + 'Bus deleted successfully' + ); + + fetchBusGroups(); + + } catch (error) { + + alert(error.message); + } +} + +async function + previewCreateBus() { + + try { + const stops = + Array.from( + document.querySelectorAll( + '#stopsContainer > div' + ) + ).map(div => ({ + + stop_name: + div.querySelector( + '.bus-stop' + ).value, + + timing: + div.querySelector( + '.bus-stop-time' + ).value, + })) + .filter( + item => item.stop_name + ); + + const payload = { + + event_date: + document.getElementById( + 'event_date' + ).value, + + stops, + + capacity: + document.getElementById( + 'capacity' + ).value, + }; + + const response = + await fetch( + + `${CONFIG.basePath}/travel/bus-group/preview-create`, + + { + method: 'POST', + + headers: { + + 'Content-Type': + 'application/json', + + Authorization: + `Bearer ${sessionStorage.getItem('token')}`, + }, + + body: + JSON.stringify( + payload + ), + } + ); + + const data = + await response.json(); + + if (!response.ok) { + + throw new Error( + data.message + ); + } + + renderCreateBusPreview( + data + ); + document.getElementById( + 'createBusPreviewModal' + ).style.display = + 'block'; + + } catch (error) { + + alert(error.message); + } +} + +async function + previewEditBus() { + + try { + const stops = + Array.from( + document.querySelectorAll( + '#editStopsContainer > div' + ) + ).map(div => ({ + + stop_name: + div.querySelector( + '.edit-bus-stop' + ).value, + + timing: + div.querySelector( + '.edit-bus-stop-time' + ).value, + })) + .filter( + item => item.stop_name + ); + + const payload = { + + bus_group_id: + document.getElementById( + 'edit_bus_id' + ).value, + + stops, + + capacity: + document.getElementById( + 'edit_capacity' + ).value, + }; + + const response = + await fetch( + + `${CONFIG.basePath}/travel/bus-group/preview-update`, + + { + method: 'POST', + + headers: { + + 'Content-Type': + 'application/json', + + Authorization: + `Bearer ${sessionStorage.getItem('token')}`, + }, + + body: + JSON.stringify( + payload + ), + } + ); + + const data = + await response.json(); + + if (!response.ok) { + + throw new Error( + data.message + ); + } + + renderEditPreview( + data + ); + + document.getElementById( + 'editBusPreviewModal' + ).style.display = + 'block'; + + } catch (error) { + + alert(error.message); + } +} + +function + renderCreateBusPreview( + data + ) { + + const container = + document.getElementById( + 'createBusPreviewContainer' + ); + + container.innerHTML = ` + +
+ +
+

+ ${data.totalMatching} +

+

+ Total Matching +

+
+ +
+

+ ${data.assignableCount} +

+

+ Assignable +

+
+ +
+

+ ${data.skippedCapacityCount} +

+

+ Capacity Overflow +

+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +${[...data.rows] + + .sort((a, b) => { + + if ( + a.alreadyAssigned && + !b.alreadyAssigned + ) { + return 1; + } + + if ( + !a.alreadyAssigned && + b.alreadyAssigned + ) { + return -1; + } + + return 0; + }) + + .map( + row => ` + + + + + + + + + + + + + + + + + + +` + ).join('')} + + + +
NameCard NoPickupDropStatusAlready AssignedAssign
+ ${row.name} + + ${row.cardno} + + ${row.pickup} + + ${row.drop} + + ${row.status} + + ${row.alreadyAssigned + ? 'Yes' + : 'No' + } + + +
+ +
+`; +} + +function + renderEditPreview( + data + ) { + + const container = + document.getElementById( + 'editBusPreviewContainer' + ); + + container.innerHTML = ` + +
+ +
+

+ ${data.assignable.length} +

+

+ New Assignable +

+
+ +
+

+ ${data.overflow.length} +

+

+ Capacity Overflow +

+
+ +
+

+ ${data.noLongerMatching.length} +

+

+ No Longer Matching +

+
+ +
+ +

+ Newly Matching Passengers +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + ${data.newlyMatching.map( + row => ` + + + + + + + + + + + + + + + ` + ).join('') + } + + + +
NameCard NoPickupDropAlready Assigned
+ ${row.name} + + ${row.cardno} + + ${row.pickup} + + ${row.drop} + + ${row.alreadyAssigned + ? 'Yes' + : 'No' + } +
+ +
+ +

+ Passengers No Longer Matching +

+ +
+ + + + + + + + + + + + + + + + + + + + + + ${data.noLongerMatching.map( + row => ` + + + + + + + + + + + + + ` + ).join('') + } + + + +
NameCard NoPickupDrop
+ ${row.name} + + ${row.cardno} + + ${row.pickup} + + ${row.drop} +
+ +
+ `; +} + +function openBulkDetailsModal( + index +) { + + const bus = + bulkMasterPreview + .buses[index]; + + document.getElementById( + 'bulkDetailsModal' + ).style.display = + 'block'; + + document.getElementById( + 'bulkDetailsContent' + ).innerHTML = ` + +

+ ${bus.bus_name} +

+ +
+ ${bus.stops + ?.map( + s => + + `${s.stop_name} + (${s.timing || '-'})` + ) + .join(' → ')} +
+ + ${renderBulkDetailsTable( + 'Valid', + bus.validPassengers, + false + )} + + ${renderBulkDetailsTable( + 'Overflow', + bus.overflowPassengers, + false + )} + + ${renderBulkDetailsTable( + 'Already Assigned', + bus.alreadyAssigned, + false + )} + + ${renderBulkDetailsTable( + 'Invalid', + bus.invalidPassengers, + true + )} + `; +} + +function renderBulkDetailsTable( + title, + rows, + isInvalid = false +) { + + return ` + +

+ ${title} +

+ + + + + + + + + + + + ${isInvalid + ? ` + + ` + : ` + + + + ` + } + + + + + + + + ${rows.length + + ? rows.map( + item => ` + + + + + + + + ${isInvalid + ? ` + + ` + : ` + + + + ` + } + + + ` + ).join('') + + : ` + + + + ` + } + + + +
+ Booking ID + + Name + + Reason + + Pickup + + Drop +
+ ${item.bookingid} + + ${item.name || '-'} + + ${item.reason || '-'} + + ${item.pickup || '-'} + + ${item.drop || '-'} +
+ No Data +
+ `; +} + +document.addEventListener( + 'click', + event => { + + if ( + event.target.id === + 'closeBulkDetailsModal' + ) { + + document.getElementById( + 'bulkDetailsModal' + ).style.display = + 'none'; + } + } +); \ No newline at end of file diff --git a/admin/utsav/createUtsav.html b/admin/utsav/createUtsav.html old mode 100644 new mode 100755 index 60a145f..a821587 --- a/admin/utsav/createUtsav.html +++ b/admin/utsav/createUtsav.html @@ -76,6 +76,24 @@

Create Utsav

+
+ + +
+ +
+ + +
+
diff --git a/admin/utsav/createUtsav.js b/admin/utsav/createUtsav.js old mode 100644 new mode 100755 index b51fd3a..3e2abbb --- a/admin/utsav/createUtsav.js +++ b/admin/utsav/createUtsav.js @@ -12,6 +12,9 @@ document.addEventListener('DOMContentLoaded', function () { event.preventDefault(); const formData = new FormData(utsavForm); + const startingMeal = Array.from(document.getElementById('starting_meal').selectedOptions).map(o => o.value); + const endingMeal = Array.from(document.getElementById('ending_meal').selectedOptions).map(o => o.value); + const requestData = { name: formData.get('name'), start_date: formatDateForDB(formData.get('start_date')), @@ -19,7 +22,9 @@ document.addEventListener('DOMContentLoaded', function () { total_seats: formData.get('total_seats'), comments: formData.get('comments'), location: formData.get('location'), - registration_deadline: formatDateForDB(formData.get('registration_deadline')) + registration_deadline: formatDateForDB(formData.get('registration_deadline')), + starting_meal: startingMeal.length ? startingMeal : null, + ending_meal: endingMeal.length ? endingMeal : null }; try { @@ -52,8 +57,38 @@ document.addEventListener('DOMContentLoaded', function () { } }); + function toggleMealFields() { + const location = document.getElementById('location').value.trim(); + const isRC = location === 'Research Centre'; + const startingMealGroup = document.getElementById('starting_meal').closest('.form-group'); + const endingMealGroup = document.getElementById('ending_meal').closest('.form-group'); + + if (!isRC) { + startingMealGroup.style.display = 'none'; + endingMealGroup.style.display = 'none'; + Array.from(document.getElementById('starting_meal').options).forEach(o => o.selected = false); + Array.from(document.getElementById('ending_meal').options).forEach(o => o.selected = false); + return; + } + + startingMealGroup.style.display = ''; + + const start = document.getElementById('start_date').value; + const end = document.getElementById('end_date').value; + if (start && end && start === end) { + endingMealGroup.style.display = 'none'; + Array.from(document.getElementById('ending_meal').options).forEach(o => o.selected = false); + } else { + endingMealGroup.style.display = ''; + } + } + + document.getElementById('location').addEventListener('input', toggleMealFields); + // Initialize Flatpickr with dd/mm/yyyy format for user - flatpickr("#start_date", { dateFormat: "d/m/Y" }); - flatpickr("#end_date", { dateFormat: "d/m/Y" }); + flatpickr("#start_date", { dateFormat: "d/m/Y", onChange: toggleMealFields }); + flatpickr("#end_date", { dateFormat: "d/m/Y", onChange: toggleMealFields }); flatpickr("#registration_deadline", { dateFormat: "d/m/Y" }); + + toggleMealFields(); }); diff --git a/admin/utsav/updateUtsav.html b/admin/utsav/updateUtsav.html old mode 100644 new mode 100755 index 3747819..eb9635f --- a/admin/utsav/updateUtsav.html +++ b/admin/utsav/updateUtsav.html @@ -88,6 +88,24 @@

Edit Utsav

+
+ + +
+ +
+ + +
+
diff --git a/admin/utsav/updateUtsav.js b/admin/utsav/updateUtsav.js old mode 100644 new mode 100755 index f2cb24f..a683a8c --- a/admin/utsav/updateUtsav.js +++ b/admin/utsav/updateUtsav.js @@ -37,6 +37,18 @@ document.addEventListener('DOMContentLoaded', () => { document.getElementById('comments').value = data.comments; document.getElementById('location').value = data.location; document.getElementById('registration_deadline').value = data.registration_deadline; + + const prefillSelect = (id, values) => { + if (!values) return; + const select = document.getElementById(id); + Array.from(select.options).forEach(opt => { + opt.selected = values.includes(opt.value); + }); + }; + prefillSelect('starting_meal', data.starting_meal); + prefillSelect('ending_meal', data.ending_meal); + + toggleMealFields(); document.getElementById('saveButton').addEventListener('click', () => { updateUtsavDetails(document.getElementById('id').value); @@ -49,6 +61,9 @@ document.addEventListener('DOMContentLoaded', () => { const utsavFormData = document.getElementById('editUtsavForm'); const utsavForm = new FormData(utsavFormData); + const startingMeal = Array.from(document.getElementById('starting_meal').selectedOptions).map(o => o.value); + const endingMeal = Array.from(document.getElementById('ending_meal').selectedOptions).map(o => o.value); + const updatedData = { name: utsavForm.get('name'), start_date: utsavForm.get('start_date'), @@ -57,7 +72,9 @@ document.addEventListener('DOMContentLoaded', () => { available_seats: utsavForm.get('available_seats'), comments: utsavForm.get('comments'), location: utsavForm.get('location'), - registration_deadline: utsavForm.get('registration_deadline') + registration_deadline: utsavForm.get('registration_deadline'), + starting_meal: startingMeal.length ? startingMeal : null, + ending_meal: endingMeal.length ? endingMeal : null }; try { @@ -87,6 +104,36 @@ document.addEventListener('DOMContentLoaded', () => { } }; + function toggleMealFields() { + const location = document.getElementById('location').value.trim(); + const isRC = location === 'Research Centre'; + const startingMealGroup = document.getElementById('starting_meal').closest('.form-group'); + const endingMealGroup = document.getElementById('ending_meal').closest('.form-group'); + + if (!isRC) { + startingMealGroup.style.display = 'none'; + endingMealGroup.style.display = 'none'; + Array.from(document.getElementById('starting_meal').options).forEach(o => o.selected = false); + Array.from(document.getElementById('ending_meal').options).forEach(o => o.selected = false); + return; + } + + startingMealGroup.style.display = ''; + + const start = document.getElementById('start_date').value; + const end = document.getElementById('end_date').value; + if (start && end && start === end) { + endingMealGroup.style.display = 'none'; + Array.from(document.getElementById('ending_meal').options).forEach(o => o.selected = false); + } else { + endingMealGroup.style.display = ''; + } + } + + document.getElementById('location').addEventListener('input', toggleMealFields); + document.getElementById('start_date').addEventListener('change', toggleMealFields); + document.getElementById('end_date').addEventListener('change', toggleMealFields); + // Call fetchUtsavDetails to retrieve the Utsav data for editing fetchUtsavDetails(utsavToEdit); }); diff --git a/admin/wifi/index.html b/admin/wifi/index.html index 8a42051..378005c 100644 --- a/admin/wifi/index.html +++ b/admin/wifi/index.html @@ -5,58 +5,60 @@ - - - - WiFi Management - - - - - - - - - - - - - - - - - -
-
-
- Back -   |   - Home -   |   - Logout -
+ + + + + WiFi Management + + + + + + + + + + + + + + + + + +
+
+
+ Back +   |   + Home +   |   + Logout
+
-
-
-
-
-
-

WiFi Management

-
+
+ + + + \ No newline at end of file diff --git a/admin/wifi/permanentCodeRequests.html b/admin/wifi/permanentCodeRequests.html index 7c1ac52..87b2548 100644 --- a/admin/wifi/permanentCodeRequests.html +++ b/admin/wifi/permanentCodeRequests.html @@ -187,6 +187,62 @@

Update WiFi Request

+ + + + + + + +
diff --git a/admin/wifi/permanentCodeRequests.js b/admin/wifi/permanentCodeRequests.js index 3680351..8464528 100644 --- a/admin/wifi/permanentCodeRequests.js +++ b/admin/wifi/permanentCodeRequests.js @@ -16,7 +16,7 @@ document.addEventListener('DOMContentLoaded', () => { row.style.display = text.includes(searchTerm) ? '' : 'none'; }); }); - + document.getElementById('manualDeviceType') .addEventListener('change', autoGenerateUsername); @@ -214,13 +214,16 @@ function showMessage(msg, type) { /* ============================================================ DOWNLOAD & UPLOAD BUTTONS - UPDATED WITH SEPARATE BUTTONS ============================================================ */ - function setupDownloadAndUploadButtons(data) { const container = document.getElementById('downloadBtnContainer'); container.innerHTML = ` + + `; @@ -277,7 +280,9 @@ function setupDownloadAndUploadButtons(data) { const file = e.target.files[0]; if (!file) return; - if (!confirm('This will UPDATE existing records. Continue?')) { + const dryRun = document.getElementById('dryRunCheckbox').checked; + + if (!dryRun && !confirm('This will UPDATE existing records in database. Continue?')) { e.target.value = ''; return; } @@ -286,7 +291,8 @@ function setupDownloadAndUploadButtons(data) { formData.append('file', file); try { - const url = `${CONFIG.basePath}/wifi/uploadpercode`; + let url = `${CONFIG.basePath}/wifi/uploadpercode`; + if (dryRun) url += '?dryRun=true'; const res = await fetch(url, { method: 'POST', @@ -297,17 +303,16 @@ function setupDownloadAndUploadButtons(data) { }); const result = await res.json(); - - if (res.ok) { - let msg = `✅ UPDATE SUCCESSFUL\n\n`; - msg += `Updated: ${result.updatedCount || 0}\n`; - msg += `Invalid rows: ${result.skipped?.invalidRows || 0}\n`; - msg += `Mismatched rows: ${result.skipped?.mismatched || 0}`; - alert(msg); - fetchRequests(); + if (res.ok) { + if (dryRun) { + showUploadResult('Dry Run: Plan for Update', result); + } else { + showUploadResult('Update Successful', result); + fetchRequests(); + } } else { - alert('❌ Update failed: ' + (result.message || result.error)); + showUploadResult('Upload Failed', result, true); } } catch (err) { console.error(err); @@ -322,7 +327,9 @@ function setupDownloadAndUploadButtons(data) { const file = e.target.files[0]; if (!file) return; - if (!confirm('This will INSERT new records. Continue?')) { + const dryRun = document.getElementById('dryRunCheckbox').checked; + + if (!dryRun && !confirm('This will INSERT new records into database. Continue?')) { e.target.value = ''; return; } @@ -331,7 +338,8 @@ function setupDownloadAndUploadButtons(data) { formData.append('file', file); try { - const url = `${CONFIG.basePath}/wifi/insertpercode?allowInsert=true`; + let url = `${CONFIG.basePath}/wifi/insertpercode?allowInsert=true`; + if (dryRun) url += '&dryRun=true'; const res = await fetch(url, { method: 'POST', @@ -342,17 +350,16 @@ function setupDownloadAndUploadButtons(data) { }); const result = await res.json(); - - if (res.ok) { - let msg = `✅ INSERT SUCCESSFUL\n\n`; - msg += `Inserted: ${result.insertedCount || 0}\n`; - msg += `Skipped (already exists): ${result.skippedExisting || 0}\n`; - msg += `Invalid rows: ${result.invalidRows || 0}`; - alert(msg); - fetchRequests(); + if (res.ok) { + if (dryRun) { + showUploadResult('Dry Run: Plan for Insert', result); + } else { + showUploadResult('Insert Successful', result); + fetchRequests(); + } } else { - alert('❌ Insert failed: ' + (result.message || result.error)); + showUploadResult('Upload Failed', result, true); } } catch (err) { console.error(err); @@ -365,7 +372,7 @@ function setupDownloadAndUploadButtons(data) { /* ============================================================ ADD CODE MANUALLY – MODAL LOGIC -============================================================ */ + ============================================================ */ // open manual add modal function openManualAddModal() { @@ -489,4 +496,197 @@ async function submitManualAdd() { } catch (err) { alert(err.message || 'Failed to add code'); } +} + +/* ============================================================ + ROUTER PORTAL EXPORT & UPLOAD RESULTS UI HELPERS +============================================================ */ + +function openRouterExportModal() { + document.getElementById('routerExportModal').style.display = 'block'; + document.getElementById('routerExportBackdrop').style.display = 'block'; + document.getElementById('exportFilterType').value = 'hours'; + toggleExportFilterFields(); +} + +function closeRouterExportModal() { + document.getElementById('routerExportModal').style.display = 'none'; + document.getElementById('routerExportBackdrop').style.display = 'none'; +} + +function toggleExportFilterFields() { + const type = document.getElementById('exportFilterType').value; + document.getElementById('exportHoursGroup').style.display = type === 'hours' ? 'block' : 'none'; + document.getElementById('exportDateRangeGroup').style.display = type === 'date-range' ? 'block' : 'none'; +} + +async function submitRouterExport() { + const type = document.getElementById('exportFilterType').value; + const token = sessionStorage.getItem('token'); + let url = `${CONFIG.basePath}/wifi/permanent/portal-export`; + const params = new URLSearchParams(); + + if (type === 'hours') { + const hours = document.getElementById('exportHours').value; + if (!hours || hours <= 0) { + alert('Please enter a valid number of hours'); + return; + } + params.set('hours', hours); + } else if (type === 'date-range') { + const start = document.getElementById('exportStartDate').value; + const end = document.getElementById('exportEndDate').value; + if (!start || !end) { + alert('Please select both start and end dates'); + return; + } + params.set('startDate', start); + params.set('endDate', end); + } + + const query = params.toString(); + if (query) url += '?' + query; + + try { + const res = await fetch(url, { + headers: { Authorization: `Bearer ${token}` } + }); + + if (!res.ok) { + const json = await res.json(); + throw new Error(json.error || 'Failed to download portal export'); + } + + const blob = await res.blob(); + const link = document.createElement('a'); + link.href = window.URL.createObjectURL(blob); + link.download = `wifi_portal_accounts_${type}_${new Date().toISOString().slice(0, 10)}.xlsx`; + link.click(); + closeRouterExportModal(); + } catch (err) { + console.error(err); + alert('Export failed: ' + err.message); + } +} + + +function showUploadResult(title, data, isError = false) { + document.getElementById('resultTitle').innerText = title; + const summaryDiv = document.getElementById('resultSummary'); + const headerRow = document.getElementById('resultTableHeader'); + const body = document.getElementById('resultTableBody'); + + headerRow.innerHTML = ''; + body.innerHTML = ''; + + if (isError) { + summaryDiv.style.borderLeftColor = '#dc3545'; + summaryDiv.innerHTML = `Error: ${data.error || 'Failed to process file'}`; + + if (data.errors && data.errors.length > 0) { + headerRow.innerHTML = 'Excel RowValidation Error Description'; + data.errors.forEach(err => { + const row = document.createElement('tr'); + row.innerHTML = ` + Row ${err.row} + ${err.error} + `; + body.appendChild(row); + }); + } else { + headerRow.innerHTML = 'Status'; + const row = document.createElement('tr'); + row.innerHTML = `No detailed row errors available. Check backend server logs.`; + body.appendChild(row); + } + } else if (data.dryRun) { + summaryDiv.style.borderLeftColor = '#ffc107'; + + if (title.includes('Update')) { + summaryDiv.innerHTML = ` + Dry Run Summary:
+ Total rows in file: ${data.summary.totalRows}
+ Valid rows: ${data.summary.validRows}
+ Mismatched rows (skipped): ${data.summary.invalidRows}
+ Planned updates: ${data.summary.changesCount} + `; + + headerRow.innerHTML = 'RowCard NoField NameOld ValueNew Value'; + if (data.changes && data.changes.length > 0) { + data.changes.forEach(ch => { + Object.keys(ch.changes).forEach(field => { + const row = document.createElement('tr'); + row.innerHTML = ` + Row ${ch.rowNumber} + ${ch.cardno} + ${field} + ${ch.changes[field].old || '-'} + ${ch.changes[field].new || '-'} + `; + body.appendChild(row); + }); + }); + } else { + body.innerHTML = 'No changes detected compared to database.'; + } + } else { + // Insert dry run + summaryDiv.innerHTML = ` + Dry Run Summary:
+ Total rows in file: ${data.summary.totalRows}
+ Valid rows: ${data.summary.validRows}
+ Skipped (already exists): ${data.summary.skippedExisting}
+ Invalid rows: ${data.summary.invalidRows}
+ Planned inserts: ${data.summary.toInsert.length} + `; + + headerRow.innerHTML = 'RowCard NoUsernameWiFi CodeSSIDStatus'; + if (data.toInsert && data.toInsert.length > 0) { + data.toInsert.forEach(item => { + const row = document.createElement('tr'); + row.innerHTML = ` + Row ${item.rowNumber} + ${item.cardno} + ${item.username || '(auto-generated)'} + ${item.code || '-'} + ${item.ssid || '-'} + ${item.status} + `; + body.appendChild(row); + }); + } else { + body.innerHTML = 'No new rows to insert.'; + } + } + } else { + // Normal Success + summaryDiv.style.borderLeftColor = '#28a745'; + if (title.includes('Update')) { + summaryDiv.innerHTML = ` + Updates Committed Successfully!
+ Updated records: ${data.updatedCount || 0}
+ Skipped invalid: ${data.skipped?.invalidRows || 0}
+ Skipped mismatched: ${data.skipped?.mismatched || 0}
+ WhatsApp notifications queued: ${data.notificationsQueued || 0} + `; + } else { + summaryDiv.innerHTML = ` + Inserts Committed Successfully!
+ Inserted records: ${data.insertedCount || 0}
+ Skipped existing: ${data.skippedExisting || 0}
+ Skipped invalid: ${data.invalidRows || 0}
+ WhatsApp notifications queued: ${data.notificationsQueued || 0} + `; + } + headerRow.innerHTML = 'Status'; + body.innerHTML = 'Data committed successfully to database!'; + } + + document.getElementById('uploadResultModal').style.display = 'block'; + document.getElementById('uploadResultBackdrop').style.display = 'block'; +} + +function closeUploadResultModal() { + document.getElementById('uploadResultModal').style.display = 'none'; + document.getElementById('uploadResultBackdrop').style.display = 'none'; } \ No newline at end of file diff --git a/admin/wifi/wifiReport.html b/admin/wifi/wifiReport.html index 7d54f61..9597fd4 100644 --- a/admin/wifi/wifiReport.html +++ b/admin/wifi/wifiReport.html @@ -5,162 +5,145 @@ - - - - WiFi Codes Usage Report - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
- Back -   |   - Home -   |   - Logout -
+ + + + + WiFi Codes Usage Report + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ Back +   |   + Home +   |   + Logout
+
+ +
+
+
+
+
+

WiFi Codes Usage Report

+
-
-
-
-
-
-

WiFi Codes Usage Report

-
- -
- - -
- - - -
- - - - - - - - - to - - - - -
-
- - -
Active Codes: 0 | Inactive Codes: 0
+
+ + +
+ + + +
+ + + + + + + + + to + + + + +
- -
- - - - - - - - - - - - - - -
#PasswordStatusCode Issued AtNameMobile NumberCheckin DateCheckout Date
-
+ +
Active Codes: 0 | Inactive Codes: 0
+
+ +
+ + + + + + + + + + + + + + +
#PasswordStatusCode Issued AtNameMobile NumberCheckin DateCheckout Date
+
- - +
+ + + \ No newline at end of file diff --git a/style/js/coordinatorAuth.js b/style/js/coordinatorAuth.js new file mode 100644 index 0000000..7edd96b --- /dev/null +++ b/style/js/coordinatorAuth.js @@ -0,0 +1,31 @@ +function checkCoordinatorAuth() { + + const token = + sessionStorage.getItem( + 'coordinatorToken' + ); + + if (!token) { + + alert( + 'Please login first' + ); + + window.location.href = + '/admin/travel/coordinatorLogin.html'; + } +} + +function coordinatorLogout() { + + sessionStorage.removeItem( + 'coordinatorToken' + ); + + sessionStorage.removeItem( + 'coordinatorUser' + ); + + window.location.href = + '/admin/travel/coordinatorLogin.html'; +} \ No newline at end of file diff --git a/style/sample_files/bulk_bus_master_upload.xlsx b/style/sample_files/bulk_bus_master_upload.xlsx new file mode 100644 index 0000000..f96ee26 Binary files /dev/null and b/style/sample_files/bulk_bus_master_upload.xlsx differ