From 365791cd358066839550fcf166f8ddc6bc817b36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Gruszczy=C5=84ski?= Date: Wed, 1 Oct 2025 21:16:45 +0200 Subject: [PATCH] zmiany uxowe w panelu --- app.py | 32 +++++++- static/js/categories_autosave.js | 43 +++++++++++ templates/admin/admin_panel.html | 2 +- ...t_categories.html => edit_categories.html} | 75 +++++++++---------- templates/admin/lists_access.html | 4 +- templates/base.html | 4 +- 6 files changed, 111 insertions(+), 49 deletions(-) create mode 100644 static/js/categories_autosave.js rename templates/admin/{mass_edit_categories.html => edit_categories.html} (71%) diff --git a/app.py b/app.py index dbfecd3..56d83fe 100644 --- a/app.py +++ b/app.py @@ -3720,10 +3720,10 @@ def recalculate_filesizes_all(): return redirect(url_for("admin_receipts", id="all")) -@app.route("/admin/mass_edit_categories", methods=["GET", "POST"]) +@app.route("/admin/edit_categories", methods=["GET", "POST"]) @login_required @admin_required -def admin_mass_edit_categories(): +def admin_edit_categories(): page, per_page = get_page_args(default_per_page=50, max_per_page=200) lists_query = ShoppingList.query.options( @@ -3752,13 +3752,13 @@ def admin_mass_edit_categories(): db.session.commit() flash("Zaktualizowano kategorie dla wybranych list", "success") return redirect( - url_for("admin_mass_edit_categories", page=page, per_page=per_page) + url_for("admin_edit_categories", page=page, per_page=per_page) ) query_string = urlencode({k: v for k, v in request.args.items() if k != "page"}) return render_template( - "admin/mass_edit_categories.html", + "admin/edit_categories.html", lists=lists, categories=categories, page=page, @@ -3768,6 +3768,30 @@ def admin_mass_edit_categories(): query_string=query_string, ) +@app.route("/admin/edit_categories//save", methods=["POST"]) +@login_required +@admin_required +def admin_edit_categories_save(list_id): + l = db.session.get(ShoppingList, list_id) + if not l: + return jsonify(ok=False, error="not_found"), 404 + + data = request.get_json(silent=True) or {} + ids = data.get("category_ids", []) + + try: + ids = [int(x) for x in ids] + except (TypeError, ValueError): + return jsonify(ok=False, error="bad_ids"), 400 + + l.categories.clear() + if ids: + cats = Category.query.filter(Category.id.in_(ids)).all() + l.categories.extend(cats) + + db.session.commit() + return jsonify(ok=True, count=len(l.categories)), 200 + @app.route("/admin/list_items/") @login_required diff --git a/static/js/categories_autosave.js b/static/js/categories_autosave.js new file mode 100644 index 0000000..acddb17 --- /dev/null +++ b/static/js/categories_autosave.js @@ -0,0 +1,43 @@ +(function () { + const $$ = (sel, ctx = document) => Array.from(ctx.querySelectorAll(sel)); + const $ = (sel, ctx = document) => ctx.querySelector(sel); + + const saveCategories = async (listId, ids, names, listTitle) => { + try { + const res = await fetch(`/admin/edit_categories/${listId}/save`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ category_ids: ids }) + }); + const data = await res.json().catch(() => ({})); + if (!res.ok || !data.ok) throw new Error(data.error || 'save_failed'); + + const cats = names.length ? names.join(', ') : 'brak'; + showToast(`Zapisano kategorie [${cats}] dla listy ${listTitle}`, 'success'); + } catch (err) { + console.error('Autosave error:', err); + showToast(`Błąd zapisu kategorii dla listy ${listTitle}`, 'danger'); + } + }; + + const timers = new Map(); + const debounce = (key, fn, delay = 300) => { + clearTimeout(timers.get(key)); + timers.set(key, setTimeout(fn, delay)); + }; + + $$('.form-select[name^="categories_"]').forEach(select => { + const listId = select.getAttribute('data-list-id') || select.name.replace('categories_', ''); + const listTitle = select.closest('tr')?.querySelector('td a')?.textContent.trim() || `#${listId}`; + + select.addEventListener('change', () => { + const selectedOptions = Array.from(select.options).filter(o => o.selected); + const ids = selectedOptions.map(o => o.value); // <-- ID + const names = selectedOptions.map(o => o.textContent.trim()); + debounce(listId, () => saveCategories(listId, ids, names, listTitle)); + }); + }); + + const fallback = $('#fallback-save-btn'); + if (fallback) fallback.classList.add('d-none'); +})(); diff --git a/templates/admin/admin_panel.html b/templates/admin/admin_panel.html index bc7346a..7006cfa 100644 --- a/templates/admin/admin_panel.html +++ b/templates/admin/admin_panel.html @@ -13,7 +13,7 @@ 👥 Użytkownicy 📸 Paragony 🛍️ Produkty - 🗂 Kategorie + 🗂 Kategorie 🔐 Uprawnienia diff --git a/templates/admin/mass_edit_categories.html b/templates/admin/edit_categories.html similarity index 71% rename from templates/admin/mass_edit_categories.html rename to templates/admin/edit_categories.html index 250cf50..c42c858 100644 --- a/templates/admin/mass_edit_categories.html +++ b/templates/admin/edit_categories.html @@ -12,25 +12,24 @@
-
-
+ +
-
- - +
+
+ - + - - + + @@ -44,22 +43,17 @@ - {% endfor %} + {% if lists|length == 0 %} - + {% endif %} @@ -92,9 +87,9 @@ -
- -
+ + {# Fallback – ukryty przez JS #} + @@ -120,8 +115,7 @@ {% for p in range(1, total_pages + 1) %}
  • - {{ - p }} + {{ p }}
  • {% endfor %}
  • @@ -132,7 +126,6 @@ -
  • ID Nazwa listy WłaścicielData utworzeniaData StatusPodgląd produktówKategoriePodglądKategorie
    {% if l.owner %} 👤 {{ l.owner.username }} ({{ l.owner.id }}) - {% else %} - - - {% endif %} + {% else %}-{% endif %} {{ l.created_at.strftime('%Y-%m-%d %H:%M') if l.created_at else '-' }} {% if l.is_archived %}Archiwalna{% - endif %} + class="badge rounded-pill bg-secondary me-1">Archiwalna{% endif %} {% if l.is_temporary %}Tymczasowa{% + class="badge rounded-pill bg-warning text-dark me-1">Tymczasowa{% endif %} - {% if l.is_public %}Publiczna{% else - %} - Prywatna{% endif %} + {% if l.is_public %}Publiczna + {% else %}Prywatna{% endif %} - + +
    + +
    - Brak list zakupowych do wyświetlenia - Brak list zakupowych do wyświetlenia
    +
    @@ -77,7 +77,7 @@ - +
    #{{ l.id }}{{ l.id }} {{ l.title diff --git a/templates/base.html b/templates/base.html index a388885..98a7fb2 100644 --- a/templates/base.html +++ b/templates/base.html @@ -31,7 +31,7 @@ {% endif %} {# --- Tom Select CSS tylko dla wybranych podstron --- #} - {% set substrings_tomselect = ['/edit_my_list', '/admin/edit_list', '/admin/mass_edit_categories'] %} + {% set substrings_tomselect = ['/edit_my_list', '/admin/edit_list', '/admin/edit_categories'] %} {% if substrings_tomselect | select("in", request.path) | list | length > 0 %} @@ -126,7 +126,7 @@ {% endif %} - {% set substrings = ['/edit_my_list', '/admin/edit_list', '/admin/mass_edit_categories'] %} + {% set substrings = ['/edit_my_list', '/admin/edit_list', '/admin/edit_categories'] %} {% if substrings | select("in", request.path) | list | length > 0 %}