diff --git a/package-lock.json b/package-lock.json index 2e4b34f..0d02bf3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,6 +6,8 @@ "": { "name": "riverside-therapeutics", "devDependencies": { + "htm": "^3.1.1", + "preact": "^10.29.2", "tailwindcss": "^3.4.17" } }, @@ -323,6 +325,12 @@ "node": ">= 0.4" } }, + "node_modules/htm": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/htm/-/htm-3.1.1.tgz", + "integrity": "sha512-983Vyg8NwUE7JkZ6NmOqpCZ+sh1bKv2iYTlUkzlWmA5JD2acKoxd4KVxbMmxX/85mtfdnDmTFoNKcg5DGAvxNQ==", + "dev": true + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -683,6 +691,16 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "dev": true }, + "node_modules/preact": { + "version": "10.29.2", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.29.2.tgz", + "integrity": "sha512-7tNmwg/7mzzAoB/8kSg6Hl37JraAZw3Z3A0JSY7VXlZwo82Xn0G7wKbNNs2qoF4ZEEsQGTwDAroNdqKs1ofJxQ==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", diff --git a/package.json b/package.json index fd35f92..7b263b4 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,8 @@ "build": "tailwindcss -i ./web/modules/custom/riverside_pt/css/tailwind.css -o ./web/modules/custom/riverside_pt/css/app.css --minify" }, "devDependencies": { + "htm": "^3.1.1", + "preact": "^10.29.2", "tailwindcss": "^3.4.17" } } diff --git a/web/modules/custom/riverside_pt/js/calendar.js b/web/modules/custom/riverside_pt/js/calendar.js deleted file mode 100644 index b3fabdc..0000000 --- a/web/modules/custom/riverside_pt/js/calendar.js +++ /dev/null @@ -1,145 +0,0 @@ -(function (drupalSettings) { - document.addEventListener('DOMContentLoaded', function () { - var el = document.getElementById('riverside-calendar'); - if (!el) return; - - requestAnimationFrame(function () { - var slotsWrap = document.getElementById('riverside-slots-wrap'); - var slotsGrid = document.getElementById('riverside-booking-slots'); - - 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 localDateStr(d); - } - - var initDate = nextBusinessDay(); - - function slotLabel(date) { - var h = date.getHours(); - return (h % 12 || 12) + (h < 12 ? 'AM' : 'PM') + ' PST'; - } - - function renderSlots(dateStr, events) { - var dayEvents = events - .filter(function (e) { return e.startStr.startsWith(dateStr); }) - .sort(function (a, b) { return a.start - b.start; }); - - if (!slotsWrap || !slotsGrid || dayEvents.length === 0) return; - - slotsGrid.innerHTML = ''; - dayEvents.forEach(function (event, idx) { - var btn = document.createElement('button'); - btn.type = 'button'; - btn.textContent = slotLabel(event.start); - btn.className = 'riverside-slot-btn' + (idx === 0 ? ' is-selected' : ''); - btn.addEventListener('click', function () { - slotsGrid.querySelectorAll('.riverside-slot-btn').forEach(function (b) { - b.classList.remove('is-selected'); - }); - btn.classList.add('is-selected'); - fetch(drupalSettings.riversidePt.storeSlotUrl, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ start: event.startStr, end: event.endStr, service: currentService }), - }).then(function (res) { - if (res.ok) { - window.location.href = drupalSettings.riversidePt.bookingUrl; - } else { - btn.textContent += ' (unavailable)'; - btn.disabled = true; - } - }); - }); - slotsGrid.appendChild(btn); - }); - - slotsWrap.hidden = false; - } - - function selectDay(dateStr, events) { - el.querySelectorAll('.fc-daygrid-day.is-selected').forEach(function (d) { - d.classList.remove('is-selected'); - }); - var dayEl = el.querySelector('.fc-daygrid-day[data-date="' + dateStr + '"]'); - if (dayEl) dayEl.classList.add('is-selected'); - selectedDate = dateStr; - renderSlots(dateStr, events); - } - - var calendar = new FullCalendar.Calendar(el, { - initialView: 'dayGridMonth', - initialDate: initDate, - headerToolbar: { left: 'prev', center: 'title', right: 'next' }, - titleFormat: { year: 'numeric', month: 'long' }, - dayHeaderFormat: { weekday: 'narrow' }, - validRange: function (now) { - return { - start: new Date(now.getFullYear(), now.getMonth(), 1), - end: new Date(now.getFullYear(), now.getMonth() + 7, 1), - }; - }, - fixedWeekCount: false, - showNonCurrentDates: false, - height: 'auto', - events: buildEventsUrl(currentService), - eventDisplay: 'none', - dayMaxEvents: false, - - datesSet: function () { - el.querySelectorAll('.fc-daygrid-day.is-selected').forEach(function (d) { - d.classList.remove('is-selected'); - }); - selectedDate = null; - if (slotsWrap) slotsWrap.hidden = true; - }, - - eventsSet: function (events) { - el.querySelectorAll('.fc-daygrid-day.has-availability').forEach(function (d) { - d.classList.remove('has-availability'); - }); - events.forEach(function (event) { - var dateStr = event.startStr.substring(0, 10); - var dayEl = el.querySelector('.fc-daygrid-day[data-date="' + dateStr + '"]'); - if (dayEl) dayEl.classList.add('has-availability'); - }); - - if (!initialized) { - initialized = true; - var targetEl = el.querySelector('.fc-daygrid-day[data-date="' + initDate + '"]'); - if (targetEl && targetEl.classList.contains('has-availability')) { - selectDay(initDate, events); - } - } - }, - - dayCellClassNames: function (arg) { - var date = arg.date.toISOString().substring(0, 10); - if (drupalSettings.riversidePt.holidays[date]) return ['is-holiday']; - }, - - dateClick: function (arg) { - if (!arg.dayEl.classList.contains('has-availability')) return; - selectDay(arg.dateStr, calendar.getEvents()); - }, - }); - - calendar.render(); - }); - }); -})(drupalSettings); diff --git a/web/modules/custom/riverside_pt/js/components/rpt-booking.js b/web/modules/custom/riverside_pt/js/components/rpt-booking.js index c735f7f..55708e4 100644 --- a/web/modules/custom/riverside_pt/js/components/rpt-booking.js +++ b/web/modules/custom/riverside_pt/js/components/rpt-booking.js @@ -209,6 +209,9 @@ function BookingPanel({ service, settings }) { cal.render(); calRef.current = cal; + + window.rptScrollTo(cal, true); + return function () { cal.destroy(); }; }, []); @@ -277,7 +280,7 @@ function BookingPanel({ service, settings }) { }, [fetchedEvents]); useEffect(function () { - if (selectedSlotId && !prevSlotIdRef.current && formRef.current) { + if (selectedSlotId && prevSlotIdRef.current !== selectedSlotId && formRef.current) { window.rptScrollTo(formRef.current, true); } prevSlotIdRef.current = selectedSlotId; @@ -515,7 +518,9 @@ function Booking({ settings }) { return html`