diff --git a/web/modules/custom/riverside_pt/css/app.css b/web/modules/custom/riverside_pt/css/app.css index 59b2489..8bfc469 100644 --- a/web/modules/custom/riverside_pt/css/app.css +++ b/web/modules/custom/riverside_pt/css/app.css @@ -729,6 +729,18 @@ html { margin-top: 2vw; } +.mb-1{ + margin-bottom: 0.25rem; +} + +.mb-6{ + margin-bottom: 1.5rem; +} + +.mt-8{ + margin-top: 2rem; +} + .block{ display: block; } @@ -882,6 +894,10 @@ html { cursor: pointer; } +.resize-none{ + resize: none; +} + .resize{ resize: both; } @@ -971,6 +987,10 @@ html { row-gap: 0px; } +.gap-y-5{ + row-gap: 1.25rem; +} + .self-end{ align-self: flex-end; } @@ -1027,18 +1047,6 @@ html { border-top-width: 1px; } -.border-\[pt-blue-200\]{ - border-color: pt-blue-200; -} - -.border-\[pt-blue-500\]{ - border-color: pt-blue-500; -} - -.border-\[pt-navy\]{ - border-color: pt-navy; -} - .border-gray-200{ --tw-border-opacity: 1; border-color: rgb(229 231 235 / var(--tw-border-opacity, 1)); @@ -1049,25 +1057,16 @@ html { border-color: rgb(209 213 219 / var(--tw-border-opacity, 1)); } -.border-pt-blue-300{ - --tw-border-opacity: 1; - border-color: rgb(157 189 203 / var(--tw-border-opacity, 1)); -} - -.border-white{ - --tw-border-opacity: 1; - border-color: rgb(255 255 255 / var(--tw-border-opacity, 1)); -} - -.border-white\/60{ - border-color: rgb(255 255 255 / 0.6); -} - .border-pt-blue-200{ --tw-border-opacity: 1; border-color: rgb(184 212 220 / var(--tw-border-opacity, 1)); } +.border-pt-blue-300{ + --tw-border-opacity: 1; + border-color: rgb(157 189 203 / var(--tw-border-opacity, 1)); +} + .border-pt-blue-500{ --tw-border-opacity: 1; border-color: rgb(48 111 142 / var(--tw-border-opacity, 1)); @@ -1078,44 +1077,29 @@ html { border-color: rgb(30 58 95 / var(--tw-border-opacity, 1)); } -.bg-\[pt-blue-100\]{ - background-color: pt-blue-100; +.border-white{ + --tw-border-opacity: 1; + border-color: rgb(255 255 255 / var(--tw-border-opacity, 1)); } -.bg-\[pt-blue-400\]{ - background-color: pt-blue-400; -} - -.bg-\[pt-blue-500\]{ - background-color: pt-blue-500; -} - -.bg-\[pt-navy\]{ - background-color: pt-navy; +.border-white\/60{ + border-color: rgb(255 255 255 / 0.6); } .bg-current{ background-color: currentColor; } -.bg-transparent{ - background-color: transparent; -} - -.bg-white{ - --tw-bg-opacity: 1; - background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1)); -} - -.bg-white\/90{ - background-color: rgb(255 255 255 / 0.9); -} - .bg-pt-blue-100{ --tw-bg-opacity: 1; background-color: rgb(221 232 240 / var(--tw-bg-opacity, 1)); } +.bg-pt-blue-300{ + --tw-bg-opacity: 1; + background-color: rgb(157 189 203 / var(--tw-bg-opacity, 1)); +} + .bg-pt-blue-400{ --tw-bg-opacity: 1; background-color: rgb(134 170 182 / var(--tw-bg-opacity, 1)); @@ -1131,11 +1115,6 @@ html { background-color: rgb(30 58 95 / var(--tw-bg-opacity, 1)); } -.bg-pt-blue-300{ - --tw-bg-opacity: 1; - background-color: rgb(157 189 203 / var(--tw-bg-opacity, 1)); -} - .bg-pt-sage-400{ --tw-bg-opacity: 1; background-color: rgb(131 161 161 / var(--tw-bg-opacity, 1)); @@ -1146,6 +1125,19 @@ html { background-color: rgb(111 143 150 / var(--tw-bg-opacity, 1)); } +.bg-transparent{ + background-color: transparent; +} + +.bg-white{ + --tw-bg-opacity: 1; + background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1)); +} + +.bg-white\/90{ + background-color: rgb(255 255 255 / 0.9); +} + .bg-gradient-to-b{ background-image: linear-gradient(to bottom, var(--tw-gradient-stops)); } @@ -1264,6 +1256,11 @@ html { padding-bottom: 1em; } +.px-3{ + padding-left: 0.75rem; + padding-right: 0.75rem; +} + .pb-1{ padding-bottom: 0.25rem; } @@ -1288,6 +1285,10 @@ html { padding-top: 1px; } +.pt-8{ + padding-top: 2rem; +} + .text-left{ text-align: left; } @@ -1430,13 +1431,8 @@ html { letter-spacing: 0.1em; } -.text-\[\#1e3a8a\]{ - --tw-text-opacity: 1; - color: rgb(30 58 138 / var(--tw-text-opacity, 1)); -} - -.text-\[pt-blue-500\]{ - color: pt-blue-500; +.text-\[blue-900\]{ + color: blue-900; } .text-gray-400{ @@ -1469,6 +1465,11 @@ html { color: rgb(17 24 39 / var(--tw-text-opacity, 1)); } +.text-pt-blue-500{ + --tw-text-opacity: 1; + color: rgb(48 111 142 / var(--tw-text-opacity, 1)); +} + .text-white{ --tw-text-opacity: 1; color: rgb(255 255 255 / var(--tw-text-opacity, 1)); @@ -1482,13 +1483,9 @@ html { color: rgb(255 255 255 / 0.8); } -.text-\[blue-900\]{ - color: blue-900; -} - -.text-pt-blue-500{ +.text-red-500{ --tw-text-opacity: 1; - color: rgb(48 111 142 / var(--tw-text-opacity, 1)); + color: rgb(239 68 68 / var(--tw-text-opacity, 1)); } .no-underline{ @@ -1507,6 +1504,10 @@ html { box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); } +.filter{ + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); +} + .transition{ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter; transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; @@ -1545,42 +1546,18 @@ html { transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); } -.\[text-shadow\:-56\.21px_2\.55px_10\.22px_\#0000001A\]{ - text-shadow: -56.21px 2.55px 10.22px #0000001A; -} - .\[text-shadow\:-56\.21px_2\.55px_10\.22px_rgb\(0_0_0\/10\%\)\]{ text-shadow: -56.21px 2.55px 10.22px rgb(0 0 0/10%); } -.hover\:border-\[pt-blue-500\]:hover{ - border-color: pt-blue-500; -} - -.hover\:border-\[pt-blue-600\]:hover{ - border-color: pt-blue-600; -} - -.hover\:border-pt-blue-600:hover{ - --tw-border-opacity: 1; - border-color: rgb(31 90 110 / var(--tw-border-opacity, 1)); -} - .hover\:border-pt-blue-500:hover{ --tw-border-opacity: 1; border-color: rgb(48 111 142 / var(--tw-border-opacity, 1)); } -.hover\:bg-\[pt-blue-50\]:hover{ - background-color: pt-blue-50; -} - -.hover\:bg-\[pt-blue-600\]:hover{ - background-color: pt-blue-600; -} - -.hover\:bg-\[pt-sage-500\]:hover{ - background-color: pt-sage-500; +.hover\:border-pt-blue-600:hover{ + --tw-border-opacity: 1; + border-color: rgb(31 90 110 / var(--tw-border-opacity, 1)); } .hover\:bg-gray-100:hover{ @@ -1603,21 +1580,6 @@ html { background-color: rgb(111 143 150 / var(--tw-bg-opacity, 1)); } -.hover\:text-\[\#1e3a8a\]:hover{ - --tw-text-opacity: 1; - color: rgb(30 58 138 / var(--tw-text-opacity, 1)); -} - -.hover\:text-\[\#285a6e\]:hover{ - --tw-text-opacity: 1; - color: rgb(40 90 110 / var(--tw-text-opacity, 1)); -} - -.hover\:text-white:hover{ - --tw-text-opacity: 1; - color: rgb(255 255 255 / var(--tw-text-opacity, 1)); -} - .hover\:text-\[blue-900\]:hover{ color: blue-900; } @@ -1626,10 +1588,29 @@ html { color: pt-blue-600; } +.hover\:text-white:hover{ + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity, 1)); +} + +.focus\:border-pt-blue-500:focus{ + --tw-border-opacity: 1; + border-color: rgb(48 111 142 / var(--tw-border-opacity, 1)); +} + +.focus\:outline-none:focus{ + outline: 2px solid transparent; + outline-offset: 2px; +} + .disabled\:opacity-30:disabled{ opacity: 0.3; } +.disabled\:opacity-50:disabled{ + opacity: 0.5; +} + @media not all and (min-width: 768px){ .max-sm\:text-sm{ font-size: 0.875rem; @@ -1844,10 +1825,6 @@ html { justify-content: center; } - .\32xl\:bg-\[pt-blue-400\]{ - background-color: pt-blue-400; - } - .\32xl\:bg-pt-blue-400{ --tw-bg-opacity: 1; background-color: rgb(134 170 182 / var(--tw-bg-opacity, 1)); diff --git a/web/modules/custom/riverside_pt/js/calendar.js b/web/modules/custom/riverside_pt/js/calendar.js index 48c9e93..984e463 100644 --- a/web/modules/custom/riverside_pt/js/calendar.js +++ b/web/modules/custom/riverside_pt/js/calendar.js @@ -9,12 +9,23 @@ var selectedDate = null; var initialized = false; + var currentService = 'diagnostic'; + + function buildEventsUrl(service) { + return drupalSettings.riversidePt.eventsUrl + '?service=' + service; + } + + function localDateStr(d) { + return d.getFullYear() + "-" + + String(d.getMonth() + 1).padStart(2, "0") + "-" + + String(d.getDate()).padStart(2, "0"); + } function nextBusinessDay() { var d = new Date(); d.setDate(d.getDate() + 1); while (d.getDay() === 0 || d.getDay() === 6) d.setDate(d.getDate() + 1); - return d.toISOString().substring(0, 10); + return localDateStr(d); } var initDate = nextBusinessDay(); @@ -45,7 +56,7 @@ fetch(drupalSettings.riversidePt.storeSlotUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ start: event.startStr, end: event.endStr }), + body: JSON.stringify({ start: event.startStr, end: event.endStr, service: currentService }), }).then(function (res) { if (res.ok) { window.location.href = drupalSettings.riversidePt.bookingUrl; @@ -85,7 +96,7 @@ }, fixedWeekCount: false, height: 'auto', - events: drupalSettings.riversidePt.eventsUrl, + events: buildEventsUrl(currentService), eventDisplay: 'none', dayMaxEvents: false, diff --git a/web/modules/custom/riverside_pt/js/components/rpt-appt-type.js b/web/modules/custom/riverside_pt/js/components/rpt-appt-type.js index 38c54e6..8c693d2 100644 --- a/web/modules/custom/riverside_pt/js/components/rpt-appt-type.js +++ b/web/modules/custom/riverside_pt/js/components/rpt-appt-type.js @@ -16,6 +16,11 @@ const CHECK = html`

Select Appointment Type

@@ -25,7 +30,7 @@ function ApptType() { return html` + `; + })} + + +
+
+ ${slots.length > 0 ? html` +
+
+ ${slots.map(function (slot) { + return html` + + `; + })} +
+
+ ` : null} +
+ + ${selectedSlot ? html` +
+

+ Your Details +

+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+ + ${submitError ? html`

${submitError}

` : null} + + +
+ ` : null} + + `; +} + +class RptBooking extends HTMLElement { + connectedCallback() { + render(html`<${Booking} settings=${window.drupalSettings.riversidePt} />`, this); + } + disconnectedCallback() { + render(null, this); + } +} + +customElements.define("rpt-booking", RptBooking); diff --git a/web/modules/custom/riverside_pt/riverside_pt.libraries.yml b/web/modules/custom/riverside_pt/riverside_pt.libraries.yml index 775f746..7437410 100644 --- a/web/modules/custom/riverside_pt/riverside_pt.libraries.yml +++ b/web/modules/custom/riverside_pt/riverside_pt.libraries.yml @@ -8,7 +8,6 @@ app: js/components/rpt-carousel.js: { attributes: { type: module } } js/components/rpt-testimonials.js: { attributes: { type: module } } js/components/rpt-faq.js: { attributes: { type: module } } - js/components/rpt-appt-type.js: { attributes: { type: module } } schedule: css: theme: @@ -16,5 +15,6 @@ schedule: js: js/fullcalendar.min.js: { minified: true } js/calendar.js: {} + js/components/rpt-booking.js: { attributes: { type: module } } dependencies: - core/drupalSettings diff --git a/web/modules/custom/riverside_pt/src/Controller/ScheduleController.php b/web/modules/custom/riverside_pt/src/Controller/ScheduleController.php index 2361ad2..57875aa 100644 --- a/web/modules/custom/riverside_pt/src/Controller/ScheduleController.php +++ b/web/modules/custom/riverside_pt/src/Controller/ScheduleController.php @@ -86,6 +86,10 @@ class ScheduleController extends ControllerBase { $this->tempStore->set('booking_slot', [ 'start' => $start, 'end' => $data['end'] ?? '', + 'service' => $data['service'] ?? 'diagnostic', + 'last_name' => $data['lastName'] ?? '', + 'phone' => $data['phone'] ?? '', + 'comments' => $data['comments'] ?? '', 'provider_id' => $data['provider_id'] ?? '', ]); @@ -95,11 +99,26 @@ class ScheduleController extends ControllerBase { public function events(Request $request): JsonResponse { $start = $request->query->get('start'); $end = $request->query->get('end'); + $service = $request->query->get('service', 'diagnostic'); + + // Each service gets different slot density and start hours so calendars + // look meaningfully distinct when switching types. + $serviceConfig = [ + 'diagnostic' => ['seeds' => [5, 7, 11], 'startHour' => 9], + 'sports' => ['seeds' => [3, 5, 8], 'startHour' => 7], + 'surgical' => ['seeds' => [4, 6, 13], 'startHour' => 10], + 'neuro' => ['seeds' => [2, 9, 7], 'startHour' => 11], + ]; + $cfg = $serviceConfig[$service] ?? $serviceConfig['diagnostic']; + [$s0, $s1, $s2] = $cfg['seeds']; $current = new \DateTime($start ?? 'now'); - $today = new \DateTime('today'); - if ($current < $today) { - $current = $today; + $earliest = new \DateTime('tomorrow'); + if ($service === 'surgical') { + $earliest = new \DateTime('+46 days'); + } + if ($current < $earliest) { + $current = $earliest; } $until = new \DateTime($end ?? 'now'); $events = []; @@ -109,10 +128,10 @@ class ScheduleController extends ControllerBase { $dow = (int) $current->format('N'); // 1=Mon … 7=Sun if ($dow <= 5) { $i = (int) floor($current->getTimestamp() / 86400); - $count = ($i % 5 + $i % 7 + $i % 11) % 6; + $count = ($i % $s0 + $i % $s1 + $i % $s2) % 6; for ($n = 0; $n < $count; $n++) { $slot = clone $current; - $slot->setTime(9 + $n, 0); + $slot->setTime($cfg['startHour'] + $n, 0); $events[] = [ 'id' => $id++, 'title' => 'Available', diff --git a/web/modules/custom/riverside_pt/templates/riverside-pt-home.html.twig b/web/modules/custom/riverside_pt/templates/riverside-pt-home.html.twig index 5ce724f..490ae27 100644 --- a/web/modules/custom/riverside_pt/templates/riverside-pt-home.html.twig +++ b/web/modules/custom/riverside_pt/templates/riverside-pt-home.html.twig @@ -118,13 +118,7 @@

Book An Appointment

- -
-
- -
+