Getting the stream right for the frontend.

Signed-off-by: Cliff Hill <xlorep@darkhelm.org>
This commit is contained in:
2025-08-28 16:24:32 -04:00
parent 7831bf6869
commit ea98e7bd07
4 changed files with 66 additions and 7 deletions

View File

@@ -78,14 +78,20 @@ const BookingList: FC<BookingListProps> = ({
eventSourceRef.current.close();
}
// Subscribe to SSE for room availability/booking changes
const url = `/rooms/${roomId}/bookings/stream?date=${date}`;
const url = `http://localhost:8000/rooms/availability/stream`;
const es = new window.EventSource(url);
eventSourceRef.current = es;
es.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
// Expecting the backend to send the full list of bookings for the room/date
setLiveBookings(data.bookings || []);
// Filter bookings for the current room and date
let filtered = [];
if (Array.isArray(data.bookings)) {
filtered = data.bookings.filter(
(b: any) => b.roomId === roomId && b.startTime.startsWith(date)
);
}
setLiveBookings(filtered);
} catch (e) {
// Ignore parse errors
}

View File

@@ -118,7 +118,15 @@ const RoomList: FC<RoomListProps> = ({
<ListItemButton
key={room.id}
selected={room.id === selectedRoomId}
onClick={() => onSelectRoom(room.id)}
onClick={(e) => {
console.log("RoomList: ListItemButton click", room.id);
e.preventDefault();
e.stopPropagation();
onSelectRoom(room.id);
setTimeout(() => {
console.log("RoomList: after onSelectRoom", room.id);
}, 0);
}}
aria-label={`Select room ${room.name}`}
data-testid={`room-item-${room.id}`}
sx={

View File

@@ -9,6 +9,25 @@ import reportWebVitals from "./reportWebVitals";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import axios from "axios";
// Global error handler to catch unhandled errors and prevent reloads
window.onerror = function (message, source, lineno, colno, error) {
console.error("Global error handler:", {
message,
source,
lineno,
colno,
error,
});
// Prevent default browser reload on error
return true;
};
window.onunhandledrejection = function (event) {
console.error("Global unhandledrejection:", event.reason);
// Prevent default browser reload on unhandled promise rejection
return true;
};
const queryClient = new QueryClient();
axios.defaults.baseURL =
process.env.REACT_APP_API_URL || "http://localhost:8000";

View File

@@ -27,6 +27,22 @@ import BookingList, { Booking } from "../components/BookingList";
* @returns {JSX.Element} The landing page UI.
*/
const LandingPage: FC = () => {
// Debug: log when LandingPage mounts
useEffect(() => {
console.log("LandingPage: mounted");
return () => {
console.log("LandingPage: unmounted");
};
}, []);
// Debug: log before page unload
useEffect(() => {
const handler = (e: BeforeUnloadEvent) => {
console.log("window.onbeforeunload: page is unloading");
};
window.addEventListener("beforeunload", handler);
return () => window.removeEventListener("beforeunload", handler);
}, []);
const navigate = useNavigate();
const today = new Date().toISOString().split("T")[0];
@@ -63,10 +79,19 @@ const LandingPage: FC = () => {
} = useQuery({
queryKey: ["bookings", today, selectedRoomId],
enabled: !!selectedRoomId,
queryFn: () =>
axios
queryFn: () => {
console.log("LandingPage: fetching bookings for", selectedRoomId, today);
return axios
.get(`/bookings/room/${selectedRoomId}?date=${today}`)
.then((res) => res.data as Booking[]),
.then((res) => {
console.log("LandingPage: bookings fetch result", res.data);
return res.data as Booking[];
})
.catch((err) => {
console.error("LandingPage: bookings fetch error", err);
throw err;
});
},
});
// Display skeleton loaders during data fetching
@@ -115,6 +140,7 @@ const LandingPage: FC = () => {
// Display error message with retry option
if (roomsError || bookingsError) {
console.error("LandingPage: error state", { roomsError, bookingsError });
return (
<div
className="landing-root"