From 5d5779c09e559c75069388b56c21c94ad2b4e53d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Gruszczy=C5=84ski?= Date: Thu, 3 Jul 2025 12:50:09 +0200 Subject: [PATCH] zmiany ux i w panelu --- alters.txt | 5 +- app.py | 144 ++++++++++++++++++++++++++++++- static/js/live.js | 16 ++++ templates/admin/admin_panel.html | 116 +++++++++++++++++-------- templates/admin/edit_list.html | 18 ++++ templates/base.html | 16 +++- templates/index.html | 24 +++--- templates/list.html | 28 ++++-- templates/list_guest.html | 1 - 9 files changed, 302 insertions(+), 66 deletions(-) create mode 100644 templates/admin/edit_list.html diff --git a/alters.txt b/alters.txt index 92ca219..d619871 100644 --- a/alters.txt +++ b/alters.txt @@ -6,4 +6,7 @@ CREATE TABLE IF NOT EXISTS suggested_product ( # NOTATKI ALTER TABLE item -ADD COLUMN note TEXT; \ No newline at end of file +ADD COLUMN note TEXT; + +# NOWE FUNKCJE ADMINA +ALTER TABLE shopping_list ADD COLUMN is_archived BOOLEAN DEFAULT FALSE; diff --git a/app.py b/app.py index d16e2c0..a31b52a 100644 --- a/app.py +++ b/app.py @@ -13,6 +13,8 @@ from config import Config from PIL import Image from werkzeug.utils import secure_filename from werkzeug.middleware.proxy_fix import ProxyFix +from sqlalchemy import func + app = Flask(__name__) app.config.from_object(Config) @@ -47,6 +49,7 @@ class ShoppingList(db.Model): share_token = db.Column(db.String(64), unique=True, nullable=True) expires_at = db.Column(db.DateTime, nullable=True) owner = db.relationship('User', backref='lists', lazy=True) + is_archived = db.Column(db.Boolean, default=False) class Item(db.Model): id = db.Column(db.Integer, primary_key=True) @@ -77,6 +80,14 @@ app.register_blueprint(static_bp) def allowed_file(filename): return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS +def get_progress(list_id): + items = Item.query.filter_by(list_id=list_id).all() + total_count = len(items) + purchased_count = len([i for i in items if i.purchased]) + percent = (purchased_count / total_count * 100) if total_count > 0 else 0 + return purchased_count, total_count, percent + + @login_manager.user_loader def load_user(user_id): return User.query.get(int(user_id)) @@ -195,12 +206,24 @@ def view_list(list_id): shopping_list = ShoppingList.query.get_or_404(list_id) items = Item.query.filter_by(list_id=list_id).all() + total_count = len(items) + purchased_count = len([i for i in items if i.purchased]) + percent = (purchased_count / total_count * 100) if total_count > 0 else 0 + receipt_pattern = f"list_{list_id}" all_files = os.listdir(app.config['UPLOAD_FOLDER']) - receipt_files = [f for f in all_files if receipt_pattern in f] - return render_template('list.html', list=shopping_list, items=items, receipt_files=receipt_files) + return render_template( + 'list.html', + list=shopping_list, + items=items, + receipt_files=receipt_files, + total_count=total_count, + purchased_count=purchased_count, + percent=percent + ) + @app.route('/share/') def share_list(token): @@ -297,11 +320,56 @@ def update_note(item_id): def admin_panel(): if not current_user.is_admin: return redirect(url_for('index_guest')) + user_count = User.query.count() list_count = ShoppingList.query.count() item_count = Item.query.count() all_lists = ShoppingList.query.options(db.joinedload(ShoppingList.owner)).all() - return render_template('admin/admin_panel.html', user_count=user_count, list_count=list_count, item_count=item_count, all_lists=all_lists) + + # Pobierz folder uploadów + all_files = os.listdir(app.config['UPLOAD_FOLDER']) + + enriched_lists = [] + for l in all_lists: + items = Item.query.filter_by(list_id=l.id).all() + total_count = len(items) + purchased_count = len([i for i in items if i.purchased]) + percent = (purchased_count / total_count * 100) if total_count > 0 else 0 + comments_count = len([i for i in items if i.note and i.note.strip() != '']) + purchased_items_count = Item.query.filter_by(purchased=True).count() + + + receipt_pattern = f"list_{l.id}" + receipt_files = [f for f in all_files if receipt_pattern in f] + + enriched_lists.append({ + 'list': l, + 'total_count': total_count, + 'purchased_count': purchased_count, + 'percent': round(percent), + 'comments_count': comments_count, + 'receipts_count': len(receipt_files) + }) + + + top_products = ( + db.session.query(Item.name, func.count(Item.id).label('count')) + .filter(Item.purchased == True) + .group_by(Item.name) + .order_by(func.count(Item.id).desc()) + .limit(5) + .all() + ) + + return render_template( + 'admin/admin_panel.html', + user_count=user_count, + list_count=list_count, + item_count=item_count, + purchased_items_count=purchased_items_count, + enriched_lists=enriched_lists, + top_products=top_products, + ) @app.route('/admin/delete_list/') @login_required @@ -392,7 +460,6 @@ def admin_receipts(): upload_folder=app.config['UPLOAD_FOLDER'] ) - @app.route('/admin/delete_receipt/') @login_required def delete_receipt(filename): @@ -406,6 +473,56 @@ def delete_receipt(filename): flash('Plik nie istnieje', 'danger') return redirect(url_for('admin_receipts')) +@app.route('/admin/edit_list/', methods=['GET', 'POST']) +@login_required +def edit_list(list_id): + if not current_user.is_admin: + return redirect(url_for('index_guest')) + l = ShoppingList.query.get_or_404(list_id) + if request.method == 'POST': + new_title = request.form.get('title') + if new_title: + l.title = new_title + db.session.commit() + flash('Zaktualizowano tytuł listy', 'success') + return redirect(url_for('admin_panel')) + return render_template('admin/edit_list.html', list=l) + +@app.route('/admin/delete_selected_lists', methods=['POST']) +@login_required +def delete_selected_lists(): + if not current_user.is_admin: + return redirect(url_for('index_guest')) + ids = request.form.getlist('list_ids') + for list_id in ids: + lst = ShoppingList.query.get(int(list_id)) + if lst: + Item.query.filter_by(list_id=lst.id).delete() + db.session.delete(lst) + db.session.commit() + flash('Usunięto wybrane listy', 'success') + return redirect(url_for('admin_panel')) + +@app.route('/admin/archive_list/') +@login_required +def archive_list(list_id): + if not current_user.is_admin: + return redirect(url_for('index_guest')) + l = ShoppingList.query.get_or_404(list_id) + l.is_archived = True + db.session.commit() + flash('Lista oznaczona jako archiwalna', 'success') + return redirect(url_for('admin_panel')) + +@app.route('/admin/delete_all_items') +@login_required +def delete_all_items(): + if not current_user.is_admin: + return redirect(url_for('index_guest')) + Item.query.delete() + db.session.commit() + flash('Usunięto wszystkie produkty', 'success') + return redirect(url_for('admin_panel')) @socketio.on('delete_item') def handle_delete_item(data): @@ -460,7 +577,16 @@ def handle_check_item(data): item.purchased = True item.purchased_at = datetime.utcnow() db.session.commit() + + purchased_count, total_count, percent = get_progress(item.list_id) + emit('item_checked', {'item_id': item.id}, to=str(item.list_id)) + emit('progress_updated', { + 'purchased_count': purchased_count, + 'total_count': total_count, + 'percent': percent + }, to=str(item.list_id)) + @socketio.on('uncheck_item') def handle_uncheck_item(data): @@ -469,7 +595,15 @@ def handle_uncheck_item(data): item.purchased = False item.purchased_at = None db.session.commit() + + purchased_count, total_count, percent = get_progress(item.list_id) + emit('item_unchecked', {'item_id': item.id}, to=str(item.list_id)) + emit('progress_updated', { + 'purchased_count': purchased_count, + 'total_count': total_count, + 'percent': percent + }, to=str(item.list_id)) @socketio.on('update_note') def handle_update_note(data): @@ -481,6 +615,8 @@ def handle_update_note(data): db.session.commit() emit('note_updated', {'item_id': item_id, 'note': note}, to=str(item.list_id)) + + @app.cli.command('create_db') def create_db(): db.create_all() diff --git a/static/js/live.js b/static/js/live.js index f01aa7b..97429e3 100644 --- a/static/js/live.js +++ b/static/js/live.js @@ -119,6 +119,22 @@ function setupList(listId, username) { updateProgressBar(); }); + + socket.on('progress_updated', function(data) { + const progressBar = document.getElementById('progress-bar'); + if (progressBar) { + progressBar.style.width = data.percent + '%'; + progressBar.setAttribute('aria-valuenow', data.percent); + progressBar.textContent = Math.round(data.percent) + '%'; + } + + const progressTitle = document.getElementById('progress-title'); + if (progressTitle) { + progressTitle.textContent = `📊 Postęp listy — ${data.purchased_count}/${data.total_count} kupionych (${Math.round(data.percent)}%)`; + } + }); + + socket.on('note_updated', data => { const itemEl = document.getElementById(`item-${data.item_id}`); if (itemEl) { diff --git a/templates/admin/admin_panel.html b/templates/admin/admin_panel.html index 04f0fa5..dceb4f8 100644 --- a/templates/admin/admin_panel.html +++ b/templates/admin/admin_panel.html @@ -7,53 +7,93 @@ ← Powrót do strony głównej + +

👤 Liczba użytkowników: {{ user_count }}

-

📝 Liczba list: {{ list_count }}

+

📝 Liczba list zakupowych: {{ list_count }}

🛒 Liczba produktów: {{ item_count }}

+

✅ Zakupionych produktów: {{ purchased_items_count }}

-
- 👥 Lista użytkowników - ➕ Dodaj użytkownika - 📸 Wszystkie paragony - 🗑️ Usuń wszystkie listy +{% if top_products %} +
+
+
🔥 Najczęściej kupowane produkty:
+
    + {% for name, count in top_products %} +
  • {{ name }} — {{ count }}×
  • + {% endfor %} +
+
+{% endif %}

📄 Wszystkie listy zakupowe

-
- - - - - - - - - - - - {% for l in all_lists %} - - - - - - - - {% endfor %} - -
IDTytułUtworzonoWłaściciel (ID / nazwa)Akcje
{{ l.id }}{{ l.title }}{{ l.created_at.strftime('%Y-%m-%d %H:%M') if l.created_at else '-' }} - {% if l.owner_id %} - {{ l.owner_id }} / {{ l.owner.username if l.owner else 'Brak użytkownika' }} - {% else %} - - - {% endif %} - - 🗑️ Usuń -
-
+
+
+ + + + + + + + + + + + + + + + + {% for e in enriched_lists %} + {% set l = e.list %} + + + + + + + + + + + + + {% endfor %} + +
IDTytułUtworzonoWłaścicielProduktyWypełnienieKomentarzeParagonyAkcje
{{ l.id }} + {{ l.title }} + {{ l.created_at.strftime('%Y-%m-%d %H:%M') if l.created_at else '-' }} + {% if l.owner_id %} + {{ l.owner_id }} / {{ l.owner.username if l.owner else 'Brak użytkownika' }} + {% else %} + - + {% endif %} + {{ e.total_count }}{{ e.purchased_count }}/{{ e.total_count }} ({{ e.percent }}%){{ e.comments_count }}{{ e.receipts_count }} + ✏️ Edytuj + 📥 Archiwizuj + 🗑️ Usuń +
+
+ +
+ + {% endblock %} diff --git a/templates/admin/edit_list.html b/templates/admin/edit_list.html new file mode 100644 index 0000000..8283b28 --- /dev/null +++ b/templates/admin/edit_list.html @@ -0,0 +1,18 @@ +{% extends 'base.html' %} +{% block title %}Edytuj listę{% endblock %} +{% block content %} + +
+

✏️ Edytuj tytuł listy

+ ← Powrót +
+ +
+
+ + +
+ +
+ +{% endblock %} diff --git a/templates/base.html b/templates/base.html index a430d1f..25b9705 100644 --- a/templates/base.html +++ b/templates/base.html @@ -14,8 +14,18 @@ + +
diff --git a/templates/index.html b/templates/index.html index cad1524..3cbfe16 100644 --- a/templates/index.html +++ b/templates/index.html @@ -3,26 +3,28 @@ {% block content %}
-

Twoje listy zakupów

- ← Powrót do panelu +

Stwórz nową listę

-
- - -
-
- - +
+ +
+ + +
+
+

Listy zakupów

+
+ {% if lists %}
    {% for l in lists %} @@ -31,7 +33,7 @@ {% set percent = (purchased_count / total_count * 100) if total_count > 0 else 0 %}
  • - {{ l.title }} + {{ l.title }} (Autor: {{ l.owner.username }})
    {% if current_user.is_authenticated %} 📄 Otwórz @@ -42,7 +44,7 @@
    -
    {{ purchased_count }}/{{ total_count }} ({{ percent|round(0) }}%)
    +
    Produkty: {{ purchased_count }}/{{ total_count }} ({{ percent|round(0) }}%)
  • {% endfor %} diff --git a/templates/list.html b/templates/list.html index 48d680e..beaf40c 100644 --- a/templates/list.html +++ b/templates/list.html @@ -3,27 +3,37 @@ {% block content %}
    -

    {{ list.title }}

    +

    Lista: {{ list.title }}

    ← Powrót do list
    -
    -
    - Udostępnij link: - {{ request.url_root }}share/{{ list.share_token }} +
    +
    + 🔗 Udostępnij link:
    + + {{ request.url_root }}share/{{ list.share_token }} +
    -
    -
    -
    0%
    +
    + 📊 Postęp listy — {{ purchased_count }}/{{ total_count }} kupionych ({{ percent|round(0) }}%) +
    +
    +
    + {{ percent|round(0) }}% +
    +
      {% for item in items %}
    • diff --git a/templates/list_guest.html b/templates/list_guest.html index 2515998..b84bb37 100644 --- a/templates/list_guest.html +++ b/templates/list_guest.html @@ -4,7 +4,6 @@

      🛍️ {{ list.title }} (Gość)

      - ← Powrót