Refine appointment booking calendar layout

This commit is contained in:
metatroncubeswdev 2026-08-31 19:12:47 -04:00
parent 27320bc0ac
commit 630a0f5046
4 changed files with 181 additions and 87 deletions

View File

@ -16,6 +16,7 @@
], ],
"assets": { "assets": {
"web.assets_frontend": [ "web.assets_frontend": [
"mcs_appointment_booking/static/src/js/appointment_booking.js",
"mcs_appointment_booking/static/src/scss/appointment_booking.scss", "mcs_appointment_booking/static/src/scss/appointment_booking.scss",
], ],
}, },

View File

@ -0,0 +1,28 @@
(function () {
"use strict";
function initAppointmentBooking(root) {
const startInput = root.querySelector(".js-mcs-start");
const stopInput = root.querySelector(".js-mcs-stop");
const selectedLabel = root.querySelector(".js-mcs-selected-slot");
const buttons = root.querySelectorAll(".mcs-appt-time");
if (!startInput || !stopInput || !selectedLabel || !buttons.length) {
return;
}
buttons.forEach((button) => {
button.addEventListener("click", () => {
buttons.forEach((item) => item.classList.remove("active"));
button.classList.add("active");
startInput.value = button.dataset.start || "";
stopInput.value = button.dataset.stop || "";
selectedLabel.textContent = button.dataset.label || button.textContent.trim();
});
});
}
document.addEventListener("DOMContentLoaded", () => {
document.querySelectorAll(".mcs-appointment").forEach(initAppointmentBooking);
});
})();

View File

@ -22,9 +22,9 @@
.mcs-appt-shell { .mcs-appt-shell {
display: grid; display: grid;
grid-template-columns: minmax(280px, 380px) minmax(0, 1fr); grid-template-columns: minmax(260px, 340px) minmax(0, 1fr);
gap: 0; gap: 0;
max-width: 1180px; max-width: 1240px;
margin: 0 auto; margin: 0 auto;
overflow: hidden; overflow: hidden;
border: 1px solid var(--mcs-appt-line); border: 1px solid var(--mcs-appt-line);
@ -34,13 +34,13 @@
} }
.mcs-appt-info { .mcs-appt-info {
padding: 34px; padding: 30px;
background: var(--mcs-appt-navy); background: var(--mcs-appt-navy);
color: #ffffff; color: #ffffff;
h1 { h1 {
margin: 18px 0 18px; margin: 18px 0 18px;
font-size: clamp(2rem, 5vw, 3.45rem); font-size: clamp(2rem, 4vw, 3rem);
line-height: 1.04; line-height: 1.04;
font-weight: 820; font-weight: 820;
} }
@ -89,7 +89,7 @@
.mcs-appt-booking { .mcs-appt-booking {
min-width: 0; min-width: 0;
padding: 32px; padding: 28px;
} }
.mcs-appt-section-head { .mcs-appt-section-head {
@ -121,15 +121,16 @@
.mcs-appt-date-strip { .mcs-appt-date-strip {
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(82px, 1fr)); grid-template-columns: repeat(7, minmax(0, 1fr));
gap: 10px; gap: 8px;
max-width: 560px;
} }
.mcs-appt-date { .mcs-appt-date {
display: grid; display: grid;
min-height: 82px; min-height: 66px;
place-items: center; place-items: center;
padding: 10px; padding: 8px 6px;
border: 1px solid var(--mcs-appt-line); border: 1px solid var(--mcs-appt-line);
border-radius: 10px; border-radius: 10px;
background: #ffffff; background: #ffffff;
@ -146,7 +147,7 @@
span { span {
color: var(--mcs-appt-ink); color: var(--mcs-appt-ink);
font-size: 1.35rem; font-size: 1.18rem;
line-height: 1; line-height: 1;
font-weight: 850; font-weight: 850;
} }
@ -181,65 +182,69 @@
background: var(--mcs-appt-soft); background: var(--mcs-appt-soft);
} }
.mcs-appt-slot-list { .mcs-appt-workspace {
display: grid; display: grid;
gap: 10px; grid-template-columns: minmax(0, 1fr) minmax(300px, 360px);
gap: 20px;
margin-top: 28px;
align-items: start;
} }
.mcs-appt-slot { .mcs-appt-times,
input[type="radio"] { .mcs-appt-details {
position: absolute; min-width: 0;
opacity: 0;
pointer-events: none;
} }
label { .mcs-appt-selected-day {
display: flex; margin: -8px 0 14px;
align-items: center; color: var(--mcs-appt-muted);
justify-content: space-between; font-weight: 750;
gap: 12px; }
min-height: 56px;
padding: 12px 14px; .mcs-appt-slot-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(128px, 1fr));
gap: 9px;
max-height: 540px;
overflow-y: auto;
padding-right: 4px;
}
.mcs-appt-time {
display: grid;
gap: 2px;
min-height: 66px;
border: 1px solid var(--mcs-appt-line); border: 1px solid var(--mcs-appt-line);
border-radius: 10px; border-radius: 10px;
padding: 10px;
background: #ffffff; background: #ffffff;
color: var(--mcs-appt-ink); color: var(--mcs-appt-ink);
cursor: pointer; text-align: left;
transition: border-color 160ms ease-out, background 160ms ease-out, transform 160ms ease-out; transition: border-color 160ms ease-out, background 160ms ease-out, box-shadow 160ms ease-out, transform 160ms ease-out;
span { span {
font-size: 1.05rem;
font-weight: 850; font-weight: 850;
} }
small { small {
color: var(--mcs-appt-teal); color: var(--mcs-appt-muted);
font-weight: 800; font-weight: 800;
} }
}
label:active { &.active {
transform: scale(0.99);
}
input[type="radio"]:checked + label {
border-color: var(--mcs-appt-blue); border-color: var(--mcs-appt-blue);
background: #eef4ff; background: #eef4ff;
box-shadow: inset 0 0 0 1px rgba(28, 78, 216, 0.15);
} }
input[type="radio"]:checked ~ .mcs-appt-form { &:active {
display: grid; transform: scale(0.98);
} }
} }
.mcs-appt-form { .mcs-appt-form {
display: none; display: grid;
gap: 16px; gap: 16px;
margin-top: 10px;
padding: 18px;
border: 1px solid #cbd8f7;
border-radius: 12px;
background: #f8fbff;
} }
.mcs-appt-form-grid { .mcs-appt-form-grid {
@ -292,6 +297,37 @@
} }
} }
.mcs-appt-details {
position: sticky;
top: 96px;
padding: 18px;
border: 1px solid #cbd8f7;
border-radius: 14px;
background: #f8fbff;
}
.mcs-appt-selected-slot {
display: grid;
gap: 4px;
margin-bottom: 16px;
padding: 12px;
border: 1px solid var(--mcs-appt-line);
border-radius: 10px;
background: #ffffff;
small {
color: var(--mcs-appt-muted);
font-size: 0.76rem;
font-weight: 900;
text-transform: uppercase;
letter-spacing: 0.08em;
}
strong {
font-size: 1.05rem;
}
}
@media (max-width: 900px) { @media (max-width: 900px) {
.mcs-appt-shell { .mcs-appt-shell {
grid-template-columns: 1fr; grid-template-columns: 1fr;
@ -301,6 +337,14 @@
.mcs-appt-booking { .mcs-appt-booking {
padding: 24px; padding: 24px;
} }
.mcs-appt-workspace {
grid-template-columns: 1fr;
}
.mcs-appt-details {
position: static;
}
} }
@media (max-width: 560px) { @media (max-width: 560px) {
@ -308,11 +352,14 @@
padding: 18px 12px 42px; padding: 18px 12px 42px;
} }
.mcs-appt-date-strip,
.mcs-appt-form-grid { .mcs-appt-form-grid {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
.mcs-appt-date-strip {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.mcs-appt-section-head { .mcs-appt-section-head {
align-items: flex-start; align-items: flex-start;
flex-direction: column; flex-direction: column;

View File

@ -66,30 +66,45 @@
</t> </t>
</div> </div>
<header class="mcs-appt-section-head mcs-appt-section-head--spaced"> <section class="mcs-appt-workspace">
<div class="mcs-appt-times">
<header class="mcs-appt-section-head">
<span>Step 2</span> <span>Step 2</span>
<h2>Choose a time on <t t-esc="selected_day.strftime('%A, %d %B')"/></h2> <h2>Available times</h2>
</header> </header>
<p class="mcs-appt-selected-day" t-esc="selected_day.strftime('%A, %d %B')"/>
<div t-if="not slots" class="mcs-appt-empty"> <div t-if="not slots" class="mcs-appt-empty">
No free slots are available on this day. No free slots are available on this day.
</div> </div>
<div t-if="slots" class="mcs-appt-slot-list"> <div t-if="slots" class="mcs-appt-slot-grid">
<t t-foreach="slots" t-as="slot"> <t t-foreach="slots" t-as="slot">
<div class="mcs-appt-slot"> <button type="button"
<input type="radio" t-attf-class="mcs-appt-time #{'active' if slot_index == 0 else ''}"
name="selected_slot" t-att-data-start="slot['start']"
t-att-id="'mcs_slot_%s' % slot_index" t-att-data-stop="slot['stop']"
t-att-checked="'checked' if slot_index == 0 else None"/> t-att-data-label="slot['label']">
<label t-att-for="'mcs_slot_%s' % slot_index"> <span t-esc="slot['label'].split(' - ')[0]"/>
<span t-esc="slot['label']"/> <small t-esc="slot['label'].split(' - ')[1]"/>
<small>Available</small> </button>
</label> </t>
<form t-att-action="'/appointments/%s/book' % appointment_type.id" method="post" class="mcs-appt-form"> </div>
</div>
<aside class="mcs-appt-details">
<header class="mcs-appt-section-head">
<span>Step 3</span>
<h2>Your details</h2>
</header>
<div t-if="slots" class="mcs-appt-selected-slot">
<small>Selected time</small>
<strong class="js-mcs-selected-slot" t-esc="slots[0]['label']"/>
</div>
<form t-if="slots" t-att-action="'/appointments/%s/book' % appointment_type.id" method="post" class="mcs-appt-form">
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/> <input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
<input type="hidden" name="start" t-att-value="slot['start']"/> <input type="hidden" name="start" class="js-mcs-start" t-att-value="slots[0]['start']"/>
<input type="hidden" name="stop" t-att-value="slot['stop']"/> <input type="hidden" name="stop" class="js-mcs-stop" t-att-value="slots[0]['stop']"/>
<div class="mcs-appt-form-grid"> <div class="mcs-appt-form-grid">
<label>Name<input name="customer_name" required="required"/></label> <label>Name<input name="customer_name" required="required"/></label>
<label>Email<input name="customer_email" type="email" required="required"/></label> <label>Email<input name="customer_email" type="email" required="required"/></label>
@ -98,9 +113,12 @@
</div> </div>
<button type="submit">Confirm Appointment</button> <button type="submit">Confirm Appointment</button>
</form> </form>
<div t-if="not slots" class="mcs-appt-selected-slot">
<small>Selected time</small>
<strong>Choose another date</strong>
</div> </div>
</t> </aside>
</div> </section>
</main> </main>
</section> </section>
</div> </div>