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
545 lines
19 KiB
|
|
<!DOCTYPE html>
|
|
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
|
|
|
<head th:replace="~{fragments/user-head :: head}">
|
|
<title>Victoria Public Hall – Entry Pass</title>
|
|
</head>
|
|
|
|
<style>
|
|
body {
|
|
background: linear-gradient(135deg, #f3efe7, #e6e0d6);
|
|
min-height: 100vh;
|
|
}
|
|
|
|
/* Remove pcoded paddings ONLY for this page */
|
|
.pcoded-content,
|
|
.pcoded-wrapper,
|
|
.page-wrapper,
|
|
.main-body {
|
|
padding-left: 0 !important;
|
|
padding-right: 0 !important;
|
|
background: transparent !important;
|
|
}
|
|
|
|
/* ================== CENTER WRAPPER ================== */
|
|
.entry-wrapper {
|
|
width: 100%;
|
|
display: flex;
|
|
justify-content: center;
|
|
padding: 30px 12px;
|
|
}
|
|
|
|
/* ================== ENTRY CARD ================== */
|
|
.entry-card {
|
|
max-width: 500px;
|
|
min-height: 400px;
|
|
width: 100%;
|
|
background: #fffdf8;
|
|
border-radius: 18px;
|
|
padding: 24px 20px 28px;
|
|
box-shadow: 0 15px 40px rgba(0, 0, 0, 0.15);
|
|
position: relative;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
/* ================== HEADER ================== */
|
|
.vph-logo {
|
|
text-align: center;
|
|
margin-bottom: 6px;
|
|
}
|
|
|
|
.vph-logo img {
|
|
width: 60px;
|
|
}
|
|
|
|
.entry-title {
|
|
text-align: center;
|
|
font-weight: 700;
|
|
font-size: 16px;
|
|
letter-spacing: 0.5px;
|
|
color: #7a4a12;
|
|
margin-bottom: 22px;
|
|
}
|
|
|
|
/* ---------- FIXED INFO GRID ---------- */
|
|
.info-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(3, 1fr);
|
|
gap: 16px 12px;
|
|
margin-bottom: 40px;
|
|
}
|
|
|
|
.info-cell {
|
|
text-align: left;
|
|
}
|
|
|
|
.info-cell.center {
|
|
text-align: center;
|
|
}
|
|
|
|
.info-cell.right {
|
|
text-align: right;
|
|
}
|
|
|
|
.info-label {
|
|
font-size: 11px;
|
|
text-transform: uppercase;
|
|
color: #777;
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.info-value {
|
|
|
|
font-size: 15px;
|
|
font-weight: 700;
|
|
color: #222;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ================== ACTION BUTTONS ================== */
|
|
.action-row {
|
|
display: flex;
|
|
gap: 12px;
|
|
}
|
|
|
|
.btn-reject {
|
|
flex: 1;
|
|
background: #d90429;
|
|
color: #fff;
|
|
border-radius: 10px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.btn-accept {
|
|
flex: 1;
|
|
background: #0a8754;
|
|
color: #fff;
|
|
border-radius: 10px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.btn-reject:hover {
|
|
background: #b80322;
|
|
}
|
|
|
|
.btn-accept:hover {
|
|
background: #086f46;
|
|
}
|
|
|
|
/* ================== MOBILE ================== */
|
|
@media (max-width: 480px) {
|
|
.entry-card {
|
|
padding: 18px 16px 22px;
|
|
}
|
|
}
|
|
|
|
|
|
/* ================= PEOPLE DETAILS ================= */
|
|
|
|
|
|
.people-label {
|
|
font-size: 11px;
|
|
text-transform: uppercase;
|
|
color: #777;
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.people-value {
|
|
font-size: 15px;
|
|
font-weight: 700;
|
|
color: #222;
|
|
}
|
|
|
|
/* Mobile */
|
|
@media (max-width: 480px) {
|
|
.people-grid {
|
|
grid-template-columns: repeat(2, 1fr);
|
|
}
|
|
}
|
|
|
|
|
|
/* Disable state */
|
|
button:disabled {
|
|
opacity: 0.6;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
input:disabled {
|
|
background: #f1f1f1;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
|
|
.ticket-closed {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
|
|
margin-top: 20px;
|
|
padding: 14px;
|
|
|
|
font-size: 16px;
|
|
font-weight: 700;
|
|
color: #0b6b44;
|
|
|
|
background: #e6fbf1;
|
|
border: 1px solid #9be3c2;
|
|
border-radius: 10px;
|
|
}
|
|
</style>
|
|
|
|
<body>
|
|
|
|
<div th:replace="~{fragments/preloader :: div}"></div>
|
|
|
|
<div id="pcoded" class="pcoded">
|
|
<div class="pcoded-container">
|
|
|
|
<!-- NAV -->
|
|
<nav th:replace="~{fragments/user-nav :: nav}"></nav>
|
|
|
|
<div class="pcoded-main-container">
|
|
<div class="pcoded-wrapper">
|
|
<div class="pcoded-content">
|
|
<div class="pcoded-inner-content">
|
|
<div class="main-body">
|
|
<div class="page-wrapper">
|
|
|
|
<!-- CENTERED ENTRY CARD -->
|
|
<div class="entry-wrapper">
|
|
<div class="entry-card">
|
|
|
|
<!-- LOGO -->
|
|
<div class="vph-logo">
|
|
<img src="/victoriapublichall/images/VPH-logo_main-EN%201.png"
|
|
alt="Victoria Public Hall">
|
|
</div>
|
|
|
|
<div class="entry-title">
|
|
VICTORIA PUBLIC HALL – ENTRY PASS
|
|
</div>
|
|
|
|
<div class="divider"></div>
|
|
|
|
<!-- DETAILS -->
|
|
<div class="info-grid">
|
|
|
|
<div class="info-cell">
|
|
<div class="info-label">Reference ID</div>
|
|
<div class="info-value" th:text="${booking.ref_id}"></div>
|
|
</div>
|
|
|
|
<div class="info-cell center">
|
|
<div class="info-label">Visit Date</div>
|
|
<div class="info-value" th:text="${booking.visit_date}"></div>
|
|
</div>
|
|
|
|
<div class="info-cell right">
|
|
<div class="info-label">ID Proof</div>
|
|
<div class="info-value" th:text="${booking.id_name}"></div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="info-grid">
|
|
|
|
<div class="info-cell">
|
|
<div class="info-label">Visit Time</div>
|
|
<div class="info-value" th:text="${booking.timing}"></div>
|
|
</div>
|
|
|
|
<div class="info-cell center">
|
|
<div class="info-label">Total Visitors</div>
|
|
<div class="info-value" th:text="${booking.no_of_people}"></div>
|
|
</div>
|
|
|
|
<div class="info-cell right">
|
|
<div class="info-label">AV Slot</div>
|
|
<div class="info-value" th:text="${booking.avslottime}"></div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
<div class="info-grid">
|
|
|
|
<div class="info-cell">
|
|
<div class="people-label">Adults</div>
|
|
<div class="people-value" th:text="${booking.adult_count}"></div>
|
|
</div>
|
|
|
|
<div class="info-cell center">
|
|
<div class="people-label">Children</div>
|
|
<div class="people-value" th:text="${booking.child_count}"></div>
|
|
</div>
|
|
|
|
<div class="info-cell right">
|
|
<div class="people-label">Senior Citizen</div>
|
|
<div class="people-value" th:text="${booking.sc_count}"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="info-grid">
|
|
<div class="info-cell">
|
|
<div class="people-label">Students</div>
|
|
<div class="people-value" th:text="${booking.students_count}"></div>
|
|
</div>
|
|
|
|
<div class="info-cell center">
|
|
<div class="people-label">Physically Challenged</div>
|
|
<div class="people-value" th:text="${booking.pc_count}"></div>
|
|
</div>
|
|
|
|
<div class="info-cell right">
|
|
<div class="people-label">Foreigners</div>
|
|
<div class="people-value" th:text="${booking.foreigner_count}">
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="info-grid"
|
|
style="border-top: 1px dashed #ddd; padding-top: 15px;">
|
|
<div class="info-cell">
|
|
<div class="info-label" style="color:#0a8754;font-weight:700;">
|
|
Accepted</div>
|
|
<div class="info-value"
|
|
th:text="${booking.accepted_people != null ? booking.accepted_people : 0}">
|
|
</div>
|
|
</div>
|
|
<div class="info-cell center">
|
|
<div class="info-label" style="color:#d90429;font-weight:700;">
|
|
Rejected</div>
|
|
<div class="info-value"
|
|
th:text="${booking.rejected_people != null ? booking.rejected_people : 0}">
|
|
</div>
|
|
</div>
|
|
<div class="info-cell right">
|
|
<div class="info-label" style="color:#333;font-weight:700;">
|
|
Remaining</div>
|
|
<div class="info-value" th:text="${booking.remaining_count}"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ACTION SECTION (ONLY WHEN OPEN) -->
|
|
<div th:if="${gateViewMode == 'PENDING'}">
|
|
|
|
<div class="row mt-3">
|
|
<div class="col-6">
|
|
<div class="form-group mb-2">
|
|
<label class="text-success fw-bold">Accepted Count</label>
|
|
<input type="number" id="acceptedCount" class="form-control"
|
|
placeholder="0" min="0">
|
|
</div>
|
|
</div>
|
|
<div class="col-6">
|
|
<div class="form-group mb-2">
|
|
<label class="text-danger fw-bold">Rejected Count</label>
|
|
<input type="number" id="rejectedCount" class="form-control"
|
|
placeholder="0" min="0">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="d-flex mt-4 action-row justify-content-center">
|
|
<button id="submitBtn" class="btn btn-primary w-100"
|
|
onclick="submitEntry()">
|
|
Submit Entry
|
|
</button>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
<!-- FINAL STATUS (AFTER ACTION / REFRESH) -->
|
|
<div th:if="${gateViewMode == 'CLOSED'}" class="text-center mt-4">
|
|
|
|
<div style="font-size:18px;font-weight:700;color:#0a8754;">
|
|
Ticket Closed
|
|
</div>
|
|
|
|
<p class="mt-2 text-muted">
|
|
Entry already processed at the gate
|
|
</p>
|
|
|
|
</div>
|
|
|
|
<div th:if="${gateViewMode == 'CANCELLED'}" class="text-center mt-4">
|
|
|
|
<div style="font-size:18px;font-weight:700;color:#d90429;">
|
|
Booking Cancelled
|
|
</div>
|
|
|
|
<p class="mt-2 text-muted">
|
|
This booking was cancelled by the visitor
|
|
</p>
|
|
|
|
</div>
|
|
|
|
<div th:if="${gateViewMode == 'INVALID'}" class="text-center mt-4">
|
|
|
|
<div style="font-size:18px;font-weight:700;color:#ff9800;">
|
|
Invalid QR Code
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- FOOTER -->
|
|
<footer th:replace="~{fragments/footer :: footer}"></footer>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script th:replace="~{fragments/user-script :: script}"></script>
|
|
|
|
|
|
<script>
|
|
// Date Validation on Load
|
|
document.addEventListener("DOMContentLoaded", function () {
|
|
const visitDateStr = '[[${booking.visit_date}]]'; // Expected format dd-MM-yyyy
|
|
|
|
// Parse visitDate (dd-MM-yyyy)
|
|
const parts = visitDateStr.split('-');
|
|
// new Date(year, monthIndex, day)
|
|
const visitDate = new Date(parts[2], parts[1] - 1, parts[0]);
|
|
visitDate.setHours(0, 0, 0, 0);
|
|
|
|
const currentSystemDateStr = '[[${currentDate}]]'; // yyyy-MM-dd
|
|
const curParts = currentSystemDateStr.split('-');
|
|
const currentDate = new Date(curParts[0], curParts[1] - 1, curParts[2]);
|
|
currentDate.setHours(0, 0, 0, 0);
|
|
|
|
const submitBtn = document.getElementById("submitBtn");
|
|
const acceptedInput = document.getElementById("acceptedCount");
|
|
const rejectedInput = document.getElementById("rejectedCount");
|
|
|
|
if (visitDate < currentDate) {
|
|
Swal.fire({
|
|
icon: "error",
|
|
title: "Ticket Expired",
|
|
text: "This ticket is for a past date (" + visitDateStr + ")",
|
|
allowOutsideClick: false
|
|
});
|
|
disableInputs();
|
|
} else if (visitDate > currentDate) {
|
|
Swal.fire({
|
|
icon: "warning",
|
|
title: "Not Allowed",
|
|
text: "Your booking date is " + visitDateStr + ". Entry not allowed today.",
|
|
allowOutsideClick: false
|
|
});
|
|
disableInputs();
|
|
}
|
|
|
|
function disableInputs() {
|
|
if (submitBtn) submitBtn.disabled = true;
|
|
if (acceptedInput) acceptedInput.disabled = true;
|
|
if (rejectedInput) rejectedInput.disabled = true;
|
|
}
|
|
});
|
|
|
|
function submitEntry() {
|
|
|
|
const acceptedEl = document.getElementById("acceptedCount");
|
|
const rejectedEl = document.getElementById("rejectedCount");
|
|
|
|
if (!acceptedEl || !rejectedEl) {
|
|
Swal.fire("Error", "Page not loaded correctly", "error");
|
|
return;
|
|
}
|
|
|
|
let accepted = parseInt(acceptedEl.value) || 0;
|
|
let rejected = parseInt(rejectedEl.value) || 0;
|
|
|
|
const remaining = parseInt('[[${booking.remaining_count}]]');
|
|
const refId = '[[${booking.ref_id}]]';
|
|
|
|
if (accepted < 0 || rejected < 0) {
|
|
Swal.fire("Error", "Counts cannot be negative", "error");
|
|
return;
|
|
}
|
|
|
|
if (accepted === 0 && rejected === 0) {
|
|
Swal.fire("Error", "Please enter at least one accepted or rejected visitor", "warning");
|
|
return;
|
|
}
|
|
|
|
if ((accepted + rejected) > remaining) {
|
|
Swal.fire(
|
|
"Error",
|
|
"Total count (" + (accepted + rejected) + ") exceeds remaining visitors (" + remaining + ")",
|
|
"error"
|
|
);
|
|
return;
|
|
}
|
|
|
|
Swal.fire({
|
|
title: "Confirm Entry?",
|
|
html: `Accept: <b>${accepted}</b> <br> Reject: <b>${rejected}</b>`,
|
|
icon: "question",
|
|
showCancelButton: true,
|
|
confirmButtonText: "Submit",
|
|
confirmButtonColor: "#3085d6",
|
|
cancelButtonColor: "#d33"
|
|
}).then(result => {
|
|
|
|
if (!result.isConfirmed) return;
|
|
|
|
$.ajax({
|
|
url: "/victoriapublichall/bookingapi/entry-action",
|
|
type: "POST",
|
|
contentType: "application/json",
|
|
data: JSON.stringify({
|
|
refId: refId,
|
|
accepted: accepted,
|
|
rejected: rejected
|
|
}),
|
|
success: function (res) {
|
|
Swal.fire({
|
|
icon: "success",
|
|
title: "Success",
|
|
text: res.message,
|
|
confirmButtonText: "OK"
|
|
}).then(() => {
|
|
location.reload();
|
|
});
|
|
},
|
|
error: function (err) {
|
|
Swal.fire(
|
|
"Error",
|
|
err.responseText || "Action failed",
|
|
"error"
|
|
);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
|
|
|
|
</script>
|
|
|
|
</body>
|
|
|
|
</html>
|
|
|
|
|