diff --git a/frontend/src/components/pages/BookingManagement/BookingManagement.tsx b/frontend/src/components/pages/BookingManagement/BookingManagement.tsx index e37d6572ebda854735b94a9d0b696808bd7d0bd5..48dfea9854ece4e25d1dd4eef7d2120db266a87a 100644 --- a/frontend/src/components/pages/BookingManagement/BookingManagement.tsx +++ b/frontend/src/components/pages/BookingManagement/BookingManagement.tsx @@ -1,17 +1,153 @@ -import { useLocation } from 'react-router-dom'; +import { useEffect, useState } from 'react'; +import { useLocation, useNavigate } from 'react-router-dom'; import styles from './BookingManagement.module.scss'; +// Define interfaces for type safety +interface Event { + id: string; + name: string; + start: Date; + end: Date; + venue: string; + capacity: number; +} + +interface BookingCreate { + user_id: string; + event_id: string; + booking_date: Date; +} + +interface BookingResponse extends BookingCreate { + id: string; +} + function BookingManagement() { - const state = useLocation(); - /* Update view based on what booking form is needed - if (state.action === "create") {} - else if (state.action === "update") {} - */ - return ( - <> - <p>Booking Management</p> - </> - ) + const location = useLocation(); + const navigate = useNavigate(); + const [event, setEvent] = useState<Event | null>(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState<string | null>(null); + const [success, setSuccess] = useState<string | null>(null); + + // Get the current user ID - this would typically come from authentication + const userId = "current-user-id"; // Replace with actual user ID from auth + + useEffect(() => { + // If there's an event in the location state, use it + if (location.state && location.state.event) { + setEvent({ + ...location.state.event, + start: new Date(location.state.event.start), + end: new Date(location.state.event.end) + }); + } + }, [location]); + + const handleCreateBooking = async () => { + if (!event) return; + + try { + setLoading(true); + setError(null); + + const bookingData: BookingCreate = { + user_id: userId, + event_id: event.id, + booking_date: new Date() + }; + + const response = await fetch('http://localhost:8000/events', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(bookingData), + }); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.detail || 'Failed to create booking'); + } + + const data = await response.json(); + setSuccess('Booking created successfully!'); + + // Redirect to a confirmation page or back to events + setTimeout(() => { + navigate('/my-bookings'); + }, 2000); + + } catch (err) { + if (err instanceof Error) { + setError(err.message); + } else { + setError('An unknown error occurred'); + } + console.error('Error creating booking:', err); + } finally { + setLoading(false); + } + }; + + // Handle other actions (update, cancel) as needed + const handleUpdateBooking = () => { + // Implementation for updating a booking + console.log("Update booking functionality to be implemented"); + }; + + return ( + <div className={styles.bookingManagement}> + <h2>Booking Management</h2> + + {loading && <p className={styles.loading}>Processing your request...</p>} + {error && <p className={styles.error}>{error}</p>} + {success && <p className={styles.success}>{success}</p>} + + {event && ( + <div className={styles.eventDetails}> + <h3>Event Details</h3> + <p><strong>Name:</strong> {event.name}</p> + <p><strong>Start:</strong> {event.start.toLocaleString()}</p> + <p><strong>End:</strong> {event.end.toLocaleString()}</p> + <p><strong>Venue:</strong> {event.venue}</p> + <p><strong>Capacity:</strong> {event.capacity}</p> + </div> + )} + + {/* Update view based on what booking form is needed */} + {location.state && location.state.action === "create" && ( + <div className={styles.bookingActions}> + <button + onClick={handleCreateBooking} + disabled={loading || !event} + className={styles.actionButton} + > + Confirm Booking + </button> + </div> + )} + + {location.state && location.state.action === "update" && ( + <div className={styles.bookingActions}> + <button + onClick={handleUpdateBooking} + disabled={loading || !event} + className={styles.actionButton} + > + Update Booking + </button> + </div> + )} + + <button + onClick={() => navigate('/events')} + className={styles.backButton} + > + Back to Events + </button> + </div> + ); } export default BookingManagement; \ No newline at end of file diff --git a/microservice-events/crud.py b/microservice-events/crud.py index b725aad1424e8a13aeb05fb1ec5dfd51f2b7b97e..1264beb1967bbcb25e8f30258eb020d1790aca0f 100644 --- a/microservice-events/crud.py +++ b/microservice-events/crud.py @@ -7,25 +7,66 @@ from typing import List, Optional, Dict, Any import models, schemas # Event CRUD operations -def get_events(db: Session, upcoming: bool = False) -> List[models.Event]: - """Get all events or only upcoming events""" +def get_events(db: Session, upcoming: bool = False) -> List[dict]: + """Get all events or only upcoming events with spaces left calculation""" query = db.query(models.Event) if upcoming: now = datetime.datetime.now() query = query.filter(models.Event.end > now) - return query.all() + + events = query.all() + result = [] + + for event in events: + # Count bookings for this event + booking_count = db.query(models.Booking).filter( + models.Booking.event_id == event.id + ).count() + + # Calculate spaces left + spaces_left = max(0, event.capacity - booking_count) + + # Convert to dict and add spaces_left + event_dict = {c.name: getattr(event, c.name) for c in event.__table__.columns} + event_dict['spaces_left'] = spaces_left + + result.append(event_dict) + + return result -def get_event(db: Session, event_id: str) -> Optional[models.Event]: - """Get a specific event by ID""" - return db.query(models.Event).filter(models.Event.id == event_id).first() +def get_event(db: Session, event_id: str) -> Optional[dict]: + """Get a specific event by ID with spaces left calculation""" + event = db.query(models.Event).filter(models.Event.id == event_id).first() + + if not event: + return None + + # Count bookings for this event + booking_count = db.query(models.Booking).filter( + models.Booking.event_id == event_id + ).count() + + # Calculate spaces left + spaces_left = max(0, event.capacity - booking_count) + + # Convert to dict and add spaces_left + event_dict = {c.name: getattr(event, c.name) for c in event.__table__.columns} + event_dict['spaces_left'] = spaces_left + + return event_dict -def create_event(db: Session, event: schemas.EventCreate) -> models.Event: +def create_event(db: Session, event: schemas.EventCreate) -> dict: """Create a new event""" db_event = models.Event(**event.model_dump()) db.add(db_event) db.commit() db.refresh(db_event) - return db_event + + # New event has no bookings, so spaces_left equals capacity + event_dict = {c.name: getattr(db_event, c.name) for c in db_event.__table__.columns} + event_dict['spaces_left'] = db_event.capacity + + return event_dict def delete_event(db: Session, event_id: str) -> None: """Delete an event""" @@ -125,11 +166,14 @@ def get_booking_stats(db: Session) -> Dict[str, Any]: for event in events: bookings_count = bookings_by_event.get(event.id, 0) capacity = event.capacity + spaces_left = max(0, capacity - bookings_count) + event_capacity[event.id] = { "name": event.name, "bookings": bookings_count, "capacity": capacity, - "fill_percentage": round((bookings_count / capacity) * 100, 2) if capacity > 0 else 0 + "fill_percentage": round((bookings_count / capacity) * 100, 2) if capacity > 0 else 0, + "spaces_left": spaces_left # Added spaces_left to stats } return { diff --git a/microservice-events/main.py b/microservice-events/main.py index d1cdbfafe2874194da1b3fa74b40a42ff0635893..db2dfdf1825062e0be98abfaf20ad0d668b2fefd 100644 --- a/microservice-events/main.py +++ b/microservice-events/main.py @@ -90,15 +90,14 @@ async def create_booking(booking: schemas.BookingCreate, db: Session = Depends(g raise HTTPException(status_code=404, detail="Event not found") # Check if event has already passed - if event.end < datetime.datetime.now(): + if event["end"] < datetime.datetime.now(): # Modified to access dict raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Cannot book an event that has already ended" ) - # Check if event is at capacity - event_bookings = crud.get_bookings(db, event_id=booking.event_id) - if len(event_bookings) >= event.capacity: + # Check if event has spaces left + if event["spaces_left"] <= 0: # Using spaces_left instead of counting bookings raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Event is at full capacity" diff --git a/microservice-events/schemas.py b/microservice-events/schemas.py index aed5fa5fbc597d264eded9263d8828a85862e611..1f80ffe556890518c28e5bf032cea9ad0432d761 100644 --- a/microservice-events/schemas.py +++ b/microservice-events/schemas.py @@ -16,7 +16,7 @@ class EventCreate(EventBase): class EventResponse(EventBase): id: str - + spaces_left: int = 0 class Config: from_attributes = True