import flet as ft import asyncio import websockets import json import requests import threading from datetime import datetime def get_initials(user_name: str): if user_name: return user_name[0].capitalize() else: return "E" def get_avatar_color(user_name: str): colors_lookup = [ ft.colors.AMBER, ft.colors.BLUE, ft.colors.BROWN, ft.colors.CYAN, ft.colors.GREEN, ft.colors.INDIGO, ft.colors.LIME, ft.colors.ORANGE, ft.colors.PINK, ft.colors.PURPLE, ft.colors.RED, ft.colors.TEAL, ] return colors_lookup[hash(user_name) % len(colors_lookup)] def join_page(page: ft.Page): page.clean() page.theme_mode = ft.ThemeMode.LIGHT # Set the theme to light page.title = "Join Rooms" page.vertical_alignment = ft.MainAxisAlignment.START # Align all content to the top page.scroll = "adaptive" page.padding = 20 # Check if the user is logged in session = page.session.get("access_token") if not session: page.go("/login") # Redirect to login if no session def test(e): print(e) page.go("/test") print("test") # Search field search_field = ft.TextField(label="Lehrkraft / Raumnummer ", expand=True) # Dropdown for time filter time_filter = ft.Dropdown( label="Stunde wählen", options=[ ft.dropdown.Option("8:00", "8:00"), ft.dropdown.Option("9:20", "9:20"), ft.dropdown.Option("10:40", "10:40"), ft.dropdown.Option("11:50", "11:50"), ft.dropdown.Option("12:50", "12:50"), ft.dropdown.Option("13:55", "13:55"), ft.dropdown.Option("15:00", "15:00"), ], width=170, # Shorter width for time filter ) # Cross icon button to clear time filter clear_time_filter = ft.IconButton( icon=ft.icons.CLOSE, on_click=lambda e: clear_filter("time"), tooltip="Clear Time Filter", ) # Container for the room list room_container = ft.Column(scroll=ft.ScrollMode.AUTO, expand=True) def ausloggen(e = None): page.go("/login") print(page.session.set("access_token", None)) # Function to clear filters def clear_filter(filter_type): if filter_type == "time": time_filter.value = None if filter_type == "lesson": lesson_filter.value = None update_room_list(json.loads(page.session.get("rooms_data", "[]"))) def update_room_list(rooms): # Ensure rooms is a list if not isinstance(rooms, list): rooms = [] # Apply search filter search_query = search_field.value.lower() filtered_rooms = [ room for room in rooms if (search_query in str(room.get("room_name", "")).lower() # Search by room_name or search_query in room.get("info", "").lower() # Search by room_info or search_query in room.get("location", "").lower() # Search by location or search_query in room.get("subjects", "").lower() # Search by subjects or search_query in room.get("first_name", "").lower() # Search by teacher_first_name or search_query in room.get("last_name", "").lower()) # Search by teacher_last_name ] if time_filter.value: # Only apply the filter if a time is selected filtered_rooms = [ room for room in filtered_rooms if room.get("lesson_time", "") == time_filter.value ] if lesson_filter.value: # Only apply the filter if a time is selected filtered_rooms = [ room for room in filtered_rooms if lesson_filter.value.lower() in room.get("subjects", "").lower().split(", ") ] if show_full_rooms.value == True: filtered_rooms = [ room for room in filtered_rooms if room.get("is_open", "") == True ] # Apply date filter (only if a date is selected) room_container.controls.clear() def page_dialog_click(e, unique_id): dialog_join = ft.AlertDialog( modal=True, title=ft.Text("Bitte bestätige"), content=ft.Text("Möchtest du diesem Raum wirklich beitreten?"), actions=[ ft.TextButton("Ja", on_click=lambda e: (page.close(dialog_join), room_clicked(e, unique_id))), ft.TextButton("Nein", on_click=lambda e: page.close(dialog_join)), ], actions_alignment=ft.MainAxisAlignment.END, ) page.dialog = dialog_join dialog_join.open = True page.update() def room_clicked(e, unique_id): try: url = f"http://awesom-o.org:8000/student/join_room/?unique_id={unique_id}" headers = {"accept": "application/json", "Content-Type": "application/json"} data = {"session_id": page.session.get("access_token")} response = requests.post(url, json=data, headers=headers) if response.status_code == 200: try: page.snack_bar = ft.SnackBar(ft.Text(f"Raum erfolgreich beigetreten: {unique_id}")) page.snack_bar.open = True page.update() page.go("/") except: page.snack_bar = ft.SnackBar(ft.Text(f"Ein fehler ist aufgetreten, bitte melde dich bei der Administration")) page.snack_bar.open = True page.update() else: dialog_join = ft.AlertDialog( modal=True, title=ft.Text("Fehler"), content=ft.Text(response.json().get("detail")), actions=[ ft.TextButton("Schließen", on_click=lambda e: page.close(dialog_join)), ], actions_alignment=ft.MainAxisAlignment.END, ) page.dialog = dialog_join dialog_join.open = True page.update() except requests.exceptions.RequestException as e: dialog_join = ft.AlertDialog( modal=True, title=ft.Text("Fehler"), content=ft.Text("Ein Fehler ist aufgetreten"), actions=[ ft.TextButton("Schließen", on_click=lambda e: page.close(dialog_join)), ], actions_alignment=ft.MainAxisAlignment.END, ) page.dialog = dialog_join dialog_join.open = True page.update() for room in filtered_rooms: room_name = room.get("room_name", "Unknown Room") room_info = room.get("info", "") location = room.get("location", "") first_name = room.get("first_name", "") last_name = room.get("last_name", "") subjects = room.get("subjects", "") lesson_time = room.get("lesson_time", "") lesson_date = room.get("lesson_date", "") max_students = room.get("max_students", 0) current_students = room.get("current_students", 0) unique_id = room.get("unique_id", "") is_open = room.get("is_open", True) joined = room.get("joined", False) if current_students == max_students: BACKROUND_COLOR = "#f10d0c" TEXT_COLOR = ft.colors.WHITE user_interface_button_text = ft.Text("Raum bereits voll") else: BACKROUND_COLOR = "#729fcf" TEXT_COLOR = ft.colors.BLACK if joined == True: user_interface_button_text = ft.Text("Raum bereits gebucht") else: user_interface_button_text = ft.TextButton( "Jetzt Platz reservieren", on_click=lambda e, unique_id=unique_id: page_dialog_click(e, unique_id), style=ft.ButtonStyle( bgcolor=ft.colors.WHITE, color=ft.colors.BLACK, shape=ft.RoundedRectangleBorder(radius=5), padding=20, )) room_card = ft.Card( content=ft.Container( content=ft.Row( [ # Room details ft.Column( [ ft.Text(f"Heute, {lesson_time} Uhr", size=16, color=TEXT_COLOR), ft.Text(f"Lehrkraft: {first_name} {last_name} - {subjects}", size=16, color=TEXT_COLOR), ft.Text(f"Raum {room_name} {room_info} | {location}", size=16, color=TEXT_COLOR), ft.Text(f"Belegte Plätze: {current_students}/{max_students}", size=16, color=TEXT_COLOR), ], expand=True, alignment=ft.MainAxisAlignment.CENTER, ), # IconButton on the right user_interface_button_text, ], alignment=ft.MainAxisAlignment.SPACE_BETWEEN, ), padding=20, # Add padding inside the card ), color=BACKROUND_COLOR, margin=10, ) room_container.controls.append(room_card) if not filtered_rooms: room_container.controls.append(ft.Text("Keine offenen Räume verfügbar für Heute.", size=18)) page.update() # WebSocket listener for real-time updates async def listen_for_updates(): uri = "ws://localhost:8000/ws/open_rooms" headers = {"session-id": page.session.get("access_token")} async with websockets.connect(uri, extra_headers=headers) as websocket: print("Connected to WebSocket server") try: while True: # Receive data from the server data = await websocket.recv() rooms = json.loads(data) # Ensure rooms is a list if isinstance(rooms, dict): rooms = rooms.get("rooms", []) elif not isinstance(rooms, list): rooms = [] # Store rooms data in session for filtering page.session.set("rooms_data", json.dumps(rooms)) # Extract unique dates and sort them from nearest to longest unique_dates = list(set(room.get("lesson_date", "") for room in rooms)) unique_dates.sort(key=lambda x: datetime.strptime(x, "%Y-%m-%d")) # Sort dates # Update the room list update_room_list(rooms) except websockets.ConnectionClosed: print("WebSocket connection closed") # Start the WebSocket listener in a separate thread def start_websocket_listener(): loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) loop.run_until_complete(listen_for_updates()) threading.Thread(target=start_websocket_listener, daemon=True).start() top_bar = ft.Row( [ search_field, ft.Row([time_filter, clear_time_filter], spacing=5), ], alignment=ft.MainAxisAlignment.START, height=50, ) lesson_filter = ft.Dropdown( label="Fach wählen", options=[ ft.dropdown.Option("De", "De"), ft.dropdown.Option("Ma", "Ma"), ft.dropdown.Option("NW", "NW"), ft.dropdown.Option("En", "En"), ft.dropdown.Option("Gs", "Gs"), ft.dropdown.Option("Re", "Re"), ft.dropdown.Option("WN", "WN"), ft.dropdown.Option("Fr", "Fr"), ft.dropdown.Option("Sp", "Sp"), ft.dropdown.Option("Sn", "Sn"), ], width=170, ) clear_lesson_filter = ft.IconButton( icon=ft.icons.CLOSE, on_click=lambda e: clear_filter("lesson"), tooltip="Clear Lesson Filter", ) show_full_rooms = ft.Checkbox(label="Volle Räume ausblenden ", value=False, label_style=ft.TextStyle(size=20)) middle_bar = ft.Row( [ show_full_rooms, ft.Row( [ ft.Column( [ ft.Text( "Nur Räume anzeigen, in denen\nfolgendes Fach angeboten wird:", size=20, ), ], ), lesson_filter, clear_lesson_filter, ], spacing=5, alignment=ft.MainAxisAlignment.END, ), ], alignment=ft.MainAxisAlignment.SPACE_BETWEEN, height=50, ) go_home_button = ft.Container( content=ft.Text("Home", size=24, color=ft.colors.BLUE_900), on_click=lambda e: page.go("/"), alignment=ft.alignment.center, width=200, height=60, bgcolor='transparent', ink=True, ) navigation_bar = ft.Row( [go_home_button, ft.Text("Räume suchen", size=23, weight=ft.FontWeight.BOLD, width=170, no_wrap=True)], ) page.add( ft.Column( [ ft.Row( [ ft.Text( "Daltonraum-Buchungssystem der IGS Garbsen", size=30), ft.Column([ ft.PopupMenuButton( items=[ ft.PopupMenuItem(content=ft.Text(f"Eingeloggt als: {page.session.get("username")}", weight=ft.FontWeight.BOLD)), ft.PopupMenuItem(text="Profil anzeigen", on_click=test), ft.PopupMenuItem(text="Ausloggen", on_click=ausloggen), ], content=ft.CircleAvatar( content=ft.Text(get_initials( page.session.get("username"))), color=ft.colors.WHITE, bgcolor=get_avatar_color( page.session.get("username")), ), menu_position=ft.PopupMenuPosition.UNDER, tooltip="", ) ], # Align column content to the end (optional) alignment=ft.MainAxisAlignment.END, ), ], alignment=ft.MainAxisAlignment.SPACE_BETWEEN, # Space out the two elements vertical_alignment=ft.CrossAxisAlignment.CENTER, # Align items vertically ), navigation_bar, top_bar, middle_bar, ft.Column( [ room_container, ], alignment=ft.MainAxisAlignment.CENTER, expand=True, ), ], expand=True, alignment=ft.MainAxisAlignment.CENTER, ) )