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
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 ? '₹ ' + parseFloat(list.depositAmount).toFixed(2) : 'N/A';
|
|
document.getElementById('res_hallAmount').innerHTML = list.hallAmount ? '₹ ' + parseFloat(list.hallAmount).toFixed(2) : 'N/A';
|
|
document.getElementById('res_totalAmount').innerHTML = list.amountReceived ? '₹ ' + 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 = '₹ ' + 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 ? '₹ ' + 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>
|