|
|
<!DOCTYPE html><html lang="en" xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{fragments/user-head :: head}"></head>
<style>
.navbar-logo{ width:100% !important; }
:root { --accent: #12363b; --muted: #6b6b6b; --card-bg: #ffffff; --slot-radius: 10px; --green-start: #16a34a; --green-end: #09905a; }
body { font-family: "Inter", "Helvetica Neue", Arial, sans-serif; min-height: 100vh; background-attachment: fixed; background-repeat: no-repeat; background-size: cover; -webkit-font-smoothing: antialiased; min-height: 100vh; display: flex; flex-direction: column;
}
label { color: black; } /* Smooth horizontal scroll on mobile */ @media (max-width: 768px) { .dataTables_wrapper { overflow-x: auto; }
table.dataTable { width: 100% !important; white-space: nowrap; } }
/* time slots container */
.time-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 12px; }
.time-box { border: 2px solid #dee2e6; padding: 12px; border-radius: 10px; background: #fff; }
.time-box.disabled { opacity: 0.5; pointer-events: none; }
/* av slots style*/
/* AV SLOT GRID */ .av-grid { display: grid; grid-template-columns: repeat(3, 1fr); /* 3 per row */ gap: 16px; }
@media (max-width: 992px) { .av-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 576px) { .av-grid { grid-template-columns: 1fr; } }
/* AV SLOT CARD */ .av-card { border: 2px solid #28a745; border-radius: 12px; padding: 14px 16px; display: flex; justify-content: space-between; align-items: center; background: #fff; }
.av-card.full { border-color: #dc3545; background: #fff5f5; }
.av-time { font-weight: 600; font-size: 15px; }
.av-seats { font-size: 13px; color: #6c757d; }
.av-check input { width: 20px; height: 20px; }
/* ===== Allocated Time Slot Card ===== */ .allocated-time-card { background: #f1f3f5; border-radius: 14px; padding: 18px 20px; border: 1px solid #e0e0e0; margin-bottom: 15px; max-width: 380px; }
.allocated-time-range { font-size: 18px; font-weight: 600; color: #6c757d; }
.allocated-time-count { margin-top: 8px; font-size: 16px; font-weight: 500; color: #28a745; }
/* ===== Allocated AV Slot Card ===== */ .allocated-av-card { border: 2px solid #dc3545; border-radius: 14px; padding: 16px 18px; background: #fff5f5; margin-bottom: 15px; }
.allocated-av-title { font-size: 16px; font-weight: 600; color: #343a40; }
.allocated-av-count { font-size: 14px; margin-top: 6px; color: #6c757d; }
</style><body><div th:replace="~{fragments/preloader :: div}"></div><div id="pcoded" class="pcoded"> <div class="pcoded-container"> <!-- TOP NAV --> <nav th:replace="~{fragments/nav-admin :: nav}"></nav>
<nav th:replace="~{fragments/nav-adminmenu :: nav}"></nav>
<div class="pcoded-main-container"> <div class="pcoded-wrapper"> <div class="pcoded-content"> <div class="pcoded-inner-content"> <br><br> <div class="container mt-4"> <div class="card shadow">
<!-- HEADER --> <div class="card-header bg-dark text-white "> <h4 class="text-white text-center mb-0">Completed List</h4>
</div>
<!-- BODY --> <div class="card-body"> <div class="table-responsive"> <table class="table table-bordered table-striped align-middle text-center" id="bookingTable" style="width:100%"> <thead class="table-dark"> <tr> <th class="text-center">S.No</th> <th class="text-center">Ref ID</th> <th class="text-center">Category</th> <th class="text-center">Mobile No</th> <th class="text-center">Prefer Date</th> <th class="text-center">Visit Date</th> <th class="text-center">No of People</th> <th class="text-center">Payment Status</th> <th class="text-center">Action</th> </tr> </thead> <tbody></tbody> </table> </div>
</div>
<!-- FOOTER --> <div class="card-footer text-center"> </div>
</div> </div>
</div> </div> </div> </div> </div></div>
<div class="modal fade" id="viewModal" tabindex="-1"> <div class="modal-dialog modal-xl modal-dialog-centered modal-dialog-scrollable"> <div class="modal-content">
<div class="modal-header bg-dark text-white"> <h5 class="modal-title">Bulk Booking Details</h5> <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button> </div>
<div class="modal-body">
<!-- hidden values --> <input type="hidden" id="v_idproofid" name="v_idproofid"> <input type="hidden" id="v_userinput" name="v_userinput">
<!-- BOOKING DETAILS --> <div class="row mb-2"> <div class="col-md-4"><strong>Reference ID:</strong> <span id="v_refid"></span></div> <div class="col-md-4"><strong>Name:</strong> <span id="v_name"></span></div> <div class="col-md-4"><strong>Contact:</strong> <span id="v_userinput_text"></span></div>
</div>
<div class="row mb-2">
<div class="col-md-4"><strong>Preferred Date:</strong> <span id="v_date"></span></div> <div class="col-md-4"><strong>Category:</strong> <span id="v_category"></span></div> <div class="col-md-4"><strong>No. of People:</strong> <span id="v_people"></span></div>
</div>
<div class="row mb-2"> <div class="col-md-4"><strong>ID Proof:</strong> <span id="v_idproof"></span></div> <div class="col-md-4"><strong>ID Details:</strong> <span id="v_id_details"></span></div> <div class="col-md-4"><strong>Total Amount:</strong> <span id="v_amount"></span></div> </div>
<div class="row mb-2">
<div class="col-md-4"><strong>Institution:</strong> <span id="v_institution"></span></div> <div class="col-md-4"><strong>Organization:</strong> <span id="v_organization"></span></div> </div>
<hr>
<div class="row"> <div class="col-md-8">
<h6 class="fw-bold mt-3 d-none" id="timeSlotTitle">Allocated Time Slot</h6> <div id="timeSlotContainer" class="time-grid mb-4"></div>
<h6 class="fw-bold d-none" id="avSlotTitle">Allocated AV Slots</h6> <div id="avSlotContainer" class="av-grid"></div>
</div>
</div>
<div class="text-center mt-4"> <button type="button" class="btn btn-secondary" onclick="goBack()">Back</button> </div>
</div> </div> </div></div>
<script th:replace="~{fragments/user-script :: script}"></script><link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"><script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<!-- DataTables --><link rel="stylesheet" href="https://cdn.datatables.net/1.13.8/css/dataTables.bootstrap5.min.css"><link rel="stylesheet" href="https://cdn.datatables.net/buttons/2.4.2/css/buttons.bootstrap5.min.css">
<script src="https://cdn.datatables.net/1.13.8/js/jquery.dataTables.min.js"></script><script src="https://cdn.datatables.net/1.13.8/js/dataTables.bootstrap5.min.js"></script><script src="https://cdn.datatables.net/responsive/2.5.0/js/dataTables.responsive.min.js"></script>
<!-- DataTable Buttons --><script src="https://cdn.datatables.net/buttons/2.4.2/js/dataTables.buttons.min.js"></script><script src="https://cdn.datatables.net/buttons/2.4.2/js/buttons.bootstrap5.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.7/pdfmake.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.7/vfs_fonts.js"></script><script src="https://cdn.datatables.net/buttons/2.4.2/js/buttons.html5.min.js"></script>
<!-- SweetAlert --><script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script>
<!-- global variables -->
let totalPeople = 0; let timeSlotAllocated = 0; let allAVSlots = [];
let currentBookingReadOnly = false; let currentRefId = null;
$(document).ready(function () {
$('#bookingTable').DataTable({ ajax: { url: '/victoriapublichall/bookingapi/getcompletedlist', type: 'GET', dataSrc: '' },
columns: [ { data: null, render: function (data, type, row, meta) { return meta.row + 1; } }, {data: 'ref_id'}, {data: 'category'}, { data: 'mob_no', defaultContent: '-' },
{ data: 'prefer_visit_date', render: function (data) { return data ? data : '-'; } }, {data: 'latest_visit_date'},
{data: 'no_of_people'}, {data: 'payment_status'}, { data: 'ref_id', render: function (data, type, row) { return `<button class="btn btn-info btn-sm" onclick="viewDetails('${data}')">View</button>`; } } ],
responsive: true, destroy: true, pageLength: 10, lengthMenu: [10, 25, 50, 100] });
});
function viewDetails(refId) {
$.ajax({ url: "/victoriapublichall/bookingapi/getrequestbyrefid", type: "GET", data: {refId: refId},
success: function (response) {
let booking = response.booking; console.log("booking", booking) let visitors = response.visitorBreakup;
currentRefId = booking.ref_id; currentBookingReadOnly = false;
// BOOKING DETAILS $("#v_refid").text(booking.ref_id || "-"); $("#v_name").text(booking.name || "-"); $("#v_mobile").text(booking.mob_no || "-"); $("#v_category").text(booking.category || "-"); $("#v_date").text(booking.prefer_visit_date || "-"); $("#v_people").text(booking.no_of_people || "-"); $("#v_idproof").text(booking.id_name || "-"); $("#v_id_details").text(booking.id_proof_number || "-"); $("#v_institution").text(booking.institution_name || "-"); $("#v_organization").text(booking.organization_name || "-"); $("#v_amount").text(booking.total_amount || "-")
// hidden
$("#v_idproofid").text(booking.idproodid); $("#v_userinput_text").text(booking.mob_no || booking.email);
// VISITOR BREAKUP let tbody = $("#visitorBreakupBody"); tbody.empty();
if (!visitors || visitors.length === 0) { tbody.append(` <tr> <td colspan="5">No visitor breakup data available</td> </tr> `); } else { visitors.forEach((row, index) => {
let people = row.no_of_people || 0; let amount = row.amount || 0; let total = row.total_amount || 0;
totalPeople += people;
tbody.append(` <tr> <td>${index + 1}</td> <td>${row.user_type}</td> <td>${people}</td> <td>₹${amount}</td> <td>₹${total}</td> </tr> `); }); }
totalPeople = Number(booking.no_of_people || 0); timeSlotAllocated = 0; $("#totalCapacity").val(`0 / ${totalPeople}`);
$("#timeSlotContainer").empty(); $("#avSlotContainer").empty();
// Always load allocation for completed page currentBookingReadOnly = true; $("#submitBtn").hide(); $("#searchdate").prop("disabled", true);
loadAdminAllocation(booking.ref_id);
$("#viewModal").modal("show"); },
error: function () { Swal.fire("Error", "Unable to load booking details", "error"); } }); }
// clear the fields while closing the modal
$('#viewModal').on('hidden.bs.modal', function () {
// 🔹 Reset globals totalPeople = 0; timeSlotAllocated = 0; currentBookingReadOnly = false; currentRefId = null; allAVSlots = [];
$("#v_refid, #v_name, #v_mobile, #v_category, #v_date").text(""); $("#v_people, #v_idproof, #v_id_details").text(""); $("#v_institution, #v_organization, #v_amount").text("");
$("#visitorBreakupBody").empty(); $("#timeSlotContainer").empty(); $("#avSlotContainer").empty();
$("#timeSlotTitle, #avSlotTitle").addClass("d-none");
// 🔹 Reset message panel $("#allocationMessage").addClass("d-none").html("");
// 🔹 Reset date & capacity $("#searchdate").val("").prop("disabled", false); $("#totalCapacity").val("0 / 0");
$("#submitBtn").show().prop("disabled", true); });
$(document).on("change", ".slot-check", function () {
timeSlotAllocated = 0; let remaining = totalPeople;
$(".allocation-text").text("");
$(".slot-check:checked").each(function () {
if (remaining <= 0) return;
const available = Number($(this).data("available")); const allocated = Math.min(available, remaining);
remaining -= allocated; timeSlotAllocated += allocated;
$(this).closest(".time-box") .find(".allocation-text") .text(`Allocated: ${allocated}`) .data("allocated", allocated); });
$("#totalCapacity").val(`${timeSlotAllocated} / ${totalPeople}`);
if (timeSlotAllocated === totalPeople) {
$(".slot-check").each(function () { if (!this.checked) { $(this).prop("disabled", true); } });
} else { // Enable again if allocation reduced $(".slot-check").prop("disabled", false); } renderSelectedAVSlots();
validateSubmitState(); });
function loadAllAVSlots(visitDate) {
$.get("/victoriapublichall/bookingapi/getTimeSlotDetailsbydate", { visitDate }, function (data) { allAVSlots = data || []; } ); }
function renderSelectedAVSlots() {
const selectedTimeIds = $(".slot-check:checked") .map(function () { return Number($(this).data("time-id")); }) .get();
if (selectedTimeIds.length === 0) { $("#avSlotContainer").html( "<p class='text-warning'>Select time slots to allocate AV</p>" ); return; }
let html = "";
selectedTimeIds.forEach(timeId => {
// get allocated people for THIS timeId const allocationText = $(`.slot-check[data-time-id="${timeId}"]`) .closest(".time-box") .find(".allocation-text") .text();
const allocatedForTime = allocationText ? Number(allocationText.replace(/\D/g, '')) : 0;
if (allocatedForTime <= 0) return;
// AV slots ONLY for this timeId const avSlotsForTime = allAVSlots.filter(av => av.time_id === timeId );
html += renderAVSlotsPerTime( timeId, allocatedForTime, avSlotsForTime ); });
$("#avSlotContainer").html(html);
if (currentBookingReadOnly) { $(".slot-check").prop("disabled", true); $(".av-checkbox").prop("disabled", true); }
validateSubmitState();
}
function renderAVSlotsPerTime(timeId, peopleForTime, avSlots) {
let remaining = peopleForTime; let html = "";
avSlots.forEach(slot => {
if (remaining <= 0 || slot.availCap <= 0) return;
const allocated = Math.min(slot.availCap, remaining); remaining -= allocated;
html += ` <div class="av-card" data-time-id="${timeId}"> <div> <div class="av-time"> ${slot.timing} (${slot.language}) </div> <div class="av-seats"> Capacity: ${slot.availCap} | Allocated: <b>${allocated}</b> </div> </div> <div class="av-check"> <input type="checkbox" checked disabled class="av-checkbox" data-slot-id="${slot.slot_id}"> </div> </div>`; });
return html; }
// validation of submit button
function validateSubmitState() {
if (currentBookingReadOnly) { $("#submitBtn").prop("disabled", true); return; }
const visitDate = $("#searchdate").val(); const selectedTimeCount = $(".slot-check:checked").length; const avCount = $(".av-card").length;
let allocated = 0; $(".allocation-text").each(function () { const txt = $(this).text(); if (txt.includes("Allocated")) { allocated += Number(txt.replace(/\D/g, '')); } });
const isValid = visitDate && selectedTimeCount > 0 && avCount > 0 && allocated === totalPeople;
$("#submitBtn").prop("disabled", !isValid); }
function loadAdminAllocation(bulkRefId) {
$.get("/victoriapublichall/bookingapi/getBulkAllocation", { bulkRefId }, function (res) {
if (!res.allocated) { // No allocation yet → admin can allocate $("#searchdate").prop("disabled", false); $("#submitBtn").prop("disabled", true);
$("#allocationMessage").addClass("d-none"); $("#timeSlotTitle, #avSlotTitle").addClass("d-none");
return; }
$("#timeSlotTitle").removeClass("d-none"); $("#avSlotTitle").removeClass("d-none");
$("#submitBtn").hide();
$("#timeSlotContainer").empty(); $("#avSlotContainer").empty();
let totalAllocated = 0;
res.timeSlots.forEach(time => {
totalAllocated += time.people;
// TIME SLOT BOX $("#timeSlotContainer").append(` <div class="allocated-time-card"> <div class="allocated-time-range"> ${time.timeRange} </div> <div class="allocated-time-count"> Allocated: ${time.people} </div> </div> `);
// AV SLOTS time.avSlots.forEach(av => { $("#avSlotContainer").append(` <div class="allocated-av-card"> <div class="allocated-av-title"> ${av.time} (${av.language}) </div> <div class="allocated-av-count"> Allocated: ${av.people} </div> </div> `); }); });
$("#totalCapacity").val(`${totalAllocated} / ${totalPeople}`);
$("#allocationMessage") .removeClass("d-none") .html(` <h6 class="fw-bold mb-2">Allocation Status</h6> <p class="mb-1"> This request (<b>${bulkRefId}</b>) has already been allocated. </p> <p class="mb-0 text-danger"> Waiting for payment confirmation. </p> `); } ); }
// goback function
function goBack() {
$("#viewModal").modal("hide");
currentBookingReadOnly = false; currentRefId = null;
$("#timeSlotContainer").empty(); $("#avSlotContainer").empty();
$("#timeSlotTitle, #avSlotTitle").addClass("d-none");
$("#searchdate") .val("") .prop("disabled", false);
$("#totalCapacity").val(`0 / 0`);
$(".slot-check, .av-checkbox").prop("disabled", false); }
</script>
</body></html>
|