|
|
<!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">×</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>
|