from nicegui import ui, app from data import database, data_api from gui import discord_login, gui_style from match_calculations import calc_match from gui.info_text import info_system def setup_routes(admin_discord_id): @ui.page('/', dark=True) def home_page(): gui_style.apply_design() # --- SICHERHEITS-CHECK (Hassan, DER TÜRSTEHER) --- if app.storage.user.get('authenticated', False): db_id = app.storage.user.get('db_id') discord_id = app.storage.user.get('discord_id') # Fehlt die Discord-ID (altes Cookie) ODER sagt die Datenbank, dass da was nicht stimmt? if not discord_id or not data_api.validate_user_session(db_id, discord_id): # Ausweis ungültig! Wir vernichten das Cookie sofort. app.storage.user.clear() ui.notify("Deine Sitzung ist ungültig oder abgelaufen. Bitte neu einloggen!", color="negative") ui.navigate.reload() return # ----------------------------------------- # --------------------------- # --- NAVIGATIONSLEISTE (HEADER) --- # --------------------------- with ui.header().classes('items-center justify-between bg-zinc-900 shadow-lg').props("reveal reveal-offset=1"): # --- LINKE SEITE --- # Vereinslogo und den Titel in einer eigenen Reihe (Reihe 1) with ui.row().classes('items-center'): ui.image("gui/pictures/wsdg.png").classes('w-15 h-15 rounded-full') ui.label('Diceghost Liga').classes('text-2xl font-bold text-normaltext') # --- MITTE --- if app.storage.user.get('authenticated', False): discord_id = app.storage.user.get("discord_id") if discord_id == admin_discord_id: ui.button(icon="hardware", on_click=lambda: ui.navigate.to('/admin')).props("round") # --- RECHTE SEITE --- if app.storage.user.get('authenticated', False): with ui.row().classes('items-center gap-4'): ui.image(app.storage.user.get('discord_avatar_url')).classes('w-15 h-15 rounded-full') discord_name = app.storage.user.get('discord_name') display_name = app.storage.user.get('display_name') player_id = app.storage.user.get('db_id') # 1. kleine Funktion, die zwischen Text und Eingabe hin- und herschaltet def toggle_edit_mode(): display_row.visible = not display_row.visible edit_row.visible = not edit_row.visible edit_button.visible = not edit_button.visible # --- ANSICHT 1: Der normale Text mit Edit-Button --- with ui.column().classes('items-center gap-0') as display_row: with ui.column(): ui.label(display_name).classes('text-xl font-bold text-normaltext') with ui.row().classes("items-center justify-between"): ui.label("'aka'").classes('text-sm text-italic text-infotext') ui.label(discord_name).classes('text-m text-bold text-infotext') edit_button = ui.button(icon='edit', color='accent', on_click=toggle_edit_mode).props('round dense') # --- ANSICHT 2: Das Eingabefeld (startet unsichtbar!) --- with ui.row().classes('items-center gap-5') as edit_row: edit_row.visible = False # Am Anfang verstecken def save_new_name(): new_name = name_input.value # Nur speichern, wenn ein Name drinsteht und er anders ist als vorher if new_name and new_name != display_name: print("save") data_api.change_display_name(player_id, new_name) # Deine DB Funktion app.storage.user['display_name'] = new_name ui.notify('Name gespeichert!', color='positive') ui.navigate.reload() else: # Wenn nichts geändert wurde, einfach wieder einklappen toggle_edit_mode() def generate_random_silly_name(): silly_name = data_api.generate_silly_name() name_input.value=silly_name name_input = ui.input('Neuer Name', value=display_name).on('keydown.enter', save_new_name) ui.button(icon='save', color='positive', on_click=save_new_name).props('round dense') ui.button(icon='casino', color="accent", on_click=generate_random_silly_name).props('round dense') ui.button(icon='close', color='negative', on_click=toggle_edit_mode).props('round dense') avatar = app.storage.user.get('avatar_url') if avatar: ui.image(avatar).classes('w-5 h-5 rounded-full border-2 border-red-500') def logout(): app.storage.user.clear() ui.navigate.to('/') ui.button(icon="logout", on_click=logout).props('round dense size=lg') else: auth_url = discord_login.get_auth_url() ui.button('Login with Discord', on_click=lambda: ui.navigate.to(auth_url)) # --------------------------- # --- Match Bestätigung --- # --------------------------- # Bestätigungs für offene Spiele --- Der "Marian Balken !!!1!11!" if app.storage.user.get('authenticated', False): unconfirmed_matches = data_api.get_unconfirmed_matches(player_id) if len(unconfirmed_matches) > 0: # Eine auffällige, rote Karte über die volle Breite with ui.card().classes('w-full bg-red-900/80 border-2 border-red-500 mb-6'): ui.label(f"Aktion erforderlich: Du hast {len(unconfirmed_matches)} offen(e) Spiel(e)!").classes('text-2xl font-bold text-normaltext mb-2') for match in unconfirmed_matches: # Button Funktionen. Akzeptieren oder Rejecten. Der Reject Button löscht das Match aus der DB. def reject_match(m_id): data_api.delete_match(m_id, player_id) ui.notify("Spiel abgelehnt und gelöscht!", color="warning") ui.navigate.reload() # Lädt die Seite neu, um die Karte zu aktualisieren def acccept_match(m_id): ui.notify("Spiel akzeptiert. Wird Berechnet.") ui.navigate.reload() # Lädt die Seite neu, um die Karte zu aktualisieren data_api.confirm_match(m_id) calc_match.calculate_match(m_id) with ui.row().classes('w-full items-center justify-between bg-zinc-900 p-3 rounded shadow-inner'): # Info-Text: Was wurde eingetragen? info_text = f"[{match['system_name']}] {match['p1_name']} behauptet: Er hat {match['score_player1']} : {match['score_player2']} gegen dich gespielt." ui.label(info_text).classes('text-lg text-gray-200') # Die Buttons (Funktion machen wir im nächsten Schritt!) with ui.row().classes('gap-2'): # ABLEHNEN und Spiel löschen ui.button(color="negative", icon="close", on_click=lambda e, m_id=match['match_id']: reject_match(m_id)) ui.space() # BESTÄTIGEN und spiel berechnen lassen ui.button(color="positive", icon="check", on_click=lambda e, m_id=match['match_id']: acccept_match(m_id)) # --------------------------- # --- Selbst eingetragene, offene Spiele --- # --------------------------- submitted_matches = data_api.get_submitted_matches(player_id) if len(submitted_matches) > 0: # Eine etwas dezentere Karte (grau) with ui.card().classes('w-full bg-zinc-800 border border-gray-600 mb-6'): ui.label(f"Warten auf Gegner: Du hast {len(submitted_matches)} offene(s) Spiel(e) eingetragen.").classes('text-xl font-bold text-gray-300 mb-2') for match in submitted_matches: # Die Lösch-Funktion, die beim Klick ausgeführt wird def retract_match(m_id): data_api.delete_match(m_id, player_id) ui.notify("Eingetragenes Spiel zurückgezogen!", color="warning") ui.navigate.reload() # Für jedes Match machen wir eine kleine Reihe with ui.row().classes('w-full items-center justify-between bg-zinc-900 p-3 rounded shadow-inner'): # Info-Text: Auf wen warten wir? info_text = f"[{match['system_name']}] Warten auf Bestätigung von {match['p2_name']} ({match['score_player1']} : {match['score_player2']})" ui.label(info_text).classes('text-lg text-gray-400') # Der Zurückziehen-Button (wieder mit unserem lambda m_id=... Trick!) ui.button("Zurückziehen", color="warning", icon="delete", on_click=lambda e, m_id=match['match_id']: retract_match(m_id)) # --------------------------- # --- Spielsysteme --- # --------------------------- if app.storage.user.get('authenticated', False): with ui.card().classes("w-full items-center"): with ui.row(): ui.label(text="Meine Ligaplätze").classes("font-bold text-white text-xl text-normaltext") info_system.create_info_button("league_info") placements = data_api.get_player_statistics(player_id) systems = data_api.get_gamesystems_data() my_stats = { p['gamesystem_id']: p for p in placements } def click_join_league(p_id, sys_id): data_api.join_league(p_id, sys_id) ui.navigate.to("/") def toggle_visibility(row_a, row_b): row_a.visible = not row_a.visible row_b.visible = not row_b.visible with ui.element('div').classes("w-full grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 items-center"): for sys in systems: sys_id = sys['id'] sys_name = sys['name'] sys_logo = sys["picture"] sys_description = sys['description'] sys_card = ui.card().classes("h-60 w-full items-center justify-center transition-colors") with sys_card: ui.label(text=sys_name).classes('text-xl font-bold text-center text-normaltext') if sys_logo: ui.image(f"/pictures/{sys_logo}").classes("w-60") if sys_description: ui.label(text=sys['description']).classes('text-xs text-gray-400 text-center mt-2 text-infotext') # Prüfen: Ist diese sys_id in den Stats des Spielers? UND hat er ein MMR? if sys_id not in my_stats or my_stats[sys_id]['mmr'] is None: ui.label(text="Du bist noch nicht in dieser Liga.").classes("text-red-500 font-bold") join_row = ui.row().classes('items-center gap-2') confirm_row = ui.row().classes('items-center gap-2') confirm_row.visible = False with join_row: ui.button("Beitreten", on_click=lambda e, r1=join_row, r2=confirm_row: toggle_visibility(r1, r2)) with confirm_row: ui.button("Liga Beitreten", color="green", on_click=lambda e, p=player_id, s=sys_id: click_join_league(p, s)) ui.button(icon='cancel', color='red', on_click=lambda e, r1=join_row, r2=confirm_row: toggle_visibility(r1, r2)).props('round dense') else: # Spieler IST in der Liga! stat = my_stats[sys_id] # Wir holen uns seine Stats aus dem Wörterbuch sys_card.classes("cursor-pointer hover:bg-zinc-800") sys_card.on('click', lambda e, name=sys_name: ui.navigate.to(f'/statistic/{name}')) with ui.row().classes('items-center gap-4'): ui.label(text=f"MMR: {stat['mmr']}").classes("text-lg font-bold text-accenttext") ui.label(text=f"Spiele: {stat['games_in_system']}").classes("text-lg font-bold text-accenttext") # --------------------------- # Match Historie # --------------------------- with ui.card().classes("w-full items-center"): ui.label(text= "Meine letzten Spiele").classes("font-bold text-normaltext text-xl") # 1. Daten aus der DB holen ABER per [:5] hart auf die neuesten 5 Listen-Einträge abschneiden! raw_matches = data_api.get_recent_matches_for_player(player_id)[:5] # 2. Daten für die Tabelle aufbereiten (Bleibt exakt gleich wie bei dir) table_rows = [] for match in raw_matches: if match['player1_id'] == player_id: opponent_name = f"{match['p2_display']} aka {match['p2_discord']}" my_score = match['score_player1'] opp_score = match['score_player2'] else: opponent_name = f"{match['p1_display']} aka {match['p1_discord']}" my_score = match['score_player2'] opp_score = match['score_player1'] if my_score > opp_score: result_text = "Gewonnen" elif my_score < opp_score: result_text = "Verloren" else: result_text = "Unentschieden" date_clean = str(match['played_at'])[:10] table_rows.append({ 'date': date_clean, 'system': match['gamesystem_name'], 'opponent': opponent_name, 'result': f"{result_text} ({my_score} : {opp_score})" }) table_columns = [ {'name': 'date', 'label': 'Gespielt am', 'field': 'date', 'align': 'left'}, {'name': 'system', 'label': 'System', 'field': 'system', 'align': 'left'}, {'name': 'opponent', 'label': 'Gegner', 'field': 'opponent', 'align': 'left'}, {'name': 'result', 'label': 'Ergebnis', 'field': 'result', 'align': 'left'} ] # 4. Die Tabelle zeichnen und den NEUEN BUTTON hinzufügen if len(table_rows) > 0: ui.table(columns=table_columns, rows=table_rows, row_key='date').classes('w-full bg-zinc-900 text-white') # NEU: Der Button, der zur großen Log-Seite führt ui.button("Komplette Historie & Log anzeigen", icon="history", on_click=lambda: ui.navigate.to('/matchhistory')).classes('mt-4 bg-zinc-700 text-white hover:bg-zinc-600') else: ui.label("Noch keine Spiele absolviert.").classes("text-infotext italic")