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.
 
 
 
 
 

1070 lines
32 KiB

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