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.

1069 lines
32 KiB

6 days ago
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
  1. <!DOCTYPE html>
  2. <html lang="en" xmlns:th="http://www.thymeleaf.org">
  3. <head th:replace="~{fragments/user-head :: head}"></head>
  4. <style>
  5. .navbar-logo{
  6. width:100% !important;
  7. }
  8. :root {
  9. --accent: #12363b;
  10. --muted: #6b6b6b;
  11. --card-bg: #ffffff;
  12. --slot-radius: 10px;
  13. --green-start: #16a34a;
  14. --green-end: #09905a;
  15. }
  16. body {
  17. font-family: "Inter", "Helvetica Neue", Arial, sans-serif;
  18. min-height: 100vh;
  19. background-attachment: fixed;
  20. background-repeat: no-repeat;
  21. background-size: cover;
  22. -webkit-font-smoothing: antialiased;
  23. min-height: 100vh;
  24. display: flex;
  25. flex-direction: column;
  26. }
  27. label {
  28. color: black;
  29. }
  30. /* Smooth horizontal scroll on mobile */
  31. @media (max-width: 768px) {
  32. .dataTables_wrapper {
  33. overflow-x: auto;
  34. }
  35. table.dataTable {
  36. width: 100% !important;
  37. white-space: nowrap;
  38. }
  39. }
  40. /* time slots container */
  41. .time-grid {
  42. display: grid;
  43. grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  44. gap: 12px;
  45. }
  46. .time-box {
  47. border: 2px solid #dee2e6;
  48. padding: 12px;
  49. border-radius: 10px;
  50. background: #fff;
  51. }
  52. .time-box.disabled {
  53. opacity: 0.5;
  54. pointer-events: none;
  55. }
  56. /* av slots style*/
  57. /* AV SLOT GRID */
  58. .av-grid {
  59. display: grid;
  60. grid-template-columns: repeat(3, 1fr); /* 3 per row */
  61. gap: 16px;
  62. }
  63. @media (max-width: 992px) {
  64. .av-grid {
  65. grid-template-columns: repeat(2, 1fr);
  66. }
  67. }
  68. @media (max-width: 576px) {
  69. .av-grid {
  70. grid-template-columns: 1fr;
  71. }
  72. }
  73. /* AV SLOT CARD */
  74. .av-card {
  75. border: 2px solid #28a745;
  76. border-radius: 12px;
  77. padding: 14px 16px;
  78. display: flex;
  79. justify-content: space-between;
  80. align-items: center;
  81. background: #fff;
  82. }
  83. .av-card.full {
  84. border-color: #dc3545;
  85. background: #fff5f5;
  86. }
  87. .av-time {
  88. font-weight: 600;
  89. font-size: 15px;
  90. }
  91. .av-seats {
  92. font-size: 13px;
  93. color: #6c757d;
  94. }
  95. .av-check input {
  96. width: 20px;
  97. height: 20px;
  98. }
  99. </style>
  100. <body>
  101. <div th:replace="~{fragments/preloader :: div}"></div>
  102. <div id="pcoded" class="pcoded">
  103. <div class="pcoded-container">
  104. <!-- TOP NAV -->
  105. <nav th:replace="~{fragments/nav-admin :: nav}"></nav>
  106. <nav th:replace="~{fragments/nav-adminmenu :: nav}"></nav>
  107. <div class="pcoded-main-container">
  108. <div class="pcoded-wrapper">
  109. <div class="pcoded-content">
  110. <div class="pcoded-inner-content">
  111. <br><br>
  112. <div class="container mt-4">
  113. <div class="card shadow">
  114. <!-- HEADER -->
  115. <div class="card-header bg-dark text-white ">
  116. <h4 class="text-white text-center mb-0">Bulk Booking Request List</h4>
  117. </div>
  118. <!-- BODY -->
  119. <div class="card-body">
  120. <div class="table-responsive">
  121. <table class="table table-bordered table-striped align-middle text-center" id="bookingTable"
  122. style="width:100%">
  123. <thead class="table-dark">
  124. <tr>
  125. <th class="text-center">S.No</th>
  126. <th class="text-center">Ref ID</th>
  127. <th class="text-center">Category</th>
  128. <th class="text-center">Mobile No</th>
  129. <th class="text-center">Prefer Date</th>
  130. <th class="text-center">No.of People</th>
  131. <th class="text-center">Payment Status</th>
  132. <th class="text-center">Action</th>
  133. </tr>
  134. </thead>
  135. <tbody></tbody>
  136. </table>
  137. </div>
  138. </div>
  139. <!-- FOOTER -->
  140. <div class="card-footer text-center">
  141. </div>
  142. </div>
  143. </div>
  144. </div>
  145. </div>
  146. </div>
  147. </div>
  148. </div>
  149. </div>
  150. <div class="modal fade" id="viewModal" tabindex="-1">
  151. <div class="modal-dialog modal-xl modal-dialog-centered modal-dialog-scrollable">
  152. <div class="modal-content">
  153. <div class="modal-header bg-dark text-white">
  154. <h5 class="modal-title">Bulk Booking Details</h5>
  155. <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
  156. </div>
  157. <div class="modal-body">
  158. <!-- hidden values -->
  159. <input type="hidden" id="v_idproofid" name="v_idproofid">
  160. <input type="hidden" id="v_userinput" name="v_userinput">
  161. <!-- BOOKING DETAILS -->
  162. <div class="row mb-2">
  163. <div class="col-md-4"><strong>Reference ID:</strong> <span id="v_refid"></span></div>
  164. <div class="col-md-4"><strong>Name:</strong> <span id="v_name"></span></div>
  165. <div class="col-md-4"><strong>Contact:</strong> <span id="v_userinput_text"></span></div>
  166. </div>
  167. <div class="row mb-2">
  168. <div class="col-md-4"><strong>Preferred Date:</strong> <span id="v_date"></span></div>
  169. <div class="col-md-4"><strong>Category:</strong> <span id="v_category"></span></div>
  170. <div class="col-md-4"><strong>No. of People:</strong> <span id="v_people"></span></div>
  171. </div>
  172. <div class="row mb-2">
  173. <div class="col-md-4"><strong>ID Proof:</strong> <span id="v_idproof"></span></div>
  174. <div class="col-md-4"><strong>ID Details:</strong> <span id="v_id_details"></span></div>
  175. <div class="col-md-4"><strong>Total Amount:</strong> <span id="v_amount"></span></div>
  176. </div>
  177. <div class="row mb-2">
  178. <div class="col-md-4"><strong>Institution:</strong> <span id="v_institution"></span></div>
  179. <div class="col-md-4"><strong>Organization:</strong> <span id="v_organization"></span></div>
  180. </div>
  181. <hr>
  182. <!-- VISITOR BREAKUP -->
  183. <h6 class="fw-bold">Visitor Breakup</h6>
  184. <div class="table-responsive">
  185. <table class="table table-bordered table-sm text-center">
  186. <thead class="table-secondary">
  187. <tr>
  188. <th>S.No</th>
  189. <th>Visitor Type</th>
  190. <th>No. of People</th>
  191. <th>Amount</th>
  192. <th>Total</th>
  193. </tr>
  194. </thead>
  195. <tbody id="visitorBreakupBody"></tbody>
  196. </table>
  197. </div>
  198. <!--<div class="row mt-2">
  199. <div class="col-md-12 text-right">
  200. <strong>Total Amount:</strong> ₹<span id="totalAmount">0</span>
  201. </div>
  202. </div>-->
  203. <!-- slots related div -->
  204. <div class="row mb-3">
  205. <div class="col-md-6">
  206. <label class="form-label">Select Date</label>
  207. <input type="date" class="form-control" id="searchdate">
  208. </div>
  209. <div class="col-md-6">
  210. <label class="form-label fw-bold">Capacity Summary</label>
  211. <input type="text"
  212. class="form-control"
  213. id="totalCapacity"
  214. value="0 / 0"
  215. readonly>
  216. </div>
  217. </div>
  218. <div class="row">
  219. <!-- LEFT : ALLOCATION -->
  220. <div class="col-md-8">
  221. <h6 class="fw-bold mt-3 d-none" id="timeSlotTitle">Allocated Time Slot</h6>
  222. <div id="timeSlotContainer" class="time-grid mb-4"></div>
  223. <h6 class="fw-bold d-none" id="avSlotTitle">Allocated AV Slots</h6>
  224. <div id="avSlotContainer" class="av-grid"></div>
  225. </div>
  226. <!-- RIGHT : INFO MESSAGE -->
  227. <div class="col-md-4">
  228. <div id="allocationMessage"
  229. class="alert alert-warning d-none"
  230. role="alert">
  231. </div>
  232. </div>
  233. </div>
  234. <!--<div class="row mb-3">
  235. <div class="col-md-8">
  236. <div id="timeSlotContainer" class="time-grid mb-4"></div>
  237. <div id="avSlotContainer" class="av-grid"></div>
  238. <div>
  239. <div class="col-md-4">
  240. <div id="allocationMessage"
  241. class="alert alert-warning d-none"
  242. role="alert">
  243. </div>
  244. </div>
  245. </div>-->
  246. <!-- onclick="saveBulkBooking()"-->
  247. <div class="text-center mt-4">
  248. <button type="button" class="btn btn-success" id="submitBtn" disabled >Submit</button>
  249. <button type="button" class="btn btn-secondary" onclick="goBack()">Back</button>
  250. </div>
  251. </div>
  252. </div>
  253. </div>
  254. </div>
  255. <script th:replace="~{fragments/user-script :: script}"></script>
  256. <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
  257. <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
  258. <!-- DataTables -->
  259. <link rel="stylesheet" href="https://cdn.datatables.net/1.13.8/css/dataTables.bootstrap5.min.css">
  260. <link rel="stylesheet" href="https://cdn.datatables.net/buttons/2.4.2/css/buttons.bootstrap5.min.css">
  261. <script src="https://cdn.datatables.net/1.13.8/js/jquery.dataTables.min.js"></script>
  262. <script src="https://cdn.datatables.net/1.13.8/js/dataTables.bootstrap5.min.js"></script>
  263. <script src="https://cdn.datatables.net/responsive/2.5.0/js/dataTables.responsive.min.js"></script>
  264. <!-- DataTable Buttons -->
  265. <script src="https://cdn.datatables.net/buttons/2.4.2/js/dataTables.buttons.min.js"></script>
  266. <script src="https://cdn.datatables.net/buttons/2.4.2/js/buttons.bootstrap5.min.js"></script>
  267. <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
  268. <script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.7/pdfmake.min.js"></script>
  269. <script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.7/vfs_fonts.js"></script>
  270. <script src="https://cdn.datatables.net/buttons/2.4.2/js/buttons.html5.min.js"></script>
  271. <!-- SweetAlert -->
  272. <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
  273. <script>
  274. <!-- global variables -->
  275. let totalPeople = 0;
  276. let timeSlotAllocated = 0;
  277. let allAVSlots = [];
  278. let currentBookingReadOnly = false;
  279. let currentRefId = null;
  280. $(document).ready(function () {
  281. $('#bookingTable').DataTable({
  282. ajax: {
  283. url: '/victoriapublichall/bookingapi/getrequestlist',
  284. type: 'GET',
  285. dataSrc: ''
  286. },
  287. columns: [
  288. {
  289. data: null,
  290. render: function (data, type, row, meta) {
  291. return meta.row + 1;
  292. }
  293. },
  294. {data: 'ref_id'},
  295. {data: 'category'},
  296. {
  297. data: 'mob_no',
  298. defaultContent: '-'
  299. },
  300. {
  301. data: 'prefer_visit_date',
  302. render: function (data) {
  303. return data ? data : '-';
  304. }
  305. },
  306. {data: 'no_of_people'},
  307. {data: 'payment_status'},
  308. {
  309. data: 'ref_id',
  310. render: function (data, type, row) {
  311. return `<button class="btn btn-info btn-sm" onclick="viewDetails('${data}')">View</button>`;
  312. }
  313. }
  314. ],
  315. responsive: true,
  316. destroy: true,
  317. pageLength: 10,
  318. lengthMenu: [10, 25, 50, 100]
  319. });
  320. });
  321. function viewDetails(refId) {
  322. $.ajax({
  323. url: "/victoriapublichall/bookingapi/getrequestbyrefid",
  324. type: "GET",
  325. data: {refId: refId},
  326. success: function (response) {
  327. let booking = response.booking;
  328. console.log("booking", booking)
  329. let visitors = response.visitorBreakup;
  330. currentRefId = booking.ref_id;
  331. currentBookingReadOnly = false;
  332. // BOOKING DETAILS
  333. $("#v_refid").text(booking.ref_id || "-");
  334. $("#v_name").text(booking.name || "-");
  335. $("#v_mobile").text(booking.mob_no || "-");
  336. $("#v_category").text(booking.category || "-");
  337. $("#v_date").text(booking.prefer_visit_date || "-");
  338. $("#v_people").text(booking.no_of_people || "-");
  339. $("#v_idproof").text(booking.id_name || "-");
  340. $("#v_id_details").text(booking.id_proof_number || "-");
  341. $("#v_institution").text(booking.institution_name || "-");
  342. $("#v_organization").text(booking.organization_name || "-");
  343. $("#v_amount").text(booking.total_amount || "-")
  344. // hidden
  345. $("#v_idproofid").text(booking.idproodid);
  346. $("#v_userinput_text").text(booking.mob_no || booking.email);
  347. // VISITOR BREAKUP
  348. let tbody = $("#visitorBreakupBody");
  349. tbody.empty();
  350. if (!visitors || visitors.length === 0) {
  351. tbody.append(`
  352. <tr>
  353. <td colspan="5">No visitor breakup data available</td>
  354. </tr>
  355. `);
  356. } else {
  357. visitors.forEach((row, index) => {
  358. let people = row.no_of_people || 0;
  359. let amount = row.amount || 0;
  360. let total = row.total_amount || 0;
  361. totalPeople += people;
  362. tbody.append(`
  363. <tr>
  364. <td>${index + 1}</td>
  365. <td>${row.user_type}</td>
  366. <td>${people}</td>
  367. <td>₹${amount}</td>
  368. <td>₹${total}</td>
  369. </tr>
  370. `);
  371. });
  372. }
  373. totalPeople = Number(booking.no_of_people || 0);
  374. timeSlotAllocated = 0;
  375. $("#totalCapacity").val(`0 / ${totalPeople}`);
  376. $("#searchdate").val("");
  377. $("#timeSlotContainer").empty();
  378. $("#avSlotContainer").empty();
  379. if (booking.reg_payment_status === "PENDING") {
  380. currentBookingReadOnly = true;
  381. $("#submitBtn").prop("disabled", true);
  382. $("#searchdate").prop("disabled", true);
  383. loadAdminAllocation(booking.ref_id);
  384. } else {
  385. currentBookingReadOnly = false;
  386. $("#submitBtn").prop("disabled", true);
  387. $("#searchdate").prop("disabled", false);
  388. }
  389. $("#viewModal").modal("show");
  390. },
  391. error: function () {
  392. Swal.fire("Error", "Unable to load booking details", "error");
  393. }
  394. });
  395. }
  396. // clear the fields while closing the modal
  397. $('#viewModal').on('hidden.bs.modal', function () {
  398. // 🔹 Reset globals
  399. totalPeople = 0;
  400. timeSlotAllocated = 0;
  401. currentBookingReadOnly = false;
  402. currentRefId = null;
  403. allAVSlots = [];
  404. $("#v_refid, #v_name, #v_mobile, #v_category, #v_date").text("");
  405. $("#v_people, #v_idproof, #v_id_details").text("");
  406. $("#v_institution, #v_organization, #v_amount").text("");
  407. $("#visitorBreakupBody").empty();
  408. $("#timeSlotContainer").empty();
  409. $("#avSlotContainer").empty();
  410. $("#timeSlotTitle, #avSlotTitle").addClass("d-none");
  411. // 🔹 Reset message panel
  412. $("#allocationMessage").addClass("d-none").html("");
  413. // 🔹 Reset date & capacity
  414. $("#searchdate").val("").prop("disabled", false);
  415. $("#totalCapacity").val("0 / 0");
  416. $("#submitBtn").show().prop("disabled", true);
  417. });
  418. // date related functions
  419. document.addEventListener("DOMContentLoaded", function () {
  420. const dateInput = document.getElementById("searchdate");
  421. const today = new Date();
  422. today.setDate(today.getDate() + 8);
  423. const yyyy = today.getFullYear();
  424. const mm = String(today.getMonth() + 1).padStart(2, '0');
  425. const dd = String(today.getDate()).padStart(2, '0');
  426. dateInput.min = `${yyyy}-${mm}-${dd}`;
  427. });
  428. $("#searchdate").on("change", function () {
  429. const visitDate = $(this).val();
  430. if (!visitDate) return;
  431. // tuesday not allowed bookings
  432. const selectedDate = new Date(visitDate);
  433. // 0=Sun, 1=Mon, 2=Tue ...
  434. if (selectedDate.getDay() === 2) {
  435. // 🔴 Clear already loaded slots
  436. $("#timeSlotContainer").empty();
  437. $("#avSlotContainer").empty();
  438. // Reset capacity
  439. timeSlotAllocated = 0;
  440. $("#totalCapacity").val(`0 / ${totalPeople}`);
  441. // Disable submit
  442. $("#submitBtn").prop("disabled", true);
  443. Swal.fire(
  444. "Maintenance Day",
  445. "Bookings not allowed on tuesdays. Please select another date.",
  446. "warning"
  447. );
  448. // Clear date
  449. $(this).val("");
  450. return;
  451. }
  452. $.get("/victoriapublichall/bookingapi/getTimeMaster", { visitDate: visitDate,
  453. view: "report" },
  454. function (data) {
  455. let html = "";
  456. data.forEach(slot => {
  457. if (slot.availCap <= 0) return;
  458. html += `
  459. <div class="time-box" data-time-id="${slot.time_id}">
  460. <div class="fw-bold">${slot.timing}</div>
  461. <div class="text-muted">
  462. <span class="time-available">${slot.availCap}</span> Available
  463. </div>
  464. <div class="form-check">
  465. <input type="checkbox"
  466. class="form-check-input slot-check"
  467. data-time-id="${slot.time_id}"
  468. data-available="${slot.availCap}">
  469. <label>Select Slot</label>
  470. </div>
  471. <div class="allocation-text text-success"></div>
  472. </div>`;
  473. });
  474. $("#timeSlotContainer").html(html);
  475. loadAllAVSlots(visitDate);
  476. });
  477. validateSubmitState();
  478. });
  479. $(document).on("change", ".slot-check", function () {
  480. timeSlotAllocated = 0;
  481. let remaining = totalPeople;
  482. $(".allocation-text").text("");
  483. $(".slot-check:checked").each(function () {
  484. if (remaining <= 0) return;
  485. const available = Number($(this).data("available"));
  486. const allocated = Math.min(available, remaining);
  487. remaining -= allocated;
  488. timeSlotAllocated += allocated;
  489. $(this).closest(".time-box")
  490. .find(".allocation-text")
  491. .text(`Allocated: ${allocated}`)
  492. .data("allocated", allocated);
  493. });
  494. $("#totalCapacity").val(`${timeSlotAllocated} / ${totalPeople}`);
  495. if (timeSlotAllocated === totalPeople) {
  496. $(".slot-check").each(function () {
  497. if (!this.checked) {
  498. $(this).prop("disabled", true);
  499. }
  500. });
  501. } else {
  502. // Enable again if allocation reduced
  503. $(".slot-check").prop("disabled", false);
  504. }
  505. renderSelectedAVSlots();
  506. validateSubmitState();
  507. });
  508. function loadAllAVSlots(visitDate) {
  509. $.get("/victoriapublichall/bookingapi/getTimeSlotDetailsbydate",
  510. { visitDate },
  511. function (data) {
  512. allAVSlots = data || [];
  513. }
  514. );
  515. }
  516. function renderSelectedAVSlots() {
  517. const selectedTimeIds = $(".slot-check:checked")
  518. .map(function () {
  519. return Number($(this).data("time-id"));
  520. })
  521. .get();
  522. if (selectedTimeIds.length === 0) {
  523. $("#avSlotContainer").html(
  524. "<p class='text-warning'>Select time slots to allocate AV</p>"
  525. );
  526. return;
  527. }
  528. let html = "";
  529. selectedTimeIds.forEach(timeId => {
  530. // get allocated people for THIS timeId
  531. const allocationText = $(`.slot-check[data-time-id="${timeId}"]`)
  532. .closest(".time-box")
  533. .find(".allocation-text")
  534. .text();
  535. const allocatedForTime = allocationText
  536. ? Number(allocationText.replace(/\D/g, ''))
  537. : 0;
  538. if (allocatedForTime <= 0) return;
  539. // AV slots ONLY for this timeId
  540. const avSlotsForTime = allAVSlots.filter(av =>
  541. av.time_id === timeId
  542. );
  543. html += renderAVSlotsPerTime(
  544. timeId,
  545. allocatedForTime,
  546. avSlotsForTime
  547. );
  548. });
  549. $("#avSlotContainer").html(html);
  550. if (currentBookingReadOnly) {
  551. $(".slot-check").prop("disabled", true);
  552. $(".av-checkbox").prop("disabled", true);
  553. }
  554. validateSubmitState();
  555. }
  556. function renderAVSlotsPerTime(timeId, peopleForTime, avSlots) {
  557. let remaining = peopleForTime;
  558. let html = "";
  559. avSlots.forEach(slot => {
  560. if (remaining <= 0 || slot.availCap <= 0) return;
  561. const allocated = Math.min(slot.availCap, remaining);
  562. remaining -= allocated;
  563. html += `
  564. <div class="av-card" data-time-id="${timeId}">
  565. <div>
  566. <div class="av-time">
  567. ${slot.timing} (${slot.language})
  568. </div>
  569. <div class="av-seats">
  570. Capacity: ${slot.availCap} | Allocated: <b>${allocated}</b>
  571. </div>
  572. </div>
  573. <div class="av-check">
  574. <input type="checkbox"
  575. checked
  576. disabled
  577. class="av-checkbox"
  578. data-slot-id="${slot.slot_id}">
  579. </div>
  580. </div>`;
  581. });
  582. return html;
  583. }
  584. function validateSubmitState() {
  585. if (currentBookingReadOnly) {
  586. $("#submitBtn").prop("disabled", true);
  587. return;
  588. }
  589. const visitDate = $("#searchdate").val();
  590. const selectedTimeCount = $(".slot-check:checked").length;
  591. const avCount = $(".av-card").length;
  592. let allocated = 0;
  593. $(".allocation-text").each(function () {
  594. const txt = $(this).text();
  595. if (txt.includes("Allocated")) {
  596. allocated += Number(txt.replace(/\D/g, ''));
  597. }
  598. });
  599. const isValid =
  600. visitDate &&
  601. selectedTimeCount > 0 &&
  602. avCount > 0 &&
  603. allocated === totalPeople;
  604. $("#submitBtn").prop("disabled", !isValid);
  605. }
  606. function loadAdminAllocation(bulkRefId) {
  607. $.get("/victoriapublichall/bookingapi/getBulkAllocation",
  608. { bulkRefId },
  609. function (res) {
  610. if (!res.allocated) {
  611. // No allocation yet → admin can allocate
  612. $("#searchdate").prop("disabled", false);
  613. $("#submitBtn").prop("disabled", true);
  614. $("#allocationMessage").addClass("d-none");
  615. $("#timeSlotTitle, #avSlotTitle").addClass("d-none");
  616. return;
  617. }
  618. $("#timeSlotTitle").removeClass("d-none");
  619. $("#avSlotTitle").removeClass("d-none");
  620. $("#searchdate").val(res.visitDate).prop("disabled", true);
  621. $("#submitBtn").hide();
  622. $("#timeSlotContainer").empty();
  623. $("#avSlotContainer").empty();
  624. let totalAllocated = 0;
  625. res.timeSlots.forEach(time => {
  626. totalAllocated += time.people;
  627. // TIME SLOT BOX
  628. $("#timeSlotContainer").append(`
  629. <div class="time-box disabled">
  630. <div class="fw-bold">${time.timeRange}</div>
  631. <div class="text-success">
  632. Allocated: ${time.people}
  633. </div>
  634. </div>
  635. `);
  636. // AV SLOTS
  637. time.avSlots.forEach(av => {
  638. $("#avSlotContainer").append(`
  639. <div class="av-card full">
  640. <div>
  641. <div class="av-time">
  642. ${av.time} (${av.language})
  643. </div>
  644. <div class="av-seats">
  645. Allocated: <b>${av.people}</b>
  646. </div>
  647. </div>
  648. </div>
  649. `);
  650. });
  651. });
  652. $("#totalCapacity").val(`${totalAllocated} / ${totalPeople}`);
  653. $("#allocationMessage")
  654. .removeClass("d-none")
  655. .html(`
  656. <h6 class="fw-bold mb-2">Allocation Status</h6>
  657. <p class="mb-1">
  658. This request (<b>${bulkRefId}</b>) has already been allocated.
  659. </p>
  660. <p class="mb-0 text-danger">
  661. Waiting for payment confirmation.
  662. </p>
  663. `);
  664. }
  665. );
  666. }
  667. // save bulk booking
  668. $("#submitBtn").on("click", function () {
  669. const bulkRefId = $("#v_refid").text();
  670. const mobNo = $("#v_mobile").text();
  671. const userInput = $("#v_userinput_text").text();
  672. const noOfPeople = $("#v_people").text();
  673. const idType = $("#v_idproofid").text();
  674. const idDetails = $("#v_id_details").text();
  675. const visitDate = $("#searchdate").val();
  676. if (currentBookingReadOnly) {
  677. Swal.fire("Info", "This booking is already submitted", "info");
  678. return;
  679. }
  680. let allocated = 0;
  681. $(".allocation-text").each(function () {
  682. const txt = $(this).text();
  683. if (txt.includes("Allocated")) {
  684. allocated += Number(txt.replace(/\D/g, ''));
  685. }
  686. });
  687. if (allocated !== totalPeople) {
  688. Swal.fire(
  689. "Allocation mismatch",
  690. `Total People: ${totalPeople}, Allocated: ${allocated}`,
  691. "error"
  692. );
  693. return;
  694. }
  695. $("#submitBtn").prop("disabled", true);
  696. if (!visitDate) {
  697. Swal.fire("Warning", "Please select visit date", "warning");
  698. return;
  699. }
  700. let timeIds = $(".slot-check:checked")
  701. .map(function () { return $(this).data("time-id"); })
  702. .get();
  703. let avSlotIds = $(".av-checkbox")
  704. .map(function () { return $(this).data("slot-id"); })
  705. .get();
  706. $.ajax({ url: "/victoriapublichall/bookingapi/saveBulkApproveBooking",
  707. type: "POST",
  708. traditional: true,
  709. data: {
  710. bulkRefId: bulkRefId,
  711. userInput: userInput,
  712. noOfPeople: noOfPeople,
  713. idType: idType,
  714. idDetails: idDetails,
  715. visitDate: visitDate,
  716. timeIds: timeIds,
  717. avSlotIds: avSlotIds },
  718. success: function () {
  719. Swal.fire({
  720. title: "Success",
  721. text: "Booking saved successfully",
  722. icon: "success",
  723. allowOutsideClick: false,
  724. allowEscapeKey: false,
  725. allowEnterKey: false,
  726. confirmButtonText: "OK"
  727. }).then((result) => {
  728. if (result.isConfirmed) {
  729. currentBookingReadOnly = true;
  730. $(".slot-check").prop("disabled", true);
  731. $(".av-checkbox").prop("disabled", true);
  732. $("#searchdate").prop("disabled", true);
  733. $("#viewModal").modal("hide");
  734. $("#bookingTable").DataTable().ajax.reload();
  735. }
  736. });
  737. },
  738. error: function () {
  739. $("#submitBtn").prop("disabled", false);
  740. Swal.fire("Error", "Save failed", "error");
  741. }
  742. });
  743. });
  744. // goback function
  745. function goBack() {
  746. $("#viewModal").modal("hide");
  747. currentBookingReadOnly = false;
  748. currentRefId = null;
  749. $("#timeSlotContainer").empty();
  750. $("#avSlotContainer").empty();
  751. $("#timeSlotTitle, #avSlotTitle").addClass("d-none");
  752. $("#searchdate")
  753. .val("")
  754. .prop("disabled", false);
  755. $("#totalCapacity").val(`0 / 0`);
  756. $(".slot-check, .av-checkbox").prop("disabled", false);
  757. }
  758. document.getElementById("searchdate").addEventListener("change", function () {
  759. const selectedDate = new Date(this.value);
  760. if (selectedDate.getDay() === 2) {
  761. Swal.fire(
  762. "Invalid Date",
  763. "Bookings are not allowed on Tuesdays.",
  764. "warning"
  765. );
  766. this.value = ""; // reset date
  767. }
  768. });
  769. </script>
  770. </body>
  771. </html>