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.

750 lines
38 KiB

​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
  1. <!DOCTYPE html>
  2. <html xmlns:th="http://www.thymeleaf.org">
  3. <head th:replace="~{fragments/head-cro :: head}"></head>
  4. <style>
  5. .card-body table {
  6. width: 100%;
  7. padding: 100px;
  8. }
  9. .card-body th,
  10. td {
  11. padding: 8px;
  12. text-align: left;
  13. }
  14. .amount-cell {
  15. text-align: right;
  16. padding-right: 1rem;
  17. }
  18. .form-control[readonly] {
  19. background-color: white;
  20. }
  21. .modal {
  22. overflow-y: auto;
  23. }
  24. /* OTP inputs */
  25. .otp-input-box {
  26. display: flex;
  27. justify-content: center;
  28. gap: 10px;
  29. }
  30. .cro-otp-input {
  31. width: 48px;
  32. height: 52px;
  33. text-align: center;
  34. font-size: 22px;
  35. font-weight: bold;
  36. border: 2px solid #ced4da;
  37. border-radius: 8px;
  38. transition: all 0.2s ease-in-out;
  39. }
  40. .cro-otp-input:focus {
  41. border-color: #0d6efd;
  42. outline: none;
  43. box-shadow: 0 0 0 4px rgba(13, 110, 253, .2);
  44. }
  45. #bookingSearchSection {
  46. border: 1px solid #dee2e6;
  47. border-radius: 8px;
  48. padding: 20px;
  49. background: #f8f9fa;
  50. margin-bottom: 20px;
  51. }
  52. .detail-section th {
  53. background-color: #f2f2f2;
  54. font-weight: 600;
  55. }
  56. .detail-section td,
  57. .detail-section th {
  58. padding: 0.45rem 0.75rem;
  59. border: 1px solid #dee2e6;
  60. }
  61. .detail-section table {
  62. width: 100%;
  63. border-collapse: collapse;
  64. margin-bottom: 10px;
  65. }
  66. #bookingDetailResult {
  67. display: none;
  68. }
  69. .status-change-card {
  70. border: 2px solid #e2e8f0;
  71. border-radius: 10px;
  72. background: #ffffff;
  73. box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
  74. padding: 24px;
  75. margin-top: 25px;
  76. }
  77. .status-badge-lg {
  78. font-size: 0.95rem;
  79. padding: 0.35rem 0.75rem;
  80. border-radius: 6px;
  81. font-weight: 600;
  82. }
  83. </style>
  84. <body>
  85. <!-- Page Container -->
  86. <div id="page-container"
  87. class="sidebar-o sidebar-dark enable-page-overlay side-scroll page-header-fixed page-header-dark">
  88. <!-- Sidebar Nav-->
  89. <aside th:replace="~{fragments/aside-right :: aside}"></aside>
  90. <!-- END Side Overlay (right)-->
  91. <!-- Sidebar Nav-->
  92. <nav th:replace="~{fragments/nav-cro :: nav}"></nav>
  93. <!-- END Sidebar -->
  94. <!-- Header -->
  95. <header th:replace="~{fragments/header-cro :: header}"></header>
  96. <!-- Main Container -->
  97. <main id="main-container">
  98. <div class="content">
  99. <!-- ═══ Search Booking by ID ═══ -->
  100. <div class="block block-rounded">
  101. <div class="block-header block-header-default">
  102. <h3 class="block-title"><i class="fa fa-search me-2 text-primary"></i>Change Booking Status</h3>
  103. </div>
  104. <div class="block-content p-4">
  105. <div id="bookingSearchSection">
  106. <div class="row align-items-end">
  107. <div class="col-md-4 mb-2">
  108. <label class="form-label fw-semibold" for="searchBookingId">Booking ID <span
  109. class="text-danger">*</span></label>
  110. <input type="number" class="form-control" id="searchBookingId"
  111. placeholder="Enter Booking ID" min="1" />
  112. </div>
  113. <div class="col-md-3 mb-2">
  114. <button type="button" class="btn btn-primary" id="btnSearchBooking"
  115. onclick="searchBookingById()">
  116. <i class="fa fa-search me-1"></i> Search
  117. </button>
  118. <button type="button" class="btn btn-secondary ms-2" id="btnClearBookingSearch"
  119. onclick="clearBookingSearch()">
  120. <i class="fa fa-times me-1"></i> Clear
  121. </button>
  122. </div>
  123. </div>
  124. </div>
  125. <!-- Booking Detail Result -->
  126. <div id="bookingDetailResult">
  127. <hr />
  128. <div class="d-flex justify-content-between align-items-center mb-3">
  129. <h5 class="mb-0">Booking Details <span class="text-primary" id="resultBookingId"></span>
  130. </h5>
  131. <span id="res_bookingStatusBadge" class="badge status-badge-lg bg-info"></span>
  132. </div>
  133. <div class="detail-section">
  134. <!-- User Details -->
  135. <table>
  136. <tr>
  137. <th colspan="6">User Details</th>
  138. </tr>
  139. <tr>
  140. <th style="font-weight:normal;">User Name:</th>
  141. <td id="res_userName"></td>
  142. <th style="font-weight:normal;">Mobile Number:</th>
  143. <td id="res_mobileNumber"></td>
  144. <th style="font-weight:normal;">User Type:</th>
  145. <td id="res_usertype"></td>
  146. </tr>
  147. <tr>
  148. <th style="font-weight:normal;">User Address:</th>
  149. <td colspan="5" id="res_useraddress"></td>
  150. </tr>
  151. </table>
  152. <!-- Hall Details -->
  153. <table>
  154. <tr>
  155. <th colspan="6">Hall Details</th>
  156. </tr>
  157. <tr>
  158. <th style="font-weight:normal;">Hall Name:</th>
  159. <td colspan="5" id="res_hallName"></td>
  160. </tr>
  161. <tr>
  162. <th style="font-weight:normal;">Hall Address:</th>
  163. <td colspan="5" id="res_halladdress"></td>
  164. </tr>
  165. <tr>
  166. <th style="font-weight:normal;">Caretaker Name:</th>
  167. <td id="res_caretakername"></td>
  168. <th style="font-weight:normal;">Caretaker Mobile:</th>
  169. <td colspan="3" id="res_caretakermobileNumber"></td>
  170. </tr>
  171. </table>
  172. <!-- Booking Details -->
  173. <table>
  174. <tr>
  175. <th colspan="4">Booking Details (Booking Id: <span id="res_booking_id"
  176. style="color:blue;"></span>)</th>
  177. </tr>
  178. <tr>
  179. <th style="font-weight:normal;">Booking Date:</th>
  180. <td id="res_bookingDate"></td>
  181. <th style="font-weight:normal;">Booking Status:</th>
  182. <td id="res_bookingStatus" class="fw-bold"></td>
  183. </tr>
  184. <tr>
  185. <th style="font-weight:normal;">Start Date:</th>
  186. <td id="res_startDate"></td>
  187. <th style="font-weight:normal;">Deposit Amount:</th>
  188. <td class="amount-cell" id="res_depositeAmount"></td>
  189. </tr>
  190. <tr>
  191. <th style="font-weight:normal;">End Date:</th>
  192. <td id="res_endDate"></td>
  193. <th style="font-weight:normal;">Hall Amount:</th>
  194. <td class="amount-cell" id="res_hallAmount"></td>
  195. </tr>
  196. <tr>
  197. <th style="font-weight:normal;">Starting Slot:</th>
  198. <td id="res_startingSlot"></td>
  199. <th style="font-weight:normal;">GST @18%:</th>
  200. <td class="amount-cell" id="res_gst"></td>
  201. </tr>
  202. <tr>
  203. <th style="font-weight:normal;">Ending Slot:</th>
  204. <td id="res_endingSlot"></td>
  205. <th style="font-weight:normal;">Total Amount:</th>
  206. <td class="amount-cell" id="res_totalAmount"></td>
  207. </tr>
  208. <tr>
  209. <th style="font-weight:normal;">Reason:</th>
  210. <td colspan="3" id="res_reason"></td>
  211. </tr>
  212. </table>
  213. <!-- Payment Details -->
  214. <table>
  215. <tr>
  216. <th colspan="4">Booking Payment Details</th>
  217. </tr>
  218. <tr>
  219. <th style="font-weight:normal;">Transaction ID:</th>
  220. <td colspan="3" id="res_transactionId"></td>
  221. </tr>
  222. <tr>
  223. <th style="font-weight:normal;">Amount Received:</th>
  224. <td id="res_payamountReceived"></td>
  225. <th style="font-weight:normal;">Transaction Status:</th>
  226. <td id="res_paymentStatus"></td>
  227. </tr>
  228. </table>
  229. </div>
  230. <!-- ═══ Change Booking Status Section (CRO) ═══ -->
  231. <div id="statusChangeSection" class="status-change-card" style="display: none;">
  232. <div class="d-flex align-items-center mb-3">
  233. <div class="bg-primary-light text-primary p-2 rounded-3 me-3"
  234. style="background: rgba(13,110,253,0.1);">
  235. <i class="fa fa-exchange-alt fa-lg text-primary"></i>
  236. </div>
  237. <div>
  238. <h5 class="mb-0 fw-bold text-dark">Change Booking Status</h5>
  239. <small class="text-muted">CRO can update booking status to REJECTED or
  240. GCC_BLOCKED with OTP verification</small>
  241. </div>
  242. </div>
  243. <div class="alert alert-warning py-2 mb-3">
  244. <i class="fa fa-info-circle me-1"></i>
  245. Status changes require OTP verification sent to your registered CRO mobile number.
  246. </div>
  247. <div class="row g-3">
  248. <div class="col-md-5">
  249. <label class="form-label fw-semibold" for="changeBookingStatusSelect">
  250. Select New Booking Status <span class="text-danger">*</span>
  251. </label>
  252. <select class="form-select" id="changeBookingStatusSelect">
  253. <option value="">-- Select New Status --</option>
  254. <option value="REJECTED">REJECTED</option>
  255. <option value="GCC_BLOCKED">GCC_BLOCKED</option>
  256. </select>
  257. </div>
  258. <div class="col-md-7">
  259. <label class="form-label fw-semibold" for="statusChangeRemarks">
  260. Reason / Remarks <span class="text-danger">*</span>
  261. </label>
  262. <textarea class="form-control" id="statusChangeRemarks" rows="2"
  263. placeholder="Enter reason or comments for changing status (required)..."></textarea>
  264. </div>
  265. <div class="col-12 text-end mt-3">
  266. <button type="button" class="btn btn-danger px-4" id="btnInitiateStatusChange"
  267. onclick="initiateStatusChange()">
  268. <i class="fa fa-paper-plane me-1"></i> Submit Status Change
  269. </button>
  270. </div>
  271. </div>
  272. </div>
  273. <!-- ═══ Not Allowed Info Box ═══ -->
  274. <div id="statusChangeNotAllowedMsg" style="display: none; margin-top: 20px;">
  275. <div class="alert alert-secondary mb-0">
  276. <i class="fa fa-info-circle me-1"></i>
  277. Status change is <strong>not permitted</strong> for this booking.
  278. <span id="notAllowedDetailMsg">Status change is only permitted for <strong>Past
  279. events</strong> with status <strong>APPROVED</strong>,
  280. <strong>WAITING_FOR_NOC</strong>, or
  281. <strong>WAITING_FOR_NOC_APPROVAL</strong>.</span>
  282. <br />
  283. <span class="small mt-1 d-inline-block">
  284. Current status: <span class="badge bg-dark"
  285. id="notAllowedCurrentStatusBadge"></span>
  286. </span>
  287. </div>
  288. </div>
  289. </div>
  290. </div>
  291. </div>
  292. </div>
  293. </main>
  294. <!-- END Main Container -->
  295. <!-- ════ CRO OTP MODAL (MATCHES cro/booking-list.html) ════ -->
  296. <div class="modal fade" id="croStatusOtpModal" tabindex="-1" role="dialog"
  297. aria-labelledby="croStatusOtpModalLabel" aria-hidden="true">
  298. <div class="modal-dialog" role="document">
  299. <div class="modal-content">
  300. <div class="modal-header">
  301. <h6 class="modal-title m-0" id="croStatusOtpModalLabel">OTP Verification</h6>
  302. <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
  303. </div>
  304. <div class="modal-body">
  305. <div class="row">
  306. <div class="mb-3 text-center">
  307. <img src="media/images/mobileotp.png" width="50%" alt="OTP">
  308. </div>
  309. <div class="mb-0 text-center">
  310. <h4>OTP Verification</h4>
  311. <p class="text-mute">Enter the OTP sent to <b class="text-info"
  312. id="croOtpMobileDisplay"></b></p>
  313. <p class="text-danger" id="croOtpErrorAlert"></p>
  314. </div>
  315. <div class="mb-3 text-center">
  316. <div class="otp-input-box">
  317. <input type="text" maxlength="1" class="otp-input cro-otp-input" id="croOtp1"
  318. required />
  319. <input type="text" maxlength="1" class="otp-input cro-otp-input" id="croOtp2"
  320. required />
  321. <input type="text" maxlength="1" class="otp-input cro-otp-input" id="croOtp3"
  322. required />
  323. <input type="text" maxlength="1" class="otp-input cro-otp-input" id="croOtp4"
  324. required />
  325. <input type="text" maxlength="1" class="otp-input cro-otp-input" id="croOtp5"
  326. required />
  327. </div>
  328. </div>
  329. <div class="mb-3 text-center">
  330. <p class="mb-0">Didn't receive OTP?</p>
  331. <p class="mb-0 text-info">
  332. <span id="croResendText">Please wait..</span>
  333. <span id="croCountdown">60</span>s
  334. </p>
  335. </div>
  336. </div>
  337. </div>
  338. <div class="modal-footer">
  339. <button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Close</button>
  340. <button type="button" class="btn btn-primary btn-sm" id="btnVerifyCroOtp"
  341. onclick="verifyAndSubmitStatusChange()">Verify OTP</button>
  342. </div>
  343. </div>
  344. </div>
  345. </div>
  346. <footer th:replace="~{fragments/footer2 :: footer}"></footer>
  347. <div th:replace="~{fragments/resetpassword :: div}"></div>
  348. <script th:replace="~{fragments/loginscript :: script}"></script>
  349. <script>
  350. var currentSearchedBooking = null;
  351. var croResendTimer = null;
  352. var croMobileNumber = [[${ mobileno }]];
  353. // ─── SEARCH BOOKING BY ID ───
  354. function searchBookingById() {
  355. var input = document.getElementById('searchBookingId');
  356. var bookingId = input ? input.value.trim() : '';
  357. if (!bookingId || isNaN(bookingId) || parseInt(bookingId) <= 0) {
  358. Swal.fire({ icon: 'warning', title: 'Invalid Input', text: 'Please enter a valid Booking ID.' });
  359. return;
  360. }
  361. Swal.fire({
  362. title: 'Fetching Booking...',
  363. text: 'Please wait while booking details are retrieved.',
  364. allowOutsideClick: false,
  365. didOpen: () => { Swal.showLoading(); }
  366. });
  367. $.ajax({
  368. type: 'GET',
  369. url: '/communityhall/cro/api/getBookingById',
  370. data: { bookingId: bookingId },
  371. success: function (data) {
  372. Swal.close();
  373. if (data && data.bookingId) {
  374. currentSearchedBooking = data;
  375. populateBookingDetails(data);
  376. $('#bookingDetailResult').show();
  377. } else {
  378. Swal.fire({ icon: 'error', title: 'Not Found', text: 'No booking found for ID: ' + bookingId });
  379. $('#bookingDetailResult').hide();
  380. }
  381. },
  382. error: function (xhr) {
  383. Swal.close();
  384. var msg = (xhr.status === 404) ? 'Booking ID ' + bookingId + ' does not exist.' : 'Error fetching booking.';
  385. Swal.fire({ icon: 'error', title: 'Error', text: msg });
  386. $('#bookingDetailResult').hide();
  387. }
  388. });
  389. }
  390. // ─── CLEAR BOOKING SEARCH ───
  391. function clearBookingSearch() {
  392. var input = document.getElementById('searchBookingId');
  393. if (input) input.value = '';
  394. $('#bookingDetailResult').hide();
  395. currentSearchedBooking = null;
  396. }
  397. $(document).ready(function () {
  398. $('#searchBookingId').on('keypress', function (e) {
  399. if (e.key === 'Enter') searchBookingById();
  400. });
  401. // OTP input auto-advance and backspace handling
  402. $('.cro-otp-input').on('keyup', function (e) {
  403. var $this = $(this);
  404. if (e.key === 'Backspace' || e.key === 'Delete') {
  405. if ($this.val().length === 0) {
  406. $this.prev('.cro-otp-input').focus();
  407. }
  408. } else if (e.key >= '0' && e.key <= '9') {
  409. if ($this.val().length === 1) {
  410. $this.next('.cro-otp-input').focus();
  411. }
  412. }
  413. });
  414. // Paste handling for OTP inputs
  415. $('.cro-otp-input').first().on('paste', function (e) {
  416. var pasteData = (e.originalEvent.clipboardData || window.clipboardData).getData('text').trim();
  417. if (/^\d{5}$/.test(pasteData)) {
  418. e.preventDefault();
  419. for (var i = 0; i < 5; i++) {
  420. $('#croOtp' + (i + 1)).val(pasteData[i]);
  421. }
  422. $('#croOtp5').focus();
  423. }
  424. });
  425. });
  426. // ─── POPULATE BOOKING DETAILS ───
  427. function populateBookingDetails(list) {
  428. var ud = list.users || {};
  429. var hd = list.hallDetails || {};
  430. var py = list.payments || {};
  431. document.getElementById('res_userName').innerHTML = ud.name || 'N/A';
  432. document.getElementById('res_mobileNumber').innerHTML = ud.mobileNo || 'N/A';
  433. document.getElementById('res_usertype').innerHTML = ud.usertype || 'N/A';
  434. document.getElementById('res_useraddress').innerHTML = ud.doorNo ? ud.doorNo + ', ' + ud.street + ', ' + ud.area + ', ' + ud.location + '- ' + ud.pinCode : 'N/A';
  435. document.getElementById('res_hallName').innerHTML = hd.hallName || 'N/A';
  436. document.getElementById('res_halladdress').innerHTML = hd.doorNo ? hd.doorNo + ', ' + hd.street + ', ' + hd.area + ', ' + hd.city + '- ' + hd.pinCode : 'N/A';
  437. document.getElementById('res_caretakername').innerHTML = hd.hallInchargeName || 'N/A';
  438. document.getElementById('res_caretakermobileNumber').innerHTML = hd.hallInchargeMobno || 'N/A';
  439. document.getElementById('res_booking_id').innerHTML = list.bookingId;
  440. document.getElementById('resultBookingId').innerHTML = ' #' + list.bookingId;
  441. document.getElementById('res_bookingDate').innerHTML = list.bookingDate || 'N/A';
  442. document.getElementById('res_bookingStatus').innerHTML = list.bookingStatus || 'N/A';
  443. document.getElementById('res_startDate').innerHTML = list.startDate || 'N/A';
  444. document.getElementById('res_endDate').innerHTML = list.endDate || 'N/A';
  445. document.getElementById('res_depositeAmount').innerHTML = list.depositAmount ? '&#8377; ' + parseFloat(list.depositAmount).toFixed(2) : 'N/A';
  446. document.getElementById('res_hallAmount').innerHTML = list.hallAmount ? '&#8377; ' + parseFloat(list.hallAmount).toFixed(2) : 'N/A';
  447. document.getElementById('res_totalAmount').innerHTML = list.amountReceived ? '&#8377; ' + parseFloat(list.amountReceived).toFixed(2) : 'N/A';
  448. var gst = (list.hallAmount && hd.gst) ? (parseFloat(list.hallAmount) * (hd.gst / 100)).toFixed(2) : '0.00';
  449. document.getElementById('res_gst').innerHTML = '&#8377; ' + gst;
  450. document.getElementById('res_startingSlot').innerHTML = getSlotLabel(list.startingSlot);
  451. document.getElementById('res_endingSlot').innerHTML = getSlotLabel(list.endingSlot);
  452. document.getElementById('res_reason').innerHTML = list.reason || 'N/A';
  453. document.getElementById('res_transactionId').innerHTML = py.transactionId || 'NA';
  454. document.getElementById('res_payamountReceived').innerHTML = py.amountReceived ? '&#8377; ' + py.amountReceived : 'NA';
  455. document.getElementById('res_paymentStatus').innerHTML = py.paymentStatus || 'NA';
  456. // Status Badge
  457. var status = (list.bookingStatus || '').toUpperCase().trim();
  458. var badgeEl = document.getElementById('res_bookingStatusBadge');
  459. badgeEl.textContent = status;
  460. badgeEl.className = 'badge status-badge-lg ' + getStatusBadgeClass(status);
  461. // ─── Status Change Eligibility Check ───
  462. // 1. Status must be APPROVED, WAITING_FOR_NOC, or WAITING_FOR_NOC_APPROVAL
  463. // 2. Start date and End date must NOT be future dates (only completed events allowed)
  464. var eligibleStatuses = ['APPROVED', 'WAITING_FOR_NOC', 'WAITING_FOR_NOC_APPROVAL'];
  465. var isStatusEligible = (eligibleStatuses.indexOf(status) !== -1);
  466. var hasEbDetails = list.ebDetails != null && (list.ebDetails.id != null || list.ebDetails.startingReading != null);
  467. // Date checks:
  468. var today = new Date();
  469. var yyyy = today.getFullYear();
  470. var mm = String(today.getMonth() + 1).padStart(2, '0');
  471. var dd = String(today.getDate()).padStart(2, '0');
  472. var todayStr = yyyy + '-' + mm + '-' + dd;
  473. var startDateStr = (list.startDate || '').trim();
  474. var endDateStr = (list.endDate || '').trim();
  475. var isFutureEvent = false;
  476. if (startDateStr && startDateStr > todayStr) {
  477. isFutureEvent = true;
  478. } else if (endDateStr && endDateStr > todayStr) {
  479. isFutureEvent = true;
  480. }
  481. if (isStatusEligible && !isFutureEvent && !hasEbDetails) {
  482. $('#statusChangeSection').show();
  483. $('#statusChangeNotAllowedMsg').hide();
  484. $('#changeBookingStatusSelect').val('');
  485. $('#statusChangeRemarks').val('');
  486. } else {
  487. $('#statusChangeSection').hide();
  488. $('#statusChangeNotAllowedMsg').show();
  489. $('#notAllowedCurrentStatusBadge').text(status || 'UNKNOWN');
  490. if (isFutureEvent) {
  491. $('#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>.');
  492. } else {
  493. $('#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.');
  494. }
  495. }
  496. }
  497. function getStatusBadgeClass(status) {
  498. switch (status) {
  499. case 'APPROVED': return 'bg-success';
  500. case 'WAITING_FOR_NOC':
  501. case 'WAITING_FOR_NOC_APPROVAL': return 'bg-warning text-dark';
  502. case 'REJECTED':
  503. case 'GCC_BLOCKED':
  504. case 'CANCELED': return 'bg-danger';
  505. case 'COMPLETED': return 'bg-info text-dark';
  506. case 'PENDING': return 'bg-secondary';
  507. default: return 'bg-primary';
  508. }
  509. }
  510. function getSlotLabel(v) {
  511. var s = String(v).trim();
  512. if (s === '1') return 'Morning';
  513. if (s === '2') return 'Evening';
  514. return 'Unknown';
  515. }
  516. // ─── INITIATE STATUS CHANGE (Trigger OTP) ───
  517. function initiateStatusChange() {
  518. if (!currentSearchedBooking || !currentSearchedBooking.bookingId) {
  519. Swal.fire({ icon: 'warning', title: 'No Booking Selected', text: 'Please search and select a booking first.' });
  520. return;
  521. }
  522. // Check future event restriction
  523. var today = new Date();
  524. var todayStr = today.getFullYear() + '-' + String(today.getMonth() + 1).padStart(2, '0') + '-' + String(today.getDate()).padStart(2, '0');
  525. var sDate = (currentSearchedBooking.startDate || '').trim();
  526. var eDate = (currentSearchedBooking.endDate || '').trim();
  527. if ((sDate && sDate > todayStr) || (eDate && eDate > todayStr)) {
  528. Swal.fire({
  529. icon: 'warning',
  530. title: 'Action Not Allowed',
  531. text: 'Status change is not permitted for future events. Only past events can be marked as REJECTED or GCC_BLOCKED.'
  532. });
  533. return;
  534. }
  535. var newStatus = $('#changeBookingStatusSelect').val();
  536. if (!newStatus || (newStatus !== 'REJECTED' && newStatus !== 'GCC_BLOCKED')) {
  537. Swal.fire({ icon: 'warning', title: 'Status Required', text: 'Please select a new Booking Status (REJECTED or GCC_BLOCKED).' });
  538. return;
  539. }
  540. var remarks = $('#statusChangeRemarks').val().trim();
  541. if (!remarks) {
  542. Swal.fire({ icon: 'warning', title: 'Remarks Required', text: 'Please enter reason / remarks for this status change.' });
  543. $('#statusChangeRemarks').focus();
  544. return;
  545. }
  546. Swal.fire({
  547. title: 'Confirm Status Change',
  548. 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>',
  549. icon: 'question',
  550. showCancelButton: true,
  551. confirmButtonText: 'Yes, Send OTP',
  552. cancelButtonText: 'Cancel',
  553. confirmButtonColor: '#d33'
  554. }).then((result) => {
  555. if (result.isConfirmed) {
  556. sendCroStatusOtp(newStatus, remarks);
  557. }
  558. });
  559. }
  560. // ─── SEND CRO OTP ───
  561. function sendCroStatusOtp(newStatus, remarks) {
  562. Swal.fire({
  563. title: 'Sending OTP...',
  564. text: 'Please wait while OTP is sent to your registered mobile number.',
  565. allowOutsideClick: false,
  566. didOpen: () => { Swal.showLoading(); }
  567. });
  568. $.ajax({
  569. type: 'GET',
  570. url: '/communityhall/user/api/sendOTP',
  571. data: {
  572. mobileNo: croMobileNumber,
  573. purpose: 'CRO Status Change'
  574. },
  575. success: function (bodyMessage) {
  576. Swal.close();
  577. if (bodyMessage && bodyMessage !== 'Error') {
  578. var mobStr = String(croMobileNumber);
  579. var maskedMobile = mobStr.length >= 10
  580. ? mobStr.substring(0, 2) + 'XXXXXX' + mobStr.substring(mobStr.length - 2)
  581. : mobStr;
  582. $('#croOtpMobileDisplay').text(maskedMobile || 'Registered CRO Number');
  583. $('#modalBookingIdText').text('#' + currentSearchedBooking.bookingId);
  584. $('#modalNewStatusText').text(newStatus);
  585. $('#croOtpErrorAlert').hide().text('');
  586. // Clear OTP boxes
  587. $('.cro-otp-input').val('');
  588. // Show modal
  589. $('#croStatusOtpModal').modal('show');
  590. setTimeout(function () { $('#croOtp1').focus(); }, 500);
  591. // Start countdown
  592. startCroCountdown();
  593. } else {
  594. Swal.fire({ icon: 'error', title: 'OTP Failed', text: 'Failed to send OTP. Please try again.' });
  595. }
  596. },
  597. error: function () {
  598. Swal.close();
  599. Swal.fire({ icon: 'error', title: 'Error', text: 'Failed to send OTP. Please check your network connection.' });
  600. }
  601. });
  602. }
  603. // ─── COUNTDOWN TIMER FOR RESEND OTP ───
  604. function startCroCountdown() {
  605. if (croResendTimer) clearInterval(croResendTimer);
  606. var timeLeft = 60;
  607. $('#croResendText').text('Please wait..');
  608. $('#croCountdown').text(timeLeft);
  609. croResendTimer = setInterval(function () {
  610. timeLeft--;
  611. $('#croCountdown').text(timeLeft);
  612. if (timeLeft <= 0) {
  613. clearInterval(croResendTimer);
  614. $('#croResendText').html('<a href="javascript:void(0)" class="text-info" onclick="resendCroOtp()">Resend OTP</a>');
  615. $('#croCountdown').text('');
  616. }
  617. }, 1000);
  618. }
  619. function resendCroOtp() {
  620. var newStatus = $('#changeBookingStatusSelect').val();
  621. var remarks = $('#statusChangeRemarks').val().trim();
  622. sendCroStatusOtp(newStatus, remarks);
  623. }
  624. // ─── VERIFY OTP & SUBMIT STATUS CHANGE ───
  625. function verifyAndSubmitStatusChange() {
  626. var otpValue = '';
  627. for (var i = 1; i <= 5; i++) {
  628. otpValue += ($('#croOtp' + i).val() || '').trim();
  629. }
  630. if (otpValue.length !== 5 || isNaN(otpValue)) {
  631. $('#croOtpErrorAlert').text('Please enter the complete 5-digit OTP.').show();
  632. return;
  633. }
  634. var newStatus = $('#changeBookingStatusSelect').val();
  635. var remarks = $('#statusChangeRemarks').val().trim();
  636. var bookingId = currentSearchedBooking.bookingId;
  637. $('#btnVerifyCroOtp').prop('disabled', true).html('<i class="fa fa-spinner fa-spin me-1"></i> Verifying...');
  638. $('#croOtpErrorAlert').hide();
  639. $.ajax({
  640. type: 'POST',
  641. url: '/communityhall/cro/api/updateBookingStatus',
  642. data: {
  643. bookingId: bookingId,
  644. status: newStatus,
  645. comments: remarks,
  646. otp: otpValue
  647. },
  648. success: function (response) {
  649. $('#btnVerifyCroOtp').prop('disabled', false).html('<i class="fa fa-check-circle me-1"></i> Verify & Update Status');
  650. if (response && response.status === 'success') {
  651. $('#croStatusOtpModal').modal('hide');
  652. if (croResendTimer) clearInterval(croResendTimer);
  653. Swal.fire({
  654. icon: 'success',
  655. title: 'Status Updated!',
  656. html: 'Booking <b>#' + bookingId + '</b> status has been successfully changed to <b class="text-danger">' + newStatus + '</b>.',
  657. confirmButtonText: 'OK'
  658. }).then(() => {
  659. // Re-fetch booking details to update view in real-time
  660. searchBookingById();
  661. });
  662. } else {
  663. var errMsg = (response && response.message) || 'Failed to update status.';
  664. $('#croOtpErrorAlert').text(errMsg).show();
  665. }
  666. },
  667. error: function (xhr) {
  668. $('#btnVerifyCroOtp').prop('disabled', false).html('<i class="fa fa-check-circle me-1"></i> Verify & Update Status');
  669. var errMsg = 'Verification failed. Please try again.';
  670. if (xhr.responseJSON && xhr.responseJSON.message) {
  671. errMsg = xhr.responseJSON.message;
  672. } else if (xhr.responseText) {
  673. try {
  674. var parsed = JSON.parse(xhr.responseText);
  675. if (parsed.message) errMsg = parsed.message;
  676. } catch (e) { }
  677. }
  678. $('#croOtpErrorAlert').text(errMsg).show();
  679. }
  680. });
  681. }
  682. </script>
  683. </div>
  684. </body>
  685. </html>