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

<!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>