wip
This commit is contained in:
parent
0d3b502126
commit
2d933926fb
5 changed files with 41 additions and 147 deletions
18
package-lock.json
generated
18
package-lock.json
generated
|
|
@ -6,6 +6,8 @@
|
||||||
"": {
|
"": {
|
||||||
"name": "riverside-therapeutics",
|
"name": "riverside-therapeutics",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"htm": "^3.1.1",
|
||||||
|
"preact": "^10.29.2",
|
||||||
"tailwindcss": "^3.4.17"
|
"tailwindcss": "^3.4.17"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -323,6 +325,12 @@
|
||||||
"node": ">= 0.4"
|
"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": {
|
"node_modules/is-binary-path": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
||||||
|
|
@ -683,6 +691,16 @@
|
||||||
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
|
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
|
||||||
"dev": true
|
"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": {
|
"node_modules/queue-microtask": {
|
||||||
"version": "1.2.3",
|
"version": "1.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||||
|
|
|
||||||
|
|
@ -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"
|
"build": "tailwindcss -i ./web/modules/custom/riverside_pt/css/tailwind.css -o ./web/modules/custom/riverside_pt/css/app.css --minify"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"htm": "^3.1.1",
|
||||||
|
"preact": "^10.29.2",
|
||||||
"tailwindcss": "^3.4.17"
|
"tailwindcss": "^3.4.17"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
|
||||||
|
|
@ -209,6 +209,9 @@ function BookingPanel({ service, settings }) {
|
||||||
|
|
||||||
cal.render();
|
cal.render();
|
||||||
calRef.current = cal;
|
calRef.current = cal;
|
||||||
|
|
||||||
|
window.rptScrollTo(cal, true);
|
||||||
|
|
||||||
return function () { cal.destroy(); };
|
return function () { cal.destroy(); };
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
@ -277,7 +280,7 @@ function BookingPanel({ service, settings }) {
|
||||||
}, [fetchedEvents]);
|
}, [fetchedEvents]);
|
||||||
|
|
||||||
useEffect(function () {
|
useEffect(function () {
|
||||||
if (selectedSlotId && !prevSlotIdRef.current && formRef.current) {
|
if (selectedSlotId && prevSlotIdRef.current !== selectedSlotId && formRef.current) {
|
||||||
window.rptScrollTo(formRef.current, true);
|
window.rptScrollTo(formRef.current, true);
|
||||||
}
|
}
|
||||||
prevSlotIdRef.current = selectedSlotId;
|
prevSlotIdRef.current = selectedSlotId;
|
||||||
|
|
@ -515,7 +518,9 @@ function Booking({ settings }) {
|
||||||
return html`
|
return html`
|
||||||
<button
|
<button
|
||||||
key=${t.id}
|
key=${t.id}
|
||||||
onClick=${function () { setService(t.id); }}
|
onClick=${function () {
|
||||||
|
setService(t.id);
|
||||||
|
}}
|
||||||
style="text-align:left; cursor:pointer;"
|
style="text-align:left; cursor:pointer;"
|
||||||
class=${CX.typeBtn + " " + (active ? CX.typeBtnActive : CX.typeBtnInactive)}
|
class=${CX.typeBtn + " " + (active ? CX.typeBtnActive : CX.typeBtnInactive)}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
14
web/modules/custom/riverside_pt/js/globals.d.ts
vendored
Normal file
14
web/modules/custom/riverside_pt/js/globals.d.ts
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
interface RiversidePtSettings {
|
||||||
|
eventsUrl: string;
|
||||||
|
storeSlotUrl: string;
|
||||||
|
bookingUrl: string;
|
||||||
|
holidays: Record<string, boolean>;
|
||||||
|
scrollTo?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Window {
|
||||||
|
rptScrollTo: (el: Element, animate?: boolean) => void;
|
||||||
|
drupalSettings?: {
|
||||||
|
riversidePt?: RiversidePtSettings;
|
||||||
|
};
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue