diff --git a/routes/games/game_dashboard.py b/routes/games/game_dashboard.py index 3f679ddc..bbb71a0e 100644 --- a/routes/games/game_dashboard.py +++ b/routes/games/game_dashboard.py @@ -22,6 +22,30 @@ def game_dashboard(slug): # simplement montré/masqué en JS, plutôt qu'un aller-retour serveur à # l'ouverture (voir openObjectEditPanel()). definitions_full = {d["id"]: db.get_definition(slug, d["id"]) for d in definitions} + + # Pour l'onglet "Données" de ce même panneau (remplace data_list.html/ + # object_view.py, retirés) : les lignes déjà enregistrées de chaque + # objet, les libellés lisibles de ses champs relation (pour l'affichage + # du tableau) et les options possibles de ces mêmes champs relation + # (pour le formulaire "Ajouter une entrée"). + rows_by_definition = {} + relation_labels_by_definition = {} + relation_options_by_definition = {} + referenced_by_definition = {} + for d in definitions: + full = definitions_full[d["id"]] + rows_by_definition[d["id"]] = db.list_rows(slug, full) + relation_labels = {} + relation_options = {} + for f in full["fields"]: + if f["type"] == "relation": + options = db.relation_options(slug, f["relation_definition_id"]) + relation_labels[f["id"]] = {opt["id"]: opt["label"] for opt in options} + relation_options[f["id"]] = options + relation_labels_by_definition[d["id"]] = relation_labels + relation_options_by_definition[d["id"]] = relation_options + referenced_by_definition[d["id"]] = db.definitions_referencing(slug, d["id"]) + screen_list = screens.list_screens(slug) element_types = screens.list_element_types(slug) element_type_usage = {t["id"]: screens.element_type_usage_count(slug, t["id"]) for t in element_types} @@ -29,6 +53,10 @@ def game_dashboard(slug): return render_template( "game_dashboard.html", game=game, definitions=definitions, definitions_full=definitions_full, + rows_by_definition=rows_by_definition, + relation_labels_by_definition=relation_labels_by_definition, + relation_options_by_definition=relation_options_by_definition, + referenced_by_definition=referenced_by_definition, screen_list=screen_list, element_types=element_types, element_type_usage=element_type_usage, variables=variables, variable_types=db.GLOBAL_VARIABLE_TYPES, field_types=db.FIELD_TYPES, diff --git a/routes/objects/data_delete.py b/routes/objects/data_delete.py index d0a60a78..e1cda644 100644 --- a/routes/objects/data_delete.py +++ b/routes/objects/data_delete.py @@ -13,8 +13,9 @@ def data_delete(slug, definition_id, row_id): referencing = db.rows_referencing(slug, definition_id, row_id) if referencing: # Suppression bloquée : d'autres lignes pointent vers celle-ci via - # une relation. On revient à la liste, qui explique pourquoi via un - # paramètre de requête simple plutôt qu'une session/flash message. - return redirect(url_for("object_view", slug=slug, definition_id=definition_id, blocked_row=row_id)) + # une relation. On revient au panneau de cet objet (onglet + # Données), qui explique pourquoi via un paramètre de requête + # simple plutôt qu'une session/flash message. + return redirect(url_for("game_dashboard", slug=slug, tab="objects", edit=definition_id, subtab="data", blocked_row=row_id)) db.delete_row(slug, definition, row_id) - return redirect(url_for("object_view", slug=slug, definition_id=definition_id)) + return redirect(url_for("game_dashboard", slug=slug, tab="objects", edit=definition_id, subtab="data")) diff --git a/routes/objects/data_form.py b/routes/objects/data_form.py index 5fbe0d37..49e68601 100644 --- a/routes/objects/data_form.py +++ b/routes/objects/data_form.py @@ -1,32 +1,24 @@ -from flask import render_template, request, redirect, url_for, abort +from flask import request, redirect, url_for, abort import db def _data_form(slug, definition_id, row): - game = db.game_meta(slug) + """Ajouter/modifier une entrée se fait désormais depuis l'onglet + "Données" du panneau déplaçable de son objet (voir game_dashboard.html, + ouvert par le crayon ✏️ de l'onglet Objets) plutôt qu'une page dédiée + (data_form.html, retirée) — cette fonction ne fait donc plus que + traiter la soumission de ce formulaire. Un GET direct redirige vers le + tableau de bord, en rouvrant le panneau de cet objet sur son onglet + Données.""" definition = db.get_definition(slug, definition_id) if not definition: abort(404) - # Pour chaque champ de type relation, on prépare les options du - - {% elif f.type == 'texte_long' %} - - - {% elif f.type == 'nombre_entier' %} - - - {% elif f.type == 'nombre_decimal' %} - - - {% elif f.type == 'booleen' %} - - - {% elif f.type == 'date' %} - - - {% elif f.type == 'relation' %} - {% set current_rel = row[col ~ '_id'] if row else none %} -
- -
- {% if not relation_options.get(f.id) %} -

Aucune entrée n'existe encore pour l'objet lié — crée-en une d'abord si tu veux pouvoir la sélectionner ici.

- {% endif %} - {% endif %} - - - {% endfor %} - - - - -{% endblock %} diff --git a/templates/data_list.html b/templates/data_list.html deleted file mode 100644 index 8a045664..00000000 --- a/templates/data_list.html +++ /dev/null @@ -1,124 +0,0 @@ -{% extends "base.html" %} -{% block title %}{{ definition.name }} — {{ game.name }}{% endblock %} -{% block content_class %}content-wide content-objectEdit{% endblock %} -{% block body_class %}objectEditBody{% endblock %} -{% block content %} -

{{ definition.name }} - ✏️ -

-

Table SQL : {{ definition.table_name }} — {{ definition.fields|length }} champ(s)

- -{% if referenced_by %} -
- ⚠️ D'autres objets ont une relation vers « {{ definition.name }} » : - {{ referenced_by|map(attribute='name')|join(', ') }}. - La suppression de cet objet est bloquée tant que ces relations existent. -
-{% endif %} - -{% if blocked_row_id %} -
- ⚠️ Impossible de supprimer l'entrée #{{ blocked_row_id }} : elle est référencée par - {% for ref in blocked_row_refs %}{{ ref.count }} entrée(s) de « {{ ref.definition_name }} » (champ « {{ ref.field_name }} »){% if not loop.last %}, {% endif %}{% endfor %}. - Modifie ou supprime d'abord ces entrées. -
-{% endif %} - -
-
-

Ajouter

-
-

Une entrée, c'est une ligne de la table SQL « {{ definition.table_name }} ».

- -
-
- -
-

Données enregistrées

- {% if rows %} - {% macro row_field_display(f, r) %} - {%- if f.type == 'relation' -%} - {{ relation_labels.get(f.id, {}).get(r[(f.name|colname) ~ '_id'], '—') }} - {%- elif f.type == 'booleen' -%} - {{ 'Oui' if r[f.name|colname] else 'Non' }} - {%- else -%} - {{ r[f.name|colname] }} - {%- endif -%} - {% endmacro %} -
- - - - - {% for f in definition.fields %}{% endfor %} - - - - - {% for r in rows %} - - - {% for f in definition.fields %} - - {% endfor %} - - - {% endfor %} - -
#{{ f.name }}
{{ r.id }}{{ row_field_display(f, r) }} -
- - ✏️ -
- -
-
-
-
- - {% for r in rows %} - - {% endfor %} - {% else %} -

Aucune donnée enregistrée pour l'instant.

- {% endif %} -
-
- - -{% endblock %} diff --git a/templates/game_dashboard.html b/templates/game_dashboard.html index 5870b6d3..c391cf33 100644 --- a/templates/game_dashboard.html +++ b/templates/game_dashboard.html @@ -75,7 +75,7 @@ {% for d in definitions %} - {{ d.name }} + {{ d.table_name }} @@ -275,94 +275,214 @@

Table SQL : {{ definition.table_name }}

-

Ajouter un champ

-
- - - - - - - -
- -

Champs existants

- {% if definition.fields %} - {% for f in definition.fields %} -
-
- {% endfor %} -
- - - - - - {% for f in definition.fields %} - - - - - - - - - - {% endfor %} - -
NomTypeObjet liéMiniMaxiObligatoire
{{ field_types.get(f.type, {}).get('label', f.type) }} - {% if f.type == 'relation' %} -
- -
- {% else %} - — - {% endif %} -
- {% if f.type in ('nombre_entier', 'nombre_decimal') %} - - {% else %} - — - {% endif %} - - {% if f.type in ('nombre_entier', 'nombre_decimal') %} - - {% else %} - — - {% endif %} - -
- -
-
- - -
+ {% if referenced_by_definition.get(d.id) %} +
+ ⚠️ D'autres objets ont une relation vers « {{ definition.name }} » : + {{ referenced_by_definition[d.id]|map(attribute='name')|join(', ') }}. + La suppression de cet objet est bloquée tant que ces relations existent.
-

Mini/Maxi (bornage automatique) : laisser vide = pas de limite.

- {% else %} -

Aucun champ pour l'instant.

{% endif %} + +
+ + +
+ +
+
+

Ajouter un champ

+
+
+ + + + + + + +
+ +

Champs existants

+ {% if definition.fields %} + {% for f in definition.fields %} +
+
+ {% endfor %} +
+ + + + + + {% for f in definition.fields %} + + + + + + + + + + {% endfor %} + +
NomTypeObjet liéMiniMaxiObligatoire
{{ field_types.get(f.type, {}).get('label', f.type) }} + {% if f.type == 'relation' %} +
+ +
+ {% else %} + — + {% endif %} +
+ {% if f.type in ('nombre_entier', 'nombre_decimal') %} + + {% else %} + — + {% endif %} + + {% if f.type in ('nombre_entier', 'nombre_decimal') %} + + {% else %} + — + {% endif %} + +
+ +
+
+ + +
+
+

Mini/Maxi (bornage automatique) : laisser vide = pas de limite.

+ {% else %} +

Aucun champ pour l'instant.

+ {% endif %} +
+ +
+ {% set blocked_row_id = request.args.get('blocked_row') if request.args.get('edit') == d.id|string else none %} + {% if blocked_row_id %} +
+ ⚠️ Impossible de supprimer l'entrée #{{ blocked_row_id }} : elle est référencée ailleurs. Modifie ou supprime d'abord ces entrées. +
+ {% endif %} +
+

Ajouter une entrée

+
+ {% if definition.fields %} +
+ {% for f in definition.fields %} + {% set col = f.name|colname %} + {% if f.type == 'texte' %} + + {% elif f.type == 'texte_long' %} + + {% elif f.type == 'nombre_entier' %} + + {% elif f.type == 'nombre_decimal' %} + + {% elif f.type == 'booleen' %} + + {% elif f.type == 'date' %} + + {% elif f.type == 'relation' %} + + {% endif %} + {% endfor %} + +
+ +

Données enregistrées

+ {% set rows = rows_by_definition[d.id] %} + {% if rows %} + {% for r in rows %} +
+
+ {% endfor %} +
+ + + {% for f in definition.fields %}{% endfor %} + + + {% for r in rows %} + + + {% for f in definition.fields %} + {% set col = f.name|colname %} + + {% endfor %} + + + {% endfor %} + +
#{{ f.name }}
{{ r.id }} + {% if f.type == 'texte' %} + + {% elif f.type == 'texte_long' %} + + {% elif f.type in ('nombre_entier', 'nombre_decimal') %} + + {% elif f.type == 'booleen' %} + + {% elif f.type == 'date' %} + + {% elif f.type == 'relation' %} +
+ +
+ {% endif %} +
+ + +
+
+ {% else %} +

Aucune donnée enregistrée pour l'instant.

+ {% endif %} + {% else %} +

Ajoute d'abord au moins un champ pour pouvoir enregistrer des données.

+ {% endif %} +
+

Supprimer cet objet

, posé par les + // suppression d'un champ ou d'une entrée, renommage...) : ?edit= + // (et ?subtab=data pour rouvrir sur le bon sous-onglet), posés par les // routes concernées (voir routes/objects/) sur leur redirection vers ce // tableau de bord — sans ça, chaque action fermait le panneau et - // obligeait à recliquer sur le crayon pour voir le résultat. + // obligeait à recliquer pour voir le résultat. var requestedEdit = {{ (request.args.get('edit') or '')|tojson }}; - if (requestedEdit) openObjectEditPanel(requestedEdit); + var requestedSubtab = {{ (request.args.get('subtab') or '')|tojson }}; + if (requestedEdit) openObjectEditPanel(requestedEdit, requestedSubtab); })(); + +// Sous-onglets Champs/Données d'UN panneau précis (voir plus haut) — scopé +// au panneau ancêtre du bouton cliqué, jamais global (plusieurs objets +// peuvent chacun avoir leurs sous-onglets sur leur propre panneau, en même +// temps, sur la même page). +function switchObjectPanelTab(btn, tab) { + var panel = btn.closest('.objectEditPanel'); + panel.querySelectorAll('.objectPanelTabs .builderTabBtn').forEach(function (b) { + b.classList.toggle('active', b.dataset.optab === tab); + }); + panel.querySelectorAll('.objectPanelTabPanel').forEach(function (p) { + p.classList.toggle('active', p.dataset.optabPanel === tab); + }); +} {% endblock %}