From e0fe5c2241dd8a3018e16ef9cd1c83e8668978f4 Mon Sep 17 00:00:00 2001 From: Cliff Hill Date: Thu, 9 Oct 2025 09:39:14 -0400 Subject: [PATCH] Making thngs cleaner. Signed-off-by: Cliff Hill --- frontend/src/components/BookingForm.tsx | 63 ++++++++++++++++--------- frontend/src/pages/ConfirmationPage.tsx | 19 ++++++-- frontend/src/styles/BookingForm.css | 24 ++++++++++ frontend/src/styles/colors.css | 29 ++++++------ 4 files changed, 96 insertions(+), 39 deletions(-) diff --git a/frontend/src/components/BookingForm.tsx b/frontend/src/components/BookingForm.tsx index 82bb2c1f..e22d8073 100644 --- a/frontend/src/components/BookingForm.tsx +++ b/frontend/src/components/BookingForm.tsx @@ -14,6 +14,7 @@ import React, { useState, useMemo, useCallback } from "react"; import { Box, + ListSubheader, Button, Chip, Dialog, @@ -88,7 +89,7 @@ const BookingForm: React.FC = ({ const bookingsContext = useBookings(); const roomsContext = useRooms(); const usersContext = useUsers(); - const bookings = useMemo( + const bookings: Booking[] = useMemo( () => bookingsContext.bookings ?? [], [bookingsContext.bookings] ); @@ -530,35 +531,53 @@ const BookingForm: React.FC = ({ )} disabled={isViewMode} + MenuProps={{ + PaperProps: { + style: { maxHeight: 400 }, + }, + // Add a custom footer with a Done button + MenuListProps: { + "aria-label": "invitee-list", + }, + }} > - - {remainingSlots >= 0 - ? `${remainingSlots} invitee slot${ - remainingSlots === 1 ? "" : "s" - } left` - : `${Math.abs(remainingSlots)} over room capacity`} - + + {remainingSlots >= 0 + ? `${remainingSlots} invitee slot${ + remainingSlots === 1 ? "" : "s" + } left` + : `${Math.abs(remainingSlots)} over room capacity`} + + {users.map((user: import("../interfaces").User) => { const email = user.email ?? user.name; const isSelected = invitees.includes(email); // User unavailable if booked for another event at this time - const unavailable = bookings.some((b: Booking) => { - if (isEdit && b.id === editBooking?.id) { + const unavailable = (bookings as unknown as Booking[]).some( + (b) => { + if (typeof b === "object" && b !== null && "id" in b) { + const booking = b as Booking; + if (isEdit && booking.id === editBooking?.id) { + return false; + } + return ( + booking.invitees?.includes(email) && + new Date(start) < new Date(booking.end_time) && + new Date(end) > new Date(booking.start_time) + ); + } return false; } - return ( - b.invitees?.includes(email) && - new Date(start) < new Date(b.end_time) && - new Date(end) > new Date(b.start_time) - ); - }); + ); const disableUnselected = !isSelected && (unavailable || invitees.length >= roomCapacity); diff --git a/frontend/src/pages/ConfirmationPage.tsx b/frontend/src/pages/ConfirmationPage.tsx index f992c5e1..41dbe2d0 100644 --- a/frontend/src/pages/ConfirmationPage.tsx +++ b/frontend/src/pages/ConfirmationPage.tsx @@ -28,6 +28,7 @@ import { getRooms } from "../apis/rooms"; // Context imports import { useBookings } from "../context/BookingContext"; +import { useUsers } from "../context/UserContext"; // Utility/helper imports @@ -46,6 +47,7 @@ const ConfirmationPage: React.FC = () => { const location = useLocation(); // Always call hooks at the top level const { bookings, refreshBookings } = useBookings(); + const { users } = useUsers(); // booking is passed from BookingForm or BookingPage /** @@ -93,7 +95,7 @@ const ConfirmationPage: React.FC = () => { return () => { es.close(); }; - }, [bookings, location.state, booking?.id, navigate]); + }, [bookings, location.state, booking?.id, navigate, booking]); /** * Subscribe to SSE for live booking status updates. */ @@ -125,7 +127,7 @@ const ConfirmationPage: React.FC = () => { return () => { es.close(); }; - }, [bookings, location.state, booking?.id]); + }, [bookings, location.state, booking?.id, booking]); /** * Editing state for toggling between confirmation and edit form. */ @@ -164,8 +166,19 @@ const ConfirmationPage: React.FC = () => { } }; - // Enrich booking with room details if missing + // Enrich booking with room details and invitee names if missing let enrichedBooking = booking; + if ( + enrichedBooking && + Array.isArray(users) && + Array.isArray(enrichedBooking.invitees) + ) { + const inviteeNames = enrichedBooking.invitees.map((email) => { + const user = users.find((u) => u.email === email); + return user ? user.name : email; + }); + enrichedBooking = { ...enrichedBooking, invitees: inviteeNames }; + } if ( enrichedBooking && !enrichedBooking.room && diff --git a/frontend/src/styles/BookingForm.css b/frontend/src/styles/BookingForm.css index 2d871543..95659bf1 100644 --- a/frontend/src/styles/BookingForm.css +++ b/frontend/src/styles/BookingForm.css @@ -117,3 +117,27 @@ flex-wrap: wrap; gap: 0.5em; } + +/* Sticky header for invitee selection */ +.bookingform-invitee-header { + display: flex; + align-items: center; + padding-left: 16px; + padding-right: 16px; + padding-top: 8px; + padding-bottom: 8px; + position: sticky; + top: 0; + z-index: 1; + background-color: var(--mui-palette-background-paper, #fff); + border-bottom: 1px solid var(--color-horizontal-rule, #eee); +} + +.bookingform-invitee-header-label { + flex: 1; + font-weight: 500; +} + +.bookingform-invitee-header-label.over { + color: var(--color-error, #d32f2f); +} diff --git a/frontend/src/styles/colors.css b/frontend/src/styles/colors.css index d229554a..94af5ba0 100644 --- a/frontend/src/styles/colors.css +++ b/frontend/src/styles/colors.css @@ -15,6 +15,7 @@ /* Text & Foreground Colors */ --color-text-primary: #23272f; /* Main text */ + --color-horizontal-rule: #eee; /* Horizontal rules */ --color-text-secondary: #555; /* Secondary text */ --color-text-muted: #444; /* Muted/disabled text */ --color-text-disabled: #222; /* Disabled/very muted text */ @@ -27,24 +28,24 @@ --room-icon: rgba(0, 0, 0, 0.54); /* Room icon color */ /* Room color variables: Color Universal Design (CUD) palette, color-blind friendly */ - --room-color-1: #e69f00; /* orange */ - --room-color-2: #56b4e9; /* sky blue */ - --room-color-3: #009e73; /* bluish green */ - --room-color-4: #f0e442; /* yellow */ - --room-color-5: #0072b2; /* blue */ - --room-color-6: #d55e00; /* vermillion */ - --room-color-7: #cc79a7; /* reddish purple */ - --room-color-8: #000000; /* black */ - --room-color-9: #999933; /* olive */ - --room-color-10: #882255; /* wine */ - --room-color-11: #44aa99; /* teal */ - --room-color-12: #117733; /* green */ + --room-color-1: #0072b2; /* blue */ + --room-color-2: #117733; /* green */ + --room-color-3: #aa3377; /* magenta */ + --room-color-4: #e69f00; /* orange */ + --room-color-5: #56b4e9; /* sky blue */ + --room-color-6: #009e73; /* bluish green */ + --room-color-7: #f0e442; /* yellow */ + --room-color-8: #d55e00; /* vermillion */ + --room-color-9: #cc79a7; /* reddish purple */ + --room-color-10: #999933; /* olive */ + --room-color-11: #882255; /* wine */ + --room-color-12: #44aa99; /* teal */ --room-color-13: #aa4499; /* purple */ --room-color-14: #332288; /* navy */ --room-color-15: #ddcc77; /* sand */ --room-color-16: #661100; /* brown */ --room-color-17: #6699cc; /* steel blue */ --room-color-18: #888888; /* gray */ - --room-color-19: #aa3377; /* magenta */ - --room-color-20: #44bb99; /* turquoise */ + --room-color-19: #44bb99; /* turquoise */ + --room-color-20: #000000; /* black */ }