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.

545 lines
19 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}">
  4. <title>Victoria Public Hall – Entry Pass</title>
  5. </head>
  6. <style>
  7. body {
  8. background: linear-gradient(135deg, #f3efe7, #e6e0d6);
  9. min-height: 100vh;
  10. }
  11. /* Remove pcoded paddings ONLY for this page */
  12. .pcoded-content,
  13. .pcoded-wrapper,
  14. .page-wrapper,
  15. .main-body {
  16. padding-left: 0 !important;
  17. padding-right: 0 !important;
  18. background: transparent !important;
  19. }
  20. /* ================== CENTER WRAPPER ================== */
  21. .entry-wrapper {
  22. width: 100%;
  23. display: flex;
  24. justify-content: center;
  25. padding: 30px 12px;
  26. }
  27. /* ================== ENTRY CARD ================== */
  28. .entry-card {
  29. max-width: 500px;
  30. min-height: 400px;
  31. width: 100%;
  32. background: #fffdf8;
  33. border-radius: 18px;
  34. padding: 24px 20px 28px;
  35. box-shadow: 0 15px 40px rgba(0, 0, 0, 0.15);
  36. position: relative;
  37. margin: 0 auto;
  38. }
  39. /* ================== HEADER ================== */
  40. .vph-logo {
  41. text-align: center;
  42. margin-bottom: 6px;
  43. }
  44. .vph-logo img {
  45. width: 60px;
  46. }
  47. .entry-title {
  48. text-align: center;
  49. font-weight: 700;
  50. font-size: 16px;
  51. letter-spacing: 0.5px;
  52. color: #7a4a12;
  53. margin-bottom: 22px;
  54. }
  55. /* ---------- FIXED INFO GRID ---------- */
  56. .info-grid {
  57. display: grid;
  58. grid-template-columns: repeat(3, 1fr);
  59. gap: 16px 12px;
  60. margin-bottom: 40px;
  61. }
  62. .info-cell {
  63. text-align: left;
  64. }
  65. .info-cell.center {
  66. text-align: center;
  67. }
  68. .info-cell.right {
  69. text-align: right;
  70. }
  71. .info-label {
  72. font-size: 11px;
  73. text-transform: uppercase;
  74. color: #777;
  75. margin-bottom: 4px;
  76. }
  77. .info-value {
  78. font-size: 15px;
  79. font-weight: 700;
  80. color: #222;
  81. }
  82. /* ================== ACTION BUTTONS ================== */
  83. .action-row {
  84. display: flex;
  85. gap: 12px;
  86. }
  87. .btn-reject {
  88. flex: 1;
  89. background: #d90429;
  90. color: #fff;
  91. border-radius: 10px;
  92. font-weight: 600;
  93. }
  94. .btn-accept {
  95. flex: 1;
  96. background: #0a8754;
  97. color: #fff;
  98. border-radius: 10px;
  99. font-weight: 600;
  100. }
  101. .btn-reject:hover {
  102. background: #b80322;
  103. }
  104. .btn-accept:hover {
  105. background: #086f46;
  106. }
  107. /* ================== MOBILE ================== */
  108. @media (max-width: 480px) {
  109. .entry-card {
  110. padding: 18px 16px 22px;
  111. }
  112. }
  113. /* ================= PEOPLE DETAILS ================= */
  114. .people-label {
  115. font-size: 11px;
  116. text-transform: uppercase;
  117. color: #777;
  118. margin-bottom: 4px;
  119. }
  120. .people-value {
  121. font-size: 15px;
  122. font-weight: 700;
  123. color: #222;
  124. }
  125. /* Mobile */
  126. @media (max-width: 480px) {
  127. .people-grid {
  128. grid-template-columns: repeat(2, 1fr);
  129. }
  130. }
  131. /* Disable state */
  132. button:disabled {
  133. opacity: 0.6;
  134. cursor: not-allowed;
  135. }
  136. input:disabled {
  137. background: #f1f1f1;
  138. cursor: not-allowed;
  139. }
  140. .ticket-closed {
  141. display: flex;
  142. align-items: center;
  143. justify-content: center;
  144. margin-top: 20px;
  145. padding: 14px;
  146. font-size: 16px;
  147. font-weight: 700;
  148. color: #0b6b44;
  149. background: #e6fbf1;
  150. border: 1px solid #9be3c2;
  151. border-radius: 10px;
  152. }
  153. </style>
  154. <body>
  155. <div th:replace="~{fragments/preloader :: div}"></div>
  156. <div id="pcoded" class="pcoded">
  157. <div class="pcoded-container">
  158. <!-- NAV -->
  159. <nav th:replace="~{fragments/user-nav :: nav}"></nav>
  160. <div class="pcoded-main-container">
  161. <div class="pcoded-wrapper">
  162. <div class="pcoded-content">
  163. <div class="pcoded-inner-content">
  164. <div class="main-body">
  165. <div class="page-wrapper">
  166. <!-- CENTERED ENTRY CARD -->
  167. <div class="entry-wrapper">
  168. <div class="entry-card">
  169. <!-- LOGO -->
  170. <div class="vph-logo">
  171. <img src="/victoriapublichall/images/VPH-logo_main-EN%201.png"
  172. alt="Victoria Public Hall">
  173. </div>
  174. <div class="entry-title">
  175. VICTORIA PUBLIC HALL – ENTRY PASS
  176. </div>
  177. <div class="divider"></div>
  178. <!-- DETAILS -->
  179. <div class="info-grid">
  180. <div class="info-cell">
  181. <div class="info-label">Reference ID</div>
  182. <div class="info-value" th:text="${booking.ref_id}"></div>
  183. </div>
  184. <div class="info-cell center">
  185. <div class="info-label">Visit Date</div>
  186. <div class="info-value" th:text="${booking.visit_date}"></div>
  187. </div>
  188. <div class="info-cell right">
  189. <div class="info-label">ID Proof</div>
  190. <div class="info-value" th:text="${booking.id_name}"></div>
  191. </div>
  192. </div>
  193. <div class="info-grid">
  194. <div class="info-cell">
  195. <div class="info-label">Visit Time</div>
  196. <div class="info-value" th:text="${booking.timing}"></div>
  197. </div>
  198. <div class="info-cell center">
  199. <div class="info-label">Total Visitors</div>
  200. <div class="info-value" th:text="${booking.no_of_people}"></div>
  201. </div>
  202. <div class="info-cell right">
  203. <div class="info-label">AV Slot</div>
  204. <div class="info-value" th:text="${booking.avslottime}"></div>
  205. </div>
  206. </div>
  207. <div class="info-grid">
  208. <div class="info-cell">
  209. <div class="people-label">Adults</div>
  210. <div class="people-value" th:text="${booking.adult_count}"></div>
  211. </div>
  212. <div class="info-cell center">
  213. <div class="people-label">Children</div>
  214. <div class="people-value" th:text="${booking.child_count}"></div>
  215. </div>
  216. <div class="info-cell right">
  217. <div class="people-label">Senior Citizen</div>
  218. <div class="people-value" th:text="${booking.sc_count}"></div>
  219. </div>
  220. </div>
  221. <div class="info-grid">
  222. <div class="info-cell">
  223. <div class="people-label">Students</div>
  224. <div class="people-value" th:text="${booking.students_count}"></div>
  225. </div>
  226. <div class="info-cell center">
  227. <div class="people-label">Physically Challenged</div>
  228. <div class="people-value" th:text="${booking.pc_count}"></div>
  229. </div>
  230. <div class="info-cell right">
  231. <div class="people-label">Foreigners</div>
  232. <div class="people-value" th:text="${booking.foreigner_count}">
  233. </div>
  234. </div>
  235. </div>
  236. <div class="info-grid"
  237. style="border-top: 1px dashed #ddd; padding-top: 15px;">
  238. <div class="info-cell">
  239. <div class="info-label" style="color:#0a8754;font-weight:700;">
  240. Accepted</div>
  241. <div class="info-value"
  242. th:text="${booking.accepted_people != null ? booking.accepted_people : 0}">
  243. </div>
  244. </div>
  245. <div class="info-cell center">
  246. <div class="info-label" style="color:#d90429;font-weight:700;">
  247. Rejected</div>
  248. <div class="info-value"
  249. th:text="${booking.rejected_people != null ? booking.rejected_people : 0}">
  250. </div>
  251. </div>
  252. <div class="info-cell right">
  253. <div class="info-label" style="color:#333;font-weight:700;">
  254. Remaining</div>
  255. <div class="info-value" th:text="${booking.remaining_count}"></div>
  256. </div>
  257. </div>
  258. <!-- ACTION SECTION (ONLY WHEN OPEN) -->
  259. <div th:if="${gateViewMode == 'PENDING'}">
  260. <div class="row mt-3">
  261. <div class="col-6">
  262. <div class="form-group mb-2">
  263. <label class="text-success fw-bold">Accepted Count</label>
  264. <input type="number" id="acceptedCount" class="form-control"
  265. placeholder="0" min="0">
  266. </div>
  267. </div>
  268. <div class="col-6">
  269. <div class="form-group mb-2">
  270. <label class="text-danger fw-bold">Rejected Count</label>
  271. <input type="number" id="rejectedCount" class="form-control"
  272. placeholder="0" min="0">
  273. </div>
  274. </div>
  275. </div>
  276. <div class="d-flex mt-4 action-row justify-content-center">
  277. <button id="submitBtn" class="btn btn-primary w-100"
  278. onclick="submitEntry()">
  279. Submit Entry
  280. </button>
  281. </div>
  282. </div>
  283. <!-- FINAL STATUS (AFTER ACTION / REFRESH) -->
  284. <div th:if="${gateViewMode == 'CLOSED'}" class="text-center mt-4">
  285. <div style="font-size:18px;font-weight:700;color:#0a8754;">
  286. Ticket Closed
  287. </div>
  288. <p class="mt-2 text-muted">
  289. Entry already processed at the gate
  290. </p>
  291. </div>
  292. <div th:if="${gateViewMode == 'CANCELLED'}" class="text-center mt-4">
  293. <div style="font-size:18px;font-weight:700;color:#d90429;">
  294. Booking Cancelled
  295. </div>
  296. <p class="mt-2 text-muted">
  297. This booking was cancelled by the visitor
  298. </p>
  299. </div>
  300. <div th:if="${gateViewMode == 'INVALID'}" class="text-center mt-4">
  301. <div style="font-size:18px;font-weight:700;color:#ff9800;">
  302. Invalid QR Code
  303. </div>
  304. </div>
  305. </div>
  306. </div>
  307. </div>
  308. </div>
  309. </div>
  310. </div>
  311. </div>
  312. <!-- FOOTER -->
  313. <footer th:replace="~{fragments/footer :: footer}"></footer>
  314. </div>
  315. </div>
  316. </div>
  317. <script th:replace="~{fragments/user-script :: script}"></script>
  318. <script>
  319. // Date Validation on Load
  320. document.addEventListener("DOMContentLoaded", function () {
  321. const visitDateStr = '[[${booking.visit_date}]]'; // Expected format dd-MM-yyyy
  322. // Parse visitDate (dd-MM-yyyy)
  323. const parts = visitDateStr.split('-');
  324. // new Date(year, monthIndex, day)
  325. const visitDate = new Date(parts[2], parts[1] - 1, parts[0]);
  326. visitDate.setHours(0, 0, 0, 0);
  327. const currentSystemDateStr = '[[${currentDate}]]'; // yyyy-MM-dd
  328. const curParts = currentSystemDateStr.split('-');
  329. const currentDate = new Date(curParts[0], curParts[1] - 1, curParts[2]);
  330. currentDate.setHours(0, 0, 0, 0);
  331. const submitBtn = document.getElementById("submitBtn");
  332. const acceptedInput = document.getElementById("acceptedCount");
  333. const rejectedInput = document.getElementById("rejectedCount");
  334. if (visitDate < currentDate) {
  335. Swal.fire({
  336. icon: "error",
  337. title: "Ticket Expired",
  338. text: "This ticket is for a past date (" + visitDateStr + ")",
  339. allowOutsideClick: false
  340. });
  341. disableInputs();
  342. } else if (visitDate > currentDate) {
  343. Swal.fire({
  344. icon: "warning",
  345. title: "Not Allowed",
  346. text: "Your booking date is " + visitDateStr + ". Entry not allowed today.",
  347. allowOutsideClick: false
  348. });
  349. disableInputs();
  350. }
  351. function disableInputs() {
  352. if (submitBtn) submitBtn.disabled = true;
  353. if (acceptedInput) acceptedInput.disabled = true;
  354. if (rejectedInput) rejectedInput.disabled = true;
  355. }
  356. });
  357. function submitEntry() {
  358. const acceptedEl = document.getElementById("acceptedCount");
  359. const rejectedEl = document.getElementById("rejectedCount");
  360. if (!acceptedEl || !rejectedEl) {
  361. Swal.fire("Error", "Page not loaded correctly", "error");
  362. return;
  363. }
  364. let accepted = parseInt(acceptedEl.value) || 0;
  365. let rejected = parseInt(rejectedEl.value) || 0;
  366. const remaining = parseInt('[[${booking.remaining_count}]]');
  367. const refId = '[[${booking.ref_id}]]';
  368. if (accepted < 0 || rejected < 0) {
  369. Swal.fire("Error", "Counts cannot be negative", "error");
  370. return;
  371. }
  372. if (accepted === 0 && rejected === 0) {
  373. Swal.fire("Error", "Please enter at least one accepted or rejected visitor", "warning");
  374. return;
  375. }
  376. if ((accepted + rejected) > remaining) {
  377. Swal.fire(
  378. "Error",
  379. "Total count (" + (accepted + rejected) + ") exceeds remaining visitors (" + remaining + ")",
  380. "error"
  381. );
  382. return;
  383. }
  384. Swal.fire({
  385. title: "Confirm Entry?",
  386. html: `Accept: <b>${accepted}</b> <br> Reject: <b>${rejected}</b>`,
  387. icon: "question",
  388. showCancelButton: true,
  389. confirmButtonText: "Submit",
  390. confirmButtonColor: "#3085d6",
  391. cancelButtonColor: "#d33"
  392. }).then(result => {
  393. if (!result.isConfirmed) return;
  394. $.ajax({
  395. url: "/victoriapublichall/bookingapi/entry-action",
  396. type: "POST",
  397. contentType: "application/json",
  398. data: JSON.stringify({
  399. refId: refId,
  400. accepted: accepted,
  401. rejected: rejected
  402. }),
  403. success: function (res) {
  404. Swal.fire({
  405. icon: "success",
  406. title: "Success",
  407. text: res.message,
  408. confirmButtonText: "OK"
  409. }).then(() => {
  410. location.reload();
  411. });
  412. },
  413. error: function (err) {
  414. Swal.fire(
  415. "Error",
  416. err.responseText || "Action failed",
  417. "error"
  418. );
  419. }
  420. });
  421. });
  422. }
  423. </script>
  424. </body>
  425. </html>