You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

751 lines
38 KiB

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{fragments/head-cro :: head}"></head>
<style>
.card-body table {
width: 100%;
padding: 100px;
}
.card-body th,
td {
padding: 8px;
text-align: left;
}
.amount-cell {
text-align: right;
padding-right: 1rem;
}
.form-control[readonly] {
background-color: white;
}
.modal {
overflow-y: auto;
}
/* OTP inputs */
.otp-input-box {
display: flex;
justify-content: center;
gap: 10px;
}
.cro-otp-input {
width: 48px;
height: 52px;
text-align: center;
font-size: 22px;
font-weight: bold;
border: 2px solid #ced4da;
border-radius: 8px;
transition: all 0.2s ease-in-out;
}
.cro-otp-input:focus {
border-color: #0d6efd;
outline: none;
box-shadow: 0 0 0 4px rgba(13, 110, 253, .2);
}
#bookingSearchSection {
border: 1px solid #dee2e6;
border-radius: 8px;
padding: 20px;
background: #f8f9fa;
margin-bottom: 20px;
}
.detail-section th {
background-color: #f2f2f2;
font-weight: 600;
}
.detail-section td,
.detail-section th {
padding: 0.45rem 0.75rem;
border: 1px solid #dee2e6;
}
.detail-section table {
width: 100%;
border-collapse: collapse;
margin-bottom: 10px;
}
#bookingDetailResult {
display: none;
}
.status-change-card {
border: 2px solid #e2e8f0;
border-radius: 10px;
background: #ffffff;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
padding: 24px;
margin-top: 25px;
}
.status-badge-lg {
font-size: 0.95rem;
padding: 0.35rem 0.75rem;
border-radius: 6px;
font-weight: 600;
}
</style>
<body>
<!-- Page Container -->
<div id="page-container"
class="sidebar-o sidebar-dark enable-page-overlay side-scroll page-header-fixed page-header-dark">
<!-- Sidebar Nav-->
<aside th:replace="~{fragments/aside-right :: aside}"></aside>
<!-- END Side Overlay (right)-->
<!-- Sidebar Nav-->
<nav th:replace="~{fragments/nav-cro :: nav}"></nav>
<!-- END Sidebar -->
<!-- Header -->
<header th:replace="~{fragments/header-cro :: header}"></header>
<!-- Main Container -->
<main id="main-container">
<div class="content">
<!-- ═══ Search Booking by ID ═══ -->
<div class="block block-rounded">
<div class="block-header block-header-default">
<h3 class="block-title"><i class="fa fa-search me-2 text-primary"></i>Change Booking Status</h3>
</div>
<div class="block-content p-4">
<div id="bookingSearchSection">
<div class="row align-items-end">
<div class="col-md-4 mb-2">
<label class="form-label fw-semibold" for="searchBookingId">Booking ID <span
class="text-danger">*</span></label>
<input type="number" class="form-control" id="searchBookingId"
placeholder="Enter Booking ID" min="1" />
</div>
<div class="col-md-3 mb-2">
<button type="button" class="btn btn-primary" id="btnSearchBooking"
onclick="searchBookingById()">
<i class="fa fa-search me-1"></i> Search
</button>
<button type="button" class="btn btn-secondary ms-2" id="btnClearBookingSearch"
onclick="clearBookingSearch()">
<i class="fa fa-times me-1"></i> Clear
</button>
</div>
</div>
</div>
<!-- Booking Detail Result -->
<div id="bookingDetailResult">
<hr />
<div class="d-flex justify-content-between align-items-center mb-3">
<h5 class="mb-0">Booking Details <span class="text-primary" id="resultBookingId"></span>
</h5>
<span id="res_bookingStatusBadge" class="badge status-badge-lg bg-info"></span>
</div>
<div class="detail-section">
<!-- User Details -->
<table>
<tr>
<th colspan="6">User Details</th>
</tr>
<tr>
<th style="font-weight:normal;">User Name:</th>
<td id="res_userName"></td>
<th style="font-weight:normal;">Mobile Number:</th>
<td id="res_mobileNumber"></td>
<th style="font-weight:normal;">User Type:</th>
<td id="res_usertype"></td>
</tr>
<tr>
<th style="font-weight:normal;">User Address:</th>
<td colspan="5" id="res_useraddress"></td>
</tr>
</table>
<!-- Hall Details -->
<table>
<tr>
<th colspan="6">Hall Details</th>
</tr>
<tr>
<th style="font-weight:normal;">Hall Name:</th>
<td colspan="5" id="res_hallName"></td>
</tr>
<tr>
<th style="font-weight:normal;">Hall Address:</th>
<td colspan="5" id="res_halladdress"></td>
</tr>
<tr>
<th style="font-weight:normal;">Caretaker Name:</th>
<td id="res_caretakername"></td>
<th style="font-weight:normal;">Caretaker Mobile:</th>
<td colspan="3" id="res_caretakermobileNumber"></td>
</tr>
</table>
<!-- Booking Details -->
<table>
<tr>
<th colspan="4">Booking Details (Booking Id: <span id="res_booking_id"
style="color:blue;"></span>)</th>
</tr>
<tr>
<th style="font-weight:normal;">Booking Date:</th>
<td id="res_bookingDate"></td>
<th style="font-weight:normal;">Booking Status:</th>
<td id="res_bookingStatus" class="fw-bold"></td>
</tr>
<tr>
<th style="font-weight:normal;">Start Date:</th>
<td id="res_startDate"></td>
<th style="font-weight:normal;">Deposit Amount:</th>
<td class="amount-cell" id="res_depositeAmount"></td>
</tr>
<tr>
<th style="font-weight:normal;">End Date:</th>
<td id="res_endDate"></td>
<th style="font-weight:normal;">Hall Amount:</th>
<td class="amount-cell" id="res_hallAmount"></td>
</tr>
<tr>
<th style="font-weight:normal;">Starting Slot:</th>
<td id="res_startingSlot"></td>
<th style="font-weight:normal;">GST @18%:</th>
<td class="amount-cell" id="res_gst"></td>
</tr>
<tr>
<th style="font-weight:normal;">Ending Slot:</th>
<td id="res_endingSlot"></td>
<th style="font-weight:normal;">Total Amount:</th>
<td class="amount-cell" id="res_totalAmount"></td>
</tr>
<tr>
<th style="font-weight:normal;">Reason:</th>
<td colspan="3" id="res_reason"></td>
</tr>
</table>
<!-- Payment Details -->
<table>
<tr>
<th colspan="4">Booking Payment Details</th>
</tr>
<tr>
<th style="font-weight:normal;">Transaction ID:</th>
<td colspan="3" id="res_transactionId"></td>
</tr>
<tr>
<th style="font-weight:normal;">Amount Received:</th>
<td id="res_payamountReceived"></td>
<th style="font-weight:normal;">Transaction Status:</th>
<td id="res_paymentStatus"></td>
</tr>
</table>
</div>
<!-- ═══ Change Booking Status Section (CRO) ═══ -->
<div id="statusChangeSection" class="status-change-card" style="display: none;">
<div class="d-flex align-items-center mb-3">
<div class="bg-primary-light text-primary p-2 rounded-3 me-3"
style="background: rgba(13,110,253,0.1);">
<i class="fa fa-exchange-alt fa-lg text-primary"></i>
</div>
<div>
<h5 class="mb-0 fw-bold text-dark">Change Booking Status</h5>
<small class="text-muted">CRO can update booking status to REJECTED or
GCC_BLOCKED with OTP verification</small>
</div>
</div>
<div class="alert alert-warning py-2 mb-3">
<i class="fa fa-info-circle me-1"></i>
Status changes require OTP verification sent to your registered CRO mobile number.
</div>
<div class="row g-3">
<div class="col-md-5">
<label class="form-label fw-semibold" for="changeBookingStatusSelect">
Select New Booking Status <span class="text-danger">*</span>
</label>
<select class="form-select" id="changeBookingStatusSelect">
<option value="">-- Select New Status --</option>
<option value="REJECTED">REJECTED</option>
<option value="GCC_BLOCKED">GCC_BLOCKED</option>
</select>
</div>
<div class="col-md-7">
<label class="form-label fw-semibold" for="statusChangeRemarks">
Reason / Remarks <span class="text-danger">*</span>
</label>
<textarea class="form-control" id="statusChangeRemarks" rows="2"
placeholder="Enter reason or comments for changing status (required)..."></textarea>
</div>
<div class="col-12 text-end mt-3">
<button type="button" class="btn btn-danger px-4" id="btnInitiateStatusChange"
onclick="initiateStatusChange()">
<i class="fa fa-paper-plane me-1"></i> Submit Status Change
</button>
</div>
</div>
</div>
<!-- ═══ Not Allowed Info Box ═══ -->
<div id="statusChangeNotAllowedMsg" style="display: none; margin-top: 20px;">
<div class="alert alert-secondary mb-0">
<i class="fa fa-info-circle me-1"></i>
Status change is <strong>not permitted</strong> for this booking.
<span id="notAllowedDetailMsg">Status change is only permitted for <strong>Past
events</strong> with status <strong>APPROVED</strong>,
<strong>WAITING_FOR_NOC</strong>, or
<strong>WAITING_FOR_NOC_APPROVAL</strong>.</span>
<br />
<span class="small mt-1 d-inline-block">
Current status: <span class="badge bg-dark"
id="notAllowedCurrentStatusBadge"></span>
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
<!-- END Main Container -->
<!-- ════ CRO OTP MODAL (MATCHES cro/booking-list.html) ════ -->
<div class="modal fade" id="croStatusOtpModal" tabindex="-1" role="dialog"
aria-labelledby="croStatusOtpModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h6 class="modal-title m-0" id="croStatusOtpModalLabel">OTP Verification</h6>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="row">
<div class="mb-3 text-center">
<img src="media/images/mobileotp.png" width="50%" alt="OTP">
</div>
<div class="mb-0 text-center">
<h4>OTP Verification</h4>
<p class="text-mute">Enter the OTP sent to <b class="text-info"
id="croOtpMobileDisplay"></b></p>
<p class="text-danger" id="croOtpErrorAlert"></p>
</div>
<div class="mb-3 text-center">
<div class="otp-input-box">
<input type="text" maxlength="1" class="otp-input cro-otp-input" id="croOtp1"
required />
<input type="text" maxlength="1" class="otp-input cro-otp-input" id="croOtp2"
required />
<input type="text" maxlength="1" class="otp-input cro-otp-input" id="croOtp3"
required />
<input type="text" maxlength="1" class="otp-input cro-otp-input" id="croOtp4"
required />
<input type="text" maxlength="1" class="otp-input cro-otp-input" id="croOtp5"
required />
</div>
</div>
<div class="mb-3 text-center">
<p class="mb-0">Didn't receive OTP?</p>
<p class="mb-0 text-info">
<span id="croResendText">Please wait..</span>
<span id="croCountdown">60</span>s
</p>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary btn-sm" id="btnVerifyCroOtp"
onclick="verifyAndSubmitStatusChange()">Verify OTP</button>
</div>
</div>
</div>
</div>
<footer th:replace="~{fragments/footer2 :: footer}"></footer>
<div th:replace="~{fragments/resetpassword :: div}"></div>
<script th:replace="~{fragments/loginscript :: script}"></script>
<script>
var currentSearchedBooking = null;
var croResendTimer = null;
var croMobileNumber = [[${ mobileno }]];
// ─── SEARCH BOOKING BY ID ───
function searchBookingById() {
var input = document.getElementById('searchBookingId');
var bookingId = input ? input.value.trim() : '';
if (!bookingId || isNaN(bookingId) || parseInt(bookingId) <= 0) {
Swal.fire({ icon: 'warning', title: 'Invalid Input', text: 'Please enter a valid Booking ID.' });
return;
}
Swal.fire({
title: 'Fetching Booking...',
text: 'Please wait while booking details are retrieved.',
allowOutsideClick: false,
didOpen: () => { Swal.showLoading(); }
});
$.ajax({
type: 'GET',
url: '/communityhall/cro/api/getBookingById',
data: { bookingId: bookingId },
success: function (data) {
Swal.close();
if (data && data.bookingId) {
currentSearchedBooking = data;
populateBookingDetails(data);
$('#bookingDetailResult').show();
} else {
Swal.fire({ icon: 'error', title: 'Not Found', text: 'No booking found for ID: ' + bookingId });
$('#bookingDetailResult').hide();
}
},
error: function (xhr) {
Swal.close();
var msg = (xhr.status === 404) ? 'Booking ID ' + bookingId + ' does not exist.' : 'Error fetching booking.';
Swal.fire({ icon: 'error', title: 'Error', text: msg });
$('#bookingDetailResult').hide();
}
});
}
// ─── CLEAR BOOKING SEARCH ───
function clearBookingSearch() {
var input = document.getElementById('searchBookingId');
if (input) input.value = '';
$('#bookingDetailResult').hide();
currentSearchedBooking = null;
}
$(document).ready(function () {
$('#searchBookingId').on('keypress', function (e) {
if (e.key === 'Enter') searchBookingById();
});
// OTP input auto-advance and backspace handling
$('.cro-otp-input').on('keyup', function (e) {
var $this = $(this);
if (e.key === 'Backspace' || e.key === 'Delete') {
if ($this.val().length === 0) {
$this.prev('.cro-otp-input').focus();
}
} else if (e.key >= '0' && e.key <= '9') {
if ($this.val().length === 1) {
$this.next('.cro-otp-input').focus();
}
}
});
// Paste handling for OTP inputs
$('.cro-otp-input').first().on('paste', function (e) {
var pasteData = (e.originalEvent.clipboardData || window.clipboardData).getData('text').trim();
if (/^\d{5}$/.test(pasteData)) {
e.preventDefault();
for (var i = 0; i < 5; i++) {
$('#croOtp' + (i + 1)).val(pasteData[i]);
}
$('#croOtp5').focus();
}
});
});
// ─── POPULATE BOOKING DETAILS ───
function populateBookingDetails(list) {
var ud = list.users || {};
var hd = list.hallDetails || {};
var py = list.payments || {};
document.getElementById('res_userName').innerHTML = ud.name || 'N/A';
document.getElementById('res_mobileNumber').innerHTML = ud.mobileNo || 'N/A';
document.getElementById('res_usertype').innerHTML = ud.usertype || 'N/A';
document.getElementById('res_useraddress').innerHTML = ud.doorNo ? ud.doorNo + ', ' + ud.street + ', ' + ud.area + ', ' + ud.location + '- ' + ud.pinCode : 'N/A';
document.getElementById('res_hallName').innerHTML = hd.hallName || 'N/A';
document.getElementById('res_halladdress').innerHTML = hd.doorNo ? hd.doorNo + ', ' + hd.street + ', ' + hd.area + ', ' + hd.city + '- ' + hd.pinCode : 'N/A';
document.getElementById('res_caretakername').innerHTML = hd.hallInchargeName || 'N/A';
document.getElementById('res_caretakermobileNumber').innerHTML = hd.hallInchargeMobno || 'N/A';
document.getElementById('res_booking_id').innerHTML = list.bookingId;
document.getElementById('resultBookingId').innerHTML = ' #' + list.bookingId;
document.getElementById('res_bookingDate').innerHTML = list.bookingDate || 'N/A';
document.getElementById('res_bookingStatus').innerHTML = list.bookingStatus || 'N/A';
document.getElementById('res_startDate').innerHTML = list.startDate || 'N/A';
document.getElementById('res_endDate').innerHTML = list.endDate || 'N/A';
document.getElementById('res_depositeAmount').innerHTML = list.depositAmount ? '&#8377; ' + parseFloat(list.depositAmount).toFixed(2) : 'N/A';
document.getElementById('res_hallAmount').innerHTML = list.hallAmount ? '&#8377; ' + parseFloat(list.hallAmount).toFixed(2) : 'N/A';
document.getElementById('res_totalAmount').innerHTML = list.amountReceived ? '&#8377; ' + parseFloat(list.amountReceived).toFixed(2) : 'N/A';
var gst = (list.hallAmount && hd.gst) ? (parseFloat(list.hallAmount) * (hd.gst / 100)).toFixed(2) : '0.00';
document.getElementById('res_gst').innerHTML = '&#8377; ' + gst;
document.getElementById('res_startingSlot').innerHTML = getSlotLabel(list.startingSlot);
document.getElementById('res_endingSlot').innerHTML = getSlotLabel(list.endingSlot);
document.getElementById('res_reason').innerHTML = list.reason || 'N/A';
document.getElementById('res_transactionId').innerHTML = py.transactionId || 'NA';
document.getElementById('res_payamountReceived').innerHTML = py.amountReceived ? '&#8377; ' + py.amountReceived : 'NA';
document.getElementById('res_paymentStatus').innerHTML = py.paymentStatus || 'NA';
// Status Badge
var status = (list.bookingStatus || '').toUpperCase().trim();
var badgeEl = document.getElementById('res_bookingStatusBadge');
badgeEl.textContent = status;
badgeEl.className = 'badge status-badge-lg ' + getStatusBadgeClass(status);
// ─── Status Change Eligibility Check ───
// 1. Status must be APPROVED, WAITING_FOR_NOC, or WAITING_FOR_NOC_APPROVAL
// 2. Start date and End date must NOT be future dates (only completed events allowed)
var eligibleStatuses = ['APPROVED', 'WAITING_FOR_NOC', 'WAITING_FOR_NOC_APPROVAL'];
var isStatusEligible = (eligibleStatuses.indexOf(status) !== -1);
var hasEbDetails = list.ebDetails != null && (list.ebDetails.id != null || list.ebDetails.startingReading != null);
// Date checks:
var today = new Date();
var yyyy = today.getFullYear();
var mm = String(today.getMonth() + 1).padStart(2, '0');
var dd = String(today.getDate()).padStart(2, '0');
var todayStr = yyyy + '-' + mm + '-' + dd;
var startDateStr = (list.startDate || '').trim();
var endDateStr = (list.endDate || '').trim();
var isFutureEvent = false;
if (startDateStr && startDateStr > todayStr) {
isFutureEvent = true;
} else if (endDateStr && endDateStr > todayStr) {
isFutureEvent = true;
}
if (isStatusEligible && !isFutureEvent && !hasEbDetails) {
$('#statusChangeSection').show();
$('#statusChangeNotAllowedMsg').hide();
$('#changeBookingStatusSelect').val('');
$('#statusChangeRemarks').val('');
} else {
$('#statusChangeSection').hide();
$('#statusChangeNotAllowedMsg').show();
$('#notAllowedCurrentStatusBadge').text(status || 'UNKNOWN');
if (isFutureEvent) {
$('#notAllowedDetailMsg').html('Booking dates (<strong>' + (startDateStr || 'N/A') + ' to ' + (endDateStr || 'N/A') + '</strong>) are in the future. CRO is <strong>not permitted</strong> to change the status for future events. Only <strong>completed events</strong> can be marked as <strong>REJECTED</strong> or <strong>GCC_BLOCKED</strong>.');
} else {
$('#notAllowedDetailMsg').html('Status change is only permitted for <strong>Past events</strong> with status <strong>APPROVED</strong>, <strong>WAITING_FOR_NOC</strong>, or <strong>WAITING_FOR_NOC_APPROVAL</strong>. EB Details must not have been entered for the booking.');
}
}
}
function getStatusBadgeClass(status) {
switch (status) {
case 'APPROVED': return 'bg-success';
case 'WAITING_FOR_NOC':
case 'WAITING_FOR_NOC_APPROVAL': return 'bg-warning text-dark';
case 'REJECTED':
case 'GCC_BLOCKED':
case 'CANCELED': return 'bg-danger';
case 'COMPLETED': return 'bg-info text-dark';
case 'PENDING': return 'bg-secondary';
default: return 'bg-primary';
}
}
function getSlotLabel(v) {
var s = String(v).trim();
if (s === '1') return 'Morning';
if (s === '2') return 'Evening';
return 'Unknown';
}
// ─── INITIATE STATUS CHANGE (Trigger OTP) ───
function initiateStatusChange() {
if (!currentSearchedBooking || !currentSearchedBooking.bookingId) {
Swal.fire({ icon: 'warning', title: 'No Booking Selected', text: 'Please search and select a booking first.' });
return;
}
// Check future event restriction
var today = new Date();
var todayStr = today.getFullYear() + '-' + String(today.getMonth() + 1).padStart(2, '0') + '-' + String(today.getDate()).padStart(2, '0');
var sDate = (currentSearchedBooking.startDate || '').trim();
var eDate = (currentSearchedBooking.endDate || '').trim();
if ((sDate && sDate > todayStr) || (eDate && eDate > todayStr)) {
Swal.fire({
icon: 'warning',
title: 'Action Not Allowed',
text: 'Status change is not permitted for future events. Only past events can be marked as REJECTED or GCC_BLOCKED.'
});
return;
}
var newStatus = $('#changeBookingStatusSelect').val();
if (!newStatus || (newStatus !== 'REJECTED' && newStatus !== 'GCC_BLOCKED')) {
Swal.fire({ icon: 'warning', title: 'Status Required', text: 'Please select a new Booking Status (REJECTED or GCC_BLOCKED).' });
return;
}
var remarks = $('#statusChangeRemarks').val().trim();
if (!remarks) {
Swal.fire({ icon: 'warning', title: 'Remarks Required', text: 'Please enter reason / remarks for this status change.' });
$('#statusChangeRemarks').focus();
return;
}
Swal.fire({
title: 'Confirm Status Change',
html: 'Are you sure you want to change Booking <b>#' + currentSearchedBooking.bookingId + '</b> status to <b class="text-danger">' + newStatus + '</b>?<br/><small class="text-muted">An OTP will be sent to your CRO mobile number to authorize this change.</small>',
icon: 'question',
showCancelButton: true,
confirmButtonText: 'Yes, Send OTP',
cancelButtonText: 'Cancel',
confirmButtonColor: '#d33'
}).then((result) => {
if (result.isConfirmed) {
sendCroStatusOtp(newStatus, remarks);
}
});
}
// ─── SEND CRO OTP ───
function sendCroStatusOtp(newStatus, remarks) {
Swal.fire({
title: 'Sending OTP...',
text: 'Please wait while OTP is sent to your registered mobile number.',
allowOutsideClick: false,
didOpen: () => { Swal.showLoading(); }
});
$.ajax({
type: 'GET',
url: '/communityhall/user/api/sendOTP',
data: {
mobileNo: croMobileNumber,
purpose: 'CRO Status Change'
},
success: function (bodyMessage) {
Swal.close();
if (bodyMessage && bodyMessage !== 'Error') {
var mobStr = String(croMobileNumber);
var maskedMobile = mobStr.length >= 10
? mobStr.substring(0, 2) + 'XXXXXX' + mobStr.substring(mobStr.length - 2)
: mobStr;
$('#croOtpMobileDisplay').text(maskedMobile || 'Registered CRO Number');
$('#modalBookingIdText').text('#' + currentSearchedBooking.bookingId);
$('#modalNewStatusText').text(newStatus);
$('#croOtpErrorAlert').hide().text('');
// Clear OTP boxes
$('.cro-otp-input').val('');
// Show modal
$('#croStatusOtpModal').modal('show');
setTimeout(function () { $('#croOtp1').focus(); }, 500);
// Start countdown
startCroCountdown();
} else {
Swal.fire({ icon: 'error', title: 'OTP Failed', text: 'Failed to send OTP. Please try again.' });
}
},
error: function () {
Swal.close();
Swal.fire({ icon: 'error', title: 'Error', text: 'Failed to send OTP. Please check your network connection.' });
}
});
}
// ─── COUNTDOWN TIMER FOR RESEND OTP ───
function startCroCountdown() {
if (croResendTimer) clearInterval(croResendTimer);
var timeLeft = 60;
$('#croResendText').text('Please wait..');
$('#croCountdown').text(timeLeft);
croResendTimer = setInterval(function () {
timeLeft--;
$('#croCountdown').text(timeLeft);
if (timeLeft <= 0) {
clearInterval(croResendTimer);
$('#croResendText').html('<a href="javascript:void(0)" class="text-info" onclick="resendCroOtp()">Resend OTP</a>');
$('#croCountdown').text('');
}
}, 1000);
}
function resendCroOtp() {
var newStatus = $('#changeBookingStatusSelect').val();
var remarks = $('#statusChangeRemarks').val().trim();
sendCroStatusOtp(newStatus, remarks);
}
// ─── VERIFY OTP & SUBMIT STATUS CHANGE ───
function verifyAndSubmitStatusChange() {
var otpValue = '';
for (var i = 1; i <= 5; i++) {
otpValue += ($('#croOtp' + i).val() || '').trim();
}
if (otpValue.length !== 5 || isNaN(otpValue)) {
$('#croOtpErrorAlert').text('Please enter the complete 5-digit OTP.').show();
return;
}
var newStatus = $('#changeBookingStatusSelect').val();
var remarks = $('#statusChangeRemarks').val().trim();
var bookingId = currentSearchedBooking.bookingId;
$('#btnVerifyCroOtp').prop('disabled', true).html('<i class="fa fa-spinner fa-spin me-1"></i> Verifying...');
$('#croOtpErrorAlert').hide();
$.ajax({
type: 'POST',
url: '/communityhall/cro/api/updateBookingStatus',
data: {
bookingId: bookingId,
status: newStatus,
comments: remarks,
otp: otpValue
},
success: function (response) {
$('#btnVerifyCroOtp').prop('disabled', false).html('<i class="fa fa-check-circle me-1"></i> Verify & Update Status');
if (response && response.status === 'success') {
$('#croStatusOtpModal').modal('hide');
if (croResendTimer) clearInterval(croResendTimer);
Swal.fire({
icon: 'success',
title: 'Status Updated!',
html: 'Booking <b>#' + bookingId + '</b> status has been successfully changed to <b class="text-danger">' + newStatus + '</b>.',
confirmButtonText: 'OK'
}).then(() => {
// Re-fetch booking details to update view in real-time
searchBookingById();
});
} else {
var errMsg = (response && response.message) || 'Failed to update status.';
$('#croOtpErrorAlert').text(errMsg).show();
}
},
error: function (xhr) {
$('#btnVerifyCroOtp').prop('disabled', false).html('<i class="fa fa-check-circle me-1"></i> Verify & Update Status');
var errMsg = 'Verification failed. Please try again.';
if (xhr.responseJSON && xhr.responseJSON.message) {
errMsg = xhr.responseJSON.message;
} else if (xhr.responseText) {
try {
var parsed = JSON.parse(xhr.responseText);
if (parsed.message) errMsg = parsed.message;
} catch (e) { }
}
$('#croOtpErrorAlert').text(errMsg).show();
}
});
}
</script>
</div>
</body>
</html>