Formular um Spiele einzutragen. Datenbank funktioniert. Liga beitreten Funktioniert.
This commit is contained in:
parent
3823dac7e3
commit
6ca230e729
|
|
@ -1 +1 @@
|
|||
{"authenticated":true,"discord_id":"277898241750859776","discord_name":"mrteels","db_id":1,"display_name":"Chaos Regelvergesser","discord_avatar_url":"https://cdn.discordapp.com/avatars/277898241750859776/7c3446bb51fafd72b1b4c21124b4994f.png"}
|
||||
{"authenticated":true,"discord_id":"277898241750859776","discord_name":"mrteels","db_id":2,"display_name":"Zorniger Grot","discord_avatar_url":"https://cdn.discordapp.com/avatars/277898241750859776/7c3446bb51fafd72b1b4c21124b4994f.png"}
|
||||
|
|
@ -64,6 +64,36 @@ def get_all_players():
|
|||
return result
|
||||
|
||||
|
||||
def get_all_players_from_system(system_name):
|
||||
connection = sqlite3.connect(DB_PATH)
|
||||
connection.row_factory = sqlite3.Row
|
||||
cursor = connection.cursor()
|
||||
|
||||
# ID und Namen der Spieler.
|
||||
# DISTINCT stellt sicher, dass jeder Spieler nur einmal vorkommt.
|
||||
query = """
|
||||
SELECT DISTINCT
|
||||
p.id AS player_id,
|
||||
p.display_name,
|
||||
p.discord_name
|
||||
FROM players p
|
||||
JOIN player_game_statistic stat ON p.id = stat.player_id
|
||||
JOIN gamesystems sys ON stat.gamesystem_id = sys.id
|
||||
WHERE sys.name = ?
|
||||
ORDER BY p.display_name ASC
|
||||
"""
|
||||
|
||||
cursor.execute(query, (system_name,))
|
||||
rows = cursor.fetchall()
|
||||
connection.close()
|
||||
|
||||
result = []
|
||||
for row in rows:
|
||||
result.append(dict(row))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
import sqlite3
|
||||
from data.setup_database import DB_PATH
|
||||
|
||||
|
|
@ -128,3 +158,69 @@ def join_league(player_id, gamesystem_id):
|
|||
|
||||
connection.close()
|
||||
|
||||
|
||||
def get_recent_matches_for_player(player_id):
|
||||
connection = sqlite3.connect(DB_PATH)
|
||||
connection.row_factory = sqlite3.Row
|
||||
cursor = connection.cursor()
|
||||
|
||||
query = """
|
||||
SELECT
|
||||
m.id AS match_id,
|
||||
sys.name AS gamesystem_name,
|
||||
m.player1_id,
|
||||
p1.display_name AS p1_display,
|
||||
p1.discord_name AS p1_discord,
|
||||
m.score_player1,
|
||||
m.player2_id,
|
||||
p2.display_name AS p2_display,
|
||||
p2.discord_name AS p2_discord,
|
||||
m.score_player2,
|
||||
m.played_at
|
||||
FROM matches m
|
||||
JOIN gamesystems sys ON m.gamesystem_id = sys.id
|
||||
JOIN players p1 ON m.player1_id = p1.id
|
||||
JOIN players p2 ON m.player2_id = p2.id
|
||||
WHERE m.player1_id = ? OR m.player2_id = ?
|
||||
ORDER BY m.played_at DESC
|
||||
LIMIT 10
|
||||
"""
|
||||
|
||||
cursor.execute(query, (player_id, player_id))
|
||||
rows = cursor.fetchall()
|
||||
connection.close()
|
||||
|
||||
result = []
|
||||
for row in rows:
|
||||
result.append(dict(row))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def add_new_match(system_name, player1_id, player2_id, score_p1, score_p2):
|
||||
connection = sqlite3.connect(DB_PATH)
|
||||
cursor = connection.cursor()
|
||||
|
||||
# 1. Wir suchen die interne ID des Spielsystems anhand des Namens (z.B. "Warhammer 40k" -> 1)
|
||||
cursor.execute("SELECT id FROM gamesystems WHERE name = ?", (system_name,))
|
||||
sys_row = cursor.fetchone()
|
||||
|
||||
# Sicherheitscheck (sollte eigentlich nie passieren)
|
||||
if not sys_row:
|
||||
connection.close()
|
||||
return False
|
||||
|
||||
sys_id = sys_row[0]
|
||||
|
||||
# 2. Das Match eintragen! (Datum 'played_at' macht SQLite durch DEFAULT CURRENT_TIMESTAMP automatisch)
|
||||
query = """
|
||||
INSERT INTO matches (gamesystem_id, player1_id, player2_id, score_player1, score_player2)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
"""
|
||||
|
||||
cursor.execute(query, (sys_id, player1_id, player2_id, score_p1, score_p2))
|
||||
|
||||
connection.commit()
|
||||
connection.close()
|
||||
|
||||
return True
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ def seed_gamesystems():
|
|||
cursor = connection.cursor()
|
||||
systems = [
|
||||
("Warhammer 40k","/gui/pictures/wsdg.png","Die Schlacht um die Galaxie in einer entfernten Zukunft." , 0, 100),
|
||||
("Age of Sigmar","/gui/pictures/wsdg.png","" , 0, 50),
|
||||
("Warhammer Age of Sigmar","/gui/pictures/wsdg.png","" , 0, 50),
|
||||
("Spearhead","/gui/pictures/wsdg.png","" , 0, 50)
|
||||
]
|
||||
|
||||
|
|
@ -135,5 +135,47 @@ def seed_achievements():
|
|||
connection.commit()
|
||||
connection.close()
|
||||
print("Achievements angelegt.")
|
||||
seed_dummy_player()
|
||||
|
||||
|
||||
def seed_dummy_player():
|
||||
connection = sqlite3.connect(DB_PATH)
|
||||
cursor = connection.cursor()
|
||||
|
||||
# 1. Dummy-Spieler anlegen (falls noch nicht vorhanden)
|
||||
query_player = """
|
||||
INSERT OR IGNORE INTO players
|
||||
(discord_id, discord_name, display_name, discord_avatar_url)
|
||||
VALUES (?, ?, ?, ?)
|
||||
"""
|
||||
cursor.execute(query_player, ("dummy_001", "dummy_user", "Dummy Mc DummDumm", ""))
|
||||
|
||||
# 2. Wir holen uns die ID des Dummys (egal ob neu oder alt)
|
||||
cursor.execute("SELECT id FROM players WHERE discord_id = 'dummy_001'")
|
||||
dummy_row = cursor.fetchone()
|
||||
|
||||
if dummy_row:
|
||||
dummy_id = dummy_row[0]
|
||||
|
||||
# 3. Wir holen alle IDs der aktuellen Spielsysteme
|
||||
cursor.execute("SELECT id FROM gamesystems")
|
||||
systems = cursor.fetchall()
|
||||
|
||||
# 4. Wir gehen jedes System durch und prüfen, ob er schon drin ist
|
||||
for sys in systems:
|
||||
sys_id = sys[0]
|
||||
|
||||
cursor.execute("SELECT id FROM player_game_statistic WHERE player_id = ? AND gamesystem_id = ?", (dummy_id, sys_id))
|
||||
is_in_league = cursor.fetchone()
|
||||
|
||||
if not is_in_league:
|
||||
# Er ist noch nicht drin -> Eintragen! (MMR startet durch DEFAULT automatisch bei 1000)
|
||||
cursor.execute("""
|
||||
INSERT INTO player_game_statistic (player_id, gamesystem_id)
|
||||
VALUES (?, ?)
|
||||
""", (dummy_id, sys_id))
|
||||
|
||||
connection.commit()
|
||||
connection.close()
|
||||
print("Test-Gegner 'Dummy Mc DummDumm' ist bereit und in allen Ligen angemeldet!")
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
from nicegui import ui, app
|
||||
from gui import gui_style
|
||||
|
||||
def setup_statistic_routes():
|
||||
|
||||
def setup_routes():
|
||||
# 1. Die {}-Klammern definieren eine dynamische Variable in der URL
|
||||
@ui.page('/statistic/{systemname}')
|
||||
def gamesystem_statistic_page(systemname: str): # <--- WICHTIG: Hier fangen wir das Wort aus der URL auf!
|
||||
sys_name = systemname
|
||||
|
||||
# Sicherheitscheck: Ist der User eingeloggt?
|
||||
if not app.storage.user.get('authenticated', False):
|
||||
|
|
@ -13,14 +13,13 @@ def setup_statistic_routes():
|
|||
return
|
||||
|
||||
gui_style.apply_design()
|
||||
print(systemname)
|
||||
with ui.header().classes('items-center justify-between bg-zinc-900 p-4 shadow-lg'):
|
||||
ui.button('Zurück zur Übersicht', on_click=lambda: ui.navigate.to('/')).classes('mt-8 bg-gray-600 text-white')
|
||||
ui.button("Spiel eintragen", on_click=lambda: ui.navigate.to('/add-match/{systemname}'))
|
||||
ui.button("Spiel eintragen", on_click=lambda: ui.navigate.to(f'/add-match/{sys_name}'))
|
||||
|
||||
with ui.column().classes('w-full items-center mt-10'):
|
||||
# Hier nutzen wir die Variable 'systemname', um den Titel anzupassen
|
||||
ui.label(f'Deine Statistik in {systemname}').classes('text-3xl font-bold text-blue-400')
|
||||
ui.label(f'Deine Statistik in {sys_name}').classes('text-3xl font-bold text-blue-400')
|
||||
|
||||
with ui.card().classes('w-full max-w-2xl mt-6 p-6 items-center'):
|
||||
ui.label('Hier laden wir im nächsten Schritt die Daten aus der player_game_statistic Tabelle!').classes('text-gray-400')
|
||||
|
|
|
|||
|
|
@ -7,7 +7,10 @@ def setup_routes():
|
|||
def home_page():
|
||||
gui_style.apply_design()
|
||||
|
||||
# ---------------------------
|
||||
# --- NAVIGATIONSLEISTE (HEADER) ---
|
||||
# ---------------------------
|
||||
|
||||
with ui.header().classes('items-center justify-between bg-zinc-900 p-4 shadow-lg'):
|
||||
|
||||
# --- LINKE SEITE ---
|
||||
|
|
@ -23,7 +26,7 @@ def setup_routes():
|
|||
display_name = app.storage.user.get('display_name')
|
||||
player_id = app.storage.user.get('db_id')
|
||||
|
||||
# 1. Wir definieren eine kleine Funktion, die zwischen Text und Eingabe hin- und herschaltet
|
||||
# 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
|
||||
|
|
@ -34,7 +37,7 @@ def setup_routes():
|
|||
ui.label('aka').classes('text-sm text-gray-500')
|
||||
ui.label(discord_name).classes('text-lg text-gray-400')
|
||||
|
||||
# Ein runder Button (.props('round')), der exakt wie ein FAB aussieht!
|
||||
# Ein runder Button (.props('round'))
|
||||
ui.button(icon='edit', color='primary', on_click=toggle_edit_mode).props('round dense')
|
||||
|
||||
# --- ANSICHT 2: Das Eingabefeld (startet unsichtbar!) ---
|
||||
|
|
@ -56,7 +59,6 @@ def setup_routes():
|
|||
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='close', color='negative', on_click=toggle_edit_mode).props('round dense')
|
||||
# ---------------------------------------------
|
||||
|
||||
avatar = app.storage.user.get('avatar_url')
|
||||
if avatar:
|
||||
|
|
@ -71,10 +73,12 @@ def setup_routes():
|
|||
else:
|
||||
auth_url = discord_login.get_auth_url()
|
||||
ui.button('Login with Discord', on_click=lambda: ui.navigate.to(auth_url))
|
||||
# ----------------------------------
|
||||
|
||||
|
||||
# ---------------------------
|
||||
# --- Spielsysteme ---
|
||||
# ---------------------------
|
||||
|
||||
if app.storage.user.get('authenticated', False):
|
||||
with ui.card().classes("w-full"):
|
||||
ui.label(text="Meine Ligaplätze").classes("font-bold text-white text-xl")
|
||||
|
|
@ -86,6 +90,11 @@ def setup_routes():
|
|||
data_api.join_league(p_id, sys_id)
|
||||
ui.navigate.to("/")
|
||||
|
||||
# --- NEU: Wir machen die Funktion allgemein ---
|
||||
def toggle_visibility(row_a, row_b):
|
||||
row_a.visible = not row_a.visible
|
||||
row_b.visible = not row_b.visible
|
||||
|
||||
with ui.grid(columns=3).classes("w-full gap-4 items-center"):
|
||||
for place in placements:
|
||||
sys_card = ui.card().classes("h-60 w-full items-center justify-center transition-colors")
|
||||
|
|
@ -102,11 +111,20 @@ def setup_routes():
|
|||
if place['mmr'] is None:
|
||||
ui.label(text="Du bist noch nicht in dieser Liga.").classes("text-red-500 font-bold")
|
||||
|
||||
# --- HIER IST DER MAGISCHE TRICK ---
|
||||
# Das 'e' fängt das NiceGUI-Event ab und wirft es weg.
|
||||
# p=player_id und s=place['gamesystem_id'] frieren die IDs für DIESEN Button bombenfest ein!
|
||||
ui.button("Liga Beitreten", on_click=lambda e, p=player_id, s=place['gamesystem_id']: click_join_league(p, s))
|
||||
# 1. Wir legen die Reihen an und speichern sie in lokalen Variablen
|
||||
join_row = ui.row().classes('items-center gap-2')
|
||||
confirm_row = ui.row().classes('items-center gap-2')
|
||||
confirm_row.visible = False # Standardmäßig unsichtbar
|
||||
|
||||
# 2. Erste Reihe (Der "Beitreten" Button)
|
||||
with join_row:
|
||||
ui.button("Beitreten", on_click=lambda e, r1=join_row, r2=confirm_row: toggle_visibility(r1, r2))
|
||||
|
||||
# 3. Zweite Reihe (Die Bestätigungs-Buttons)
|
||||
with confirm_row:
|
||||
ui.button("Liga Beitreten", color="green", on_click=lambda e, p=player_id, s=place['gamesystem_id']: click_join_league(p, s))
|
||||
# Der Abbrechen Button kriegt den gleichen Toggle-Befehl wie oben:
|
||||
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!
|
||||
sys_card.classes("cursor-pointer hover:bg-zinc-800")
|
||||
|
|
@ -119,6 +137,63 @@ def setup_routes():
|
|||
ui.label(text=f"Spiele: {place['games_in_system']}")
|
||||
ui.label(text=f"Punkte: {place['points']}")
|
||||
|
||||
# ---------------------------
|
||||
# Match Historie
|
||||
# ---------------------------
|
||||
|
||||
with ui.card().classes("w-full"):
|
||||
ui.label(text= "Meine letzten Spiele").classes("font-bold text-white text-xl")
|
||||
# 1. Daten aus der DB holen
|
||||
raw_matches = data_api.get_recent_matches_for_player(player_id)
|
||||
|
||||
# 2. Daten für die Tabelle aufbereiten
|
||||
table_rows = []
|
||||
for match in raw_matches:
|
||||
# Bin ich Spieler 1 oder Spieler 2?
|
||||
if match['player1_id'] == player_id:
|
||||
# Ich bin Spieler 1, also ist Spieler 2 der Gegner
|
||||
opponent_name = f"{match['p2_display']} aka {match['p2_discord']}"
|
||||
my_score = match['score_player1']
|
||||
opp_score = match['score_player2']
|
||||
else:
|
||||
# Ich bin Spieler 2, also ist Spieler 1 der Gegner
|
||||
opponent_name = f"{match['p1_display']} aka {match['p1_discord']}"
|
||||
my_score = match['score_player2']
|
||||
opp_score = match['score_player1']
|
||||
|
||||
# Gewonnen oder Verloren?
|
||||
if my_score > opp_score:
|
||||
result_text = "Gewonnen"
|
||||
elif my_score < opp_score:
|
||||
result_text = "Verloren"
|
||||
else:
|
||||
result_text = "Unentschieden"
|
||||
|
||||
# Datum hübsch machen (schneidet die Millisekunden weg)
|
||||
date_clean = str(match['played_at'])[:10]
|
||||
|
||||
# Eine fertige Zeile für unsere Tabelle bauen
|
||||
table_rows.append({
|
||||
'date': date_clean,
|
||||
'system': match['gamesystem_name'],
|
||||
'opponent': opponent_name,
|
||||
'result': f"{result_text} ({my_score} : {opp_score})"
|
||||
})
|
||||
|
||||
# 3. Das Layout (Die Spalten) der Tabelle definieren
|
||||
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!
|
||||
if len(table_rows) > 0:
|
||||
ui.table(columns=table_columns, rows=table_rows, row_key='date').classes('w-full bg-zinc-900 text-white')
|
||||
else:
|
||||
ui.label("Noch keine Spiele absolviert.").classes("text-gray-500 italic")
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
from nicegui import ui, app
|
||||
from gui import gui_style
|
||||
from data import data_api
|
||||
|
||||
def setup_match_routes():
|
||||
def setup_routes():
|
||||
|
||||
# 1. Die {}-Klammern definieren eine dynamische Variable in der URL
|
||||
@ui.page('/add-match/{systemname}')
|
||||
|
|
@ -15,11 +16,62 @@ def setup_match_routes():
|
|||
ui.button('Back to Home', on_click=lambda: ui.navigate.to('/'))
|
||||
return
|
||||
|
||||
with ui.card().classes('w-full items-center mt-10'):
|
||||
with ui.card().classes('w-150 items-center mt-10 justify-center'):
|
||||
ui.label('Neues Spiel für '+ systemname + " eintragen").classes('text-2xl font-bold')
|
||||
|
||||
# --- PLATZHALTER ---
|
||||
ui.label('Hier kommt im nächsten Schritt das Eingabe-Formular hin!').classes('text-gray-500 my-4')
|
||||
ui.space()
|
||||
|
||||
# Ein Button, um wieder zurück zur Startseite zu kommen
|
||||
ui.label("Meine Punkte:").classes('text-xl font-bold')
|
||||
|
||||
ui.space()
|
||||
|
||||
with ui.column().classes("w-full items-center h-60"):
|
||||
# 1. Daten aus der DB holen
|
||||
raw_players = data_api.get_all_players_from_system(systemname)
|
||||
my_id = app.storage.user.get('db_id')
|
||||
|
||||
# 3. Eine saubere Optionen-Liste (Dictionary) für NiceGUI bauen
|
||||
dropdown_options = {}
|
||||
|
||||
for p in raw_players:
|
||||
# FILTERN: Wenn die ID des Spielers MEINE EIGENE ID ist, überspringen wir ihn!
|
||||
if p['player_id'] == my_id:
|
||||
continue # Geht sofort zum nächsten Spieler in der Schleife
|
||||
|
||||
# Die ID wird der Schlüssel, der kombinierte Name wird der Anzeigetext
|
||||
# Ergebnis sieht z.B. so aus: { 2: "Dummy Mc DummDumm aka dummy_user" }
|
||||
dropdown_options[p['player_id']] = f"{p['display_name']} aka {p['discord_name']}"
|
||||
|
||||
# 4. Eigene Punkte eintragen
|
||||
p1_points = ui.slider(min= 0, max=100, value = 10).props("label-always")
|
||||
|
||||
ui.space()
|
||||
|
||||
# 5. Das Dropdown mit unseren sauberen Optionen füttern
|
||||
ui.label("Gegner:").classes('text-xl font-bold')
|
||||
opponent_select = ui.select(options=dropdown_options, label='Gegner auswählen').classes('w-64')
|
||||
p2_points = ui.slider(min= 0, max=100, value = 10).props("label-always")
|
||||
|
||||
ui.space()
|
||||
|
||||
def input_match_to_database():
|
||||
# 1. Prüfen, ob ein Gegner ausgewählt wurde
|
||||
p2_id = opponent_select.value
|
||||
if p2_id is None:
|
||||
ui.notify("Bitte wähle zuerst einen Gegner aus!", color="red", position="top")
|
||||
return # Bricht die Funktion hier ab!
|
||||
|
||||
# 2. Punkte von den Slidern holen
|
||||
score_p1 = p1_points.value
|
||||
score_p2 = p2_points.value
|
||||
|
||||
# 3. Daten an die API schicken (my_id haben wir oben schon von app.storage.user geholt)
|
||||
data_api.add_new_match(systemname, my_id, p2_id, score_p1, score_p2)
|
||||
|
||||
# 4. Erfolgsmeldung und zurück zur Übersicht
|
||||
ui.notify("Match erfolgreich eingetragen!", color="green")
|
||||
ui.navigate.to('/')
|
||||
|
||||
with ui.row().classes("w-full items-center justify-between"):
|
||||
ui.button('Cancel', on_click=lambda: ui.navigate.to('/')).classes('bg-gray-500 text-white')
|
||||
ui.button(text="Absenden", color="positive", on_click=lambda: input_match_to_database()).classes("")
|
||||
|
|
|
|||
4
main.py
4
main.py
|
|
@ -20,9 +20,9 @@ client_secret = os.getenv("DISCORD_CLIENT_SECRET")
|
|||
|
||||
# 3. Seitenrouten aufbauen
|
||||
main_gui.setup_routes()
|
||||
match_gui.setup_match_routes()
|
||||
discord_login.setup_login_routes()
|
||||
league_statistic.setup_statistic_routes()
|
||||
league_statistic.setup_routes()
|
||||
match_gui.setup_routes()
|
||||
|
||||
# 4. Wir starten die NiceGUI App
|
||||
ui.run(title="Warhammer Liga", port=9000, storage_secret="ein_sehr_geheimes_passwort_fuer_die_cookies")
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user