diff --git a/shopping_app/helpers.py b/shopping_app/helpers.py index fd90bb9..642e69b 100644 --- a/shopping_app/helpers.py +++ b/shopping_app/helpers.py @@ -821,7 +821,7 @@ def get_progress(list_id: int) -> tuple[int, int, float]: result = ( db.session.query( func.count(Item.id), - func.sum(case((Item.purchased == True, 1), else_=0)), + func.sum(case(((Item.purchased == True) & (Item.not_purchased == False), 1), else_=0)), ) .filter(Item.list_id == list_id) .first() diff --git a/shopping_app/routes_admin.py b/shopping_app/routes_admin.py index d458f89..8de99ac 100644 --- a/shopping_app/routes_admin.py +++ b/shopping_app/routes_admin.py @@ -682,6 +682,12 @@ def edit_list(list_id): elif action == "toggle_purchased": item = get_valid_item_or_404(request.form.get("item_id"), list_id) item.purchased = not item.purchased + if item.purchased: + item.not_purchased = False + item.not_purchased_reason = None + item.purchased_at = utcnow() + else: + item.purchased_at = None db.session.commit() flash("Zmieniono status oznaczenia produktu", "success") return redirect(url_for("edit_list", list_id=list_id)) diff --git a/shopping_app/routes_main.py b/shopping_app/routes_main.py index 4f9fb88..d86dab1 100644 --- a/shopping_app/routes_main.py +++ b/shopping_app/routes_main.py @@ -115,7 +115,7 @@ def main_page(): db.session.query( Item.list_id, func.count(Item.id).label("total_count"), - func.sum(case((Item.purchased == True, 1), else_=0)).label( + func.sum(case((((Item.purchased == True) & (Item.not_purchased == False)), 1), else_=0)).label( "purchased_count" ), func.sum(case((Item.not_purchased == True, 1), else_=0)).label( @@ -163,6 +163,28 @@ def main_page(): l.total_expense = 0 l.category_badges = [] + def build_progress_summary(lists_): + total_lists = len(lists_) + total_products = sum(getattr(l, "total_count", 0) or 0 for l in lists_) + purchased_products = sum(getattr(l, "purchased_count", 0) or 0 for l in lists_) + not_purchased_products = sum(getattr(l, "not_purchased_count", 0) or 0 for l in lists_) + total_expense = float(sum((getattr(l, "total_expense", 0) or 0) for l in lists_)) + completion_percent = ( + (purchased_products / total_products) * 100 if total_products > 0 else 0 + ) + return { + "list_count": total_lists, + "total_products": total_products, + "purchased_products": purchased_products, + "not_purchased_products": not_purchased_products, + "remaining_products": max(total_products - purchased_products - not_purchased_products, 0), + "total_expense": round(total_expense, 2), + "completion_percent": completion_percent, + } + + user_lists_summary = build_progress_summary(user_lists) + accessible_lists_summary = build_progress_summary(accessible_lists) + expiring_lists = get_expiring_lists_for_user(current_user.id) if current_user.is_authenticated else [] templates = (ListTemplate.query.filter_by(is_active=True, created_by=current_user.id).order_by(ListTemplate.name.asc()).all() if current_user.is_authenticated else []) @@ -178,6 +200,8 @@ def main_page(): selected_month=month_str, expiring_lists=expiring_lists, templates=templates, + user_lists_summary=user_lists_summary, + accessible_lists_summary=accessible_lists_summary, ) diff --git a/shopping_app/sockets.py b/shopping_app/sockets.py index 95468a7..1b96f89 100644 --- a/shopping_app/sockets.py +++ b/shopping_app/sockets.py @@ -345,6 +345,8 @@ def handle_check_item(data): if item: item.purchased = True item.purchased_at = datetime.now(UTC) + item.not_purchased = False + item.not_purchased_reason = None log_list_activity(item.list_id, 'item_checked', item_name=item.name, actor=current_user if current_user.is_authenticated else None, actor_name=current_user.username if current_user.is_authenticated else 'Gość') db.session.commit() @@ -470,6 +472,8 @@ def handle_mark_not_purchased(data): if item: item.not_purchased = True item.not_purchased_reason = reason + item.purchased = False + item.purchased_at = None log_list_activity(item.list_id, 'item_marked_not_purchased', item_name=item.name, actor=current_user if current_user.is_authenticated else None, actor_name=current_user.username if current_user.is_authenticated else 'Gość', details=reason or None) db.session.commit() emit( diff --git a/shopping_app/static/css/style.css b/shopping_app/static/css/style.css index 06fcd0c..01ec2fc 100644 --- a/shopping_app/static/css/style.css +++ b/shopping_app/static/css/style.css @@ -5701,3 +5701,125 @@ body:not(.sorting-active) .drag-handle { display: none; } } + +/* --- Main page list progress consistency --- */ +.endpoint-main_page .list-group-item { + display: flex; + flex-direction: column; + align-items: stretch; + justify-content: flex-start; +} + +.endpoint-main_page .main-list-progress-wrap { + display: block; + width: 100%; + flex: 0 0 100%; + margin-top: 0.8rem !important; +} + +.endpoint-main_page .list-group-item > .main-list-row + .main-list-progress-wrap { + align-self: stretch; +} + +.endpoint-main_page .main-list-progress { + width: 100%; + height: 16px; + margin-top: 0 !important; + border: 1px solid rgba(255, 255, 255, 0.08); + background: + linear-gradient(180deg, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.02)), + var(--dark-700) !important; + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.05), + 0 4px 10px rgba(0, 0, 0, 0.18); +} + +.endpoint-main_page .main-list-progress .progress-bar.bg-success { + background: linear-gradient(135deg, rgba(40, 199, 111, 0.98), rgba(22, 163, 74, 0.98)) !important; +} + +.endpoint-main_page .main-list-progress .progress-bar.bg-warning { + background: linear-gradient(135deg, rgba(245, 189, 65, 0.98), rgba(217, 119, 6, 0.98)) !important; +} + +.endpoint-main_page .main-list-progress .progress-bar.bg-transparent { + background: rgba(255, 255, 255, 0.08) !important; +} + +.endpoint-main_page .main-list-progress__label { + max-width: calc(100% - 0.85rem); + padding: 0 0.45rem; + overflow: hidden; + text-overflow: ellipsis; + text-align: center; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.45); + letter-spacing: 0.01em; +} + +@media (max-width: 575.98px) { + .endpoint-main_page .main-list-progress { + height: 15px; + } + + .endpoint-main_page .main-list-progress__label { + font-size: 0.64rem; + } +} + +/* --- Main page progress summary cards --- */ +.endpoint-main_page #mainStatsCollapse.collapsing, +.endpoint-main_page #mainStatsCollapse.show { + overflow: visible; +} + +.endpoint-main_page .main-summary-card { + height: 100%; + padding: 1rem 1rem 1.05rem; + border-radius: 1rem; + border: 1px solid rgba(255, 255, 255, 0.08); + background: linear-gradient(180deg, rgba(255,255,255,0.04), rgba(255,255,255,0.02)), rgba(9, 16, 28, 0.88); + box-shadow: 0 12px 28px rgba(0, 0, 0, 0.2); +} + +.endpoint-main_page .main-summary-card__eyebrow { + font-size: 0.72rem; + text-transform: uppercase; + letter-spacing: 0.08em; + color: rgba(255, 255, 255, 0.65); + margin-bottom: 0.2rem; +} + +.endpoint-main_page .main-summary-card__title { + font-size: 1.05rem; +} + +.endpoint-main_page .main-summary-stats { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 0.7rem; +} + +.endpoint-main_page .main-summary-stat { + padding: 0.65rem 0.75rem; + border-radius: 0.85rem; + background: rgba(255, 255, 255, 0.04); + border: 1px solid rgba(255, 255, 255, 0.06); +} + +.endpoint-main_page .main-summary-stat__label { + display: block; + font-size: 0.73rem; + color: rgba(255, 255, 255, 0.66); + margin-bottom: 0.15rem; +} + + +@media (max-width: 575.98px) { + .endpoint-main_page .main-summary-card { + padding: 0.9rem; + } + + .endpoint-main_page .main-summary-stats { + grid-template-columns: 1fr; + } +} diff --git a/shopping_app/templates/_list_progress.html b/shopping_app/templates/_list_progress.html new file mode 100644 index 0000000..04e3cae --- /dev/null +++ b/shopping_app/templates/_list_progress.html @@ -0,0 +1,31 @@ +{% set total_count = total_count or 0 %} +{% set purchased_count = purchased_count or 0 %} +{% set not_purchased_count = not_purchased_count or 0 %} +{% set accounted_count = purchased_count + not_purchased_count %} +{% set percent = (purchased_count / total_count * 100) if total_count > 0 else 0 %} +{% set purchased_percent = (purchased_count / total_count * 100) if total_count > 0 else 0 %} +{% set not_purchased_percent = (not_purchased_count / total_count * 100) if total_count > 0 else 0 %} +{% set remaining_count = (total_count - accounted_count) if total_count > accounted_count else 0 %} +{% set remaining_percent = (remaining_count / total_count * 100) if total_count > 0 else 100 %} + +
+
+
+ +
+ +
+ + + Produkty: {{ purchased_count }}/{{ total_count }} ({{ percent|round(0) }}%) + {% if total_expense > 0 %} — 💸 {{ '%.2f'|format(total_expense) }} PLN{% endif %} + +
+
diff --git a/shopping_app/templates/main.html b/shopping_app/templates/main.html index 80b41b4..97879b1 100644 --- a/shopping_app/templates/main.html +++ b/shopping_app/templates/main.html @@ -69,12 +69,61 @@ +{% macro render_summary_panel(title, summary, accent='success') -%} +
+
+
+
+
Postęp
+

{{ title }}

+
+ Listy: {{ summary.list_count }} +
+ + {% with total_count=summary.total_products, purchased_count=summary.purchased_products, not_purchased_count=summary.not_purchased_products, total_expense=summary.total_expense %} + {% include '_list_progress.html' %} + {% endwith %} + +
+
+ Kupione + {{ summary.purchased_products }} +
+
+ Niekupione + {{ summary.not_purchased_products }} +
+
+ Nieoznaczone + {{ summary.remaining_products }} +
+
+ Wydatki + {{ '%.2f'|format(summary.total_expense) }} PLN +
+
+
+
+{%- endmacro %} + {% if current_user.is_authenticated %} -

- Twoje listy - + + +
+
+ {{ render_summary_panel('Twoje listy', user_lists_summary) }} + {{ render_summary_panel('Udostępnione i publiczne', accessible_lists_summary, 'info') }} +
+
+ +

+ Twoje listy

{% if user_lists %} @@ -127,31 +176,28 @@ -
-
- - {% set not_purchased_count = l.not_purchased_count if l.total_count else 0 %} -
- -
- - - Produkty: {{ purchased_count }}/{{ total_count }} ({{ percent|round(0) }}%) - {% if l.total_expense > 0 %} — 💸 {{ '%.2f'|format(l.total_expense) }} PLN{% endif %} - -
+ {% with total_count=total_count, purchased_count=purchased_count, not_purchased_count=l.not_purchased_count, total_expense=l.total_expense %} + {% include '_list_progress.html' %} + {% endwith %} {% endfor %} {% else %}

Nie utworzono żadnej listy

{% endif %} +{% else %} +
+ +
+
+
+
+ {{ render_summary_panel('Publiczne listy innych użytkowników', accessible_lists_summary, 'info') }} +
+
+
{% endif %}

@@ -201,25 +247,9 @@ -
-
- - {% set not_purchased_count = l.not_purchased_count if l.total_count else 0 %} -
- -
- - - Produkty: {{ purchased_count }}/{{ total_count }} ({{ percent|round(0) }}%) - {% if l.total_expense > 0 %} — 💸 {{ '%.2f'|format(l.total_expense) }} PLN{% endif %} - -
+ {% with total_count=total_count, purchased_count=purchased_count, not_purchased_count=l.not_purchased_count, total_expense=l.total_expense %} + {% include '_list_progress.html' %} + {% endwith %} {% endfor %}