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.
 
 
 
 
 

1337 lines
42 KiB

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{fragments/user-head :: head}"></head>
<style>
: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;
}
/* container */
.container {
width: 100%;
max-width: 1200px;
background-color: white;
}
/******************************
PAGE / HERO / PANEL
******************************/
.page {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
}
.panel-wrap {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
margin-top: 18px;
}
.hero {
width: 100%;
max-width: 920px;
text-align: center;
margin-bottom: 12px;
}
.input {
width: 100%;
padding: 14px 16px;
border-radius: 12px;
border: 1px solid rgba(0, 0, 0, 0.06);
background: white;
font-size: 16px;
}
.btn {
display: inline-block;
padding: 14px 18px;
border-radius: 12px;
background: var(--accent);
color: #fff;
font-weight: 700;
border: none;
cursor: pointer;
box-shadow: 0 8px 26px rgba(18, 54, 59, 0.12);
}
/* CSS - responsive behavior */
/* default field behaviour */
.field {
width: 100%;
max-width: 760px;
/* original maximum */
margin: 0 auto 18px;
/* center and spacing */
margin-top: 0 auto 18px;
}
/* compact variant to match your 536x48 measurement */
.field .input.compact {
box-sizing: border-box;
width: 100%;
height: 48px;
/* your requested height */
padding: 10px 14px;
/* inner spacing */
border-radius: 10px;
border: 1px solid rgba(0, 0, 0, 0.06);
font-size: 15px;
background-color: white;
}
/* People and Contact styles removed */
.date-icon {
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 10px;
background: rgba(0, 0, 0, 0.04);
}
.counter {
width: 46px;
text-align: center;
font-weight: 700;
}
/* slots grid and slot styles */
.slots-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
/* 4 columns */
gap: 15px;
margin-top: 14px;
}
.slot {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 12px;
border-radius: 10px;
background: var(--card-bg);
border: 1px solid rgba(0, 0, 0, 0.06);
cursor: pointer;
}
.slot.disabled {
background: #f1f1f1;
color: #8b8b8b;
cursor: not-allowed;
}
.left {
display: flex;
flex-direction: column;
gap: 6px;
}
.time {
font-weight: 700;
}
.meta {
font-size: 13px;
color: var(--muted);
}
.choose {
width: 36px;
height: 36px;
border-radius: 8px;
border: 1px solid rgba(0, 0, 0, 0.08);
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
font-size: 16px;
}
.slot .time {
font-weight: 700;
color: #000 !important;
/* always black */
}
.slot.chosen {
background: #ffffff !important;
/* keep slot white */
color: #fff;
border-color: transparent;
border-color: rgba(0, 0, 0, 0.06) !important;
/* optional subtle glow around the entire slot (comment out if not wanted) */
box-shadow: 0 6px 18px rgba(9, 144, 90, 0.04);
}
/* .slot.chosen .meta{ color: rgba(255,255,255,0.9); } */
.slot.chosen .choose {
background: linear-gradient(180deg, var(--green-start), var(--green-end)) !important;
color: #fff;
border-color: transparent !important;
box-shadow: 0 6px 16px rgba(9, 144, 90, 0.12) !important;
}
/* container that keeps left (multi-line) and right (inline summary) in one row */
/* ===== Single-column stacked layout (no left/right) ===== */
.info-row {
display: flex !important;
flex-direction: column !important;
/* always stacked */
align-items: center !important;
/* center items horizontally */
justify-content: flex-start;
gap: 14px;
width: 100%;
max-width: 820px;
margin: 0 auto;
box-sizing: border-box;
}
/* preview note becomes a centered block (fluid width) */
.preview-note-box {
width: 100%;
max-width: 640px;
flex: 0 0 auto;
font-size: 13px;
color: #000000;
line-height: 1.45;
text-align: left !important;
/* ← LEFT aligned */
padding: 6px 8px;
}
/* summary-inline becomes a centered row under the note */
/* Inline container */
/* ---- summary inline: left / right alignment (desktop + mobile) ---- */
/* Strong override to force left / right placement */
.summary-inline {
width: 100% !important;
/* use the full container width */
max-width: 100% !important;
/* don't constrain to 420px here */
display: flex !important;
align-items: center !important;
justify-content: flex-end !important;
/* left / right extremes */
gap: 24px !important;
padding: 6px 8px !important;
box-sizing: border-box !important;
margin: 0 auto !important;
white-space: nowrap !important;
}
/* Make each item expand to fill half the line */
.summary-item {
display: flex !important;
justify-content: flex-end !important;
/* both right-aligned */
/* align-items: center !important; */
gap: 8px !important;
line-height: 1 !important;
flex: 0 0 auto !important;
/* allow items to grow and take space */
min-width: 0 !important;
/* prevent overflow due to long text */
text-align: right !important;
}
/* Left block: align contents to left */
.summary-item.unallocated {
justify-content: flex-end !important;
text-align: right !important;
/* color: #E63946 !important; */
}
/* Right block: align contents to right */
.summary-item.allocated {
justify-content: flex-end !important;
text-align: right !important;
/* color: #16A34A !important; */
}
/* Keep numeric counter compact and centered inside its area */
.summary-item strong {
min-width: 28px !important;
display: inline-block !important;
text-align: center !important;
}
/* DEFAULT color = black */
.summary-item span,
.summary-item strong {
color: #000 !important;
}
/* Red */
.text-red span,
.text-red strong {
color: #E63946 !important;
}
/* Green */
.text-green span,
.text-green strong {
color: #16A34A !important;
}
.submit-note {
font-family: "Inter", sans-serif;
font-size: 13px;
color: #000000;
text-align: left;
/* left aligned */
margin: 3px 0 10px 0;
/* top:3px, bottom:10px */
margin-left: 3px;
}
.submit-wrap {
margin-top: 16px;
}
/* ===============================
DEFAULT VISIBILITY STATES
================================ */
/* Book Visit — visible by default */
.book-vist {
opacity: 1;
pointer-events: auto;
}
/* Cancel Book — HIDDEN by default */
.cancel-book {
display: none;
/* ⬅ start hidden */
}
/* Utility classes that JS toggles */
.section-hidden {
display: none !important;
}
.section-enabled {
display: block !important;
opacity: 1 !important;
pointer-events: auto !important;
}
/* When Cancel is visible, style the button normally */
.cancel-book.section-enabled .btn {
background: #043B55;
color: #ffffff;
border-radius: 10px;
border: none;
cursor: pointer;
font-weight: 700;
box-shadow: 0 8px 26px rgba(18, 54, 59, 0.12);
}
/* summary card inside cancel section (you already had this) */
.summary-card {
max-width: 100%;
background: #fff;
border-radius: 10px;
padding: 18px 20px;
border: 2px solid rgba(0, 0, 0, 0.12);
box-sizing: border-box;
font-family: Inter, sans-serif;
color: #111;
margin: 0 auto;
}
/* Top row */
.summary-row {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 12px;
}
.summary-left .label {
font-size: 13px;
font-weight: 700;
margin-bottom: 10px;
color: black;
}
.summary-right div {
font-size: 13px;
font-weight: 400;
margin-bottom: 10px;
text-align: right;
color: black;
}
/* main buttons (book visit / preview etc.) */
.submit-wrap {
margin-top: 16px;
}
.btn {
width: 100%;
padding: 12px 26px;
background: #043B55;
color: #ffffff;
border-radius: 10px;
border: none;
cursor: pointer;
font-weight: 700;
box-sizing: border-box;
margin-bottom: 15px;
}
/* ---------- Mobile polish (drop this at the end of your CSS) ---------- */
@media (max-width: 520px) {
/* fields */
.field {
width: calc(100% - 20px);
margin-left: 10px;
margin-right: 10px;
max-width: none;
}
.field .input,
.field .input.compact {
width: 100%;
font-size: 15px;
padding: 12px 14px;
height: 46px;
border-radius: 10px;
}
/* Hover effect for Select Date input */
.date-wrap .input[type="date"]:hover {
border-color: #043B55;
/* brand color */
box-shadow: 0 0 0 2px rgba(4, 59, 85, 0.12);
cursor: pointer;
}
/* Focus (keyboard / click) */
.date-wrap .input[type="date"]:focus {
outline: none;
border-color: #043B55;
box-shadow: 0 0 0 3px rgba(4, 59, 85, 0.18);
}
/* slots grid -> single column */
.slots-grid {
grid-template-columns: 1fr;
gap: 10px;
margin-top: 12px;
}
.slot {
padding: 10px 12px;
border-radius: 10px;
}
/* info row -> stack */
.info-row {
flex-direction: column;
align-items: stretch;
gap: 10px;
margin-top: 10px;
}
.preview-note-box {
width: 100%;
max-width: none;
font-size: 13px;
}
/* submit area */
.submit-wrap {
margin-top: 18px;
padding-bottom: 18px;
}
.btn {
width: 100%;
padding: 12px 14px;
font-size: 16px;
border-radius: 12px;
}
}
/* =========================================
CALENDAR HOVER — FINAL GUARANTEED FIX
========================================= */
/* Target the REAL visible flatpickr input */
.date-wrap input.input.form-control {
cursor: pointer;
background-color: #fff !important;
transition: border-color 0.15s ease,
box-shadow 0.15s ease,
background-color 0.15s ease;
}
/* Hover */
.date-wrap input.input.form-control:hover {
border-color: #043B55 !important;
box-shadow: 0 0 0 2px rgba(4, 59, 85, 0.18) !important;
background-color: #fafafa !important;
}
/* Focus */
.date-wrap input.input.form-control:focus {
outline: none !important;
border-color: #043B55 !important;
box-shadow: 0 0 0 3px rgba(4, 59, 85, 0.28) !important;
}
/* Optional: hover anywhere in wrapper */
.date-wrap:hover input.input.form-control {
border-color: #043B55 !important;
}
.border-field {
border: 1px solid rgba(0, 0, 0, 0.15);
/* visible but subtle */
border-radius: 12px;
/* matches your UI */
padding: 16px 18px;
/* space inside box */
background: #ffffff;
/* white card look */
}
</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">
<div class="page-body">
<br>
<br>
<br>
<div class="container">
<br>
<br>
<main class="page" role="main" aria-labelledby="hero-heading">
<div class="panel-wrap">
<div class="panel" role="region" aria-label="Booking panel">
<div id="bookVisitSection" class="book-vist">
<!-- select date -->
<div class="field border-field" style="max-width: 1000px;">
<h5
style="text-align: center;font-weight: bold;font-family:Arial, Helvetica, sans-serif;">
Visit Blocking:
</h5>
<!-- Date Selection -->
<div style="margin-bottom: 30px;">
<input type="hidden" id="blockId"> <!-- Hidden ID -->
<label class="muted-label">Select Blocking Date:</label>
<div class="date-wrap">
<input id="dateInput" class="input" type="date"
aria-label="Select date" placeholder="Select Date">
</div>
</div>
<!-- Slots Section -->
<div
style="display:flex; justify-content:space-between; align-items:center; margin-bottom:12px;">
<label style="margin:8px 0 6px 0" class="muted-label">Pick
the slots to be
blocked:</label>
<button id="selectAllBtn" class="btn"
style="width:auto; padding:8px 16px; font-size:14px; margin:0; display:none;">Select
All 8 Slots</button>
</div>
<div id="slotsGrid" class="slots-grid" aria-live="polite">
</div>
</div>
<div class="submit-wrap">
<p class="submit-note">Confirm blocking for the selected
date and slots.</p>
<button id="previewBtn" class="btn">Block Visit</button>
<button id="cancelEditBtn" class="btn"
style="background:var(--muted); display:none; margin-top:10px;">Cancel
Edit</button>
</div>
<br>
<hr><br>
<h5
style="text-align: center;font-weight: bold;font-family:Arial, Helvetica, sans-serif;">
Visit Blocked List
</h5>
<div style="overflow-x:auto;">
<table class="table table-striped table-bordered"
style="width:100%; background:white;">
<thead>
<tr>
<th>Visit Blocked Date</th>
<th>Visit Blocked Slots</th>
<th style="width: 120px;">Action</th>
</tr>
</thead>
<tbody id="activeBlocksTableBody">
<!-- Populated by JS -->
</tbody>
</table>
</div>
</div>
</div>
</div>
</main>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
<!-- Conflict Modal -->
<div id="conflictModal" class="modal fade" role="dialog" data-backdrop="static">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Previously booked Visits on selected block date</h5>
<button type="button" class="close" data-dismiss="modal">&times;</button>
</div>
<div class="modal-body">
<div style="max-height: 400px; overflow-y: auto;">
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Slot ID</th>
<th>Mob No</th>
<th>Visit Date</th>
<th>No of People</th>
<th>Total Amount</th>
</tr>
</thead>
<tbody id="conflictTableBody">
<!-- Populated by JS -->
</tbody>
</table>
<p class="text-danger" style="font-weight:bold; margin-top:10px;">This slot already have bookings.
</p>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-danger" id="proceedBlockingBtn">Proceed Blocking</button>
</div>
</div>
</div>
</div>
<script th:replace="~{fragments/user-script :: script}"></script>
<script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
<script type="text/javascript">
function showLoader() {
$('#pageLoader').show();
}
function hideLoader() {
$('#pageLoader').hide();
}
$('#pageLoader').hide();
//checking
/* =========================================
ADMIN HALL BLOCKING LOGIC (REAL API)
========================================= */
// Core State
let chosenSlots = new Map(); // time -> { slotId, language, seats }
let conflictModal; // Added global variable
let activeBlocksList = []; // Store active blocks for validation
// Mock Data for Display (Structure only)
// Elements
let dateInput, previewBtn, selectAllBtn, slotsGrid, activeBlocksTableBody;
document.addEventListener("DOMContentLoaded", () => {
dateInput = document.getElementById("dateInput");
previewBtn = document.getElementById("previewBtn");
selectAllBtn = document.getElementById("selectAllBtn");
slotsGrid = document.getElementById("slotsGrid");
activeBlocksTableBody = document.getElementById("activeBlocksTableBody");
// Set min date to today and default to today
if (dateInput) {
flatpickr(dateInput, {
minDate: "today",
dateFormat: "Y-m-d",
disable: [
function (date) {
return (date.getDay() === 2); // 0=Sun, 1=Mon, 2=Tue
}
],
locale: {
firstDayOfWeek: 1
},
defaultDate: "today",
onChange: function (selectedDates, dateStr, instance) {
if (dateStr) {
loadActiveBlocks();
loadTimeSlotsAndRender(dateStr); // Reload with availability
}
}
});
// Load slots
const initDate = dateInput.value;
loadTimeSlotsAndRender(initDate);
// Load Active Blocks
loadActiveBlocks();
// Native picker events removed for Flatpickr
}
if (selectAllBtn) {
selectAllBtn.addEventListener("click", (e) => {
e.preventDefault();
selectAllSlots();
});
}
if (previewBtn) {
previewBtn.addEventListener("click", handleBlockSubmit);
}
// Modal events
document.getElementById("proceedBlockingBtn").addEventListener("click", () => {
$('#conflictModal').modal('hide');
const dateVal = dateInput.value;
const blockId = document.getElementById("blockId").value;
// Collect selected slots
let slotIds = [];
chosenSlots.forEach((val, key) => {
slotIds.push(val.slotId);
});
slotIds.sort((a, b) => a - b);
const slotString = slotIds.join(",");
// Call submitBlock with autoCancel = false (as per requirement: dont update reg_details)
// The 4th argument 'autoCancel' set to false means we FORCE block but DO NOT cancel existing bookings.
submitBlock(dateVal, slotString, blockId, false);
});
});
// Load slots from API
function loadTimeSlotsAndRender(selectedDate) {
let url = "/victoriapublichall/bookingapi/getAllTimeSlots";
if (selectedDate) {
url += "?visitDate=" + selectedDate;
}
$.ajax({
url: url,
type: "GET",
success: function (response) {
if (response && response.length > 0) {
renderSlotsMap(response);
} else {
console.warn("No time slots found");
if (slotsGrid) slotsGrid.innerHTML = "<p>No slots found</p>";
}
},
error: function (e) {
console.error("Failed to load time slots", e);
}
});
}
// Render the slots (data from API)
function renderSlotsMap(slotsData) {
if (!slotsGrid) return;
slotsGrid.innerHTML = "";
chosenSlots.clear();
slotsData.forEach(item => {
const slotEl = document.createElement("div");
slotEl.className = "slot";
slotEl.tabIndex = 0;
// Map DB fields to UI data attributes
// item.time_id -> slotId
// item.timing -> time
slotEl.dataset.time = item.timing;
slotEl.dataset.slotId = item.time_id;
// Check if this slot is already blocked
const currentDate = dateInput.value;
const activeBlock = activeBlocksList.find(b => b.block_date === currentDate);
let isBlocked = false;
if (activeBlock && activeBlock.time_slot) {
const blockedIds = activeBlock.time_slot.split(',').map(s => s.trim());
if (blockedIds.includes(String(item.time_id))) {
isBlocked = true;
}
}
// Availability
let avail = item.availCap !== undefined ? item.availCap : item.max_capacity;
let max = item.max_capacity;
// Logic for visual style (optional: disable if full?)
// For admin blocking, we allow blocking even if full (logic handles conflict),
// but we show the state.
// Green border initially (available)
slotEl.style.border = "1px solid #28a745";
if (isBlocked) {
// RED BACKGROUND if blocked
slotEl.style.background = "#dc3545";
slotEl.style.color = "white";
slotEl.style.borderColor = "#dc3545";
} else if (avail <= 0) {
slotEl.style.border = "1px solid #dc3545"; // Red border if full but not blocked via this tool
slotEl.style.background = "#fff5f5";
}
slotEl.innerHTML = `
<div class="left">
<div class="time" style="${isBlocked ? 'color:white !important;' : ''}">${item.timing}</div>
<div class="meta" style="font-size:12px; color: ${isBlocked ? '#ffdddd' : (avail > 0 ? 'green' : 'red')};">
${isBlocked ? 'BLOCKED' : 'Available: ' + avail + ' / ' + max}
</div>
</div>
<div class="choose"></div>
`;
slotEl.addEventListener("click", () => toggleSlot(slotEl));
slotsGrid.appendChild(slotEl);
});
if (selectAllBtn) selectAllBtn.style.display = "block";
}
// Toggle Slot Selection
function toggleSlot(slotEl) {
const time = slotEl.dataset.time;
if (chosenSlots.has(time)) {
// Deselect
chosenSlots.delete(time);
slotEl.classList.remove('chosen');
const choose = slotEl.querySelector('.choose');
if (choose) choose.textContent = '';
} else {
// Select
chosenSlots.set(time, {
slotId: slotEl.dataset.slotId
});
slotEl.classList.add('chosen');
const choose = slotEl.querySelector('.choose');
if (choose) choose.textContent = '✓';
}
updateSelectAllText();
}
// Select All Slots
function selectAllSlots() {
const allSlots = document.querySelectorAll("#slotsGrid .slot");
if (allSlots.length === 0) return;
let allSelected = true;
allSlots.forEach(el => {
if (!el.classList.contains("chosen")) allSelected = false;
});
if (allSelected) {
// Deselect All
allSlots.forEach(el => {
if (el.classList.contains("chosen")) toggleSlot(el);
});
} else {
// Select All
allSlots.forEach(el => {
if (!el.classList.contains("chosen")) toggleSlot(el);
});
}
updateSelectAllText();
}
function updateSelectAllText() {
const allSlots = document.querySelectorAll("#slotsGrid .slot");
let allSelected = true;
allSlots.forEach(el => {
if (!el.classList.contains("chosen")) allSelected = false;
});
if (selectAllBtn) {
selectAllBtn.textContent = allSelected ? "Deselect All" : "Select All 8 Slots";
}
}
// API ACTIONS
function loadActiveBlocks() {
showLoader();
$.ajax({
url: "/victoriapublichall/bookingapi/getActiveBlockTimeSlots",
type: "GET",
success: function (response) {
hideLoader();
renderActiveBlocks(response);
},
error: function (xhr) {
hideLoader();
console.error("Failed to load blocks", xhr);
Swal.fire("Error", "Failed to load active blocked slots.", "error");
}
});
}
function renderActiveBlocks(data) {
activeBlocksList = data || []; // Update global list
activeBlocksTableBody.innerHTML = "";
if (!data || data.length === 0) {
activeBlocksTableBody.innerHTML = "<tr><td colspan='3' class='text-center'>No active blocks found.</td></tr>";
return;
}
data.forEach(row => {
const tr = document.createElement("tr");
tr.innerHTML = `
<td>${row.block_date_formatted}</td>
<td>${row.time_slot}</td>
<td>
<button class="btn btn-primary btn-sm" style="width:auto; padding:6px 12px; margin:0 5px;"
onclick="editBlock(${row.id}, '${row.block_date}', '${row.time_slot}')" title="Edit">
<i class="fa-solid fa-pen"></i>
</button>
<button class="btn btn-danger btn-sm" style="width:auto; padding:6px 12px; margin:0 5px;"
onclick="deleteBlock(${row.id})" title="Delete">
<i class="fa-solid fa-trash"></i>
</button>
</td>
`;
activeBlocksTableBody.appendChild(tr);
});
}
// Edit Block Function
window.editBlock = function (id, date, slots) {
// Set ID
document.getElementById("blockId").value = id;
// Set Date (flatpickr)
if (dateInput && dateInput._flatpickr) {
dateInput._flatpickr.setDate(date);
} else {
dateInput.value = date;
}
// Clear choices
chosenSlots.clear();
document.querySelectorAll("#slotsGrid .slot").forEach(el => {
el.classList.remove('chosen');
const choose = el.querySelector('.choose');
if (choose) choose.textContent = '';
});
// Set slots
if (slots) {
const slotIds = slots.split(',').map(s => s.trim());
slotIds.forEach(sid => {
// Find element by data-slot-id
const el = document.querySelector(`.slot[data-slot-id='${sid}']`);
if (el) {
toggleSlot(el); // This will add it to chosenSlots map
}
});
}
// Scroll to top
window.scrollTo({ top: 0, behavior: 'smooth' });
// Update UI Text
previewBtn.textContent = "Update Block";
document.getElementById("cancelEditBtn").style.display = "inline-block";
}
// Cancel Edit
document.getElementById("cancelEditBtn").addEventListener("click", function () {
resetForm();
});
function resetForm() {
document.getElementById("blockId").value = "";
previewBtn.textContent = "Block Visit";
document.getElementById("cancelEditBtn").style.display = "none";
// Reset date to today
if (dateInput && dateInput._flatpickr) {
dateInput._flatpickr.setDate("today");
}
// Clear slots
chosenSlots.clear();
document.querySelectorAll("#slotsGrid .slot").forEach(el => {
el.classList.remove('chosen');
const choose = el.querySelector('.choose');
if (choose) choose.textContent = '';
});
updateSelectAllText();
}
function handleBlockSubmit() {
const dateVal = dateInput.value;
const blockId = document.getElementById("blockId").value;
const isEdit = blockId && blockId.trim() !== "";
if (!dateVal) {
Swal.fire("Warning", "Please select a date.", "warning");
return;
}
// Check for duplicate blocks on the same date
// If creating new (isEdit false) OR editing (isEdit true) and changing date to one that exists
const duplicate = activeBlocksList.find(b => b.block_date === dateVal && b.id != blockId);
if (duplicate) {
Swal.fire("Warning", "For the selected date, there is already a blocking. Modify it in the table below.", "warning");
return;
}
if (chosenSlots.size === 0) {
Swal.fire("Warning", "Please select at least one slot to block.", "warning");
return;
}
// Collect slot IDs
let slotIds = [];
chosenSlots.forEach((val, key) => {
slotIds.push(val.slotId);
});
slotIds.sort((a, b) => a - b);
const slotString = slotIds.join(",");
// Check for conflicts first
checkConflicts(dateVal, slotString, blockId, isEdit);
}
function checkConflicts(dateVal, slotString, blockId, isEdit) {
showLoader();
$.ajax({
url: "/victoriapublichall/bookingapi/checkConflictingBookings",
type: "GET",
data: { blockDate: dateVal, timeSlots: slotString }, // Pass timeSlots
success: function (response) {
hideLoader();
if (response && response.length > 0) {
showConflictModal(response);
} else {
// No conflicts, proceed with normal confirmation
confirmAndSubmit(dateVal, slotString, blockId, isEdit);
}
},
error: function (xhr) {
hideLoader();
// If check fails, maybe warn user or proceed?
// Creating block shouldn't be blocked by check failure implicitly, but safe to warn.
console.error("Conflict check failed", xhr);
confirmAndSubmit(dateVal, slotString, blockId, isEdit);
}
});
}
function showConflictModal(conflicts) {
const tbody = document.getElementById("conflictTableBody");
tbody.innerHTML = "";
conflicts.forEach(row => {
const tr = document.createElement("tr");
tr.innerHTML = `
<td style="color:orange; font-weight:bold;">${row.time_id || '-'}</td>
<td>${row.mob_no || '-'}</td>
<td>${row.visit_date}</td>
<td>${row.no_of_people}</td>
<td>${row.total_amount}</td>
`;
tbody.appendChild(tr);
});
$('#conflictModal').modal('show');
}
function confirmAndSubmit(dateVal, slotString, blockId, isEdit) {
const actionText = isEdit ? "Update" : "Block";
const confirmText = isEdit ? "Yes, update it!" : "Yes, block it!";
Swal.fire({
title: `Confirm ${actionText}?`,
text: `${actionText} ${dateVal} for slots: ${slotString}?`,
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
confirmButtonText: confirmText
}).then((result) => {
if (result.isConfirmed) {
submitBlock(dateVal, slotString, blockId, false); // autoCancel = false
}
});
}
function submitBlock(dateVal, slotString, blockId, autoCancel) {
showLoader();
let payload = {
blockDate: dateVal,
timeSlot: slotString,
autoCancel: autoCancel
};
if (blockId) {
payload.id = blockId;
}
$.ajax({
url: "/victoriapublichall/bookingapi/saveBlockTimeSlot",
type: "POST",
data: payload,
success: function (response) {
hideLoader();
if (response.status === "success") {
const msg = blockId ? "Block updated successfully!" : "Visit blocked successfully!";
Swal.fire("Success", msg, "success");
resetForm();
loadActiveBlocks();
const currentSelectedDate = dateInput.value;
if (currentSelectedDate) {
loadTimeSlotsAndRender(currentSelectedDate); // Reload with availability
}
} else {
Swal.fire("Error", response.message, "error");
}
},
error: function (xhr) {
hideLoader();
Swal.fire("Error", "Server error while saving block.", "error");
}
});
}
window.deleteBlock = function (id) {
Swal.fire({
title: 'Are you sure?',
text: "Do you want to delete this block? Slots will become available.",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
confirmButtonText: 'Yes, delete it!'
}).then((result) => {
if (result.isConfirmed) {
executeDelete(id);
}
});
}
function executeDelete(id) {
showLoader();
$.ajax({
url: "/victoriapublichall/bookingapi/deleteBlockTimeSlot",
type: "POST",
data: { id: id },
success: function (response) {
hideLoader();
if (response.status === "success") {
Swal.fire("Deleted!", "Block has been deleted.", "success");
// If we were editing this ID, reset form
const currentEditId = document.getElementById("blockId").value;
if (currentEditId == id) {
resetForm();
}
loadActiveBlocks();
const dateStr = dateInput.value;
if (dateStr) {
loadTimeSlotsAndRender(dateStr); // Reload with availability
}
} else {
Swal.fire("Error", response.message, "error");
}
},
error: function (xhr) {
hideLoader();
Swal.fire("Error", "Server error while deleting block.", "error");
}
});
}
</script>
<script>
(function () {
const hambtn = document.getElementById('hambtn');
const mobileNav = document.getElementById('mobileNav');
const mobileOverlay = document.getElementById('mobileOverlay');
const desktopBook = document.getElementById('desktopBookBtn');
const mobileBook = document.getElementById('mobileBook');
if (!hambtn || !mobileNav || !mobileOverlay) {
// nothing to do if elements missing
} else {
function openMenu() {
hambtn.setAttribute('aria-expanded', 'true');
mobileNav.classList.add('open'); mobileNav.setAttribute('aria-hidden', 'false');
mobileOverlay.classList.add('show'); mobileOverlay.setAttribute('aria-hidden', 'false');
document.body.style.overflow = 'hidden';
const first = mobileNav.querySelector('a'); if (first) first.focus();
}
function closeMenu() {
hambtn.setAttribute('aria-expanded', 'false');
mobileNav.classList.remove('open'); mobileNav.setAttribute('aria-hidden', 'true');
mobileOverlay.classList.remove('show'); mobileOverlay.setAttribute('aria-hidden', 'true');
document.body.style.overflow = '';
hambtn.focus();
}
hambtn.addEventListener('click', () => {
const isOpen = hambtn.getAttribute('aria-expanded') === 'true';
if (isOpen) closeMenu(); else openMenu();
});
mobileOverlay.addEventListener('click', closeMenu);
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && mobileNav.classList.contains('open')) closeMenu();
});
mobileNav.querySelectorAll('a').forEach(a => {
a.addEventListener('click', () => {
// close after clicking any mobile link
closeMenu();
});
});
// ensure menu closes when resizing to desktop
window.addEventListener('resize', () => {
if (window.innerWidth > 900 && mobileNav.classList.contains('open')) closeMenu();
});
}
// Optional: wire desktop Book a Visit to the same booking action as mobile
// if (desktopBook && mobileBook) {
// desktopBook.addEventListener('click', (e) => {
// e.preventDefault();
// alert('Book a Visit clicked — open booking flow here.');
// });
// mobileBook.addEventListener('click', (e) => {
// });
// }
})();
</script>
</body>
</html>