mirror of
https://github.com/xlorepdarkhelm/numinar-coding-project.git
synced 2026-09-10 01:55:48 -04:00
Getting frontend working better.
Signed-off-by: Cliff Hill <xlorep@darkhelm.org>
This commit is contained in:
@@ -34,6 +34,12 @@ RUN yarn install --frozen-lockfile
|
||||
|
||||
# Copying the rest of the application code
|
||||
COPY frontend/ .
|
||||
# Copy .env for build-time environment variables
|
||||
COPY .env ./
|
||||
|
||||
# Set build-time ARG for log level (optional, for CI/CD)
|
||||
ARG REACT_APP_FRONTEND_LOG_LEVEL=info
|
||||
ENV REACT_APP_FRONTEND_LOG_LEVEL=$REACT_APP_FRONTEND_LOG_LEVEL
|
||||
|
||||
# Building the TypeScript React app for production
|
||||
RUN yarn run build
|
||||
@@ -59,9 +65,11 @@ COPY --from=builder /app/config-overrides.js ./
|
||||
COPY --from=builder /app/node_modules ./node_modules
|
||||
COPY --from=builder /app/public ./public
|
||||
COPY --from=builder /app/build ./build
|
||||
COPY --from=builder /app/.env ./
|
||||
|
||||
# Set environment variables for runtime
|
||||
ENV FRONTEND_PORT=${FRONTEND_PORT}
|
||||
ENV REACT_APP_FRONTEND_LOG_LEVEL=${REACT_APP_FRONTEND_LOG_LEVEL}
|
||||
|
||||
# Starting the server to serve the built React app
|
||||
CMD ["serve", "-s", "build", "-l", "$FRONTEND_PORT"]
|
||||
|
||||
@@ -116,6 +116,10 @@ async def validate_no_overlap(
|
||||
Raises:
|
||||
ValueError: If booking times overlap with an existing booking.
|
||||
"""
|
||||
logger.debug(
|
||||
f"validate_no_overlap: room_id={room_id}, start_time={start_time},"
|
||||
f" end_time={end_time}, exclude_booking_id={exclude_booking_id}"
|
||||
)
|
||||
conditions: list[ColumnElement[bool]] = [
|
||||
Booking.room_id == room_id,
|
||||
func.tstzrange(Booking.start_time, Booking.end_time, "[)").op("&&")(
|
||||
@@ -123,12 +127,17 @@ async def validate_no_overlap(
|
||||
),
|
||||
]
|
||||
if exclude_booking_id is not None:
|
||||
logger.debug(f"validate_no_overlap: excluding booking id {exclude_booking_id}")
|
||||
conditions.append(Booking.id != exclude_booking_id)
|
||||
overlap_stmt = select(Booking).where(*conditions)
|
||||
result = await session.scalars(overlap_stmt)
|
||||
overlapping = result.first()
|
||||
if overlapping:
|
||||
logger.warning(f"Attempted to create overlapping booking for room_id {room_id}")
|
||||
logger.warning(
|
||||
f"Attempted to create overlapping booking for room_id {room_id}."
|
||||
f" Overlapping booking id: {overlapping.id if overlapping else None},"
|
||||
f" exclude_booking_id: {exclude_booking_id}"
|
||||
)
|
||||
raise ValueError(
|
||||
"Booking times overlap with an existing booking for this room."
|
||||
)
|
||||
@@ -428,6 +437,11 @@ async def update_booking(
|
||||
await _remove_old_invitees(session, current, new_invitees)
|
||||
|
||||
# Run validations
|
||||
logger.debug(
|
||||
"update_booking: calling _do_validation with"
|
||||
f" exclude_booking_id={booking_id}, new_room_id={new_room_id},"
|
||||
f" new_start={new_start}, new_end={new_end}, new_invitees={new_invitees}"
|
||||
)
|
||||
await _do_validation(
|
||||
session,
|
||||
new_room_id,
|
||||
|
||||
@@ -30,6 +30,8 @@ services:
|
||||
extends:
|
||||
file: compose.yml
|
||||
service: frontend
|
||||
environment:
|
||||
REACT_APP_FRONTEND_LOG_LEVEL: debug
|
||||
volumes:
|
||||
- ./frontend/src:/app/src
|
||||
- ./frontend/public:/app/public
|
||||
@@ -42,6 +44,7 @@ services:
|
||||
- ./frontend/babel.config.js:/app/babel.config.js
|
||||
- ./frontend/jest.config.js:/app/jest.config.js
|
||||
- ./frontend/jest.setup.js:/app/jest.setup.js
|
||||
- ./.env:/app/.env
|
||||
command: ["yarn", "start"]
|
||||
restart: always
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ services:
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
FRONTEND_LOG_LEVEL: WARNING
|
||||
REACT_APP_FRONTEND_LOG_LEVEL: WARNING
|
||||
ports:
|
||||
- "${FRONTEND_PORT}:${FRONTEND_PORT}"
|
||||
depends_on:
|
||||
|
||||
@@ -17,7 +17,7 @@ import { Box, Button } from "@mui/material";
|
||||
// Internal imports
|
||||
|
||||
// Type-only imports
|
||||
import type { BookingConfirmationProps } from "../interfaces";
|
||||
import type { Booking } from "../interfaces";
|
||||
|
||||
/**
|
||||
* BookingConfirmation component
|
||||
@@ -27,15 +27,23 @@ import type { BookingConfirmationProps } from "../interfaces";
|
||||
* @param {BookingConfirmationProps} props - Component props
|
||||
* @returns {JSX.Element} Confirmation UI
|
||||
*/
|
||||
|
||||
interface BookingConfirmationProps {
|
||||
booking: Booking | null;
|
||||
deleted?: boolean;
|
||||
onEdit?: () => void;
|
||||
onBack: () => void;
|
||||
}
|
||||
|
||||
const BookingConfirmation: React.FC<BookingConfirmationProps> = ({
|
||||
booking,
|
||||
deleted = false,
|
||||
onEdit,
|
||||
onBack,
|
||||
}) => {
|
||||
if (!booking) {
|
||||
if (!booking && !deleted) {
|
||||
return null;
|
||||
}
|
||||
const { room, start_time, end_time, title, invitees } = booking;
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
@@ -48,28 +56,42 @@ const BookingConfirmation: React.FC<BookingConfirmationProps> = ({
|
||||
textAlign: "center",
|
||||
}}
|
||||
>
|
||||
<h2>Booking Confirmed</h2>
|
||||
<Box mb={2}>
|
||||
<strong>Room:</strong> {room?.name || "-"}
|
||||
</Box>
|
||||
<Box mb={2}>
|
||||
<strong>Time:</strong> {new Date(start_time).toLocaleString()} –{" "}
|
||||
{new Date(end_time).toLocaleString()}
|
||||
</Box>
|
||||
{title && (
|
||||
<Box mb={2}>
|
||||
<strong>Title:</strong> {title}
|
||||
</Box>
|
||||
)}
|
||||
{invitees && invitees.length > 0 && (
|
||||
<Box mb={2}>
|
||||
<strong>Invitees:</strong> {invitees.join(", ")}
|
||||
</Box>
|
||||
{deleted ? (
|
||||
<>
|
||||
<h2>Booking Deleted</h2>
|
||||
<Box mb={2}>
|
||||
This booking has been deleted and can no longer be edited.
|
||||
</Box>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<h2>Booking Confirmed</h2>
|
||||
<Box mb={2}>
|
||||
<strong>Room:</strong> {booking?.room?.name || "-"}
|
||||
</Box>
|
||||
<Box mb={2}>
|
||||
<strong>Time:</strong>{" "}
|
||||
{booking && new Date(booking.start_time).toLocaleString()} –{" "}
|
||||
{booking && new Date(booking.end_time).toLocaleString()}
|
||||
</Box>
|
||||
{booking?.title && (
|
||||
<Box mb={2}>
|
||||
<strong>Title:</strong> {booking.title}
|
||||
</Box>
|
||||
)}
|
||||
{booking?.invitees && booking.invitees.length > 0 && (
|
||||
<Box mb={2}>
|
||||
<strong>Invitees:</strong> {booking.invitees.join(", ")}
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<Box mt={4} display="flex" justifyContent="center" gap={2}>
|
||||
<Button variant="outlined" onClick={onEdit}>
|
||||
Edit Booking
|
||||
</Button>
|
||||
{!deleted && onEdit && (
|
||||
<Button variant="outlined" onClick={onEdit}>
|
||||
Edit Booking
|
||||
</Button>
|
||||
)}
|
||||
<Button variant="contained" onClick={onBack}>
|
||||
Back to Bookings
|
||||
</Button>
|
||||
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
validateInvitees,
|
||||
validateRoomId,
|
||||
validateStart,
|
||||
validateRoomAvailability,
|
||||
} from "../helpers/validation";
|
||||
import { roundToStrictlyFutureQuarter } from "../utils/date";
|
||||
import { useBookings } from "../context/BookingContext";
|
||||
@@ -46,12 +47,17 @@ import { useUsers } from "../context/UserContext";
|
||||
// Type-only imports
|
||||
import type { Booking, ConferenceRoom } from "../interfaces";
|
||||
// BookingForm component
|
||||
|
||||
interface BookingFormProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
editBooking?: Booking;
|
||||
slotInfo?: { room_id?: string | number; start?: string };
|
||||
onBookingSuccess?: () => void;
|
||||
/**
|
||||
* Called when booking is successful or deleted.
|
||||
* If booking is provided, navigate to confirmation. If not, just close the form.
|
||||
*/
|
||||
onBookingSuccess?: (_booking?: Booking) => void;
|
||||
}
|
||||
|
||||
const BookingForm: React.FC<BookingFormProps> = ({
|
||||
@@ -69,11 +75,11 @@ const BookingForm: React.FC<BookingFormProps> = ({
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
logger.debug("[BookingForm] editBooking updated", editBooking);
|
||||
// logger.debug("[BookingForm] editBooking updated", editBooking);
|
||||
}, [editBooking]);
|
||||
|
||||
React.useEffect(() => {
|
||||
logger.debug("[BookingForm] slotInfo updated", slotInfo);
|
||||
// logger.debug("[BookingForm] slotInfo updated", slotInfo);
|
||||
}, [slotInfo]);
|
||||
// Contexts
|
||||
const bookingsContext = useBookings();
|
||||
@@ -98,20 +104,18 @@ const BookingForm: React.FC<BookingFormProps> = ({
|
||||
// Room selection
|
||||
const initialRoomId = useMemo(() => {
|
||||
if (isEdit && editBooking) {
|
||||
return getEditBookingRoomId(editBooking);
|
||||
return Number(getEditBookingRoomId(editBooking));
|
||||
}
|
||||
if (slotInfo?.room_id) {
|
||||
return String(slotInfo.room_id);
|
||||
return Number(slotInfo.room_id);
|
||||
}
|
||||
if (rooms.length > 0) {
|
||||
return String((rooms[0] as ConferenceRoom).id);
|
||||
return rooms[0].id;
|
||||
}
|
||||
return "";
|
||||
return 0;
|
||||
}, [isEdit, editBooking, slotInfo, rooms]);
|
||||
const [room_id, setRoomId] = useState(initialRoomId);
|
||||
const currentRoom = rooms.find(
|
||||
(r: ConferenceRoom) => String(r.id) === String(room_id)
|
||||
);
|
||||
const [room_id, setRoomId] = useState<number>(initialRoomId);
|
||||
const currentRoom = rooms.find((r: ConferenceRoom) => r.id === room_id);
|
||||
const roomCapacity = currentRoom?.capacity || Infinity;
|
||||
|
||||
// Title
|
||||
@@ -192,7 +196,7 @@ const BookingForm: React.FC<BookingFormProps> = ({
|
||||
function validateFields() {
|
||||
const newErrors: { [key: string]: string } = {};
|
||||
// Room
|
||||
const roomIdError = validateRoomId(room_id);
|
||||
const roomIdError = validateRoomId(String(room_id));
|
||||
if (roomIdError) {
|
||||
newErrors.room_id = roomIdError;
|
||||
}
|
||||
@@ -206,22 +210,24 @@ const BookingForm: React.FC<BookingFormProps> = ({
|
||||
if (endError) {
|
||||
newErrors.end = endError;
|
||||
}
|
||||
// Overlap
|
||||
const roomBookings = bookings.filter(
|
||||
(b: Booking) => String(b.room_id) === String(room_id)
|
||||
// Overlap (use helper)
|
||||
const roomBookings = bookings.filter((b: Booking) => b.room_id === room_id);
|
||||
const overlapErrors = validateRoomAvailability(
|
||||
room_id,
|
||||
start,
|
||||
end,
|
||||
roomBookings,
|
||||
isEdit,
|
||||
isEdit ? editBooking ?? null : null
|
||||
);
|
||||
const overlaps = roomBookings.some((b: Booking) => {
|
||||
if (isEdit && b.id === editBooking?.id) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
new Date(start) < new Date(b.end_time) &&
|
||||
new Date(end) > new Date(b.start_time)
|
||||
);
|
||||
});
|
||||
if (overlaps) {
|
||||
newErrors.start = "Start/end time overlaps another booking.";
|
||||
newErrors.end = "Start/end time overlaps another booking.";
|
||||
if (overlapErrors.roomError) {
|
||||
newErrors.room_id = overlapErrors.roomError;
|
||||
}
|
||||
if (overlapErrors.startError) {
|
||||
newErrors.start = overlapErrors.startError;
|
||||
}
|
||||
if (overlapErrors.endError) {
|
||||
newErrors.end = overlapErrors.endError;
|
||||
}
|
||||
// Invitees
|
||||
const inviteesError = validateInvitees(invitees, roomCapacity);
|
||||
@@ -261,7 +267,7 @@ const BookingForm: React.FC<BookingFormProps> = ({
|
||||
validateFields();
|
||||
}
|
||||
function handleRoomChange(event: SelectChangeEvent) {
|
||||
setRoomId(String(event.target.value));
|
||||
setRoomId(Number(event.target.value));
|
||||
validateFields();
|
||||
}
|
||||
|
||||
@@ -278,7 +284,7 @@ const BookingForm: React.FC<BookingFormProps> = ({
|
||||
let bookingResult;
|
||||
if (!isEdit) {
|
||||
bookingResult = await createBooking({
|
||||
room_id,
|
||||
room_id: Number(room_id),
|
||||
start_time: start,
|
||||
end_time: end,
|
||||
title,
|
||||
@@ -286,17 +292,20 @@ const BookingForm: React.FC<BookingFormProps> = ({
|
||||
});
|
||||
} else if (editBooking) {
|
||||
const updatePayload = {
|
||||
room_id,
|
||||
room_id: Number(room_id),
|
||||
start_time: start,
|
||||
end_time: end,
|
||||
title,
|
||||
invitees,
|
||||
};
|
||||
logger.info("[BookingForm] updateBooking payload:", updatePayload);
|
||||
bookingResult = await updateBooking(editBooking.id, updatePayload);
|
||||
// logger.info("[BookingForm] updateBooking payload:", updatePayload);
|
||||
bookingResult = await updateBooking(
|
||||
Number(editBooking.id),
|
||||
updatePayload
|
||||
);
|
||||
}
|
||||
if (onBookingSuccess && bookingResult) {
|
||||
onBookingSuccess();
|
||||
onBookingSuccess(bookingResult);
|
||||
}
|
||||
onClose();
|
||||
} catch (err: unknown) {
|
||||
@@ -551,7 +560,7 @@ const BookingForm: React.FC<BookingFormProps> = ({
|
||||
try {
|
||||
await deleteBooking(editBooking.id);
|
||||
if (onBookingSuccess) {
|
||||
onBookingSuccess();
|
||||
onBookingSuccess(); // No booking param: just close
|
||||
}
|
||||
onClose();
|
||||
} catch (err: unknown) {
|
||||
|
||||
@@ -9,7 +9,7 @@ export const mockRoom: ConferenceRoom = {
|
||||
};
|
||||
|
||||
export const mockBooking: Booking = {
|
||||
id: "1",
|
||||
id: 1,
|
||||
room_id: 1,
|
||||
start_time: new Date(Date.now() + 24 * 3600000 + 3 * 3600000).toISOString(),
|
||||
end_time: new Date(Date.now() + 24 * 3600000 + 4 * 3600000).toISOString(),
|
||||
|
||||
@@ -22,15 +22,35 @@
|
||||
* @property {string} BUSINESS_END - Business end time (e.g., '18:00')
|
||||
*/
|
||||
export const ENV = {
|
||||
BACKEND_PROTOCOL: process.env.BACKEND_PROTOCOL || "http",
|
||||
BACKEND_HOST: process.env.BACKEND_HOST || "localhost",
|
||||
BACKEND_PORT: process.env.BACKEND_PORT || "8000",
|
||||
FRONTEND_LOG_LEVEL: process.env.FRONTEND_LOG_LEVEL || "info",
|
||||
NODE_ENV: process.env.FRONTEND_ENVIRONMENT || "development",
|
||||
BACKEND_PROTOCOL:
|
||||
process.env.REACT_APP_BACKEND_PROTOCOL ||
|
||||
process.env.BACKEND_PROTOCOL ||
|
||||
"http",
|
||||
BACKEND_HOST:
|
||||
process.env.REACT_APP_BACKEND_HOST ||
|
||||
process.env.BACKEND_HOST ||
|
||||
"localhost",
|
||||
BACKEND_PORT:
|
||||
process.env.REACT_APP_BACKEND_PORT || process.env.BACKEND_PORT || "8000",
|
||||
FRONTEND_LOG_LEVEL:
|
||||
process.env.REACT_APP_FRONTEND_LOG_LEVEL ||
|
||||
process.env.FRONTEND_LOG_LEVEL ||
|
||||
"info",
|
||||
NODE_ENV:
|
||||
process.env.REACT_APP_FRONTEND_ENVIRONMENT ||
|
||||
process.env.FRONTEND_ENVIRONMENT ||
|
||||
"development",
|
||||
DEFAULT_BOOKING_INTERVAL_MINUTES:
|
||||
Number(process.env.DEFAULT_BOOKING_INTERVAL_MINUTES) || 30,
|
||||
BUSINESS_START: process.env.BUSINESS_START || "08:00",
|
||||
BUSINESS_END: process.env.BUSINESS_END || "18:00",
|
||||
Number(
|
||||
process.env.REACT_APP_DEFAULT_BOOKING_INTERVAL_MINUTES ||
|
||||
process.env.DEFAULT_BOOKING_INTERVAL_MINUTES
|
||||
) || 30,
|
||||
BUSINESS_START:
|
||||
process.env.REACT_APP_BUSINESS_START ||
|
||||
process.env.BUSINESS_START ||
|
||||
"08:00",
|
||||
BUSINESS_END:
|
||||
process.env.REACT_APP_BUSINESS_END || process.env.BUSINESS_END || "18:00",
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -114,12 +114,14 @@ export const BookingProvider = ({ children }: { children: ReactNode }) => {
|
||||
invitees?: string[];
|
||||
};
|
||||
const booking: Booking = {
|
||||
id: String(singleData.booking_id),
|
||||
id:
|
||||
typeof singleData.booking_id === "number"
|
||||
? singleData.booking_id
|
||||
: parseInt(singleData.booking_id as string, 10),
|
||||
room_id:
|
||||
typeof singleData.room_id === "number" ||
|
||||
typeof singleData.room_id === "string"
|
||||
typeof singleData.room_id === "number"
|
||||
? singleData.room_id
|
||||
: String(singleData.room_id),
|
||||
: parseInt(singleData.room_id as string, 10),
|
||||
start_time: String(singleData.start_time),
|
||||
end_time: String(singleData.end_time),
|
||||
title:
|
||||
|
||||
@@ -104,7 +104,7 @@ export function validateInvitees(
|
||||
* @returns Error object with roomError, startError, endError
|
||||
*/
|
||||
export function validateRoomAvailability(
|
||||
room_id: string,
|
||||
room_id: number,
|
||||
start: string,
|
||||
end: string,
|
||||
roomBookings: Booking[],
|
||||
@@ -113,7 +113,7 @@ export function validateRoomAvailability(
|
||||
): { roomError?: string; startError?: string; endError?: string } {
|
||||
// Filter out the current booking if editing
|
||||
const filteredBookings =
|
||||
isEdit && editBooking?.id
|
||||
isEdit && editBooking?.id !== undefined
|
||||
? roomBookings.filter((b: Booking) => b.id !== editBooking.id)
|
||||
: roomBookings;
|
||||
let startConflict = false;
|
||||
|
||||
@@ -94,8 +94,8 @@ export interface User {
|
||||
}
|
||||
|
||||
export interface Booking {
|
||||
id: string;
|
||||
room_id?: number | string;
|
||||
id: number;
|
||||
room_id?: number;
|
||||
room?: { name?: string };
|
||||
start_time: string;
|
||||
end_time: string;
|
||||
|
||||
@@ -40,7 +40,7 @@ import { useRooms } from "../context/RoomContext";
|
||||
import { useBookings } from "../context/BookingContext";
|
||||
|
||||
// Utility/helper imports
|
||||
import { logger } from "../utils/logger";
|
||||
// import { logger } from "../utils/logger";
|
||||
import { getRoomClass } from "../helpers/calendar";
|
||||
import { getInviteeName } from "../helpers/bookingList";
|
||||
|
||||
@@ -100,9 +100,7 @@ const BookingPage: React.FC = () => {
|
||||
return [];
|
||||
}
|
||||
const mappedEvents = bookings.map((booking: Booking) => {
|
||||
const room = rooms.find(
|
||||
(r: ConferenceRoom) => String(r.id) === String(booking.room_id)
|
||||
);
|
||||
const room = rooms.find((r: ConferenceRoom) => r.id === booking.room_id);
|
||||
let roomName = room?.name;
|
||||
if (!roomName || roomName.trim() === "") {
|
||||
if (
|
||||
@@ -110,11 +108,6 @@ const BookingPage: React.FC = () => {
|
||||
booking.room?.name.trim() !== ""
|
||||
) {
|
||||
roomName = booking.room?.name;
|
||||
} else if (
|
||||
typeof booking.room_id === "string" &&
|
||||
booking.room_id.trim() !== ""
|
||||
) {
|
||||
roomName = booking.room_id as string;
|
||||
} else if (typeof booking.room_id === "number") {
|
||||
roomName = String(booking.room_id);
|
||||
} else {
|
||||
@@ -127,11 +120,7 @@ const BookingPage: React.FC = () => {
|
||||
: [];
|
||||
const start = new Date(booking.start_time);
|
||||
const end = new Date(booking.end_time);
|
||||
const roomClass = getRoomClass(
|
||||
typeof booking.room_id === "number"
|
||||
? booking.room_id
|
||||
: parseInt(booking.room_id as string, 10)
|
||||
);
|
||||
const roomClass = getRoomClass(booking.room_id ?? 0);
|
||||
return {
|
||||
id: booking.id,
|
||||
title: booking.title ?? "",
|
||||
@@ -160,7 +149,7 @@ const BookingPage: React.FC = () => {
|
||||
|
||||
/** Set default room when rooms load */
|
||||
useEffect(() => {
|
||||
logger.debug("[BookingPage] Rooms updated", rooms);
|
||||
// logger.debug("[BookingPage] Rooms updated", rooms);
|
||||
if (rooms.length > 0 && !selectedRoomId) {
|
||||
setSelectedRoomId(rooms[0].id);
|
||||
}
|
||||
@@ -289,18 +278,18 @@ const BookingPage: React.FC = () => {
|
||||
start = new Date(slotInfo.start);
|
||||
end = new Date(start.getTime() + 30 * 60000);
|
||||
}
|
||||
logger.info("[BookingPage] Slot selected", {
|
||||
start,
|
||||
end,
|
||||
room_id: selectedRoomId,
|
||||
});
|
||||
// logger.info("[BookingPage] Slot selected", {
|
||||
// start,
|
||||
// end,
|
||||
// room_id: selectedRoomId,
|
||||
// });
|
||||
setFormSlot({ start, end, room_id: selectedRoomId });
|
||||
setEditBooking(null);
|
||||
setFormOpen(true);
|
||||
};
|
||||
|
||||
const handleSelectEvent = (event: BookingEvent) => {
|
||||
logger.info("[BookingPage] Event selected", event);
|
||||
// logger.info("[BookingPage] Event selected", event);
|
||||
const originalBooking = event.originalBooking;
|
||||
const eventIdStr = String(event.id);
|
||||
if (originalBooking && originalBooking.id) {
|
||||
@@ -323,8 +312,14 @@ const BookingPage: React.FC = () => {
|
||||
});
|
||||
} else {
|
||||
setEditBooking({
|
||||
id: String(event.id),
|
||||
room_id: event.room_id,
|
||||
id:
|
||||
typeof event.id === "number"
|
||||
? event.id
|
||||
: parseInt(event.id as string, 10),
|
||||
room_id:
|
||||
typeof event.room_id === "number"
|
||||
? event.room_id
|
||||
: parseInt(event.room_id as string, 10),
|
||||
room: event.room_name ? { name: event.room_name } : undefined,
|
||||
start_time: event.start ? event.start.toString() : "",
|
||||
end_time: event.end ? event.end.toString() : "",
|
||||
@@ -343,7 +338,7 @@ const BookingPage: React.FC = () => {
|
||||
|
||||
const navigate = useNavigate();
|
||||
const handleFormClose = (refresh = false, booking?: Booking) => {
|
||||
logger.info("[BookingPage] Booking form closed", { refresh, booking });
|
||||
// logger.info("[BookingPage] Booking form closed", { refresh, booking });
|
||||
setFormOpen(false);
|
||||
setFormSlot(null);
|
||||
setEditBooking(null);
|
||||
@@ -372,7 +367,7 @@ const BookingPage: React.FC = () => {
|
||||
variant="outlined"
|
||||
sx={{ ml: 2 }}
|
||||
onClick={() => {
|
||||
logger.info("[BookingPage] Opening room modal");
|
||||
// logger.info("[BookingPage] Opening room modal");
|
||||
setRoomModalOpen(true);
|
||||
}}
|
||||
disabled={!selectedRoomId}
|
||||
@@ -383,7 +378,7 @@ const BookingPage: React.FC = () => {
|
||||
<RoomDetailsModal
|
||||
open={roomModalOpen}
|
||||
onClose={() => {
|
||||
logger.info("[BookingPage] Closing room modal");
|
||||
// logger.info("[BookingPage] Closing room modal");
|
||||
setRoomModalOpen(false);
|
||||
}}
|
||||
room={
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
*/
|
||||
|
||||
// External imports
|
||||
import React, { useState } from "react";
|
||||
// External imports
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
|
||||
// MUI imports
|
||||
@@ -19,6 +20,7 @@ import { useQuery } from "@tanstack/react-query";
|
||||
// Internal component imports
|
||||
import BookingConfirmation from "../components/BookingConfirmation";
|
||||
import BookingForm from "../components/BookingForm";
|
||||
// import { logger } from "../utils/logger";
|
||||
|
||||
// API imports
|
||||
import { connectRoomsAvailabilityStream } from "../apis/sse";
|
||||
@@ -51,17 +53,46 @@ const ConfirmationPage: React.FC = () => {
|
||||
const [booking, setBooking] = useState<Booking | null>(
|
||||
location.state?.booking || null
|
||||
);
|
||||
// Track deleted state
|
||||
const [deleted, setDeleted] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
// If no booking, redirect to booking page
|
||||
if (!booking) {
|
||||
navigate("/booking");
|
||||
return;
|
||||
}
|
||||
|
||||
const bookingId = location.state?.booking?.id || booking?.id;
|
||||
if (!bookingId) {
|
||||
return;
|
||||
}
|
||||
// Always get latest booking from context
|
||||
const latestBooking = bookings.find((b: Booking) => b.id === bookingId);
|
||||
if (latestBooking) {
|
||||
setBooking(latestBooking);
|
||||
}
|
||||
|
||||
const es = connectRoomsAvailabilityStream({
|
||||
onMessage: (data) => {
|
||||
if (Array.isArray(data.bookings)) {
|
||||
const updated = data.bookings.find(
|
||||
(b: Booking) => b.id === bookingId
|
||||
);
|
||||
if (updated) {
|
||||
setBooking(updated);
|
||||
}
|
||||
}
|
||||
},
|
||||
onError() {
|
||||
// Optionally handle SSE errors
|
||||
},
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
// [ConfirmationPage] Mounted log removed
|
||||
return () => {
|
||||
// [ConfirmationPage] Unmounted log removed
|
||||
es.close();
|
||||
};
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
// [ConfirmationPage] booking updated log removed
|
||||
}, [booking]);
|
||||
}, [bookings, location.state, booking?.id, navigate]);
|
||||
/**
|
||||
* Subscribe to SSE for live booking status updates.
|
||||
*/
|
||||
@@ -82,9 +113,7 @@ const ConfirmationPage: React.FC = () => {
|
||||
(b: Booking) => b.id === bookingId
|
||||
);
|
||||
if (updated) {
|
||||
setBooking((prev: Booking | null) =>
|
||||
prev ? { ...prev, ...updated } : updated
|
||||
);
|
||||
setBooking(updated);
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -115,8 +144,7 @@ const ConfirmationPage: React.FC = () => {
|
||||
});
|
||||
|
||||
if (!booking) {
|
||||
// If no booking, redirect to booking page
|
||||
navigate("/booking");
|
||||
// Already handled by useEffect, but return null to avoid rendering
|
||||
return null;
|
||||
}
|
||||
if (roomsLoading) {
|
||||
@@ -143,7 +171,7 @@ const ConfirmationPage: React.FC = () => {
|
||||
minHeight: "100vh",
|
||||
}}
|
||||
>
|
||||
{editing ? (
|
||||
{editing && !deleted ? (
|
||||
<BookingForm
|
||||
open={true}
|
||||
onClose={handleFormClose}
|
||||
@@ -156,14 +184,27 @@ const ConfirmationPage: React.FC = () => {
|
||||
start: booking.start_time,
|
||||
end: booking.end_time,
|
||||
}}
|
||||
onBookingSuccess={() => {
|
||||
setEditing(false);
|
||||
// Optionally update booking state
|
||||
onBookingSuccess={(resultBooking?: Booking) => {
|
||||
if (!resultBooking) {
|
||||
// Deleted
|
||||
// logger.info("[ConfirmationPage] Booking deleted", booking);
|
||||
setDeleted(true);
|
||||
setEditing(false);
|
||||
setBooking(null);
|
||||
} else {
|
||||
// logger.info(
|
||||
// "[ConfirmationPage] Booking success (created or edited)",
|
||||
// resultBooking
|
||||
// );
|
||||
setEditing(false);
|
||||
setBooking(resultBooking);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<BookingConfirmation
|
||||
booking={booking}
|
||||
deleted={deleted}
|
||||
onEdit={handleEdit}
|
||||
onBack={handleBack}
|
||||
/>
|
||||
|
||||
@@ -11,7 +11,10 @@ import log, { LogLevelDesc } from "loglevel";
|
||||
* Gets the log level from environment and sets it for loglevel.
|
||||
* @type {LogLevelDesc}
|
||||
*/
|
||||
const level: LogLevelDesc = (process.env.FRONTEND_LOG_LEVEL ||
|
||||
|
||||
// Use REACT_APP_ prefix for Create React App compatibility
|
||||
const level: LogLevelDesc = (process.env.REACT_APP_FRONTEND_LOG_LEVEL ||
|
||||
process.env.FRONTEND_LOG_LEVEL ||
|
||||
"info") as LogLevelDesc;
|
||||
log.setLevel(level);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user