Public Access
mirror of
https://github.com/xlorepdarkhelm/numinar-coding-project.git
synced 2026-09-11 18:37:36 -04:00
Added better logging to the system.
Signed-off-by: Cliff Hill <xlorep@darkhelm.org>
This commit is contained in:
@@ -35,6 +35,7 @@ services:
|
||||
service: frontend
|
||||
environment:
|
||||
REACT_APP_API_URL: http://localhost:8000
|
||||
REACT_APP_LOG_LEVEL: debug
|
||||
volumes:
|
||||
- ./frontend/src:/app/src
|
||||
- ./frontend/public:/app/public
|
||||
|
||||
@@ -24,6 +24,7 @@ services:
|
||||
dockerfile: Dockerfile
|
||||
environment:
|
||||
REACT_APP_API_URL: http://localhost:8000
|
||||
REACT_APP_LOG_LEVEL: warning
|
||||
ports:
|
||||
- "3000:3000"
|
||||
depends_on:
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"axios": "^1.11.0",
|
||||
"date-fns": "^4.1.0",
|
||||
"loglevel": "^1.9.2",
|
||||
"react": "^19.1.1",
|
||||
"react-big-calendar": "^1.19.4",
|
||||
"react-dom": "^19.1.1",
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { logger } from "./utils/logger";
|
||||
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
|
||||
|
||||
import LandingPage from "./pages/LandingPage";
|
||||
import BookingPage from "./pages/BookingPage";
|
||||
|
||||
const App: React.FC = () => {
|
||||
// Log when App mounts
|
||||
logger.info("[App] Mounted");
|
||||
return (
|
||||
<Router>
|
||||
<Routes>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { logger } from "../utils/logger";
|
||||
// BookingForm.tsx
|
||||
// Booking form for selecting room, date, time, title, and invitees
|
||||
|
||||
@@ -93,6 +94,9 @@ const BookingForm: React.FC<BookingFormProps> = ({
|
||||
}
|
||||
});
|
||||
setErrors(newErrors);
|
||||
if (Object.keys(newErrors).length > 0) {
|
||||
logger.warn("[BookingForm] Validation failed", newErrors);
|
||||
}
|
||||
return Object.keys(newErrors).length === 0;
|
||||
};
|
||||
|
||||
@@ -104,6 +108,12 @@ const BookingForm: React.FC<BookingFormProps> = ({
|
||||
let bookingId = isEdit ? editBooking.id : undefined;
|
||||
let bookingRes;
|
||||
if (!isEdit) {
|
||||
logger.info("[BookingForm] Creating new booking", {
|
||||
roomId,
|
||||
start,
|
||||
end,
|
||||
title,
|
||||
});
|
||||
bookingRes = await axios.post("/bookings/", {
|
||||
room_id: roomId,
|
||||
start,
|
||||
@@ -111,14 +121,23 @@ const BookingForm: React.FC<BookingFormProps> = ({
|
||||
title: title || undefined,
|
||||
});
|
||||
bookingId = bookingRes.data.id;
|
||||
logger.info("[BookingForm] Booking created", bookingRes.data);
|
||||
} else {
|
||||
// Update booking details
|
||||
logger.info("[BookingForm] Updating booking", {
|
||||
id: editBooking.id,
|
||||
roomId,
|
||||
start,
|
||||
end,
|
||||
title,
|
||||
});
|
||||
await axios.put(`/bookings/${editBooking.id}`, {
|
||||
room_id: roomId,
|
||||
start,
|
||||
end,
|
||||
title: title || undefined,
|
||||
});
|
||||
logger.info("[BookingForm] Booking updated", { id: editBooking.id });
|
||||
}
|
||||
// For edit: remove all old invitees first
|
||||
if (isEdit) {
|
||||
@@ -128,6 +147,7 @@ const BookingForm: React.FC<BookingFormProps> = ({
|
||||
data: { user_email: inv.user_email },
|
||||
});
|
||||
}
|
||||
logger.debug("[BookingForm] Old invitees removed", oldInvitees.data);
|
||||
}
|
||||
// Add invitees
|
||||
for (const email of invitees) {
|
||||
@@ -136,6 +156,7 @@ const BookingForm: React.FC<BookingFormProps> = ({
|
||||
user_email: email,
|
||||
});
|
||||
}
|
||||
logger.info("[BookingForm] Invitees added", invitees);
|
||||
alert(
|
||||
isEdit
|
||||
? "Booking updated successfully!"
|
||||
@@ -156,8 +177,10 @@ const BookingForm: React.FC<BookingFormProps> = ({
|
||||
setSubmitError(
|
||||
"Booking times overlap with an existing booking for this room. Please choose a different time."
|
||||
);
|
||||
logger.warn("[BookingForm] Booking overlap error", detail);
|
||||
} else {
|
||||
setSubmitError("Failed to submit booking: " + detail);
|
||||
logger.error("[BookingForm] Booking submit error", detail);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -166,6 +189,7 @@ const BookingForm: React.FC<BookingFormProps> = ({
|
||||
if (!isEdit) return;
|
||||
if (!window.confirm("Delete this booking?")) return;
|
||||
try {
|
||||
logger.info("[BookingForm] Deleting booking", { id: editBooking.id });
|
||||
await axios.delete(`/bookings/${editBooking.id}`);
|
||||
alert("Booking deleted.");
|
||||
if (onBookingSuccess) {
|
||||
@@ -174,10 +198,9 @@ const BookingForm: React.FC<BookingFormProps> = ({
|
||||
onClose();
|
||||
}
|
||||
} catch (err: any) {
|
||||
alert(
|
||||
"Failed to delete booking: " +
|
||||
(err?.response?.data?.detail || err.message)
|
||||
);
|
||||
const detail = err?.response?.data?.detail || err.message;
|
||||
alert("Failed to delete booking: " + detail);
|
||||
logger.error("[BookingForm] Booking delete error", detail);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { logger } from "../utils/logger";
|
||||
/**
|
||||
* Displays a list of bookings for the current day in a Material-UI Card.
|
||||
*
|
||||
@@ -61,6 +62,7 @@ const BookingList: FC<BookingListProps> = ({
|
||||
// Keep liveBookings in sync with prop changes
|
||||
useEffect(() => {
|
||||
setLiveBookings(bookings);
|
||||
logger.debug("[BookingList] Bookings prop changed", bookings);
|
||||
}, [bookings]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -71,6 +73,7 @@ const BookingList: FC<BookingListProps> = ({
|
||||
typeof window.EventSource !== "undefined" &&
|
||||
(enableSSE || process.env.NODE_ENV !== "test");
|
||||
if (!sseAllowed) {
|
||||
logger.warn("[BookingList] SSE not enabled or supported");
|
||||
return;
|
||||
}
|
||||
// Close any previous EventSource
|
||||
@@ -92,15 +95,18 @@ const BookingList: FC<BookingListProps> = ({
|
||||
);
|
||||
}
|
||||
setLiveBookings(filtered);
|
||||
logger.debug("[BookingList] SSE update received", filtered);
|
||||
} catch (e) {
|
||||
// Ignore parse errors
|
||||
logger.warn("[BookingList] Failed to parse SSE data", e);
|
||||
}
|
||||
};
|
||||
es.onerror = () => {
|
||||
es.onerror = (err) => {
|
||||
es.close();
|
||||
logger.error("[BookingList] SSE connection error", err);
|
||||
};
|
||||
return () => {
|
||||
es.close();
|
||||
logger.info("[BookingList] SSE connection closed");
|
||||
};
|
||||
}, [roomId, date, enableSSE]);
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { logger } from "../utils/logger";
|
||||
// BookingPage.tsx
|
||||
// Booking page for conference room system
|
||||
// See README for requirements
|
||||
@@ -25,6 +26,11 @@ import {
|
||||
* @component
|
||||
*/
|
||||
const BookingPage: React.FC = () => {
|
||||
// Log mount
|
||||
useEffect(() => {
|
||||
logger.info("[BookingPage] Mounted");
|
||||
return () => logger.info("[BookingPage] Unmounted");
|
||||
}, []);
|
||||
// Fetch all rooms
|
||||
const {
|
||||
data: rooms = [],
|
||||
@@ -59,20 +65,30 @@ const BookingPage: React.FC = () => {
|
||||
try {
|
||||
const res = await axios.get(`/bookings/room/${room.id}`);
|
||||
results[room.id] = res.data;
|
||||
logger.debug(
|
||||
`[BookingPage] Bookings fetched for room ${room.id}`,
|
||||
res.data
|
||||
);
|
||||
} catch (e) {
|
||||
results[room.id] = [];
|
||||
logger.warn(
|
||||
`[BookingPage] Failed to fetch bookings for room ${room.id}`
|
||||
);
|
||||
}
|
||||
})
|
||||
);
|
||||
if (!cancelled) setBookingsByRoom(results);
|
||||
logger.info("[BookingPage] All bookings fetched and set");
|
||||
} catch (err) {
|
||||
if (!cancelled) setBookingsError(err);
|
||||
logger.error("[BookingPage] Error fetching bookings", err);
|
||||
} finally {
|
||||
if (!cancelled) setBookingsLoading(false);
|
||||
}
|
||||
}
|
||||
if (rooms.length > 0) {
|
||||
fetchBookingsAndSet();
|
||||
logger.info("[BookingPage] Fetching bookings for all rooms");
|
||||
// Fallback: If EventSource/SSE is not supported, use polling
|
||||
const sseAllowed =
|
||||
typeof window !== "undefined" &&
|
||||
@@ -92,18 +108,22 @@ const BookingPage: React.FC = () => {
|
||||
grouped[b.roomId].push(b);
|
||||
});
|
||||
setBookingsByRoom(grouped);
|
||||
logger.debug("[BookingPage] SSE update received", grouped);
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore parse errors
|
||||
logger.warn("[BookingPage] Failed to parse SSE data", e);
|
||||
}
|
||||
};
|
||||
es.onerror = () => {
|
||||
es.onerror = (err) => {
|
||||
es.close();
|
||||
logger.error("[BookingPage] SSE connection error", err);
|
||||
};
|
||||
} else {
|
||||
setUsingPollingFallback(true);
|
||||
logger.warn("[BookingPage] SSE not supported, falling back to polling");
|
||||
// Poll every 10 seconds if SSE not available
|
||||
pollInterval = setInterval(() => {
|
||||
logger.info("[BookingPage] Polling for bookings update");
|
||||
fetchBookingsAndSet();
|
||||
}, 10000);
|
||||
}
|
||||
@@ -139,23 +159,28 @@ const BookingPage: React.FC = () => {
|
||||
const [selectedRoomId, setSelectedRoomId] = useState<any>(rooms[0]?.id || "");
|
||||
|
||||
const handleSelectSlot = (slotInfo: any) => {
|
||||
logger.debug("[BookingPage] Slot selected", slotInfo);
|
||||
setFormSlot(slotInfo);
|
||||
setEditBooking(null);
|
||||
setFormOpen(true);
|
||||
};
|
||||
|
||||
const handleSelectEvent = (event: CalendarEvent) => {
|
||||
// Open form in edit mode
|
||||
logger.debug("[BookingPage] Event selected for edit", event);
|
||||
setEditBooking(event);
|
||||
setFormSlot(null);
|
||||
setFormOpen(true);
|
||||
};
|
||||
|
||||
const handleFormClose = (refresh = false) => {
|
||||
logger.info("[BookingPage] Booking form closed", { refresh });
|
||||
setFormOpen(false);
|
||||
setFormSlot(null);
|
||||
setEditBooking(null);
|
||||
if (refresh) refetchBookings();
|
||||
if (refresh) {
|
||||
logger.info("[BookingPage] Refetching bookings after form close");
|
||||
refetchBookings();
|
||||
}
|
||||
};
|
||||
|
||||
// (removed old handleSelectEvent)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { logger } from "../utils/logger";
|
||||
/**
|
||||
* Main entry point for the conference room booking system.
|
||||
* Displays available rooms, today's bookings, and a navigation button to the booking page.
|
||||
@@ -27,11 +28,11 @@ import BookingList, { Booking } from "../components/BookingList";
|
||||
* @returns {JSX.Element} The landing page UI.
|
||||
*/
|
||||
const LandingPage: FC = () => {
|
||||
// Debug: log when LandingPage mounts
|
||||
// Log when LandingPage mounts/unmounts
|
||||
useEffect(() => {
|
||||
// ...removed debug log...
|
||||
logger.info("[LandingPage] Mounted");
|
||||
return () => {
|
||||
// ...removed debug log...
|
||||
logger.info("[LandingPage] Unmounted");
|
||||
};
|
||||
}, []);
|
||||
|
||||
@@ -54,8 +55,13 @@ const LandingPage: FC = () => {
|
||||
refetch: refetchRooms,
|
||||
} = useQuery({
|
||||
queryKey: ["rooms"],
|
||||
queryFn: () =>
|
||||
axios.get("/rooms/").then((res) => res.data as ConferenceRoom[]),
|
||||
queryFn: () => {
|
||||
logger.info("[LandingPage] Fetching rooms");
|
||||
return axios.get("/rooms/").then((res) => {
|
||||
logger.debug("[LandingPage] Rooms fetched", res.data);
|
||||
return res.data as ConferenceRoom[];
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// Room selection state
|
||||
@@ -80,15 +86,15 @@ const LandingPage: FC = () => {
|
||||
queryKey: ["bookings", today, selectedRoomId],
|
||||
enabled: !!selectedRoomId,
|
||||
queryFn: () => {
|
||||
// ...removed debug log...
|
||||
logger.info("[LandingPage] Fetching bookings", { today, selectedRoomId });
|
||||
return axios
|
||||
.get(`/bookings/room/${selectedRoomId}?date=${today}`)
|
||||
.then((res) => {
|
||||
// ...removed debug log...
|
||||
logger.debug("[LandingPage] Bookings fetched", res.data);
|
||||
return res.data as Booking[];
|
||||
})
|
||||
.catch((err) => {
|
||||
// ...removed debug error log...
|
||||
logger.error("[LandingPage] Failed to fetch bookings", err);
|
||||
throw err;
|
||||
});
|
||||
},
|
||||
@@ -140,7 +146,9 @@ const LandingPage: FC = () => {
|
||||
|
||||
// Display error message with retry option
|
||||
if (roomsError || bookingsError) {
|
||||
// ...removed debug error log...
|
||||
if (roomsError) logger.error("[LandingPage] Rooms error", roomsError);
|
||||
if (bookingsError)
|
||||
logger.error("[LandingPage] Bookings error", bookingsError);
|
||||
return (
|
||||
<div
|
||||
className="landing-root"
|
||||
|
||||
@@ -7743,6 +7743,11 @@ lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.0:
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||
|
||||
loglevel@^1.9.2:
|
||||
version "1.9.2"
|
||||
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.9.2.tgz#c2e028d6c757720107df4e64508530db6621ba08"
|
||||
integrity sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==
|
||||
|
||||
loose-envify@^1.0.0, loose-envify@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
|
||||
|
||||
Reference in New Issue
Block a user