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.
4109 lines
107 KiB
4109 lines
107 KiB
<!DOCTYPE html>
|
|
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
|
|
|
<head th:replace="~{fragments/user-head :: head}"></head>
|
|
|
|
<header th:replace="~{user/razor-pay :: header}"></header>
|
|
|
|
<style>
|
|
:root {
|
|
--accent: #12363b;
|
|
--muted: #6b6b6b;
|
|
--card-bg: #ffffff;
|
|
--slot-radius: 10px;
|
|
--green-start: #16a34a;
|
|
--green-end: #09905a;
|
|
}
|
|
|
|
* {
|
|
box-sizing: border-box;
|
|
margin: 0;
|
|
padding: 0
|
|
}
|
|
|
|
html,
|
|
body {
|
|
height: 100%
|
|
}
|
|
|
|
body {
|
|
font-family: "Inter", "Helvetica Neue", Arial, sans-serif;
|
|
|
|
background:
|
|
linear-gradient(rgba(255, 255, 255, 0.70), rgba(255, 255, 255, 0.82)),
|
|
url('/victoriapublichall/images/vph-background_v1.jpg') top center / cover no-repeat,
|
|
url('/victoriapublichall/images/vph-background_v1.jpg') bottom center / cover no-repeat;
|
|
|
|
min-height: 100vh;
|
|
background-attachment: fixed;
|
|
/* keeps both images static */
|
|
background-repeat: no-repeat;
|
|
background-size: cover;
|
|
|
|
-webkit-font-smoothing: antialiased;
|
|
min-height: 100vh;
|
|
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
padding: 18px 24px;
|
|
}
|
|
|
|
|
|
|
|
|
|
/* container */
|
|
.container {
|
|
width: 100%;
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
/******************************
|
|
NAVBAR (desktop + mobile)
|
|
******************************/
|
|
.topbar {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 16px;
|
|
padding: 10px 0;
|
|
position: relative;
|
|
z-index: 1300;
|
|
}
|
|
|
|
.leftbar {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 20px;
|
|
}
|
|
|
|
|
|
.nav-left {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
}
|
|
|
|
.logo {
|
|
width: 72px;
|
|
height: 72px;
|
|
object-fit: contain;
|
|
border-radius: 50%;
|
|
}
|
|
|
|
/* default desktop nav */
|
|
nav.primary {
|
|
display: flex;
|
|
gap: 18px;
|
|
align-items: center;
|
|
}
|
|
|
|
/* Desktop Navigation Links (Figma Accurate) */
|
|
nav.primary a {
|
|
font-family: "Playfair Display", serif !important;
|
|
font-weight: 400 !important;
|
|
/* Regular */
|
|
font-style: normal !important;
|
|
font-size: 15px !important;
|
|
/* Figma size */
|
|
line-height: 100% !important;
|
|
letter-spacing: 0 !important;
|
|
|
|
color: #3C1E12;
|
|
/* matches Book a Visit color */
|
|
text-decoration: none;
|
|
|
|
padding: 6px 8px;
|
|
/* keep your layout spacing */
|
|
border-radius: 6px;
|
|
|
|
transition: background 0.2s ease;
|
|
}
|
|
|
|
/* Hover state (optional subtle effect like Figma preview) */
|
|
nav.primary a:hover {
|
|
background: rgba(0, 0, 0, 0.06);
|
|
color: #3C1E12;
|
|
/* no change in color */
|
|
}
|
|
|
|
/* Mobile menu links */
|
|
.mobile-nav a {
|
|
font-family: "Playfair Display", serif !important;
|
|
font-weight: 400 !important;
|
|
font-size: 15px !important;
|
|
letter-spacing: 0 !important;
|
|
color: #3C1E12;
|
|
}
|
|
|
|
.rightbar {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
}
|
|
|
|
|
|
#desktopBookBtn {
|
|
width: 174px;
|
|
/* Figma button width */
|
|
height: 57px;
|
|
/* Figma button height */
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
|
|
background: transparent;
|
|
border-radius: 8px;
|
|
/* Figma corner radius */
|
|
|
|
/* Border from Figma (thin outline) */
|
|
border: 1px solid rgba(60, 30, 18, 0.40);
|
|
/* darker visible border */
|
|
|
|
cursor: pointer;
|
|
|
|
/* Figma Typography */
|
|
font-family: "Playfair Display", serif !important;
|
|
font-weight: 700 !important;
|
|
/* Bold */
|
|
font-style: italic !important;
|
|
font-size: 24px !important;
|
|
line-height: 100% !important;
|
|
|
|
/* Letter spacing -6% = -0.06em */
|
|
letter-spacing: -0.06em !important;
|
|
|
|
color: #3C1E12 !important;
|
|
}
|
|
|
|
/* Hover effect (Figma-like subtle shadow) */
|
|
#desktopBookBtn:hover {
|
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.10);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* hamburger (hidden on desktop) */
|
|
.hamburger {
|
|
display: none;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 44px;
|
|
height: 44px;
|
|
border-radius: 8px;
|
|
border: 1px solid rgba(0, 0, 0, 0.06);
|
|
background: transparent;
|
|
cursor: pointer;
|
|
padding: 8px;
|
|
}
|
|
|
|
.hamburger .bar {
|
|
display: block;
|
|
width: 18px;
|
|
height: 2px;
|
|
background: var(--dark);
|
|
margin: 3px 0;
|
|
transition: transform .2s, opacity .2s;
|
|
}
|
|
|
|
/* mobile floating menu + overlay (initial hidden) */
|
|
.mobile-overlay {
|
|
position: fixed;
|
|
inset: 0;
|
|
background: rgba(0, 0, 0, 0.22);
|
|
z-index: 1350;
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
transition: opacity .22s ease;
|
|
}
|
|
|
|
.mobile-overlay.show {
|
|
opacity: 1;
|
|
pointer-events: auto;
|
|
}
|
|
|
|
.mobile-nav {
|
|
position: fixed;
|
|
left: 12px;
|
|
right: 12px;
|
|
top: 68px;
|
|
max-width: 420px;
|
|
margin: 0 auto;
|
|
background: #fff;
|
|
border-radius: 12px;
|
|
padding: 14px;
|
|
box-shadow: 0 22px 60px rgba(0, 0, 0, 0.16);
|
|
border: 1px solid rgba(0, 0, 0, 0.06);
|
|
z-index: 1400;
|
|
transform: translateY(-8px) scale(.995);
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
transition: opacity .22s ease, transform .22s ease;
|
|
}
|
|
|
|
.mobile-nav.open {
|
|
opacity: 1;
|
|
transform: translateY(0) scale(1);
|
|
pointer-events: auto;
|
|
}
|
|
|
|
.mobile-nav a {
|
|
display: block;
|
|
padding: 10px 12px;
|
|
color: var(--dark);
|
|
text-decoration: none;
|
|
border-radius: 8px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.mobile-nav a+a {
|
|
margin-top: 8px;
|
|
}
|
|
|
|
.mobile-nav .book-cta {
|
|
background: #f7f9f9;
|
|
text-align: center;
|
|
padding: 12px;
|
|
color: var(--accent);
|
|
font-weight: 700;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/******************************
|
|
PAGE / HERO / PANEL
|
|
******************************/
|
|
.page {
|
|
width: 100%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: flex-start;
|
|
margin-top: 20px;
|
|
}
|
|
|
|
|
|
|
|
.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;
|
|
}
|
|
|
|
/* Desktop style */
|
|
.page h1 {
|
|
font-family: "Inter", sans-serif;
|
|
font-weight: 700;
|
|
font-style: normal;
|
|
font-size: 48px;
|
|
line-height: 100%;
|
|
letter-spacing: 0;
|
|
text-align: center;
|
|
|
|
width: 100%;
|
|
max-width: 444px;
|
|
/* Figma width */
|
|
margin: 0 auto 10px;
|
|
/* Center + bottom spacing */
|
|
color: #3B1E14 !important;
|
|
}
|
|
|
|
|
|
|
|
.hero .lead {
|
|
font-family: "Inter", sans-serif;
|
|
font-weight: 400;
|
|
font-style: normal;
|
|
font-size: 20px;
|
|
line-height: 100%;
|
|
letter-spacing: 0;
|
|
text-align: center;
|
|
|
|
width: 100%;
|
|
max-width: 417px;
|
|
/* From Figma */
|
|
margin: 0 auto 20px;
|
|
/* Center + spacing */
|
|
color: #000000;
|
|
/* BLACK COLOR as requested */
|
|
}
|
|
|
|
|
|
.muted-label {
|
|
font-family: "Inter", sans-serif;
|
|
font-weight: 700;
|
|
font-style: normal;
|
|
font-size: 16px;
|
|
line-height: 100%;
|
|
letter-spacing: 0;
|
|
color: #3B1E14;
|
|
|
|
width: 100%;
|
|
max-width: 100%;
|
|
margin-bottom: 8px;
|
|
|
|
text-align: left;
|
|
display: block;
|
|
|
|
margin-left: 0px !important;
|
|
/* ⬅ FIXES CENTER ISSUE */
|
|
}
|
|
|
|
|
|
|
|
.input {
|
|
width: 100%;
|
|
padding: 14px 16px;
|
|
border-radius: 12px;
|
|
border: 1px solid rgba(0, 0, 0, 0.06);
|
|
background: #fff;
|
|
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;
|
|
}
|
|
|
|
|
|
|
|
#contactDisplay {
|
|
color: #666;
|
|
font-weight: 700;
|
|
padding: 10px 14px;
|
|
border-radius: 10px;
|
|
min-width: 180px;
|
|
|
|
}
|
|
|
|
#changeContact {
|
|
color: #0478D0;
|
|
text-decoration: underline;
|
|
cursor: pointer;
|
|
/*margin-left: 40px;*/
|
|
font-size: medium;
|
|
}
|
|
|
|
|
|
|
|
.muted-label {
|
|
display: block;
|
|
margin-bottom: 8px;
|
|
|
|
color: #000;
|
|
font-weight: 700;
|
|
}
|
|
|
|
|
|
|
|
/* Desktop */
|
|
.people-row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 16px;
|
|
/*white-space: nowrap;*/
|
|
}
|
|
|
|
.muted-label-inline {
|
|
font-weight: 600;
|
|
color: black;
|
|
/*margin-right: 320px;*/
|
|
font-size: 16px;
|
|
}
|
|
|
|
|
|
.small {
|
|
height: 40px;
|
|
width: 40px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
border-radius: 8px;
|
|
border: 1px solid #ddd;
|
|
background: #fff;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.counter {
|
|
min-width: 40px;
|
|
height: 40px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-weight: 700;
|
|
border: 1px solid #ddd;
|
|
background: #fff;
|
|
border-radius: 8px;
|
|
}
|
|
|
|
|
|
.people-support {
|
|
margin-top: 10px;
|
|
color: #000000 !important;
|
|
font-size: 13px;
|
|
}
|
|
|
|
.people-support .link {
|
|
color: #0478D0;
|
|
text-decoration: none;
|
|
}
|
|
|
|
|
|
/* a little spacing when controls are inline with other content */
|
|
.spacer {
|
|
width: 12px;
|
|
}
|
|
|
|
|
|
|
|
|
|
.date-wrap {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
position: relative;
|
|
}
|
|
|
|
/* main date input visual */
|
|
.date-wrap .input[type="date"] {
|
|
font-family: "Inter", sans-serif !important;
|
|
font-size: 16px;
|
|
color: #0000 !important;
|
|
padding: 12px 44px 12px 12px;
|
|
/* reserve space on right for icon */
|
|
width: 100%;
|
|
background: #fff;
|
|
border: 1px solid #ccc;
|
|
border-radius: 12px;
|
|
box-sizing: border-box;
|
|
cursor: pointer;
|
|
background-color: white;
|
|
|
|
/* keep native appearance so browsers show their indicator */
|
|
-webkit-appearance: textfield;
|
|
appearance: textfield;
|
|
}
|
|
|
|
/* Force inner pieces to be black in WebKit browsers */
|
|
.date-wrap .input[type="date"]::-webkit-datetime-edit,
|
|
.date-wrap .input[type="date"]::-webkit-datetime-edit-text,
|
|
.date-wrap .input[type="date"]::-webkit-datetime-edit-month-field,
|
|
.date-wrap .input[type="date"]::-webkit-datetime-edit-day-field,
|
|
.date-wrap .input[type="date"]::-webkit-datetime-edit-year-field {
|
|
color: #000 !important;
|
|
}
|
|
|
|
.date-wrap .input[type="date"]::-webkit-calendar-picker-indicator {
|
|
position: absolute;
|
|
/* allow us to place it over the input area */
|
|
right: 12px;
|
|
/* matches padding-right */
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
width: 22px;
|
|
height: 22px;
|
|
opacity: 1;
|
|
/* ensure visible */
|
|
cursor: pointer;
|
|
filter: none;
|
|
/* remove inversion if any */
|
|
-webkit-appearance: auto;
|
|
/* let browser draw its icon */
|
|
}
|
|
|
|
.date-wrap .input[type="date"]::-moz-focus-inner {
|
|
border: 0;
|
|
}
|
|
|
|
/* fallback: custom inline SVG icon placed absolutely (only shows if native icon not visible) */
|
|
.date-wrap::after {
|
|
content: "";
|
|
position: absolute;
|
|
right: 12px;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
width: 22px;
|
|
height: 22px;
|
|
pointer-events: none;
|
|
/* indicator should not intercept clicks; native indicator will */
|
|
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='22' height='22' viewBox='0 0 24 24' fill='%23000'><path d='M7 10h5v5H7zm-2 9h14V8H5zm14-13h-2V4h-2v2H9V4H7v2H5v2h14z'/></svg>");
|
|
background-repeat: no-repeat;
|
|
background-position: center;
|
|
background-size: 20px 20px;
|
|
opacity: 0.9;
|
|
}
|
|
|
|
|
|
.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;
|
|
}
|
|
|
|
/* time range pills */
|
|
/* pills */
|
|
.time-ranges {
|
|
display: flex;
|
|
gap: 10px;
|
|
flex-wrap: wrap;
|
|
margin: 14px 0 8px;
|
|
}
|
|
|
|
.pill {
|
|
padding: 10px 12px;
|
|
border-radius: 10px;
|
|
background: rgba(255, 255, 255, 0.95);
|
|
border: 1px solid rgba(0, 0, 0, 0.06);
|
|
min-width: 110px;
|
|
text-align: center;
|
|
cursor: pointer;
|
|
user-select: none;
|
|
}
|
|
|
|
.pill small {
|
|
display: block;
|
|
color: var(--muted);
|
|
margin-top: 6px;
|
|
font-weight: 600;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.pill.active {
|
|
background: linear-gradient(180deg, var(--green-start), var(--green-end));
|
|
color: #fff;
|
|
border-color: transparent;
|
|
box-shadow: 0 8px 26px rgba(0, 0, 0, 0.08);
|
|
}
|
|
|
|
.pill.active small {
|
|
color: #fff !important;
|
|
}
|
|
|
|
/* info row: left multi-line, right inline summary */
|
|
.info-row {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: flex-start;
|
|
gap: 18px;
|
|
margin-top: 6px;
|
|
}
|
|
|
|
.preview-note-box {
|
|
width: 320px;
|
|
font-size: 13px;
|
|
color: var(--muted);
|
|
line-height: 1.45;
|
|
}
|
|
|
|
.summary-inline {
|
|
display: flex;
|
|
gap: 18px;
|
|
align-items: center;
|
|
/*white-space: nowrap;*/
|
|
flex-shrink: 1;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.summary-item {
|
|
font-size: 13px;
|
|
color: var(--muted);
|
|
}
|
|
|
|
.summary-item strong {
|
|
margin-left: 6px;
|
|
font-weight: 700;
|
|
}
|
|
|
|
/* once a slot is chosen and locked, make it look non-clickable */
|
|
.slot.locked {
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
|
|
/* slots grid and slot styles */
|
|
.slots-grid {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 12px;
|
|
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;
|
|
}
|
|
|
|
.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 {
|
|
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;
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/* AV */
|
|
.av-title {
|
|
font-size: 13px;
|
|
font-weight: 700;
|
|
color: black;
|
|
margin: 6px 0 10px;
|
|
}
|
|
|
|
.av-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
}
|
|
|
|
.av-row {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
font-size: 13px;
|
|
}
|
|
|
|
.av-time,
|
|
.av-seats {
|
|
font-size: 13px;
|
|
font-weight: 400;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/* small note text under cards */
|
|
.submit-note {
|
|
font-family: "Inter", sans-serif;
|
|
font-size: 13px;
|
|
color: #000000;
|
|
text-align: left;
|
|
margin: 10px 0;
|
|
margin-left: 3px;
|
|
font-weight: 700;
|
|
}
|
|
|
|
/* make the button INSIDE cancel-book clearly disabled */
|
|
.cancel-book.section-enabled .btn {
|
|
background: #fff;
|
|
/* SAME background */
|
|
color: #000;
|
|
border-radius: 10px;
|
|
border: 3px solid #000;
|
|
/* SAME border */
|
|
cursor: pointer;
|
|
font-weight: 700;
|
|
box-shadow: 0 8px 26px rgba(18, 54, 59, 0.12);
|
|
}
|
|
|
|
|
|
|
|
/* ---------- Mobile polish (drop this at the end of your CSS) ---------- */
|
|
@media (max-width: 520px) {
|
|
|
|
/* page / body */
|
|
body {
|
|
padding: 12px 10px;
|
|
background-position: center top;
|
|
background-size: cover;
|
|
-webkit-font-smoothing: antialiased;
|
|
}
|
|
|
|
/* container & panels */
|
|
.container,
|
|
.panel-wrap,
|
|
.page {
|
|
width: 100%;
|
|
max-width: 420px;
|
|
margin: 0 auto;
|
|
padding: 0 8px;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.panel-wrap {
|
|
padding: 6px 0 18px;
|
|
}
|
|
|
|
/* header / nav */
|
|
.logo {
|
|
width: 61px;
|
|
height: 61px;
|
|
}
|
|
|
|
nav.primary {
|
|
display: none !important;
|
|
}
|
|
|
|
#desktopBookBtn {
|
|
display: none !important;
|
|
}
|
|
|
|
.hamburger {
|
|
display: flex !important;
|
|
}
|
|
|
|
/* hero */
|
|
.page h1 {
|
|
font-size: 32px !important;
|
|
width: 100%;
|
|
max-width: 360px;
|
|
margin: 6px auto 8px;
|
|
}
|
|
|
|
.hero .lead {
|
|
font-size: 14px !important;
|
|
max-width: 320px;
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/* contact row */
|
|
#contactLine {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 8px;
|
|
}
|
|
|
|
#contactDisplay {
|
|
min-width: 0;
|
|
flex: 1 1 auto;
|
|
padding: 10px 12px;
|
|
font-weight: 700;
|
|
border-radius: 10px;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
margin-left: -14px
|
|
}
|
|
|
|
#changeContact {
|
|
margin-left: 10px;
|
|
}
|
|
|
|
/* label and inline people row */
|
|
.muted-label {
|
|
font-size: 16px;
|
|
margin-left: 0 !important;
|
|
display: block;
|
|
}
|
|
|
|
/* whole row */
|
|
.people-row {
|
|
display: flex;
|
|
align-items: center;
|
|
width: 100%;
|
|
gap: 8px;
|
|
/* IMPORTANT: allow wrapping so row doesn't overflow */
|
|
white-space: normal;
|
|
}
|
|
|
|
/* label on the left */
|
|
.muted-label-inline {
|
|
flex: 1 1 auto;
|
|
/* take left side space */
|
|
margin-right: 0;
|
|
font-size: 15px;
|
|
font-weight: 600;
|
|
color: black;
|
|
text-align: left;
|
|
}
|
|
|
|
/* 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);
|
|
}
|
|
|
|
|
|
/* push controls to the right */
|
|
#minusPeople {
|
|
margin-left: auto;
|
|
/* this pushes the group to the right */
|
|
}
|
|
|
|
#peopleCount,
|
|
#plusPeople {
|
|
margin-left: 6px;
|
|
/* spacing between controls */
|
|
}
|
|
|
|
/* touch-friendly sizes */
|
|
.small {
|
|
width: 40px;
|
|
height: 40px;
|
|
border-radius: 10px;
|
|
font-size: 18px;
|
|
}
|
|
|
|
.counter {
|
|
min-width: 52px;
|
|
height: 40px;
|
|
border-radius: 10px;
|
|
font-size: 16px;
|
|
}
|
|
|
|
|
|
/* pills / time ranges */
|
|
.time-ranges {
|
|
gap: 8px;
|
|
margin: 12px 0;
|
|
justify-content: flex-start;
|
|
overflow-x: auto;
|
|
-webkit-overflow-scrolling: touch;
|
|
padding-bottom: 6px;
|
|
}
|
|
|
|
.time-ranges::-webkit-scrollbar {
|
|
display: none;
|
|
}
|
|
|
|
.pill {
|
|
min-width: 96px;
|
|
padding: 5px;
|
|
border-radius: 10px;
|
|
flex: 0 0 auto;
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
.summary-inline {
|
|
justify-content: flex-end !important;
|
|
gap: 12px !important;
|
|
flex-wrap: wrap !important;
|
|
white-space: normal !important;
|
|
}
|
|
|
|
.summary-item {
|
|
flex: 0 0 48% !important;
|
|
/* two-column layout on small screens */
|
|
box-sizing: border-box !important;
|
|
}
|
|
|
|
.summary-item.unallocated {
|
|
justify-content: flex-start !important;
|
|
text-align: left !important;
|
|
}
|
|
|
|
.summary-item.allocated {
|
|
justify-content: flex-end !important;
|
|
text-align: right !important;
|
|
}
|
|
|
|
/* submit area */
|
|
.submit-wrap {
|
|
margin-top: 18px;
|
|
padding-bottom: 18px;
|
|
}
|
|
|
|
.btn {
|
|
width: 100%;
|
|
padding: 12px 14px;
|
|
font-size: 16px;
|
|
border-radius: 12px;
|
|
}
|
|
|
|
/* small text adjustments */
|
|
.muted,
|
|
.people-support,
|
|
.summary-item {
|
|
font-size: 13px;
|
|
line-height: 1.3;
|
|
}
|
|
|
|
/* avoid long fixed widths on mobile that break layout */
|
|
.page h1,
|
|
.hero .lead,
|
|
.preview-note-box,
|
|
.summary-inline,
|
|
.field {
|
|
max-width: 100%;
|
|
}
|
|
|
|
/* small helper: ensure form controls never overflow */
|
|
input[type="text"],
|
|
input[type="date"],
|
|
textarea,
|
|
select {
|
|
max-width: 100%;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
/* interaction: slightly larger tap areas on interactive elements */
|
|
.pill,
|
|
.slot,
|
|
.small,
|
|
.btn {
|
|
touch-action: manipulation;
|
|
-webkit-tap-highlight-color: rgba(0, 0, 0, 0.06);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/* small helpers */
|
|
.link {
|
|
color: #0b76c6;
|
|
text-decoration: underline;
|
|
cursor: pointer;
|
|
font-size: 14px;
|
|
}
|
|
|
|
|
|
.vph-modal-overlay {
|
|
position: fixed;
|
|
inset: 0;
|
|
background: rgba(0, 0, 0, 0.56);
|
|
display: none;
|
|
align-items: center;
|
|
justify-content: center;
|
|
z-index: 9999;
|
|
padding: 28px;
|
|
-webkit-font-smoothing: antialiased;
|
|
-moz-osx-font-smoothing: grayscale;
|
|
}
|
|
|
|
.vph-modal-overlay.show {
|
|
display: flex !important;
|
|
}
|
|
|
|
/* modal card */
|
|
.vph-modal {
|
|
width: 500px;
|
|
max-width: 100%;
|
|
background: #fff;
|
|
border-radius: 12px;
|
|
box-shadow: 0 30px 80px rgba(7, 25, 30, 0.45);
|
|
padding: 20px 22px;
|
|
position: relative;
|
|
outline: none;
|
|
|
|
/* keep on-screen */
|
|
overflow: hidden;
|
|
/* inner area handles overflow */
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
transition: transform .18s ease, opacity .18s ease;
|
|
transform: translateY(0);
|
|
}
|
|
|
|
|
|
|
|
/* when overlay appears the modal can fade in (optional) */
|
|
.vph-modal-overlay.show .vph-modal {
|
|
transform: translateY(0);
|
|
opacity: 1;
|
|
}
|
|
|
|
/* header */
|
|
.vph-modal-header {
|
|
text-align: center;
|
|
margin-top: 2px;
|
|
}
|
|
|
|
.vph-modal-header h3 {
|
|
margin: 0;
|
|
font-weight: 700;
|
|
color: #3B1E14;
|
|
font-size: 18px;
|
|
letter-spacing: 0;
|
|
font-family: "Inter", sans-serif !important;
|
|
/* Add Inter font */
|
|
}
|
|
|
|
/* content area (scrollable) */
|
|
.vph-modal .vph-modal-content {
|
|
flex: 1 1 auto;
|
|
overflow-y: auto;
|
|
padding-right: 6px;
|
|
}
|
|
|
|
/* summary box (top) */
|
|
.vph-summary-box {
|
|
|
|
min-height: auto;
|
|
/* ✅ allow growth */
|
|
height: auto;
|
|
/* ✅ dynamic */
|
|
|
|
|
|
background: #f3f4f2;
|
|
/* subtle off-white */
|
|
border-radius: 8px;
|
|
padding: 12px;
|
|
margin: 0;
|
|
border: 1px solid rgba(0, 0, 0, 0.02);
|
|
}
|
|
|
|
/* two-column summary row */
|
|
.vph-summary-row {
|
|
display: flex;
|
|
gap: 12px;
|
|
align-items: flex-start;
|
|
justify-content: space-between;
|
|
}
|
|
|
|
.vph-summary-left,
|
|
.vph-summary-right {
|
|
width: 48%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
}
|
|
|
|
.vph-summary-left .vph-label {
|
|
color: #000 !important;
|
|
/* Black text */
|
|
font-size: 13px;
|
|
font-weight: 700 !important;
|
|
/* Normal, not bold */
|
|
font-family: "Inter", sans-serif !important;
|
|
/* Add Inter font */
|
|
}
|
|
|
|
.vph-summary-right {
|
|
text-align: right;
|
|
color: #0b0b0b;
|
|
font-size: 14px;
|
|
font-weight: 400;
|
|
|
|
}
|
|
|
|
/* AV Experience box */
|
|
.vph-av-box {
|
|
/* width:562px; */
|
|
/* height:114px; */
|
|
max-height: 30vh;
|
|
overflow-y: auto;
|
|
background: #f3f4f2;
|
|
border-radius: 8px;
|
|
padding: 10px 12px;
|
|
border: 1px solid rgba(0, 0, 0, 0.02);
|
|
}
|
|
|
|
.vph-av-box::-webkit-scrollbar {
|
|
width: 4px;
|
|
}
|
|
|
|
.vph-av-box::-webkit-scrollbar-thumb {
|
|
background: #b5b5b5;
|
|
border-radius: 5px;
|
|
}
|
|
|
|
|
|
|
|
.vph-av-row {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 8px 10px;
|
|
border-radius: 6px;
|
|
background: transparent;
|
|
}
|
|
|
|
.vph-av-time {
|
|
font-weight: 400;
|
|
color: #0b0b0b;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.vph-av-meta {
|
|
font-weight: 400;
|
|
color: #0b0b0b;
|
|
font-size: 14px;
|
|
}
|
|
|
|
/* footer area — controls */
|
|
.vph-modal-footer {
|
|
display: flex;
|
|
gap: 12px;
|
|
align-items: center;
|
|
margin-top: auto;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
/* left icon button (circle) */
|
|
.vph-modal-icon-btn {
|
|
Width: 66px;
|
|
Height: 66px;
|
|
border-radius: 10px;
|
|
border: 1px solid #043B55;
|
|
background: #fff;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
cursor: pointer;
|
|
box-shadow: 0 6px 18px rgba(7, 25, 30, 0.04);
|
|
}
|
|
|
|
.vph-modal-submit {
|
|
width: 100%;
|
|
/* responsive */
|
|
max-width: 482px;
|
|
/* desktop size */
|
|
height: 66px;
|
|
|
|
padding: 12px 36px;
|
|
border-radius: 10px;
|
|
background: #043B55;
|
|
color: #fff;
|
|
border: none;
|
|
font-weight: 700;
|
|
cursor: pointer;
|
|
box-shadow: 0 10px 30px rgba(11, 75, 72, 0.14);
|
|
}
|
|
|
|
/* close X */
|
|
.vph-modal-close {
|
|
position: absolute;
|
|
right: 12px;
|
|
top: 12px;
|
|
width: 36px;
|
|
height: 36px;
|
|
border-radius: 8px;
|
|
border: none;
|
|
background: transparent;
|
|
font-size: 20px;
|
|
cursor: pointer;
|
|
color: #6b6b6b;
|
|
}
|
|
|
|
|
|
/* ===== Cancel Booking modal helpers (re-use existing .vph-modal styles) ===== */
|
|
|
|
/* two-step views */
|
|
.vph-view {
|
|
display: none;
|
|
}
|
|
|
|
.vph-view.active {
|
|
display: block;
|
|
}
|
|
|
|
/* scrollable content area if you ever need body text */
|
|
|
|
|
|
/* footer: normal row + column variant for success view */
|
|
.vph-modal-footer {
|
|
display: flex;
|
|
gap: 12px;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
margin-top: 25px;
|
|
}
|
|
|
|
.vph-modal-footer.column {
|
|
flex-direction: column;
|
|
align-items: stretch;
|
|
gap: 12px;
|
|
margin-top: 14px;
|
|
}
|
|
|
|
/* icon button on left */
|
|
.vph-modal-icon-btn {
|
|
width: 66px;
|
|
height: 50px;
|
|
border-radius: 10px;
|
|
border: 1px solid #043B55;
|
|
background: #fff;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
cursor: pointer;
|
|
box-shadow: 0 6px 18px rgba(7, 25, 30, 0.04);
|
|
}
|
|
|
|
/* primary + ghost buttons for cancel modal */
|
|
.vph-modal-submit.primary {
|
|
height: 50px;
|
|
width: 100%;
|
|
padding: 14px 18px;
|
|
border-radius: 10px;
|
|
background: #043B55;
|
|
color: #fff;
|
|
border: none;
|
|
font-weight: 700;
|
|
cursor: pointer;
|
|
box-shadow: 0 10px 30px rgba(11, 75, 72, 0.14);
|
|
}
|
|
|
|
.vph-modal-submit.ghost {
|
|
height: 50px;
|
|
width: 100%;
|
|
padding: 12px 16px;
|
|
border-radius: 10px;
|
|
background: #fff;
|
|
color: #0b0b0b;
|
|
border: 2px solid rgba(0, 0, 0, 0.08);
|
|
font-weight: 700;
|
|
cursor: pointer;
|
|
}
|
|
|
|
/* small compact info box (if needed later) */
|
|
.vph-summary-box.small {
|
|
background: #f7f7f6;
|
|
border-radius: 8px;
|
|
padding: 10px;
|
|
border: 1px solid rgba(0, 0, 0, 0.04);
|
|
margin-bottom: 6px;
|
|
text-align: center;
|
|
}
|
|
|
|
/* small screens: stack and make scrollable */
|
|
@media (max-width: 520px) {
|
|
.vph-modal {
|
|
width: 94%;
|
|
padding: 14px;
|
|
border-radius: 12px;
|
|
max-height: 92vh;
|
|
}
|
|
|
|
.vph-summary-row {
|
|
gap: 10px;
|
|
flex-direction: row;
|
|
}
|
|
|
|
.vph-summary-left,
|
|
.vph-summary-right {
|
|
width: 48%;
|
|
}
|
|
|
|
/*.vph-av-list {*/
|
|
/* max-height: 30vh;*/
|
|
/*}*/
|
|
|
|
.vph-modal-footer {
|
|
gap: 10px;
|
|
align-items: center;
|
|
}
|
|
|
|
.vph-modal-submit {
|
|
width: 56%;
|
|
padding: 12px;
|
|
width: 256px;
|
|
}
|
|
}
|
|
|
|
/* optional: thin custom scrollbar inside modal for better UX */
|
|
|
|
.vph-modal ::-webkit-scrollbar {
|
|
height: 8px;
|
|
width: 8px;
|
|
}
|
|
|
|
|
|
.vph-modal ::-webkit-scrollbar-thumb {
|
|
background: rgba(0, 0, 0, 0.08);
|
|
border-radius: 6px;
|
|
}
|
|
|
|
/* make sure text wrapping and truncation are tidy */
|
|
.vph-summary-right div,
|
|
.vph-summary-left div,
|
|
.vph-av-row {
|
|
word-break: break-word;
|
|
}
|
|
|
|
.panel {
|
|
background: transparent;
|
|
}
|
|
|
|
.date-wrap .form-control[readonly] {
|
|
background-color: #fff !important;
|
|
color: #000 !important;
|
|
}
|
|
|
|
.disabled-pill {
|
|
background-color: #eee !important;
|
|
border-color: #ddd !important;
|
|
}
|
|
|
|
.no-slots-message {
|
|
margin-top: 10px;
|
|
color: #c62828;
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.no-slots-message-box {
|
|
padding: 12px;
|
|
margin-top: 10px;
|
|
text-align: center;
|
|
color: #d9534f;
|
|
/* red */
|
|
font-weight: 600;
|
|
font-size: 14px;
|
|
/* background: #ffeaea; */
|
|
/* soft red background */
|
|
border-radius: 6px;
|
|
}
|
|
|
|
/* make sure summary left labels are visible and take up space */
|
|
.summary-row {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: 12px;
|
|
}
|
|
|
|
.summary-left,
|
|
.summary-right {
|
|
width: 50%;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.summary-left .label {
|
|
display: block;
|
|
color: #111 !important;
|
|
/* force readable color */
|
|
font-size: 13px;
|
|
font-weight: 700;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.summary-right>div {
|
|
display: block;
|
|
color: #111 !important;
|
|
font-weight: 400;
|
|
text-align: right;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
/* =========================================
|
|
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;
|
|
}
|
|
|
|
/* FIX: Cancel Booking button should NOT be full width */
|
|
#cancelBookSection .submit-wrap {
|
|
display: flex;
|
|
gap:12px;
|
|
justify-content: center;
|
|
|
|
flex-wrap: wrap; /* allow stacking */
|
|
}
|
|
|
|
#cancelBookSection .submit-wrap .btn {
|
|
width: auto !important;
|
|
max-width: none !important;
|
|
min-width: 240px;
|
|
padding: 12px 32px;
|
|
margin-right: 4px
|
|
}
|
|
|
|
/*
|
|
bugs follows
|
|
*/
|
|
|
|
/* Interchange font sizes */
|
|
|
|
/* Make preview note text bigger */
|
|
.preview-note-box {
|
|
font-size: 16px !important;
|
|
font-weight: 500;
|
|
line-height: 1.6;
|
|
}
|
|
|
|
/* Make the label slightly smaller */
|
|
.field>.muted-label {
|
|
font-size: 14px !important;
|
|
font-weight: 600;
|
|
}
|
|
|
|
@keyframes shake {
|
|
0% {
|
|
transform: translateX(0);
|
|
}
|
|
|
|
20% {
|
|
transform: translateX(-5px);
|
|
}
|
|
|
|
40% {
|
|
transform: translateX(5px);
|
|
}
|
|
|
|
60% {
|
|
transform: translateX(-5px);
|
|
}
|
|
|
|
80% {
|
|
transform: translateX(5px);
|
|
}
|
|
|
|
100% {
|
|
transform: translateX(0);
|
|
}
|
|
}
|
|
|
|
.slot.shake {
|
|
animation: shake 0.35s ease;
|
|
border-color: #dc2626 !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 */
|
|
}
|
|
|
|
|
|
|
|
|
|
/* age group details Container */
|
|
/* Container */
|
|
.people-type-list {
|
|
margin-top: 12px;
|
|
display: grid;
|
|
/*grid-template-columns: repeat(2, 1fr); !* 2 by 2 *!*/
|
|
column-gap: 24px;
|
|
row-gap: 16px;
|
|
}
|
|
|
|
/* Each card */
|
|
.people-type-row {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
/* Label */
|
|
.people-type-label {
|
|
font-weight: 600;
|
|
}
|
|
|
|
/* Controls */
|
|
.people-type-controls {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
|
|
|
|
.people-total-row {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
/* ✅ pushes content to right */
|
|
align-items: center;
|
|
margin-top: 20px;
|
|
font-weight: 700;
|
|
font-size: 20px;
|
|
}
|
|
|
|
.people-total-label {
|
|
margin-right: 15px;
|
|
}
|
|
|
|
.people-total-value {
|
|
color: #043B55;
|
|
}
|
|
|
|
|
|
/* aadhar & passport */
|
|
.id-row {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 12px;
|
|
margin-top: 6px;
|
|
padding-top: 10px;
|
|
padding-bottom: 10px;
|
|
}
|
|
|
|
|
|
|
|
/* ================================
|
|
PREVIEW MODAL – EXTRA DETAILS
|
|
================================ */
|
|
|
|
/* Shared box (re-uses your visual language) */
|
|
.breakdown-box {
|
|
background: #f3f4f2;
|
|
border-radius: 8px;
|
|
padding: 14px 16px;
|
|
margin-top: 14px;
|
|
border: 1px solid rgba(0, 0, 0, 0.05);
|
|
}
|
|
|
|
/* Header row */
|
|
.breakdown-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
font-family: "Inter", sans-serif;
|
|
font-weight: 700;
|
|
font-size: 14px;
|
|
color: #111;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
/* Rows container */
|
|
.breakdown-body {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
}
|
|
|
|
/* Each row */
|
|
.breakdown-row {
|
|
display: grid;
|
|
grid-template-columns: 1fr auto;
|
|
align-items: center;
|
|
font-family: "Inter", sans-serif;
|
|
font-size: 14px;
|
|
}
|
|
|
|
/* Labels */
|
|
.breakdown-label {
|
|
color: #111;
|
|
font-weight: 500;
|
|
}
|
|
|
|
/* Amount values */
|
|
.breakdown-value {
|
|
font-weight: 600;
|
|
text-align: right;
|
|
min-width: 70px;
|
|
}
|
|
|
|
/* Divider line */
|
|
.breakdown-divider {
|
|
margin: 10px 0;
|
|
border-top: 1px solid #d1d5db;
|
|
}
|
|
|
|
/* Total row */
|
|
.breakdown-row.total {
|
|
font-size: 16px;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.breakdown-row.total .breakdown-value {
|
|
color: #043B55;
|
|
}
|
|
|
|
/* ================================
|
|
ID PROOF SECTION
|
|
================================ */
|
|
|
|
.breakdown-idproof {
|
|
margin-top: 8px;
|
|
font-family: "Inter", sans-serif;
|
|
font-size: 14px;
|
|
color: #111;
|
|
display: flex;
|
|
gap: 8px;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.breakdown-idproof strong {
|
|
font-weight: 700;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@media (min-width: 768px) {
|
|
|
|
|
|
.muted-label-inline {
|
|
margin-right: 320px;
|
|
}
|
|
|
|
.people-type-list {
|
|
grid-template-columns: repeat(2, 1fr);
|
|
}
|
|
}
|
|
|
|
@media(min-width: 768px) {
|
|
|
|
#contactLine {
|
|
display: flex;
|
|
gap: 360px;
|
|
margin-left: -14px;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@media (max-width: 520px) {
|
|
.people-type-list {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
|
|
|
|
@media (min-width: 521px) {
|
|
#cancelBookSection .submit-wrap .btn {
|
|
min-width: 200px;
|
|
width: auto !important;
|
|
}
|
|
}
|
|
|
|
/* Mobile */
|
|
@media (max-width: 520px) {
|
|
#cancelBookSection .submit-wrap {
|
|
flex-direction: column;
|
|
align-items: stretch;
|
|
}
|
|
|
|
#cancelBookSection .submit-wrap .btn {
|
|
width: 100% !important;
|
|
min-width: unset !important;
|
|
margin-right: 0 !important;
|
|
}
|
|
}
|
|
|
|
/* Cancel button – Red */
|
|
#cancelBtn {
|
|
background: #dc2626 !important; /* red-600 */
|
|
color: #fff !important;
|
|
border: none;
|
|
}
|
|
|
|
#cancelBtn:hover {
|
|
background: #b91c1c !important; /* darker red */
|
|
}
|
|
|
|
/* QR button – Green */
|
|
#qrBtn {
|
|
background: #16a34a !important; /* green-600 */
|
|
color: #fff !important;
|
|
border: none;
|
|
}
|
|
|
|
#qrBtn:hover {
|
|
background: #15803d !important; /* darker green */
|
|
}
|
|
|
|
</style>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<!-- </head> -->
|
|
|
|
<body>
|
|
<div class="container">
|
|
<header class="topbar" role="banner">
|
|
<div class="leftbar">
|
|
<div class="nav-left">
|
|
<!-- logo image — use same img src you already have -->
|
|
|
|
<img th:src="@{images/VPH-logo_main-EN 1.png}" alt="VPH logo" class="logo">
|
|
</div>
|
|
|
|
<!-- desktop nav (hidden on mobile) -->
|
|
<nav class="primary" aria-label="Primary navigation">
|
|
<a href="/victoriapublichall/home">Home</a>
|
|
<!-- <a href="#">About VPH</a>-->
|
|
<a href="/victoriapublichall/visit">Visit</a>
|
|
<!--<a href="#">Contact Us</a>-->
|
|
</nav>
|
|
</div>
|
|
|
|
<!-- right side: hamburger (mobile) + desktop CTA -->
|
|
<div class="rightbar">
|
|
<button id="hambtn" class="hamburger" aria-label="Open menu" aria-expanded="false" aria-controls="mobileNav">
|
|
<span class="bar bar1" aria-hidden="true"></span>
|
|
<span class="bar bar2" aria-hidden="true"></span>
|
|
<span class="bar bar3" aria-hidden="true"></span>
|
|
</button>
|
|
|
|
|
|
</div>
|
|
</header>
|
|
|
|
<!-- overlay (click to close) -->
|
|
<div id="mobileOverlay" class="mobile-overlay" aria-hidden="true"></div>
|
|
|
|
<!-- mobile nav -->
|
|
<nav id="mobileNav" class="mobile-nav" aria-hidden="true" role="dialog" aria-modal="true" aria-label="Mobile menu">
|
|
|
|
<a href="/victoriapublichall/home">Home</a>
|
|
<a href="#">About VPH</a>
|
|
<a href="/victoriapublichall/visit">Visit</a>
|
|
<a href="#">Contact Us</a>
|
|
|
|
</nav>
|
|
|
|
<main class="page" role="main" aria-labelledby="hero-heading">
|
|
<div class="hero" aria-hidden="false">
|
|
<h1 id="hero-heading">Victoria Public Hall</h1>
|
|
<div class="lead">Experience Chennai's First Town Hall</div>
|
|
</div>
|
|
|
|
<div class="panel-wrap">
|
|
|
|
<div class="panel" role="region" aria-label="Booking panel">
|
|
|
|
|
|
<div class="people-support">
|
|
For booking more than 10 people, please click here:
|
|
<!-- <a class="link" href="/victoriapublichall/bulkbooking" onclick="storeMobileForBulk()"> </a>-->
|
|
<a class="link" href="javascript:void(0)" onclick="handleBulkBookingClick()">
|
|
Bulk Booking Request
|
|
</a>
|
|
|
|
</div>
|
|
|
|
|
|
<!-- phone/email & OTP -->
|
|
|
|
|
|
<div class="field mt-4">
|
|
<label class="muted-label">Phone / Email</label>
|
|
<div id="contactLine">
|
|
<div id="contactDisplay">
|
|
|
|
</div>
|
|
<a id="changeContact" href="/victoriapublichall/register">
|
|
Change
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<div id="bookVisitSection" class="book-vist">
|
|
|
|
<!-- select date -->
|
|
<div class="field">
|
|
<label class="muted-label">Select Date</label>
|
|
<div class="date-wrap">
|
|
<input id="dateInput" class="input" type="date" aria-label="Select date">
|
|
</div>
|
|
</div>
|
|
<div class="field">
|
|
<div class="date-note">
|
|
<strong>Note:</strong> Victoria Public Hall remains closed every Tuesday for maintenance.
|
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<div class="field">
|
|
<label class="muted-label">Identity Proof</label>
|
|
|
|
<div class="id-row">
|
|
<!-- DROPDOWN -->
|
|
<select id="idType" class="input compact">
|
|
<option value="">Select ID Proof</option>
|
|
</select>
|
|
|
|
<!-- NUMBER INPUT -->
|
|
<input type="text" id="idNumber" class="input compact" placeholder="Enter ID Number" disabled />
|
|
<small id="idError" style="color:#dc2626; display:none; font-weight:600;"></small>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="field" style="margin-bottom:30px;">
|
|
|
|
<!-- SINGLE LABEL -->
|
|
<label class="muted-label-inline">Number of people</label>
|
|
|
|
<!-- DYNAMIC LIST -->
|
|
<div id="peopleTypeList" class="people-type-list"></div>
|
|
|
|
|
|
<div class="field">
|
|
<div class="people-total-row">
|
|
<span class="people-total-label">Total Amount</span>
|
|
<span id="totalAmount" class="people-total-value">₹0</span>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
<hr>
|
|
|
|
<br>
|
|
|
|
<!-- Pick your time ranges -->
|
|
<!-- time-range pills -->
|
|
|
|
|
|
<div class="field border-field">
|
|
<label class="muted-label">Pick your time</label>
|
|
<div class="time-ranges" id="timeRanges" role="tablist" aria-label="Time ranges">
|
|
|
|
</div>
|
|
|
|
<div id="noAvInfoMsg" class="no-slots-message-box" style="display:none;">
|
|
</div>
|
|
|
|
<!-- <div id="noAvInfoMsg" class="no-slots-message-box" style="display:none;">
|
|
During the above time slots, the public will be permitted only within the premises of Victoria Public Hall.
|
|
Entry into the building will not be allowed.
|
|
</div>-->
|
|
|
|
<!--<div class="info-row">
|
|
<div class="preview-note-box">
|
|
<Strong>Your visit includes a audio visual experience, please select as many shows as required
|
|
to
|
|
accommodate all visitors.</Strong>
|
|
</div>
|
|
|
|
<div class="summary-inline" aria-hidden="false">
|
|
<div class="summary-item unallocated" aria-label="Guest unallocated">
|
|
<span>Guest unallocated</span>
|
|
<strong>03</strong>
|
|
</div>
|
|
|
|
<div class="summary-item allocated" aria-label="Guest allocated">
|
|
<span>Guest allocated</span>
|
|
<strong>03</strong>
|
|
</div>
|
|
</div>
|
|
|
|
</div>-->
|
|
|
|
|
|
</div>
|
|
|
|
<!-- slots grid -->
|
|
<div class="field border-field">
|
|
|
|
<div class="info-row">
|
|
<div class="preview-note-box">
|
|
<Strong>Your visit includes a audio visual experience, please select as many shows as required
|
|
to
|
|
accommodate all visitors.</Strong>
|
|
</div>
|
|
|
|
<div class="summary-inline" aria-hidden="false">
|
|
<div class="summary-item unallocated" aria-label="Guest unallocated">
|
|
<span>Guest unallocated</span>
|
|
<strong>03</strong>
|
|
</div>
|
|
|
|
<div class="summary-item allocated" aria-label="Guest allocated">
|
|
<span>Guest allocated</span>
|
|
<strong>03</strong>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
<label style="margin:8px 0 6px 0" class="muted-label">Pick one or more shows for all visitors</label>
|
|
<div id="slotsGrid" class="slots-grid" aria-live="polite"></div>
|
|
</div>
|
|
|
|
<div class="submit-wrap">
|
|
<p class="submit-note">Your booking details will be sent to you via email/SMS.</p>
|
|
<button id="previewBtn" class="btn">Preview & Submit</button>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
<!-- CANCEL BOOK SECTION (DISABLED BUT VISIBLE) -->
|
|
<div id="cancelBookSection" class="cancel-book">
|
|
<div class="summary-card">
|
|
|
|
<!-- TOP SUMMARY ROW -->
|
|
<div class="summary-row">
|
|
<div class="summary-left">
|
|
<div class="label">Visiting on</div>
|
|
<div class="label">Time</div>
|
|
<div class="label">Total Guest(s)</div>
|
|
</div>
|
|
|
|
<div class="summary-right">
|
|
<div id="confirmDate"></div>
|
|
<div id="confirmRange"></div>
|
|
<div id="confirmPeople"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="av-title">AV Experience</div>
|
|
<!-- AV LIST -->
|
|
<div class="av-list">
|
|
<div class="av-row">
|
|
<div class="av-time"></div>
|
|
<div class="av-seats"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Disabled cancel button inside cancel-book -->
|
|
<div class="submit-wrap">
|
|
<button id="cancelBtn" class="btn" type="button">Cancel Booking</button>
|
|
<button id="qrBtn" class="btn" type="button">Get BookingQR</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<p class="submit-note">
|
|
You can book additional visits only after completing/canceling your current visit.
|
|
</p>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
|
|
<!-- Booking Preview Modal (add to end of <body>) -->
|
|
<div id="vphBookingModalOverlay" class="vph-modal-overlay" aria-hidden="true">
|
|
<div id="vphBookingModal" class="vph-modal" role="dialog" aria-modal="true" aria-labelledby="vphModalTitle"
|
|
tabindex="-1">
|
|
<header class="vph-modal-header">
|
|
<h3 id="vphModalTitle">Your visit details</h3>
|
|
</header>
|
|
|
|
<section class="vph-summary-box" aria-live="polite">
|
|
<div class="vph-summary-row">
|
|
<div class="vph-summary-left">
|
|
<div class="vph-label">Visiting on</div>
|
|
<div class="vph-label">Time</div>
|
|
<div class="vph-label">ID Proof</div>
|
|
<div class="vph-label">ID Number</div>
|
|
<div class="vph-label">Total Guest(s)</div>
|
|
</div>
|
|
<div class="vph-summary-right">
|
|
<div id="vphModalDate">—</div>
|
|
<div id="vphModalRange">—</div>
|
|
<div id="vphModalIdType">—</div>
|
|
<div id="vphModalIdNumber">—</div>
|
|
<div id="vphModalPeople">—</div>
|
|
</div>
|
|
</div>
|
|
<!--class="vph-av-title"-->
|
|
<div id="vphAvTitle" class="vph-label">AV Experience</div>
|
|
<div id="vphModalSlotsList" aria-hidden="false">
|
|
<!-- populated by JS -->
|
|
</div>
|
|
|
|
</section>
|
|
|
|
|
|
<!-- Guest Breakdown -->
|
|
<section class="vph-summary-box breakdown-box">
|
|
<div class="breakdown-header">
|
|
<span>Guest Details</span>
|
|
<span>Amount</span>
|
|
</div>
|
|
|
|
<div id="previewGuestBreakdown" class="breakdown-body">
|
|
<!-- Filled by JS -->
|
|
</div>
|
|
|
|
<div class="breakdown-divider"></div>
|
|
|
|
<div class="breakdown-row total">
|
|
<span>Total Amount</span>
|
|
<span id="previewTotalAmount">₹0</span>
|
|
</div>
|
|
</section>
|
|
|
|
|
|
<footer class="vph-modal-footer">
|
|
<button id="vphModalBack" class="vph-modal-icon-btn" aria-label="Back">
|
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
|
<path d="M15 18l-6-6 6-6" stroke="#0f4b4a" stroke-width="2" stroke-linecap="round"
|
|
stroke-linejoin="round" />
|
|
</svg>
|
|
</button>
|
|
|
|
<button id="vphModalSubmit" class="vph-modal-submit">Proceed to Payment</button>
|
|
</footer>
|
|
|
|
<!-- <button id="vphModalClose" class="vph-modal-close" aria-label="Close dialog">×</button> -->
|
|
<button class="vph-modal-close preview-close" aria-label="Close dialog">×</button>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<!-- Cancel Booking – 2-step modal -->
|
|
<div id="vphModalOverlay" class="vph-modal-overlay" aria-hidden="true">
|
|
<div id="vphModal" class="vph-modal" role="dialog" aria-modal="true" aria-labelledby="vphModalTitleConfirm"
|
|
tabindex="-1">
|
|
|
|
<div class="vph-modal-body">
|
|
|
|
<!-- VIEW 1: Confirmation -->
|
|
<div id="vphViewConfirm" class="vph-view" role="document" aria-hidden="true">
|
|
<header class="vph-modal-header">
|
|
<h3 id="vphModalTitleConfirm">Are you sure you want to cancel your visit?</h3>
|
|
</header>
|
|
|
|
<footer class="vph-modal-footer">
|
|
<button id="vphConfirmBack" class="vph-modal-icon-btn" aria-label="Go back">
|
|
<!-- left arrow icon -->
|
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
|
<path d="M15 18l-6-6 6-6" stroke="#0f4b4a" stroke-width="2" stroke-linecap="round"
|
|
stroke-linejoin="round" />
|
|
</svg>
|
|
</button>
|
|
|
|
<button id="vphConfirmYes" class="vph-modal-submit primary">
|
|
Yes, Cancel booking
|
|
</button>
|
|
</footer>
|
|
</div>
|
|
|
|
<!-- VIEW 2: Result / Success -->
|
|
<div id="vphViewCancelled" class="vph-view" role="document" aria-hidden="true">
|
|
<header class="vph-modal-header">
|
|
<h3 id="vphModalTitleCancelled">Booking Cancelled!</h3>
|
|
</header>
|
|
|
|
|
|
|
|
<footer class="vph-modal-footer column">
|
|
<button id="vphGoMain" class="vph-modal-submit ghost">
|
|
Go back to main page
|
|
</button>
|
|
<button id="vphBookAnother" class="vph-modal-submit primary">
|
|
Book another visit
|
|
</button>
|
|
</footer>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- Close X -->
|
|
<!-- <button id="vphModalClose" class="vph-modal-close" aria-label="Close dialog">×</button> -->
|
|
<button class="vph-modal-close cancel-close" aria-label="Close dialog">×</button>
|
|
|
|
</div>
|
|
</div>
|
|
<div id="pageLoader"
|
|
style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(255,255,255,0.8); z-index: 9999; text-align: center;">
|
|
<div style="position: relative; top: 50%; transform: translateY(-50%);">
|
|
<div class="spinner-border text-primary" style="width: 3rem; height: 3rem;"></div>
|
|
<p style="margin-top: 1rem; font-size: 1.2rem; color: #007bff;">Please wait...</p>
|
|
</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
|
|
|
|
|
|
/* Full corrected booking JS */
|
|
let activeRange = null;
|
|
let activeTimeId = null;
|
|
let bookingId = null;
|
|
let bookingemail = null;
|
|
let bookingmob_no = null;
|
|
|
|
let people = 0;
|
|
// const MIN_PEOPLE = 1;
|
|
// const MAX_PEOPLE = 10;
|
|
|
|
// Selected time slots map
|
|
const chosenSlots = new Map();
|
|
let slotsGrid;
|
|
let unallocEl, allocEl, dateInput;
|
|
|
|
let peopleTypes = [];
|
|
|
|
let isIdNumberValid = false;
|
|
|
|
|
|
|
|
|
|
$(document).ready(function () {
|
|
|
|
updateGuestSummary();
|
|
// DOM refs (safe)
|
|
slotsGrid = document.getElementById('slotsGrid');
|
|
// minusPeople = document.getElementById('minusPeople');
|
|
// plusPeople = document.getElementById('plusPeople');
|
|
// peopleCount = document.getElementById('peopleCount');
|
|
unallocEl = document.querySelector('.summary-item.unallocated strong');
|
|
allocEl = document.querySelector('.summary-item.allocated strong');
|
|
dateInput = document.getElementById('dateInput');
|
|
//Booking / cancel sections
|
|
const bookVisit = document.getElementById("bookVisitSection");
|
|
const cancelBook = document.getElementById("cancelBookSection");
|
|
const cancelBtn = document.getElementById("cancelBtn");
|
|
|
|
const previewBtn = document.getElementById('previewBtn');
|
|
|
|
const qrBtn = document.getElementById("qrBtn");
|
|
|
|
// Basic session handling
|
|
let hasBooking = sessionStorage.getItem("hasBooking") === "true";
|
|
let hasuser = sessionStorage.getItem("hasuser");
|
|
let bookingdetails = sessionStorage.getItem("bookingdetails");
|
|
console.log("→ hasBooking:", hasBooking);
|
|
console.log("→ hasuser:", hasuser);
|
|
console.log("→ bookingdetails:", bookingdetails);
|
|
|
|
|
|
|
|
|
|
|
|
if (hasuser === null || hasuser.trim() === "") {
|
|
window.location.href = "/victoriapublichall/register";
|
|
return;
|
|
}
|
|
|
|
const Displayname = document.getElementById("contactDisplay");
|
|
if (Displayname && hasuser) Displayname.textContent = hasuser;
|
|
|
|
let validBooking = false;
|
|
|
|
if (bookingdetails && bookingdetails.trim() !== "") {
|
|
try {
|
|
let b = JSON.parse(bookingdetails);
|
|
validBooking = b && b.id; // valid only if id exists
|
|
} catch (e) {
|
|
validBooking = false;
|
|
}
|
|
}
|
|
if (hasBooking || validBooking) {
|
|
|
|
bookVisit.classList.add("section-hidden");
|
|
cancelBook.classList.add("section-enabled");
|
|
cancelBtn.disabled = false;
|
|
|
|
if (validBooking) {
|
|
let b = JSON.parse(bookingdetails);
|
|
|
|
console.log("details>>>>>>>>>",b)
|
|
|
|
bookingId = b.id;
|
|
|
|
bookingemail = b.email;
|
|
bookingmob_no = b.mob_no;
|
|
|
|
bookingorderid=b.order_id;
|
|
|
|
console.log(bookingemail);
|
|
console.log(bookingmob_no);
|
|
|
|
console.log("bookingorderid>>>>",bookingorderid);
|
|
|
|
document.getElementById("confirmDate").textContent = formatDisplayDate(b.visitingDate);
|
|
document.getElementById("confirmRange").textContent = b.timeRange;
|
|
document.getElementById("confirmPeople").textContent =
|
|
String(b.no_of_people).padStart(2, "0");
|
|
|
|
const avList = document.querySelector(".av-list");
|
|
avList.innerHTML = "";
|
|
|
|
b.allocatedSlots.forEach(s => {
|
|
console.log(s);
|
|
avList.innerHTML += `
|
|
<div class="av-row">
|
|
<div class="av-time">${s.time} (${s.language})</div>
|
|
<div class="av-seats">${s.people} Seat(s)</div>
|
|
</div>
|
|
`;
|
|
});
|
|
}
|
|
|
|
} else {
|
|
|
|
cancelBook.classList.remove("section-enabled");
|
|
bookVisit.classList.remove("section-hidden");
|
|
cancelBtn.disabled = true;
|
|
}
|
|
|
|
|
|
|
|
if (!dateInput) return;
|
|
|
|
// flatpickr setup
|
|
const now = new Date();
|
|
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
const maxDate = new Date(today);
|
|
maxDate.setDate(maxDate.getDate() + 7);
|
|
|
|
const fp = flatpickr(dateInput, {
|
|
dateFormat: "Y-m-d",
|
|
altInput: true,
|
|
altFormat: "d-m-Y",
|
|
minDate: today,
|
|
maxDate: maxDate,
|
|
allowInput: false,
|
|
clickOpens: true,
|
|
disable: [
|
|
function (date) {
|
|
if (!(date instanceof Date)) return false;
|
|
return date.getDay() === 2;
|
|
}
|
|
],
|
|
onReady: function (selectedDates, dateStr, instance) {
|
|
instance.altInput.setAttribute("placeholder", "dd-mm-yyyy");
|
|
},
|
|
|
|
onChange: function (selectedDates, dateStr, instance) {
|
|
if (!dateStr) instance.altInput.setAttribute("placeholder", "dd-mm-yyyy");
|
|
// When date changes we must reset state and reload ranges
|
|
activeTimeId = null;
|
|
activeRange = null;
|
|
chosenSlots.clear();
|
|
if (slotsGrid) slotsGrid.innerHTML = "";
|
|
loadTimeRanges();
|
|
}
|
|
});
|
|
|
|
fp.setDate(today);
|
|
|
|
// enable opening on click/focus
|
|
dateInput.addEventListener('focus', () => fp.open());
|
|
dateInput.addEventListener('click', () => fp.open());
|
|
|
|
|
|
loadTimeRanges();
|
|
|
|
// modal submit handler
|
|
document.getElementById("vphModalSubmit").addEventListener("click", function () {
|
|
|
|
|
|
|
|
const userInput = (document.getElementById("contactDisplay") || {}).textContent?.trim() || "";
|
|
const visitDate = (dateInput || {}).value || "";
|
|
const no_of_people = people;
|
|
const timeId = activeTimeId;
|
|
|
|
|
|
|
|
const slotDetails = [...chosenSlots].map(([time, obj]) => {
|
|
const el = Array.from(document.querySelectorAll(".slot"))
|
|
.find(s => s.dataset.time === time);
|
|
|
|
return {
|
|
slotId: Number(el?.dataset?.slotId || 0),
|
|
people: obj.seats
|
|
|
|
};
|
|
});
|
|
|
|
|
|
/*if (slotDetails.length === 0) {
|
|
Swal.fire({ icon: "warning", text: "Please select at least one show." });
|
|
return;
|
|
}
|
|
*/
|
|
// Close modal before showing any Swal to avoid focus-lock issues
|
|
const overlayEl = document.getElementById('vphBookingModalOverlay');
|
|
if (overlayEl) {
|
|
overlayEl.classList.remove("show");
|
|
document.body.style.overflow = "";
|
|
// blur modal to remove active focus
|
|
const modalEl = document.getElementById('vphBookingModal');
|
|
if (modalEl) modalEl.blur();
|
|
}
|
|
|
|
|
|
$.ajax({
|
|
type: "POST",
|
|
url: "/victoriapublichall/bookingapi/saveRegistration",
|
|
data: {
|
|
userInput: userInput,
|
|
visitDate: visitDate,
|
|
no_of_people: no_of_people,
|
|
timeId: timeId,
|
|
slotDetails: JSON.stringify(slotDetails),
|
|
|
|
peopleDetails: JSON.stringify(
|
|
peopleTypes
|
|
.filter(p => p.count > 0)
|
|
.map(p => ({
|
|
id: p.id,
|
|
count: p.count
|
|
}))
|
|
),
|
|
|
|
idType: $("#idType").val() || null,
|
|
idDetails: $("#idNumber").val() || null,
|
|
totalAmount: $("#totalAmount").text().replace("₹", ""),
|
|
source: 1
|
|
|
|
},
|
|
success: function (response) {
|
|
console.log("response", response)
|
|
// response might be string or JSON. handle both.
|
|
|
|
if (response.status === "success") {
|
|
// 1️ Save booking reference
|
|
const refId = response.refId;
|
|
const amount = $("#totalAmount").text().replace("₹", "");
|
|
|
|
sessionStorage.setItem("confirmrefid", response.refId);
|
|
|
|
showLoader();
|
|
paymentStart(refId, amount);
|
|
|
|
} else if (response.status === "error") {
|
|
hideLoader();
|
|
Swal.fire({ icon: "error", title: "Booking Failed", text: "Slots are fully booked. Please select another slot" });
|
|
} else {
|
|
hideLoader();
|
|
Swal.fire({ icon: "error", title: "Booking Failed", text: response.message || "Failed" });
|
|
}
|
|
|
|
},
|
|
error: function (xhr) {
|
|
Swal.fire({ icon: "error", title: "Booking Failed", text: xhr.responseText || "Something went wrong!" });
|
|
}
|
|
});
|
|
});
|
|
|
|
/* PEOPLE COUNT – DB DRIVEN (JDBC) */
|
|
|
|
const MAX_TOTAL_PEOPLE = 10;
|
|
|
|
|
|
/* TOTAL PEOPLE */
|
|
function getTotalPeople() {
|
|
return peopleTypes.reduce((sum, p) => sum + p.count, 0);
|
|
}
|
|
|
|
|
|
/* TOTAL AMOUNT */
|
|
function getTotalAmount() {
|
|
return peopleTypes.reduce((sum, p) => {
|
|
return sum + (p.count * p.amount);
|
|
}, 0);
|
|
}
|
|
|
|
/* UPDATE TOTAL AMOUNT UI */
|
|
function updateTotalAmount() {
|
|
const total = getTotalAmount();
|
|
const totalEl = document.getElementById("totalAmount");
|
|
if (totalEl) {
|
|
totalEl.textContent = "₹" + total;
|
|
}
|
|
}
|
|
|
|
/* RENDER PEOPLE UI */
|
|
function renderPeopleTypes() {
|
|
const container = document.getElementById("peopleTypeList");
|
|
if (!container) return;
|
|
|
|
container.innerHTML = "";
|
|
|
|
const total = getTotalPeople();
|
|
|
|
peopleTypes.forEach((item, index) => {
|
|
const row = document.createElement("div");
|
|
row.className = "people-type-row";
|
|
|
|
row.innerHTML = `
|
|
<div class="people-type-label">${item.user_type}
|
|
<span class="people-amount">(₹${item.amount})</span>
|
|
</div>
|
|
<div class="people-type-controls">
|
|
<button class="small minus-btn" data-i="${index}">−</button>
|
|
<div class="counter">${String(item.count).padStart(2, "0")}</div>
|
|
<button class="small plus-btn" data-i="${index}">+</button>
|
|
</div>
|
|
`;
|
|
|
|
container.appendChild(row);
|
|
});
|
|
|
|
// minus
|
|
container.querySelectorAll(".minus-btn").forEach(btn => {
|
|
btn.onclick = () => {
|
|
const i = btn.dataset.i;
|
|
if (peopleTypes[i].count > 0) {
|
|
peopleTypes[i].count--;
|
|
renderPeopleTypes();
|
|
syncPeopleWithBooking();
|
|
}
|
|
};
|
|
});
|
|
|
|
// plus
|
|
container.querySelectorAll(".plus-btn").forEach(btn => {
|
|
btn.onclick = () => {
|
|
if (getTotalPeople() >= MAX_TOTAL_PEOPLE) return;
|
|
const i = btn.dataset.i;
|
|
peopleTypes[i].count++;
|
|
renderPeopleTypes();
|
|
syncPeopleWithBooking();
|
|
};
|
|
});
|
|
|
|
// disable + buttons when max reached
|
|
if (total >= MAX_TOTAL_PEOPLE) {
|
|
container.querySelectorAll(".plus-btn").forEach(btn => btn.disabled = true);
|
|
}
|
|
}
|
|
|
|
function syncPeopleWithBooking() {
|
|
const total = getTotalPeople();
|
|
|
|
// single source of truth
|
|
window.people = total;
|
|
people = total;
|
|
|
|
// auto adjust slot allocations
|
|
autoAllocateExtraGuests();
|
|
deallocateExtraGuests();
|
|
|
|
// update summary counts correctly
|
|
updateGuestSummary();
|
|
|
|
updateTotalAmount();
|
|
}
|
|
|
|
/* LOAD PEOPLE TYPES FROM DB */
|
|
$.ajax({
|
|
url: "/victoriapublichall/bookingapi/getamountbyusertype",
|
|
type: "GET",
|
|
success: function (data) {
|
|
peopleTypes = data.map(row => ({
|
|
id: row.id,
|
|
user_type: row.user_type,
|
|
amount: row.amount,
|
|
count: 0,
|
|
maxCount: row.user_type &&
|
|
row.user_type.trim().toLowerCase() === 'family' ? 5 : 0
|
|
}));
|
|
|
|
renderPeopleTypes();
|
|
syncPeopleWithBooking();
|
|
},
|
|
error: function () {
|
|
Swal.fire({
|
|
icon: "error",
|
|
text: "Unable to load people categories"
|
|
});
|
|
}
|
|
});
|
|
|
|
$.ajax({
|
|
url: "/victoriapublichall/bookingapi/getidproofs",
|
|
type: "GET",
|
|
success: function (data) {
|
|
const select = document.getElementById("idType");
|
|
|
|
data.forEach(row => {
|
|
const opt = document.createElement("option");
|
|
opt.value = row.id; // store ID
|
|
opt.textContent = row.id_name; // show Aadhar Card / Passport
|
|
select.appendChild(opt);
|
|
});
|
|
},
|
|
error: function () {
|
|
Swal.fire({
|
|
icon: "error",
|
|
text: "Unable to load ID proof types"
|
|
});
|
|
}
|
|
});
|
|
|
|
/* ENABLE ID NUMBER ONLY AFTER SELECT */
|
|
const idType = document.getElementById("idType");
|
|
const idNumber = document.getElementById("idNumber");
|
|
const idError = document.getElementById("idError");
|
|
|
|
idType.addEventListener("change", function () {
|
|
const selectedText = this.options[this.selectedIndex].text.toLowerCase();
|
|
|
|
idNumber.disabled = !this.value;
|
|
idNumber.value = "";
|
|
idError.style.display = "none";
|
|
idError.textContent = "";
|
|
|
|
if (!this.value) return;
|
|
|
|
idNumber.focus();
|
|
|
|
// 🔹 AADHAR
|
|
|
|
if (selectedText.includes("aadhar")) {
|
|
idNumber.maxLength = 12;
|
|
idNumber.placeholder = "Enter 12-digit Aadhar Number";
|
|
|
|
idNumber.oninput = function () {
|
|
this.value = this.value.replace(/\D/g, "");
|
|
|
|
if (this.value.length === 12) {
|
|
idError.style.display = "none";
|
|
isIdNumberValid = true;
|
|
} else {
|
|
idError.textContent = "Aadhar number must be exactly 12 digits";
|
|
idError.style.display = "block";
|
|
isIdNumberValid = false;
|
|
}
|
|
};
|
|
}
|
|
|
|
// 🔹 PASSPORT
|
|
|
|
else if (selectedText.includes("passport")) {
|
|
idNumber.maxLength = 9;
|
|
idNumber.placeholder = "Enter Passport Number";
|
|
|
|
idNumber.oninput = function () {
|
|
this.value = this.value.replace(/[^a-zA-Z0-9]/g, "");
|
|
|
|
if (this.value.length >= 6 && this.value.length <= 9) {
|
|
idError.style.display = "none";
|
|
isIdNumberValid = true;
|
|
} else {
|
|
idError.textContent = "Passport number must be 6–9 characters";
|
|
idError.style.display = "block";
|
|
isIdNumberValid = false;
|
|
}
|
|
};
|
|
}
|
|
});
|
|
|
|
|
|
qrBtn && qrBtn.addEventListener("click", function () {
|
|
|
|
if (!bookingorderid) {
|
|
Swal.fire({
|
|
icon: "error",
|
|
text: "Order ID not found"
|
|
});
|
|
return;
|
|
}
|
|
|
|
const qrUrl =
|
|
"/victoriapublichall/bookingQRCode?orderid=" + bookingorderid;
|
|
|
|
window.open(qrUrl, "_blank");
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
function formatDisplayDate(dateStr) {
|
|
if (!dateStr || typeof dateStr !== "string") return "--";
|
|
|
|
// Trim & normalize
|
|
const s = dateStr.trim();
|
|
|
|
// If already in an ISO-ish form, handle both "yyyy-mm-dd" and "dd-mm-yyyy"
|
|
// Detect by first token length: if first token has length 4 -> yyyy-mm-dd
|
|
const parts = s.split("-");
|
|
let yyyy, mm, dd;
|
|
|
|
if (parts.length === 3) {
|
|
if (parts[0].length === 4) {
|
|
// format: yyyy-mm-dd
|
|
yyyy = parts[0];
|
|
mm = parts[1];
|
|
dd = parts[2];
|
|
} else {
|
|
// assume dd-mm-yyyy
|
|
dd = parts[0];
|
|
mm = parts[1];
|
|
yyyy = parts[2];
|
|
}
|
|
} else {
|
|
// fallback parse attempt — try Date constructor
|
|
const d = new Date(s);
|
|
if (isNaN(d)) return s;
|
|
return d.toLocaleDateString("en-IN", { day: "2-digit", month: "short", year: "numeric" });
|
|
}
|
|
|
|
// safe integer conversion
|
|
const y = Number(yyyy), m = Number(mm) - 1, d = Number(dd);
|
|
const dateObj = new Date(y, m, d);
|
|
if (isNaN(dateObj)) return dateStr;
|
|
|
|
// Example output: "13 Dec 2025"
|
|
return dateObj.toLocaleDateString("en-IN", {
|
|
day: "2-digit",
|
|
month: "short",
|
|
year: "numeric"
|
|
});
|
|
}
|
|
|
|
|
|
|
|
/* ---------- API functions ---------- */
|
|
|
|
function loadSlotsFromAPI() {
|
|
const dateValue = dateInput?.value;
|
|
if (!dateValue || !activeTimeId) {
|
|
// clear slotsGrid and show message to indicate no selection
|
|
if (slotsGrid) slotsGrid.innerHTML = `<div class="no-slots-message-box">Please select a time range to view AV slots.</div>`;
|
|
return;
|
|
}
|
|
|
|
$.ajax({
|
|
type: "GET",
|
|
url: "/victoriapublichall/bookingapi/getTimeSlotDetails",
|
|
data: { visitDate: dateValue, timeId: activeTimeId },
|
|
success: function (data) {
|
|
const slotList = (data || []).map(item => ({
|
|
slotId: item.slot_id,
|
|
time: item.timing,
|
|
seats: item.availCap,
|
|
language: item.language
|
|
}));
|
|
renderSlotsWithData(slotList);
|
|
},
|
|
error: function () {
|
|
console.error("Failed to load slot details");
|
|
}
|
|
});
|
|
}
|
|
|
|
function renderSlotsWithData(slotArray) {
|
|
if (!slotsGrid) return;
|
|
|
|
slotsGrid.innerHTML = "";
|
|
chosenSlots.clear();
|
|
|
|
if (!slotArray || slotArray.length === 0) {
|
|
slotsGrid.innerHTML = `<div class="no-slots-message-box">Sorry, No AV time slots available for the selected date.</div>`;
|
|
return;
|
|
}
|
|
|
|
slotArray.forEach(item => {
|
|
const time = item.time;
|
|
const seats = Number(item.seats);
|
|
const slotId = item.slotId;
|
|
const language = item.language
|
|
|
|
const slotEl = document.createElement("div");
|
|
slotEl.className = "slot";
|
|
slotEl.tabIndex = 0;
|
|
|
|
slotEl.dataset.time = time;
|
|
slotEl.dataset.seats = seats;
|
|
slotEl.dataset.originalSeats = seats;
|
|
slotEl.dataset.slotId = slotId;
|
|
slotEl.dataset.language = language;
|
|
|
|
if (seats <= 0) {
|
|
slotEl.classList.add("disabled");
|
|
slotEl.style.border = "1px solid #d9534f";
|
|
} else {
|
|
slotEl.style.border = "1px solid #28a745";
|
|
}
|
|
|
|
slotEl.innerHTML = `
|
|
<div class="left">
|
|
<div class="time">${time} <span class="slot-lang">(${language})</span></div>
|
|
<div class="meta">${seats <= 0 ? "0 Seats available" : seats + (seats === 1 ? " Seat available" : " Seats available")}</div>
|
|
</div>
|
|
<div class="choose"></div>
|
|
`;
|
|
|
|
slotEl.addEventListener("click", () => toggleSlot(slotEl));
|
|
slotsGrid.appendChild(slotEl);
|
|
});
|
|
|
|
updateGuestSummary();
|
|
}
|
|
|
|
function loadTimeRanges() {
|
|
const timeRanges = document.getElementById("timeRanges");
|
|
const previewBtn = document.getElementById("previewBtn");
|
|
if (!timeRanges) return;
|
|
|
|
// clear UI + old messages and reset state
|
|
timeRanges.innerHTML = "";
|
|
const oldMsg = document.getElementById("noSlotsMsg");
|
|
if (oldMsg) oldMsg.remove();
|
|
|
|
activeTimeId = null;
|
|
activeRange = null;
|
|
chosenSlots.clear();
|
|
if (slotsGrid) slotsGrid.innerHTML = "";
|
|
|
|
if (previewBtn) {
|
|
previewBtn.disabled = true;
|
|
previewBtn.classList.add("disabled-btn");
|
|
}
|
|
|
|
const visitDate = dateInput?.value;
|
|
if (!visitDate) {
|
|
return;
|
|
}
|
|
|
|
$.ajax({
|
|
type: "GET",
|
|
url: "/victoriapublichall/bookingapi/getTimeMaster",
|
|
data: { visitDate: visitDate,
|
|
view: "book"},
|
|
success: function (data) {
|
|
if (!data || data.length === 0) {
|
|
// No ranges at all
|
|
if (slotsGrid) {
|
|
slotsGrid.innerHTML = `<div class="no-slots-message-box">Sorry,No AV time slots available for the selected date.</div>`;
|
|
}
|
|
const msg = document.createElement("div");
|
|
msg.id = "noSlotsMsg";
|
|
msg.className = "no-slots-message";
|
|
msg.textContent = "Sorry, No time slots available for the selected date.";
|
|
timeRanges.after(msg);
|
|
return;
|
|
}
|
|
|
|
let firstActiveSet = false;
|
|
let allZero = true;
|
|
|
|
// create pills
|
|
data.forEach(item => {
|
|
if (item.availCap > 0) allZero = false;
|
|
|
|
const pill = document.createElement("div");
|
|
pill.className = "pill";
|
|
pill.setAttribute("data-range", item.timing);
|
|
pill.setAttribute("data-id", item.time_id);
|
|
pill.innerHTML = `${item.timing}<br><small>${item.availCap} Available</small>`;
|
|
|
|
if (item.availCap === 0) {
|
|
pill.classList.add("disabled-pill");
|
|
pill.style.pointerEvents = "none";
|
|
} else if (!firstActiveSet) {
|
|
pill.classList.add("active");
|
|
activeRange = item.timing;
|
|
activeTimeId = item.time_id;
|
|
firstActiveSet = true;
|
|
}
|
|
|
|
timeRanges.appendChild(pill);
|
|
});
|
|
|
|
attachPillClickHandlers();
|
|
|
|
// if any pill active, load slots for it and enable preview
|
|
if (activeTimeId) {
|
|
loadSlotsFromAPI();
|
|
|
|
onTimeSelected(activeTimeId,false);
|
|
if (previewBtn) {
|
|
previewBtn.disabled = false;
|
|
previewBtn.classList.remove("disabled-btn");
|
|
}
|
|
}
|
|
|
|
// if every pill had zero availability -> show message in slots area
|
|
if (allZero) {
|
|
if (slotsGrid) {
|
|
slotsGrid.innerHTML = `<div class="no-slots-message-box">Sorry, No AV time slots available for the selected date and time.</div>`;
|
|
}
|
|
const msg = document.createElement("div");
|
|
msg.id = "noSlotsMsg";
|
|
msg.className = "no-slots-message";
|
|
msg.textContent = "No time slots available for the selected date.";
|
|
timeRanges.after(msg);
|
|
if (previewBtn) {
|
|
previewBtn.disabled = true;
|
|
previewBtn.classList.add("disabled-btn");
|
|
}
|
|
}
|
|
},
|
|
error: function () {
|
|
console.error("Failed to load time ranges");
|
|
}
|
|
});
|
|
}
|
|
|
|
function attachPillClickHandlers() {
|
|
const timeRanges = document.getElementById("timeRanges");
|
|
if (!timeRanges) return;
|
|
|
|
// since we recreate pills every time, it's safe to add listeners directly
|
|
timeRanges.querySelectorAll('.pill').forEach(pill => {
|
|
pill.addEventListener('click', () => {
|
|
// if this pill is disabled, ignore
|
|
if (pill.classList.contains('disabled-pill')) return;
|
|
|
|
|
|
|
|
// RESET message + Swal state on EVERY time change
|
|
resetAvInfoState();
|
|
|
|
// clear active class
|
|
timeRanges.querySelectorAll('.pill').forEach(x => x.classList.remove('active'));
|
|
pill.classList.add('active');
|
|
|
|
activeRange = pill.getAttribute('data-range');
|
|
activeTimeId = pill.getAttribute('data-id');
|
|
|
|
// reset chosen slots and reload slot grid
|
|
chosenSlots.clear();
|
|
if (slotsGrid) slotsGrid.innerHTML = "";
|
|
|
|
onTimeSelected(activeTimeId,true);
|
|
|
|
});
|
|
});
|
|
}
|
|
|
|
let requiresSlot = true; // default safe value
|
|
let avInfoShownOnce = false;
|
|
|
|
let lastTimeId = null;
|
|
|
|
function onTimeSelected(timeId,fromClick = false) {
|
|
|
|
fetch(`/victoriapublichall/bookingapi/slot-rule?timeId=${timeId}`)
|
|
.then(res => {
|
|
if (!res.ok) throw new Error("Slot rule API failed");
|
|
return res.json();
|
|
})
|
|
.then(data => {
|
|
|
|
requiresSlot = Boolean(data.requiresSlot);
|
|
const message = (data.message || "").trim();
|
|
|
|
const slotsGrid = document.getElementById("slotsGrid");
|
|
const avSection = slotsGrid ? slotsGrid.closest(".field") : null;
|
|
const infoBox = document.getElementById("noAvInfoMsg");
|
|
|
|
if (!avSection || !infoBox) return;
|
|
|
|
if (requiresSlot) {
|
|
|
|
avSection.style.display = "block";
|
|
infoBox.style.display = "none";
|
|
infoBox.textContent = "";
|
|
|
|
chosenSlots.clear();
|
|
loadSlotsFromAPI();
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
avSection.style.display = "none";
|
|
chosenSlots.clear();
|
|
updateGuestSummary();
|
|
|
|
// show DB-driven message
|
|
if (message) {
|
|
infoBox.textContent = message;
|
|
infoBox.style.display = "block";
|
|
|
|
// show Swal only when switching to a NEW timeId
|
|
if (fromClick) {
|
|
Swal.fire({
|
|
icon: "info",
|
|
title: "Important Information",
|
|
text: message,
|
|
confirmButtonText: "OK",
|
|
allowOutsideClick: false
|
|
});
|
|
}
|
|
} else {
|
|
infoBox.style.display = "none";
|
|
}
|
|
}
|
|
|
|
lastTimeId = timeId;
|
|
})
|
|
.catch(err => {
|
|
console.error("Slot rule error:", err);
|
|
});
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function resetAvInfoState() {
|
|
const infoMsg = document.getElementById("noAvInfoMsg");
|
|
if (infoMsg) infoMsg.style.display = "none";
|
|
|
|
avInfoShownOnce = false;
|
|
}
|
|
|
|
|
|
function updateGuestSummary() {
|
|
let totalAllocated = 0;
|
|
|
|
chosenSlots.forEach(obj => {
|
|
totalAllocated += Number(obj?.seats || 0);
|
|
});
|
|
|
|
const allocated = Math.min(people, totalAllocated);
|
|
const unallocated = Math.max(people - allocated, 0);
|
|
|
|
if (unallocEl) unallocEl.textContent = String(unallocated).padStart(2, '0');
|
|
if (allocEl) allocEl.textContent = String(allocated).padStart(2, '0');
|
|
|
|
const unallocItem = document.querySelector('.summary-item.unallocated');
|
|
const allocItem = document.querySelector('.summary-item.allocated');
|
|
|
|
// Remove old states
|
|
unallocItem?.classList.remove('text-red', 'text-green');
|
|
allocItem?.classList.remove('text-red', 'text-green');
|
|
|
|
// Apply new colors
|
|
if (allocated === people) {
|
|
allocItem?.classList.add('text-green'); // fully allocated
|
|
} else {
|
|
unallocItem?.classList.add('text-red'); // still missing allocation
|
|
}
|
|
}
|
|
|
|
//ak minus function
|
|
function deallocateExtraGuests() {
|
|
let totalAllocated = 0;
|
|
|
|
chosenSlots.forEach(obj => {
|
|
totalAllocated += Number(obj?.seats || 0);
|
|
});
|
|
|
|
// Nothing to remove
|
|
if (totalAllocated <= people) return;
|
|
|
|
let extraToRemove = totalAllocated - people;
|
|
|
|
for (const [time, obj] of chosenSlots.entries()) {
|
|
if (extraToRemove <= 0) break;
|
|
|
|
const slotEl = [...document.querySelectorAll('#slotsGrid .slot')]
|
|
.find(s => s.dataset.time === time);
|
|
|
|
if (!slotEl) continue;
|
|
|
|
const originalSeats = Number(slotEl.dataset.originalSeats || 0);
|
|
const usedSoFar = Number(obj.seats || 0);
|
|
|
|
const removeSeats = Math.min(usedSoFar, extraToRemove);
|
|
const newUsed = usedSoFar - removeSeats;
|
|
const newRemaining = originalSeats - newUsed;
|
|
|
|
if (newUsed > 0) {
|
|
// preserve language
|
|
chosenSlots.set(time, {
|
|
seats: newUsed,
|
|
language: obj.language
|
|
});
|
|
} else {
|
|
chosenSlots.delete(time);
|
|
slotEl.classList.remove('chosen');
|
|
const choose = slotEl.querySelector('.choose');
|
|
if (choose) choose.textContent = '';
|
|
}
|
|
|
|
slotEl.dataset.seats = newRemaining;
|
|
|
|
const meta = slotEl.querySelector('.meta');
|
|
if (meta) {
|
|
meta.textContent =
|
|
`${newRemaining} Seat${newRemaining === 1 ? '' : 's'} available`;
|
|
}
|
|
|
|
slotEl.classList.remove('disabled');
|
|
extraToRemove -= removeSeats;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//plus function
|
|
function autoAllocateExtraGuests() {
|
|
let currentlyAllocated = 0;
|
|
|
|
chosenSlots.forEach(obj => {
|
|
currentlyAllocated += Number(obj?.seats || 0);
|
|
});
|
|
|
|
let remainingGuests = people - currentlyAllocated;
|
|
if (remainingGuests <= 0) return;
|
|
|
|
const chosenSlotNodes = document.querySelectorAll('#slotsGrid .slot.chosen');
|
|
|
|
chosenSlotNodes.forEach(slotEl => {
|
|
if (remainingGuests <= 0) return;
|
|
|
|
const time = slotEl.dataset.time;
|
|
const originalSeats = Number(slotEl.dataset.originalSeats || 0);
|
|
|
|
const slotObj = chosenSlots.get(time);
|
|
const usedSoFar = Number(slotObj?.seats || 0);
|
|
|
|
const remainingCap = originalSeats - usedSoFar;
|
|
if (remainingCap <= 0) return;
|
|
|
|
const extraUse = Math.min(remainingCap, remainingGuests);
|
|
const newUsed = usedSoFar + extraUse;
|
|
const newRemaining = originalSeats - newUsed;
|
|
|
|
// keep language intact
|
|
chosenSlots.set(time, {
|
|
seats: newUsed,
|
|
language: slotObj.language
|
|
});
|
|
|
|
slotEl.dataset.seats = newRemaining;
|
|
|
|
const meta = slotEl.querySelector('.meta');
|
|
if (meta) {
|
|
meta.textContent =
|
|
`${newRemaining} Seat${newRemaining === 1 ? '' : 's'} available`;
|
|
}
|
|
|
|
if (newRemaining === 0) {
|
|
slotEl.classList.add('disabled');
|
|
} else {
|
|
slotEl.classList.remove('disabled');
|
|
}
|
|
|
|
remainingGuests -= extraUse;
|
|
});
|
|
}
|
|
|
|
|
|
|
|
function toggleSlot(slotEl) {
|
|
const time = slotEl.dataset.time;
|
|
|
|
if (chosenSlots.has(time)) {
|
|
slotEl.classList.add('shake');
|
|
|
|
// remove shake after animation
|
|
setTimeout(() => {
|
|
slotEl.classList.remove('shake');
|
|
}, 350);
|
|
|
|
return;
|
|
}
|
|
|
|
const currentSeats = Number(slotEl.dataset.seats || 0);
|
|
const originalSeats = Number(slotEl.dataset.originalSeats || 0);
|
|
/* NOT ENOUGH SEATS FOR SELECTED PEOPLE → SHAKE & STOP */
|
|
if (currentSeats < people) {
|
|
slotEl.classList.add('shake');
|
|
setTimeout(() => slotEl.classList.remove('shake'), 400);
|
|
return;
|
|
}
|
|
|
|
if (currentSeats <= 0) return;
|
|
|
|
|
|
document.querySelectorAll('#slotsGrid .slot.chosen').forEach(el => {
|
|
el.classList.remove('chosen', 'disabled');
|
|
|
|
const choose = el.querySelector('.choose');
|
|
const meta = el.querySelector('.meta');
|
|
const origSeats = Number(el.dataset.originalSeats || 0);
|
|
|
|
el.dataset.seats = origSeats;
|
|
if (choose) choose.textContent = '';
|
|
if (meta) {
|
|
meta.textContent =
|
|
`${origSeats} Seat${origSeats === 1 ? '' : 's'} available`;
|
|
}
|
|
});
|
|
|
|
// Reset selection
|
|
chosenSlots.clear();
|
|
|
|
|
|
const seatsUsed = Math.min(currentSeats, people);
|
|
const remainingSeats = currentSeats - seatsUsed;
|
|
|
|
//chosenSlots.set(time, seatsUsed);
|
|
chosenSlots.set(time, {
|
|
seats: seatsUsed,
|
|
language: slotEl.dataset.language
|
|
});
|
|
|
|
|
|
|
|
slotEl.classList.add('chosen');
|
|
slotEl.dataset.seats = remainingSeats;
|
|
|
|
const choose = slotEl.querySelector('.choose');
|
|
const meta = slotEl.querySelector('.meta');
|
|
|
|
if (choose) choose.textContent = '✓';
|
|
if (meta) {
|
|
meta.textContent =
|
|
`${remainingSeats} Seat${remainingSeats === 1 ? '' : 's'} available`;
|
|
}
|
|
|
|
if (remainingSeats === 0) {
|
|
slotEl.classList.add('disabled');
|
|
}
|
|
|
|
updateGuestSummary();
|
|
}
|
|
|
|
|
|
/* Validation helpers */
|
|
function isEmail(text) { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(text); }
|
|
function isPhone(text) { const d = (text || '').replace(/\D/g, ''); return d.length === 10; }
|
|
function isValidContact(text) { const v = text.trim(); return isEmail(v) || isPhone(v); }
|
|
</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();
|
|
// mimic mobile behavior for demo: show alert (replace with real action)
|
|
alert('Book a Visit clicked — open booking flow here.');
|
|
});
|
|
mobileBook.addEventListener('click', (e) => {
|
|
// mobile link closes menu via handler above and then can navigate or open booking flow
|
|
// if you want to override, uncomment below
|
|
// e.preventDefault(); alert('Mobile Book clicked (replace with action).');
|
|
});
|
|
}
|
|
})();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
</script>
|
|
|
|
|
|
<!-- Booking preview Button -->
|
|
<script>
|
|
(function () {
|
|
const previewBtn = document.getElementById('previewBtn');
|
|
const overlay = document.getElementById('vphBookingModalOverlay');
|
|
const modal = document.getElementById('vphBookingModal');
|
|
|
|
const modalDate = document.getElementById('vphModalDate');
|
|
const modalRange = document.getElementById('vphModalRange');
|
|
const modalPeople = document.getElementById('vphModalPeople');
|
|
const modalSlotsList = document.getElementById('vphModalSlotsList');
|
|
|
|
const closeBtn = document.querySelector('.preview-close');
|
|
const backBtn = document.getElementById('vphModalBack');
|
|
const submitBtn = document.getElementById('vphModalSubmit');
|
|
|
|
if (!previewBtn || !overlay || !modal) {
|
|
console.warn('VPH modal: required elements not found.');
|
|
return;
|
|
}
|
|
|
|
|
|
function gatherData() {
|
|
|
|
// 1️ DATE
|
|
const dateInput = document.getElementById('dateInput');
|
|
const dateVal = dateInput?.value ? new Date(dateInput.value) : null;
|
|
const dateFormat = dateVal
|
|
? dateVal.toLocaleDateString("en-IN", { day: '2-digit', month: 'short', year: 'numeric' })
|
|
: "";
|
|
|
|
// TIME RANGE (active pill)
|
|
const activePill = document.querySelector('.time-ranges .pill.active');
|
|
const timeRangeText = activePill
|
|
? activePill.getAttribute("data-range")
|
|
: "";
|
|
|
|
|
|
|
|
// PEOPLE BREAKDOWN
|
|
const peopleBreakdown = peopleTypes
|
|
.filter(p => p.count > 0)
|
|
.map(p => ({
|
|
label: p.user_type,
|
|
count: p.count,
|
|
amount: p.amount,
|
|
total: p.count * p.amount
|
|
}));
|
|
|
|
// TOTAL AMOUNT
|
|
const totalAmount = peopleBreakdown.reduce(
|
|
(sum, p) => sum + p.total, 0
|
|
);
|
|
|
|
// ID PROOF
|
|
const idTypeSelect = document.getElementById("idType");
|
|
const idNumberInput = document.getElementById("idNumber");
|
|
|
|
const idProof = {
|
|
type: idTypeSelect?.value || "",
|
|
label: idTypeSelect?.selectedOptions[0]?.text || "",
|
|
number: idNumberInput?.value || ""
|
|
};
|
|
|
|
|
|
|
|
|
|
const slots = [...chosenSlots].map(([time, obj]) => ({
|
|
time,
|
|
language: obj.language,
|
|
seats: obj.seats + " Seat(s)"
|
|
}));
|
|
|
|
|
|
|
|
return { dateFormat, timeRangeText, people, slots, peopleBreakdown, totalAmount, idProof };
|
|
}
|
|
|
|
|
|
// SHOW MODAL WITH FILLED DATA
|
|
|
|
function showModal() {
|
|
|
|
const {
|
|
dateFormat,
|
|
timeRangeText,
|
|
people,
|
|
slots, peopleBreakdown,
|
|
totalAmount,
|
|
idProof
|
|
} = gatherData();
|
|
|
|
// Date validation
|
|
if (!dateFormat) {
|
|
Swal.fire({
|
|
icon: "warning",
|
|
text: "Please select a date before previewing."
|
|
});
|
|
document.getElementById('dateInput').focus();
|
|
return;
|
|
}
|
|
|
|
|
|
if (!idProof.type) {
|
|
Swal.fire({
|
|
icon: "warning",
|
|
title: "ID proof required",
|
|
text: "Please select an identity proof."
|
|
}).then(() => {
|
|
idTypeSelect?.focus();
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (!idProof.number || idProof.number.trim() === "") {
|
|
Swal.fire({
|
|
icon: "warning",
|
|
title: "ID Number Required",
|
|
text: "Please enter your ID number to continue.",
|
|
confirmButtonText: "OK",
|
|
allowOutsideClick: false
|
|
}).then(() => {
|
|
idNumberInput.focus();
|
|
});
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
if (!isIdNumberValid) {
|
|
Swal.fire({
|
|
icon: "error",
|
|
title: "Invalid ID Number",
|
|
text: "Please enter a valid ID number before proceeding."
|
|
});
|
|
idNumberInput.focus();
|
|
return; // BLOCK SUBMIT
|
|
}
|
|
|
|
|
|
if (!people || people <= 0) {
|
|
Swal.fire({
|
|
icon: "warning",
|
|
title: "People count required",
|
|
text: "Please select at least one visitor before proceeding."
|
|
}).then(() => {
|
|
document.querySelector(".people-type-list")?.focus();
|
|
|
|
});
|
|
return;
|
|
}
|
|
|
|
|
|
// Slot selection validation
|
|
if (requiresSlot && slots.length === 0) {
|
|
Swal.fire({
|
|
icon: "warning",
|
|
text: "Please select at least one show before previewing."
|
|
});
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
modalDate.textContent = dateFormat;
|
|
modalRange.textContent = timeRangeText;
|
|
modalPeople.textContent = people;
|
|
|
|
|
|
// ADD ID PROOF HERE
|
|
const idTypeEl = document.getElementById("vphModalIdType");
|
|
const idNumberEl = document.getElementById("vphModalIdNumber");
|
|
|
|
if (idTypeEl) {
|
|
idTypeEl.textContent = idProof?.label?.trim() || "—";
|
|
}
|
|
|
|
if (idNumberEl) {
|
|
idNumberEl.textContent = idProof?.number?.trim() || "—";
|
|
}
|
|
|
|
modalSlotsList.innerHTML = "";
|
|
if (slots.length === 0) {
|
|
modalSlotsList.innerHTML = `
|
|
<div class="vph-av-row">
|
|
<div class="vph-av-time">No shows selected</div>
|
|
</div>`;
|
|
} else {
|
|
slots.forEach(s => {
|
|
console.log(s);
|
|
const row = document.createElement("div");
|
|
row.className = "vph-av-row";
|
|
row.innerHTML = `
|
|
<div class="vph-av-time">
|
|
${s.time} <span class="slot-lang">(${s.language})</span>
|
|
</div>
|
|
<div class="vph-av-meta">${s.seats}</div>
|
|
`;
|
|
modalSlotsList.appendChild(row);
|
|
});
|
|
|
|
}
|
|
|
|
const breakdownEl = document.getElementById("previewGuestBreakdown");
|
|
const totalAmountEl = document.getElementById("previewTotalAmount");
|
|
|
|
breakdownEl.innerHTML = "";
|
|
|
|
peopleBreakdown.forEach(p => {
|
|
const row = document.createElement("div");
|
|
row.className = "breakdown-row";
|
|
row.innerHTML = `
|
|
<span class="breakdown-label">
|
|
${p.label} × ${p.count}
|
|
</span>
|
|
<span class="breakdown-value">
|
|
₹${p.total}
|
|
</span>
|
|
`;
|
|
breakdownEl.appendChild(row);
|
|
});
|
|
|
|
totalAmountEl.textContent = "₹" + totalAmount;
|
|
|
|
|
|
overlay.classList.add("show");
|
|
modal.focus();
|
|
document.body.style.overflow = "hidden";
|
|
}
|
|
|
|
|
|
// CLOSE MODAL
|
|
function closeModal() {
|
|
overlay.classList.remove("show");
|
|
document.body.style.overflow = "";
|
|
previewBtn.focus();
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
previewBtn.addEventListener(
|
|
"click",
|
|
function (e) {
|
|
e.preventDefault();
|
|
|
|
|
|
showModal();
|
|
},
|
|
true
|
|
);
|
|
|
|
// Close options
|
|
overlay.addEventListener("click", (e) => { if (e.target === overlay) closeModal(); });
|
|
closeBtn?.addEventListener("click", closeModal);
|
|
backBtn?.addEventListener("click", closeModal);
|
|
|
|
})();
|
|
|
|
</script>
|
|
|
|
<script>
|
|
(function () {
|
|
// Button inside the cancel-card
|
|
const cancelBtn = document.getElementById('cancelBtn');
|
|
const overlay = document.getElementById('vphModalOverlay');
|
|
const modal = document.getElementById('vphModal');
|
|
const viewConfirm = document.getElementById('vphViewConfirm');
|
|
const viewCancelled = document.getElementById('vphViewCancelled');
|
|
const confirmBack = document.getElementById('vphConfirmBack');
|
|
const confirmYes = document.getElementById('vphConfirmYes');
|
|
const goMain = document.getElementById('vphGoMain');
|
|
const bookAnother = document.getElementById('vphBookAnother');
|
|
const closeBtn = document.querySelector('.cancel-close');
|
|
|
|
if (!cancelBtn || !overlay || !modal) {
|
|
console.warn('Cancel modal: required elements not found.');
|
|
return;
|
|
}
|
|
|
|
function showView(view) {
|
|
[viewConfirm, viewCancelled].forEach(v => {
|
|
if (!v) return;
|
|
v.classList.remove('active');
|
|
v.setAttribute('aria-hidden', 'true');
|
|
});
|
|
if (view === 'confirm' && viewConfirm) {
|
|
viewConfirm.classList.add('active');
|
|
viewConfirm.setAttribute('aria-hidden', 'false');
|
|
setTimeout(() => confirmYes && confirmYes.focus(), 40);
|
|
} else if (view === 'cancelled' && viewCancelled) {
|
|
viewCancelled.classList.add('active');
|
|
viewCancelled.setAttribute('aria-hidden', 'false');
|
|
setTimeout(() => bookAnother && bookAnother.focus(), 40);
|
|
}
|
|
}
|
|
|
|
function openModal(initialView) {
|
|
overlay.classList.add('show');
|
|
overlay.setAttribute('aria-hidden', 'false');
|
|
document.body.style.overflow = 'hidden';
|
|
showView(initialView || 'confirm');
|
|
}
|
|
|
|
function closeModal() {
|
|
overlay.classList.remove('show');
|
|
overlay.setAttribute('aria-hidden', 'true');
|
|
document.body.style.overflow = '';
|
|
cancelBtn && cancelBtn.focus();
|
|
}
|
|
|
|
|
|
cancelBtn.addEventListener('click', function (e) {
|
|
e.preventDefault();
|
|
openModal('confirm');
|
|
});
|
|
|
|
|
|
closeBtn && closeBtn.addEventListener('click', closeModal);
|
|
overlay.addEventListener('click', function (e) {
|
|
if (e.target === overlay) closeModal();
|
|
});
|
|
|
|
|
|
confirmBack && confirmBack.addEventListener('click', closeModal);
|
|
|
|
|
|
confirmYes && confirmYes.addEventListener('click', function () {
|
|
if (!bookingId) {
|
|
Swal.fire({
|
|
icon: "error",
|
|
text: "Invalid booking. Try again."
|
|
});
|
|
return;
|
|
}
|
|
|
|
$.ajax({
|
|
type: "POST",
|
|
url: "/victoriapublichall/bookingapi/cancelBooking",
|
|
data: { id: bookingId },
|
|
success: function (res) {
|
|
if (res.status === "success") {
|
|
showView('cancelled');
|
|
} else {
|
|
closeCancelModal();
|
|
Swal.fire({
|
|
icon: "error",
|
|
text: res.message || "Cancellation failed"
|
|
});
|
|
}
|
|
},
|
|
error: function () {
|
|
closeCancelModal();
|
|
Swal.fire({
|
|
icon: "error",
|
|
text: "Server error. Try again later."
|
|
});
|
|
}
|
|
});
|
|
|
|
|
|
});
|
|
|
|
// success view actions
|
|
goMain && goMain.addEventListener('click', function () {
|
|
window.location.href = '/victoriapublichall/home'; // change if needed
|
|
});
|
|
|
|
bookAnother && bookAnother.addEventListener('click', function () {
|
|
|
|
let userentry = null;
|
|
|
|
if (bookingmob_no && bookingmob_no.trim() !== "") {
|
|
userentry = bookingmob_no;
|
|
}
|
|
else if (bookingemail && bookingemail.trim() !== "") {
|
|
userentry = bookingemail;
|
|
}
|
|
|
|
if (userentry) {
|
|
// user used mobile or email earlier → continue booking
|
|
sessionStorage.setItem("hasBooking", "false");
|
|
sessionStorage.setItem("hasuser", userentry);
|
|
sessionStorage.setItem("bookingdetails", ""); // very important!
|
|
window.location.href = '/victoriapublichall/registerbook';
|
|
} else {
|
|
// no mobile or email found; force fresh login
|
|
window.location.href = '/victoriapublichall/register';
|
|
}
|
|
|
|
});
|
|
|
|
document.addEventListener('keydown', function (e) {
|
|
if (e.key === 'Escape' && overlay.classList.contains('show')) {
|
|
closeModal();
|
|
}
|
|
});
|
|
|
|
document.addEventListener('focus', function (e) {
|
|
if (overlay.classList.contains('show') && !modal.contains(e.target)) {
|
|
e.stopPropagation();
|
|
modal.focus();
|
|
}
|
|
}, true);
|
|
})();
|
|
|
|
function closeCancelModal() {
|
|
const overlay = document.getElementById('vphModalOverlay');
|
|
overlay.classList.remove('show');
|
|
overlay.setAttribute('aria-hidden', 'true');
|
|
document.body.style.overflow = '';
|
|
}
|
|
</script>
|
|
|
|
|
|
|
|
<script>
|
|
const redirectPath = '/victoriapublichall/register';
|
|
// This runs when the user presses the browser's back button
|
|
window.addEventListener('popstate', function (event) {
|
|
|
|
window.location.href = redirectPath;
|
|
|
|
});
|
|
// Push a dummy state so that the popstate event triggers properly
|
|
history.pushState(null, null, location.href);
|
|
|
|
|
|
function storeMobileForBulk() {
|
|
const mobile = sessionStorage.getItem("hasuser"); // already stored
|
|
if (mobile) {
|
|
sessionStorage.setItem("bulkMobile", mobile);
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<script>
|
|
|
|
sessionStorage.setItem("bulkMobile", contact);
|
|
|
|
function handleBulkBookingClick() {
|
|
|
|
// Get contact from UI / session
|
|
const contact =
|
|
$("#contactDisplay").text().trim() ||
|
|
sessionStorage.getItem("bulkMobile");
|
|
|
|
if (!contact) {
|
|
Swal.fire("Error", "Contact not found", "error");
|
|
return;
|
|
}
|
|
|
|
// Call backend to check existing booking
|
|
$.get("/victoriapublichall/bookingapi/checkBulkBooking",
|
|
{ contact: contact },
|
|
function (res) {
|
|
|
|
if (res.status === "found") {
|
|
|
|
window.location.href =
|
|
"/victoriapublichall/acknowledgement?refId=" + res.refId;
|
|
|
|
} else {
|
|
|
|
sessionStorage.setItem("bulkMobile", contact);
|
|
window.location.href =
|
|
"/victoriapublichall/bulkbooking";
|
|
}
|
|
}
|
|
).fail(function () {
|
|
Swal.fire("Error", "Unable to verify booking", "error");
|
|
});
|
|
}
|
|
|
|
|
|
</script>
|
|
|
|
|
|
</body>
|
|
|
|
</html>
|