|
|
<!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; } </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">Bulk Booking Request 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">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>
<!-- VISITOR BREAKUP --> <h6 class="fw-bold">Visitor Breakup</h6>
<div class="table-responsive"> <table class="table table-bordered table-sm text-center"> <thead class="table-secondary"> <tr> <th>S.No</th> <th>Visitor Type</th> <th>No. of People</th> <th>Amount</th> <th>Total</th> </tr> </thead> <tbody id="visitorBreakupBody"></tbody> </table> </div>
<!--<div class="row mt-2">
<div class="col-md-12 text-right"> <strong>Total Amount:</strong> ₹<span id="totalAmount">0</span> </div> </div>-->
<!-- slots related div -->
<div class="row mb-3"> <div class="col-md-6"> <label class="form-label">Select Date</label> <input type="date" class="form-control" id="searchdate"> </div>
<div class="col-md-6"> <label class="form-label fw-bold">Capacity Summary</label> <input type="text" class="form-control" id="totalCapacity" value="0 / 0" readonly> </div> </div>
<div class="row"> <!-- LEFT : ALLOCATION --> <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>
<!-- RIGHT : INFO MESSAGE --> <div class="col-md-4"> <div id="allocationMessage" class="alert alert-warning d-none" role="alert"> </div> </div> </div> <!--<div class="row mb-3">
<div class="col-md-8"> <div id="timeSlotContainer" class="time-grid mb-4"></div>
<div id="avSlotContainer" class="av-grid"></div> <div> <div class="col-md-4"> <div id="allocationMessage" class="alert alert-warning d-none" role="alert"> </div> </div> </div>-->
<!-- onclick="saveBulkBooking()"-->
<div class="text-center mt-4"> <button type="button" class="btn btn-success" id="submitBtn" disabled >Submit</button>
<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/getrequestlist', 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: '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}`);
$("#searchdate").val(""); $("#timeSlotContainer").empty(); $("#avSlotContainer").empty();
if (booking.reg_payment_status === "PENDING") { currentBookingReadOnly = true; $("#submitBtn").prop("disabled", true); $("#searchdate").prop("disabled", true);
loadAdminAllocation(booking.ref_id);
} else { currentBookingReadOnly = false; $("#submitBtn").prop("disabled", true); $("#searchdate").prop("disabled", false); }
$("#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); });
// date related functions
document.addEventListener("DOMContentLoaded", function () {
const dateInput = document.getElementById("searchdate");
const today = new Date(); today.setDate(today.getDate() + 8);
const yyyy = today.getFullYear(); const mm = String(today.getMonth() + 1).padStart(2, '0'); const dd = String(today.getDate()).padStart(2, '0');
dateInput.min = `${yyyy}-${mm}-${dd}`;
});
$("#searchdate").on("change", function () {
const visitDate = $(this).val(); if (!visitDate) return;
// tuesday not allowed bookings
const selectedDate = new Date(visitDate);
// 0=Sun, 1=Mon, 2=Tue ... if (selectedDate.getDay() === 2) {
// 🔴 Clear already loaded slots $("#timeSlotContainer").empty(); $("#avSlotContainer").empty();
// Reset capacity timeSlotAllocated = 0; $("#totalCapacity").val(`0 / ${totalPeople}`);
// Disable submit $("#submitBtn").prop("disabled", true);
Swal.fire( "Maintenance Day", "Bookings not allowed on tuesdays. Please select another date.", "warning" );
// Clear date $(this).val(""); return; }
$.get("/victoriapublichall/bookingapi/getTimeMaster", { visitDate: visitDate, view: "report" }, function (data) {
let html = "";
data.forEach(slot => {
if (slot.availCap <= 0) return;
html += ` <div class="time-box" data-time-id="${slot.time_id}"> <div class="fw-bold">${slot.timing}</div> <div class="text-muted"> <span class="time-available">${slot.availCap}</span> Available </div>
<div class="form-check"> <input type="checkbox" class="form-check-input slot-check" data-time-id="${slot.time_id}" data-available="${slot.availCap}"> <label>Select Slot</label> </div>
<div class="allocation-text text-success"></div> </div>`; });
$("#timeSlotContainer").html(html); loadAllAVSlots(visitDate); });
validateSubmitState(); });
$(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; }
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");
$("#searchdate").val(res.visitDate).prop("disabled", true); $("#submitBtn").hide();
$("#timeSlotContainer").empty(); $("#avSlotContainer").empty();
let totalAllocated = 0;
res.timeSlots.forEach(time => {
totalAllocated += time.people;
// TIME SLOT BOX $("#timeSlotContainer").append(` <div class="time-box disabled"> <div class="fw-bold">${time.timeRange}</div> <div class="text-success"> Allocated: ${time.people} </div> </div> `);
// AV SLOTS time.avSlots.forEach(av => { $("#avSlotContainer").append(` <div class="av-card full"> <div> <div class="av-time"> ${av.time} (${av.language}) </div> <div class="av-seats"> Allocated: <b>${av.people}</b> </div> </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> `); } ); }
// save bulk booking $("#submitBtn").on("click", function () {
const bulkRefId = $("#v_refid").text(); const mobNo = $("#v_mobile").text();
const userInput = $("#v_userinput_text").text();
const noOfPeople = $("#v_people").text(); const idType = $("#v_idproofid").text(); const idDetails = $("#v_id_details").text(); const visitDate = $("#searchdate").val();
if (currentBookingReadOnly) { Swal.fire("Info", "This booking is already submitted", "info"); return; }
let allocated = 0; $(".allocation-text").each(function () { const txt = $(this).text(); if (txt.includes("Allocated")) { allocated += Number(txt.replace(/\D/g, '')); } });
if (allocated !== totalPeople) { Swal.fire( "Allocation mismatch", `Total People: ${totalPeople}, Allocated: ${allocated}`, "error" ); return; }
$("#submitBtn").prop("disabled", true);
if (!visitDate) { Swal.fire("Warning", "Please select visit date", "warning"); return; }
let timeIds = $(".slot-check:checked") .map(function () { return $(this).data("time-id"); }) .get();
let avSlotIds = $(".av-checkbox") .map(function () { return $(this).data("slot-id"); }) .get();
$.ajax({ url: "/victoriapublichall/bookingapi/saveBulkApproveBooking", type: "POST", traditional: true, data: { bulkRefId: bulkRefId, userInput: userInput, noOfPeople: noOfPeople, idType: idType, idDetails: idDetails, visitDate: visitDate, timeIds: timeIds, avSlotIds: avSlotIds }, success: function () {
Swal.fire({ title: "Success", text: "Booking saved successfully", icon: "success", allowOutsideClick: false, allowEscapeKey: false, allowEnterKey: false, confirmButtonText: "OK" }).then((result) => {
if (result.isConfirmed) {
currentBookingReadOnly = true;
$(".slot-check").prop("disabled", true); $(".av-checkbox").prop("disabled", true); $("#searchdate").prop("disabled", true);
$("#viewModal").modal("hide");
$("#bookingTable").DataTable().ajax.reload(); } });
},
error: function () {
$("#submitBtn").prop("disabled", false);
Swal.fire("Error", "Save failed", "error"); } }); });
// 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); }
document.getElementById("searchdate").addEventListener("change", function () { const selectedDate = new Date(this.value);
if (selectedDate.getDay() === 2) { Swal.fire( "Invalid Date", "Bookings are not allowed on Tuesdays.", "warning" ); this.value = ""; // reset date } });
</script>
</body></html>
|