File "direct_booking.php"
Full path: /home/itsevak/public_html/prepaiddev.itsevak.com/operator/direct_booking.php
File
size: 43.92 B (43.92 KB bytes)
MIME-type: text/x-php
Charset: utf-8
Download Open Edit Advanced Editor Back
<?php
require_once '../includes/functions.php';
// Require login and operator access
requireLogin();
if ($_SESSION['user_type'] !== 'operator') {
redirect('/auth/login.php');
}
$user = getCurrentUser();
$db = Database::getInstance();
$errors = [];
$success = '';
$booking = null;
// Check if operator has active session
$currentSession = $db->fetch(
"SELECT os.*, fl.name as from_location_name
FROM operator_sessions os
JOIN from_locations fl ON os.from_location_id = fl.id
WHERE os.operator_id = ? AND os.status = 'active'",
[$user['id']]
);
if (!$currentSession) {
redirect('/operator/');
}
// Get available destinations
$destinations = $db->fetchAll("SELECT * FROM to_locations WHERE status = 'active' ORDER BY name");
// Handle direct booking submission
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!validateCSRFToken($_POST['csrf_token'] ?? '')) {
$errors[] = 'Invalid request. Please try again.';
} else {
$toLocationId = (int)($_POST['to_location_id'] ?? 0);
$rickshawId = (int)($_POST['rickshaw_id'] ?? 0);
// Validation
if (empty($toLocationId)) {
$errors[] = 'Please select a destination.';
}
if (empty($rickshawId)) {
$errors[] = 'Please select an auto rickshaw.';
}
if (empty($errors)) {
try {
// Get fare details
$fareDetails = $db->fetch(
"SELECT * FROM fare_details WHERE to_location_id = ? AND status = 'active' LIMIT 1",
[$toLocationId]
);
if (!$fareDetails) {
$errors[] = 'Fare information not available for this destination.';
} else {
$baseFare = $fareDetails['fix_rate'] ?? 50.00; // Default fare
$commissionAmount = calculateCommission($baseFare); // Configurable commission
$totalAmount = $baseFare; // Total equals base fare (commission not added to passenger)
// Create a temporary passenger record for direct booking
$db->query(
"INSERT INTO passengers (name, phone, email, status) VALUES (?, ?, ?, 'active')",
['Direct Passenger', 'N/A', 'direct_' . time() . '@direct.booking']
);
$passengerId = $db->lastInsertId();
// Create booking record
$db->query(
"INSERT INTO bookings (passenger_id, from_location_id, to_location_id, auto_rickshaw_id, operator_id,
booking_type, payment_status, payment_method, commission_amount, fare_amount,
total_amount, status, confirmation_time)
VALUES (?, ?, ?, ?, ?, 'direct', 'completed', 'offline', ?, ?, ?, 'in_progress', NOW())",
[$passengerId, $currentSession['from_location_id'], $toLocationId, $rickshawId, $user['id'],
$commissionAmount, $baseFare, $totalAmount]
);
$bookingId = $db->lastInsertId();
// Create payment record
$db->query(
"INSERT INTO payments (booking_id, amount, payment_method, transaction_id, payment_status, payment_time)
VALUES (?, ?, 'offline', ?, 'completed', NOW())",
[$bookingId, $totalAmount, 'DIRECT_' . time()]
);
// Update operator session stats
$db->query(
"UPDATE operator_sessions SET total_bookings = total_bookings + 1, total_earnings = total_earnings + ? WHERE id = ?",
[$commissionAmount, $currentSession['id']]
);
// Get the complete booking
$booking = $db->fetch(
"SELECT b.*, p.name as passenger_name, p.phone as passenger_phone,
tl.name as to_location_name, ar.number_plate, ar.unique_local_id,
ao.name as owner_name
FROM bookings b
JOIN passengers p ON b.passenger_id = p.id
JOIN to_locations tl ON b.to_location_id = tl.id
JOIN auto_rickshaws ar ON b.auto_rickshaw_id = ar.id
JOIN auto_owners ao ON ar.owner_id = ao.id
WHERE b.id = ?",
[$bookingId]
);
$success = 'Direct booking completed successfully!';
}
} catch (Exception $e) {
$errors[] = 'Error creating booking: ' . $e->getMessage();
}
}
}
}
// Get available auto rickshaws
$availableRickshaws = $db->fetchAll(
"SELECT ar.*, ao.name as owner_name
FROM auto_rickshaws ar
JOIN auto_owners ao ON ar.owner_id = ao.id
WHERE ar.status = 'active'
ORDER BY ar.number_plate"
);
$pageTitle = 'Direct Booking';
require_once '../includes/header.php'; ?>
<!-- Add Select2 CSS and JS for searchable selects -->
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
<link href="https://cdn.jsdelivr.net/npm/select2-bootstrap-5-theme@1.3.0/dist/select2-bootstrap-5-theme.min.css" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
<!-- Add HTML5 QR Code Scanner -->
<script src="https://unpkg.com/html5-qrcode@2.3.8/html5-qrcode.min.js"></script>
<style>
/* Custom styling for Select2 integration */
.select2-container--bootstrap-5 .select2-selection {
border: 1px solid #dee2e6;
border-radius: 0.375rem;
min-height: 38px;
}
.select2-container--bootstrap-5 .select2-selection--single {
padding: 0.375rem 0.75rem;
}
.select2-container--bootstrap-5 .select2-selection__rendered {
padding: 0;
line-height: 1.5;
}
.select2-container--bootstrap-5 .select2-selection__placeholder {
color: #6c757d;
}
.select2-container--bootstrap-5 .select2-dropdown {
border: 1px solid #dee2e6;
border-radius: 0.375rem;
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
}
.select2-container--bootstrap-5 .select2-results__option--highlighted[aria-selected] {
background-color: #0d6efd;
color: white;
}
.select2-container--bootstrap-5 .select2-search__field {
border: 1px solid #dee2e6;
border-radius: 0.25rem;
padding: 0.375rem 0.75rem;
}
.select2-container--bootstrap-5 .select2-search__field:focus {
border-color: #86b7fe;
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
outline: 0;
}
/* QR Scanner styling */
#qr-reader {
border: 2px solid #dee2e6;
border-radius: 0.5rem;
overflow: hidden;
}
#qr-reader video {
border-radius: 0.5rem;
}
#qr-reader-results {
min-height: 60px;
}
/* Custom QR Scanner Overlay */
.qr-scanner-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
opacity: 1;
visibility: visible;
transition: none;
pointer-events: auto;
}
.qr-scanner-overlay[style*="display: none"] {
opacity: 0;
visibility: hidden;
pointer-events: none;
}
.qr-scanner-content {
background: white;
border-radius: 0.5rem;
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
max-width: 600px;
width: 100%;
max-height: 90vh;
overflow: hidden;
display: flex;
flex-direction: column;
transform: none;
transition: none;
border: 1px solid #dee2e6;
}
.qr-scanner-overlay[style*="display: none"] .qr-scanner-content {
transform: none;
}
.qr-scanner-header {
padding: 1rem 1.5rem;
border-bottom: 1px solid #dee2e6;
display: flex;
justify-content: space-between;
align-items: center;
background-color: #f8f9fa;
margin: 0;
}
.qr-scanner-title {
margin: 0;
font-size: 1.1rem;
font-weight: 600;
color: #495057;
}
.qr-scanner-close {
background: none;
border: none;
font-size: 1.25rem;
color: #6c757d;
cursor: pointer;
padding: 0.25rem;
border-radius: 0.25rem;
transition: none;
}
.qr-scanner-close:hover {
color: #495057;
background-color: #e9ecef;
}
.qr-scanner-body {
padding: 2rem;
text-align: center;
flex: 1;
overflow-y: auto;
margin: 0;
}
.qr-scanner-footer {
padding: 1rem 1.5rem;
border-top: 1px solid #dee2e6;
background-color: #f8f9fa;
text-align: right;
margin: 0;
}
/* Disable any Bootstrap card effects */
.qr-scanner-overlay *,
.qr-scanner-overlay *:hover,
.qr-scanner-overlay *:focus,
.qr-scanner-overlay *:active {
transition: none !important;
animation: none !important;
transform: none !important;
box-shadow: none !important;
}
.qr-scanner-content:hover,
.qr-scanner-header:hover,
.qr-scanner-body:hover,
.qr-scanner-footer:hover {
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important;
transform: none !important;
}
/* Modal styling */
#qrScannerModal .modal-dialog {
max-width: 600px;
}
#qrScannerModal .modal-body {
padding: 2rem;
}
/* Scan button styling */
#scanQRBtn {
border-left: none;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
#rickshaw_search {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
/* Auto rickshaw selection styling */
#rickshaw_search.is-valid {
border-color: #198754;
box-shadow: 0 0 0 0.25rem rgba(25, 135, 84, 0.25);
}
#rickshaw_search.is-invalid {
border-color: #dc3545;
box-shadow: 0 0 0 0.25rem rgba(220, 53, 69, 0.25);
}
/* Custom form styling to replace Bootstrap cards */
.booking-form-container {
background: white;
border: 1px solid #dee2e6;
border-radius: 0.5rem;
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
overflow: hidden;
}
.booking-form-header {
padding: 1rem 1.25rem;
background-color: #f8f9fa;
border-bottom: 1px solid #dee2e6;
}
.booking-form-title {
color: #495057;
font-weight: 600;
}
.booking-form-subtitle {
margin: 0;
}
.booking-form-body {
padding: 1.25rem;
}
.fare-preview-container {
background: white;
border: 1px solid #dee2e6;
border-radius: 0.5rem;
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
}
.fare-preview-body {
padding: 1.25rem;
}
.fare-preview-title {
color: #495057;
font-weight: 600;
margin-bottom: 1rem;
}
.confirmation-details-container {
background: white;
border: 1px solid #dee2e6;
border-radius: 0.5rem;
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
overflow: hidden;
}
.confirmation-details-header {
padding: 0.75rem 1.25rem;
background-color: #f8f9fa;
border-bottom: 1px solid #dee2e6;
}
.confirmation-details-title {
color: #495057;
font-weight: 600;
}
.confirmation-details-body {
padding: 1.25rem;
}
/* Disable all hover effects */
.booking-form-container:hover,
.booking-form-header:hover,
.booking-form-body:hover,
.fare-preview-container:hover,
.fare-preview-body:hover,
.confirmation-details-container:hover,
.confirmation-details-header:hover,
.confirmation-details-body:hover {
transform: none !important;
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075) !important;
transition: none !important;
}
/* Print-specific styles */
@media print {
body * {
visibility: hidden;
}
#printableReceipt,
#printableReceipt * {
visibility: visible;
}
#printableReceipt {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
margin: 0;
padding: 20px;
}
.receipt-container {
max-width: none !important;
margin: 0 !important;
padding: 0 !important;
}
}
/* Receipt styling */
.receipt-container {
background: white;
border: 1px solid #ddd;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.receipt-container h3 {
color: #333;
font-weight: bold;
}
.receipt-container strong {
color: #333;
}
/* Cancel button styling */
#cancelBookingBtn {
transition: all 0.2s ease-in-out;
}
#cancelBookingBtn:hover {
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(220, 53, 69, 0.3);
}
#cancelBookingBtn:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
</style>
<div class="container py-4">
<div class="row">
<div class="col-md-8 mx-auto">
<div class="booking-form-container">
<div class="booking-form-header">
<h3 class="booking-form-title mb-0">
<i class="bi bi-plus-circle"></i> Direct Booking
</h3>
<p class="booking-form-subtitle text-muted mb-0">For passengers without QR codes</p>
</div>
<div class="booking-form-body">
<?php if (!empty($errors)): ?>
<div class="alert alert-danger">
<ul class="mb-0">
<?php foreach ($errors as $error): ?>
<li><?php echo htmlspecialchars($error); ?></li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
<?php if ($success): ?>
<div class="alert alert-success">
<?php echo htmlspecialchars($success); ?>
</div>
<?php endif; ?>
<?php if (!$booking): ?>
<form method="POST" action="">
<input type="hidden" name="csrf_token" value="<?php echo generateCSRFToken(); ?>">
<div class="mb-3">
<label for="to_location_id" class="form-label">Destination</label>
<select class="form-select searchable-select" id="to_location_id" name="to_location_id" required>
<option value="">Search and select destination...</option>
<?php foreach ($destinations as $destination): ?>
<option value="<?php echo $destination['id']; ?>"
<?php echo (isset($_POST['to_location_id']) && $_POST['to_location_id'] == $destination['id']) ? 'selected' : ''; ?>>
<?php echo htmlspecialchars($destination['name']); ?> -
<?php echo htmlspecialchars($destination['city']); ?>
</option>
<?php endforeach; ?>
</select>
<div class="form-text">Type to search destinations by name or city</div>
</div>
<div class="mb-3">
<label for="rickshaw_id" class="form-label">Auto Rickshaw</label>
<div class="input-group">
<input type="text" class="form-control" id="rickshaw_search" placeholder="Search by number plate or scan QR code..." readonly>
<input type="hidden" id="rickshaw_id" name="rickshaw_id" required>
<button type="button" class="btn btn-outline-primary" id="scanQRBtn">
<i class="bi bi-qr-code-scan"></i> Scan QR
</button>
</div>
<div class="form-text">Scan the auto rickshaw's QR code or search by number plate</div>
<!-- QR Scanner Modal -->
<div id="qrScannerOverlay" class="qr-scanner-overlay" style="display: none;">
<div class="qr-scanner-content">
<div class="qr-scanner-header">
<h5 class="qr-scanner-title">
<i class="bi bi-qr-code-scan"></i> Scan Auto Rickshaw QR Code
</h5>
<button type="button" class="qr-scanner-close" id="closeQRScanner">
<i class="bi bi-x-lg"></i>
</button>
</div>
<div class="qr-scanner-body">
<div id="qr-reader" style="width: 100%; max-width: 500px; margin: 0 auto;"></div>
<div id="qr-reader-results" class="mt-3"></div>
</div>
<div class="qr-scanner-footer">
<button type="button" class="btn btn-secondary" id="cancelQRScanner">Cancel</button>
</div>
</div>
</div>
</div>
<!-- Fare Preview -->
<div class="fare-preview-container mb-3">
<div class="fare-preview-body">
<h6 class="fare-preview-title">Fare Preview</h6>
<div class="row">
<div class="col-md-4">
<p class="mb-1"><strong>Base Fare:</strong></p>
<p class="text-muted">₹<span id="baseFare">--</span></p>
</div>
<div class="col-md-4">
<p class="mb-1"><strong>Commission:</strong></p>
<p class="text-muted">₹<span id="commission">--</span></p>
</div>
<div class="col-md-4">
<p class="mb-1"><strong>Total:</strong></p>
<p class="text-primary fw-bold">₹<span id="totalFare">--</span></p>
</div>
</div>
</div>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-success btn-lg">
<i class="bi bi-check-circle"></i> Complete Direct Booking
</button>
</div>
</form>
<?php else: ?>
<!-- Booking Confirmation -->
<div class="text-center mb-4">
<i class="bi bi-check-circle-fill text-success" style="font-size: 4rem;"></i>
<h4 class="mt-3">Booking Completed!</h4>
<p class="text-muted">Direct booking has been processed successfully.</p>
</div>
<div class="row">
<div class="col-md-6">
<div class="confirmation-details-container">
<div class="confirmation-details-header">
<h6 class="confirmation-details-title mb-0">Journey Details</h6>
</div>
<div class="confirmation-details-body">
<p><strong>Destination:</strong> <?php echo htmlspecialchars($booking['to_location_name']); ?></p>
<p><strong>Auto Rickshaw:</strong> <?php echo htmlspecialchars($booking['number_plate']); ?></p>
<p><strong>Booking Type:</strong> Direct Booking</p>
</div>
</div>
</div>
<div class="col-md-6">
<div class="confirmation-details-container">
<div class="confirmation-details-header">
<h6 class="confirmation-details-title mb-0">Payment Details</h6>
</div>
<div class="confirmation-details-body">
<p><strong>Fare Amount:</strong> ₹<?php echo number_format($booking['fare_amount'], 2); ?></p>
<p><strong>Commission:</strong> ₹<?php echo number_format($booking['commission_amount'], 2); ?></p>
<p><strong>Total:</strong> ₹<?php echo number_format($booking['total_amount'], 2); ?></p>
</div>
</div>
</div>
</div>
<div class="alert alert-info mt-4">
<h6><i class="bi bi-info-circle"></i> Instructions:</h6>
<ol class="mb-0">
<li>Collect ₹<?php echo number_format($booking['total_amount'], 2); ?> from the passenger</li>
<li>Passenger pays ₹<?php echo number_format($booking['fare_amount'], 2); ?> (this is the total amount)</li>
<li>You keep ₹<?php echo number_format($booking['commission_amount'], 2); ?> as commission from the fare</li>
</ol>
</div>
<!-- Print Receipt Section -->
<div class="d-print-none mb-4">
<button type="button" class="btn btn-primary" id="printReceiptBtn" onclick="printReceipt()">
<i class="bi bi-printer"></i> Print Receipt
</button>
<!-- Cancel Booking Button - Only show when status is in_progress -->
<?php if ($booking['status'] === 'in_progress'): ?>
<button type="button" class="btn btn-danger ms-2" id="cancelBookingBtn" onclick="cancelBooking()">
<i class="bi bi-x-circle"></i> Cancel Booking
</button>
<?php endif; ?>
</div>
<!-- Printable Receipt -->
<div id="printableReceipt" class="d-none d-print-block">
<div class="receipt-container" style="max-width: 300px; margin: 0 auto; padding: 8px; font-family: 'Courier New', monospace; font-size: 10px; line-height: 1.2;">
<div style="text-align: center; border-bottom: 1px solid #000; padding-bottom: 5px; margin-bottom: 8px;">
<h3 style="margin: 0 0 2px 0; font-size: 12px; font-weight: bold;">AUTO RICKSHAW SERVICE</h3>
<p style="margin: 1px 0; font-size: 9px;">Direct Booking Receipt</p>
<p style="margin: 1px 0; font-size: 9px;">Date: <?php echo date('d/m/Y H:i:s'); ?></p>
</div>
<div style="margin-bottom: 8px;">
<div style="border-bottom: 1px solid #ccc; padding: 3px 0; font-size: 9px;">
<strong>Passenger:</strong> Direct Passenger
</div>
<div style="border-bottom: 1px solid #ccc; padding: 3px 0; font-size: 9px;">
<strong>From:</strong> <?php echo htmlspecialchars($currentSession['from_location_name'] ?? 'Operator Location'); ?>
</div>
<div style="border-bottom: 1px solid #ccc; padding: 3px 0; font-size: 9px;">
<strong>To:</strong> <?php echo htmlspecialchars($booking['to_location_name']); ?>
</div>
<div style="border-bottom: 1px solid #ccc; padding: 3px 0; font-size: 9px;">
<strong>Rickshaw:</strong> <?php echo htmlspecialchars($booking['number_plate']); ?>
</div>
<div style="border-bottom: 1px solid #ccc; padding: 3px 0; font-size: 9px;">
<strong>Driver:</strong> <?php echo htmlspecialchars($booking['owner_name'] ?? 'N/A'); ?>
</div>
</div>
<div style="border-top: 1px solid #000; padding-top: 5px; margin-top: 8px;">
<div style="display: flex; justify-content: space-between; margin-bottom: 3px; font-size: 9px;">
<span>Commission:</span>
<span>₹<?php echo number_format($booking['commission_amount'], 2); ?></span>
</div>
<div style="display: flex; justify-content: space-between; margin-bottom: 3px; font-size: 9px;">
<span>Fare:</span>
<span>₹<?php echo number_format($booking['fare_amount'], 2); ?></span>
</div>
<div style="display: flex; justify-content: space-between; font-weight: bold; font-size: 16px; margin-top: 8px; padding-top: 8px; border-top: 2px solid #000;">
<span>Total:</span>
<span>₹<?php echo number_format($booking['fare_amount'], 2); ?></span>
</div>
</div>
<div style="text-align: center; margin-top: 8px; padding-top: 5px; border-top: 1px solid #ccc; font-size: 8px;">
<p style="margin: 1px 0;">Thank you for using our service!</p>
<p style="margin: 1px 0;">Status: In Progress</p>
</div>
</div>
</div>
<div class="mt-4">
<a href="direct_booking.php" class="btn btn-primary">
<i class="bi bi-plus-circle"></i> New Direct Booking
</a>
<a href="index.php" class="btn btn-outline-secondary">
<i class="bi bi-arrow-left"></i> Back to Dashboard
</a>
</div>
<?php endif; ?>
</div>
</div>
</div>
</div>
</div>
<script>
// Initialize Select2 for searchable destination selection
$(document).ready(function() {
$('.searchable-select').select2({
theme: 'bootstrap-5',
placeholder: 'Search and select destination...',
allowClear: true,
width: '100%',
language: {
noResults: function() {
return "No destinations found. Try a different search term.";
},
searching: function() {
return "Searching...";
}
}
});
// Update fare preview when destination changes
$('#to_location_id').on('change', function() {
const destinationId = this.value;
if (destinationId) {
// In a real application, you would fetch fare details via AJAX
// For now, we'll use a simple calculation
const baseFare = 50; // Default fare
// Note: Commission is calculated but not added to passenger total
// Commission is kept by operator from the base fare
const commission = baseFare * 0.1; // Placeholder for display
const total = baseFare; // Total equals base fare
document.getElementById('baseFare').textContent = baseFare.toFixed(2);
document.getElementById('commission').textContent = commission.toFixed(2);
document.getElementById('totalFare').textContent = total.toFixed(2);
} else {
document.getElementById('baseFare').textContent = '--';
document.getElementById('commission').textContent = '--';
document.getElementById('totalFare').textContent = '--';
}
});
// QR Scanner functionality
let html5QrcodeScanner = null;
let isScannerInitialized = false;
// Handle QR scan button click - simplified
$('#scanQRBtn').on('click', function() {
$('#qrScannerOverlay').show(); // Show the overlay
initializeQRScanner();
});
// Handle close button click
$('#closeQRScanner').on('click', function() {
closeQRScanner();
});
// Handle cancel button click
$('#cancelQRScanner').on('click', function() {
closeQRScanner();
});
// Close scanner when clicking outside the content
$('#qrScannerOverlay').on('click', function(e) {
if (e.target === this) {
closeQRScanner();
}
});
function closeQRScanner() {
$('#qrScannerOverlay').hide();
if (html5QrcodeScanner && isScannerInitialized) {
try {
html5QrcodeScanner.stop();
html5QrcodeScanner.clear();
} catch (e) {
// Scanner cleanup error handled silently
}
html5QrcodeScanner = null;
isScannerInitialized = false;
}
$('#qr-reader-results').empty();
}
function initializeQRScanner() {
if (html5QrcodeScanner) {
try {
html5QrcodeScanner.clear();
} catch (e) {
// Scanner clear error handled silently
}
}
try {
html5QrcodeScanner = new Html5Qrcode("qr-reader");
const config = {
fps: 10,
qrbox: { width: 250, height: 250 },
aspectRatio: 1.0
};
html5QrcodeScanner.start(
{ facingMode: "environment" },
config,
onScanSuccess,
onScanFailure
).then(() => {
isScannerInitialized = true;
}).catch(function (err) {
console.error('Scanner start error:', err);
$('#qr-reader-results').html(`
<div class="alert alert-danger">
<i class="bi bi-exclamation-triangle"></i> Camera access error: ${err.message}
</div>
`);
});
} catch (error) {
$('#qr-reader-results').html(`
<div class="alert alert-danger">
<i class="bi bi-exclamation-triangle"></i> Failed to initialize scanner
</div>
`);
}
}
function onScanSuccess(decodedText, decodedResult) {
// Stop scanning
html5QrcodeScanner.stop();
// Process the scanned QR code
processScannedQR(decodedText);
// Close modal
$('#qrScannerOverlay').hide();
// Show success message
$('#qr-reader-results').html(`
<div class="alert alert-success">
<i class="bi bi-check-circle"></i> QR Code scanned successfully!
</div>
`);
}
function onScanFailure(error) {
// Handle scan failure silently
}
function processScannedQR(qrData) {
try {
// Try to parse JSON data first
let rickshawData = null;
try {
rickshawData = JSON.parse(qrData);
} catch (e) {
// If not JSON, treat as plain text
rickshawData = { id: qrData, number_plate: qrData };
}
// Find the rickshaw in our available list
const availableRickshaws = <?php echo json_encode($availableRickshaws); ?>;
let selectedRickshaw = null;
if (rickshawData.id) {
selectedRickshaw = availableRickshaws.find(r => r.id == rickshawData.id);
}
if (!selectedRickshaw && rickshawData.number_plate) {
selectedRickshaw = availableRickshaws.find(r =>
r.number_plate.toLowerCase().includes(rickshawData.number_plate.toLowerCase())
);
}
if (selectedRickshaw) {
// Update the form
$('#rickshaw_id').val(selectedRickshaw.id);
$('#rickshaw_search').val(`${selectedRickshaw.number_plate} - ${selectedRickshaw.owner_name}`);
// Show success feedback
$('#rickshaw_search').addClass('is-valid');
setTimeout(() => {
$('#rickshaw_search').removeClass('is-valid');
}, 2000);
} else {
// Show error - rickshaw not found
$('#rickshaw_search').addClass('is-invalid');
$('#rickshaw_search').val('Rickshaw not found in system');
setTimeout(() => {
$('#rickshaw_search').removeClass('is-invalid');
$('#rickshaw_search').val('');
}, 3000);
}
} catch (error) {
console.error('Error processing QR data:', error);
$('#rickshaw_search').addClass('is-invalid');
$('#rickshaw_search').val('Invalid QR code format');
setTimeout(() => {
$('#rickshaw_search').removeClass('is-invalid');
$('#rickshaw_search').val('');
}, 3000);
}
}
// Print receipt functionality
window.printReceipt = function() {
// Show the printable receipt
$('#printableReceipt').removeClass('d-none');
// Hide non-printable elements
$('.d-print-none').hide();
// Print the page
window.print();
// After printing, update booking status to completed
setTimeout(() => {
// Restore the display
$('#printableReceipt').addClass('d-none');
$('.d-print-none').show();
// Update booking status to completed via AJAX
updateBookingStatus();
}, 1000);
};
// Function to update booking status to completed
function updateBookingStatus() {
const bookingId = <?php echo $booking['id'] ?? 'null'; ?>;
if (bookingId) {
$.ajax({
url: 'update_booking_status.php',
type: 'POST',
data: {
booking_id: bookingId,
status: 'completed',
csrf_token: '<?php echo generateCSRFToken(); ?>'
},
success: function(response) {
try {
const result = JSON.parse(response);
if (result.success) {
// Update the display to show completed status
updateDisplayToCompleted();
} else {
console.error('Failed to update booking status:', result.message);
}
} catch (e) {
console.error('Error parsing response:', e);
}
},
error: function(xhr, status, error) {
console.error('AJAX error:', error);
}
});
}
}
// Function to update display to show completed status
function updateDisplayToCompleted() {
// Update the success alert
$('.alert-success h6').html('<i class="bi bi-check-circle"></i> Booking Completed and Receipt Printed!');
$('.alert-success p').text('The direct booking has been marked as completed and receipt has been printed.');
// Update the print button to show completed
$('#printReceiptBtn').html('<i class="bi bi-check-circle"></i> Receipt Printed');
$('#printReceiptBtn').removeClass('btn-primary').addClass('btn-success');
$('#printReceiptBtn').prop('disabled', true);
// Hide the cancel booking button since status is now completed
$('#cancelBookingBtn').hide();
// Update the receipt status
$('#printableReceipt .receipt-container .footer p:last-child').text('Status: Completed');
// Add a completion timestamp
if (!$('.completion-timestamp').length) {
$('.alert-success').append('<p class="completion-timestamp mb-0 mt-2"><small>Completed at: ' + new Date().toLocaleString() + '</small></p>');
}
}
// Function to cancel booking
window.cancelBooking = function() {
if (confirm('Are you sure you want to cancel this booking? This action cannot be undone.')) {
const bookingId = <?php echo $booking['id'] ?? 'null'; ?>;
if (bookingId) {
// Show loading state
$('#cancelBookingBtn').html('<i class="bi bi-hourglass-split"></i> Cancelling...');
$('#cancelBookingBtn').prop('disabled', true);
$.ajax({
url: 'update_booking_status.php',
type: 'POST',
data: {
booking_id: bookingId,
status: 'cancelled',
csrf_token: '<?php echo generateCSRFToken(); ?>'
},
success: function(response) {
try {
const result = JSON.parse(response);
if (result.success) {
// Update the display to show cancelled status
updateDisplayToCancelled();
} else {
alert('Failed to cancel booking: ' + result.message);
// Reset button state
$('#cancelBookingBtn').html('<i class="bi bi-x-circle"></i> Cancel Booking');
$('#cancelBookingBtn').prop('disabled', false);
}
} catch (e) {
alert('Error processing response. Please try again.');
// Reset button state
$('#cancelBookingBtn').html('<i class="bi bi-x-circle"></i> Cancel Booking');
$('#cancelBookingBtn').prop('disabled', false);
}
},
error: function(xhr, status, error) {
let errorMessage = 'Network error. Please try again.';
if (xhr.responseText) {
try {
const response = JSON.parse(xhr.responseText);
if (response.message) {
errorMessage = response.message;
}
} catch (e) {
// If response is not JSON, use the raw text
if (xhr.responseText.length < 100) {
errorMessage = 'Server error: ' + xhr.responseText;
}
}
}
alert(errorMessage);
// Reset button state
$('#cancelBookingBtn').html('<i class="bi bi-x-circle"></i> Cancel Booking');
$('#cancelBookingBtn').prop('disabled', false);
}
});
}
}
};
// Function to update display to show cancelled status
function updateDisplayToCancelled() {
// Update the success alert to show cancelled status
$('.alert-success').removeClass('alert-success').addClass('alert-warning');
$('.alert-success h6').html('<i class="bi bi-exclamation-triangle"></i> Booking Cancelled');
$('.alert-success p').text('The direct booking has been cancelled successfully.');
// Update the print button to show cancelled
$('#printReceiptBtn').html('<i class="bi bi-x-circle"></i> Receipt Cancelled');
$('#printReceiptBtn').removeClass('btn-primary btn-success').addClass('btn-secondary');
$('#printReceiptBtn').prop('disabled', true);
// Hide the cancel booking button
$('#cancelBookingBtn').hide();
// Update the receipt status
$('#printableReceipt .receipt-container .footer p:last-child').text('Status: Cancelled');
// Add a cancellation timestamp
if (!$('.cancellation-timestamp').length) {
$('.alert-warning').append('<p class="cancellation-timestamp mb-0 mt-2"><small>Cancelled at: ' + new Date().toLocaleString() + '</small></p>');
}
// Update the instructions
$('.alert-info').removeClass('alert-info').addClass('alert-warning');
$('.alert-info h6').html('<i class="bi bi-info-circle"></i> Booking Cancelled');
$('.alert-info ol').html('<li>This booking has been cancelled</li><li>No payment is required</li><li>No commission will be earned</li>');
}
});
</script>
<?php require_once '../includes/footer.php'; ?>