duzo zmian ux

This commit is contained in:
Mateusz Gruszczyński
2025-07-07 12:59:18 +02:00
parent 8854d5b558
commit 86bf3e1a86
10 changed files with 521 additions and 62 deletions

186
app.py
View File

@@ -3,7 +3,7 @@ import secrets
import time
import mimetypes
from datetime import datetime, timedelta
from flask import Flask, render_template, redirect, url_for, request, flash, Blueprint, send_from_directory, request, abort, session
from flask import Flask, render_template, redirect, url_for, request, flash, Blueprint, send_from_directory, request, abort, session, jsonify, make_response
from markupsafe import Markup
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user, current_user
@@ -270,11 +270,15 @@ def index_guest():
now = datetime.utcnow()
if current_user.is_authenticated:
# Twoje listy
# Twoje listy aktywne
user_lists = ShoppingList.query.filter_by(owner_id=current_user.id, is_archived=False).filter(
(ShoppingList.expires_at == None) | (ShoppingList.expires_at > now)
).order_by(ShoppingList.created_at.desc()).all()
# Zarchiwizowane listy
archived_lists = ShoppingList.query.filter_by(owner_id=current_user.id, is_archived=True).order_by(ShoppingList.created_at.desc()).all()
# Publiczne listy innych użytkowników
public_lists = ShoppingList.query.filter(
ShoppingList.is_public == True,
ShoppingList.owner_id != current_user.id,
@@ -283,20 +287,22 @@ def index_guest():
).order_by(ShoppingList.created_at.desc()).all()
else:
user_lists = []
archived_lists = []
public_lists = ShoppingList.query.filter(
ShoppingList.is_public == True,
((ShoppingList.expires_at == None) | (ShoppingList.expires_at > now)),
ShoppingList.is_archived == False
).order_by(ShoppingList.created_at.desc()).all()
for l in user_lists + public_lists:
# Dodajemy dane o przedmiotach i wydatkach
for l in user_lists + public_lists + archived_lists:
items = Item.query.filter_by(list_id=l.id).all()
l.total_count = len(items)
l.purchased_count = len([i for i in items if i.purchased])
expenses = Expense.query.filter_by(list_id=l.id).all()
l.total_expense = sum(e.amount for e in expenses)
return render_template("main.html", user_lists=user_lists, public_lists=public_lists)
return render_template("main.html", user_lists=user_lists, public_lists=public_lists, archived_lists=archived_lists)
@app.route('/system-auth', methods=['GET', 'POST'])
def system_auth():
@@ -322,16 +328,25 @@ def system_auth():
flash(f'Nieprawidłowe hasło do systemu. Pozostało prób: {remaining}', 'warning')
return render_template('system_auth.html')
@app.route('/archive_my_list/<int:list_id>')
@app.route('/toggle_archive_list/<int:list_id>')
@login_required
def archive_my_list(list_id):
def toggle_archive_list(list_id):
l = ShoppingList.query.get_or_404(list_id)
if l.owner_id != current_user.id:
flash('Nie masz uprawnień do tej listy', 'danger')
return redirect(url_for('index_guest'))
l.is_archived = True
# Pobieramy parametr archive z query string
archive = request.args.get('archive', 'true').lower() == 'true'
if archive:
l.is_archived = True
flash(f'Lista „{l.title}” została zarchiwizowana.', 'success')
else:
l.is_archived = False
flash(f'Lista „{l.title}” została przywrócona.', 'success')
db.session.commit()
flash('Lista została zarchiwizowana', 'success')
return redirect(url_for('index_guest'))
@app.route('/edit_my_list/<int:list_id>', methods=['GET', 'POST'])
@@ -378,7 +393,6 @@ def toggle_visibility(list_id):
return redirect(url_for('index_guest'))
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
@@ -485,7 +499,7 @@ def guest_list(list_id):
@login_required
def copy_list(list_id):
original = ShoppingList.query.get_or_404(list_id)
token = secrets.token_hex(16)
token = secrets.token_hex(8)
new_list = ShoppingList(title=original.title + ' (Kopia)', owner_id=current_user.id, share_token=token)
db.session.add(new_list)
db.session.commit()
@@ -547,7 +561,8 @@ def uploaded_file(filename):
def admin_panel():
if not current_user.is_admin:
return redirect(url_for('index_guest'))
now = datetime.utcnow()
user_count = User.query.count()
list_count = ShoppingList.query.count()
item_count = Item.query.count()
@@ -614,6 +629,7 @@ def admin_panel():
total_expense_sum=total_expense_sum,
year_expense_sum=year_expense_sum,
month_expense_sum=month_expense_sum,
now=now
)
@app.route('/admin/delete_list/<int:list_id>')
@@ -805,7 +821,155 @@ def edit_list(list_id):
return render_template('admin/edit_list.html', list=l, total_expense=total_expense, users=users)
@app.route('/admin/products')
@login_required
def list_products():
if not current_user.is_admin:
return redirect(url_for('index_guest'))
items = Item.query.order_by(Item.id.desc()).all()
users = User.query.all()
users_dict = {user.id: user.username for user in users}
# Wszystkie sugestie do słownika
suggestions = SuggestedProduct.query.all()
suggestions_dict = {s.name.lower(): s for s in suggestions}
return render_template(
'admin/list_products.html',
items=items,
users_dict=users_dict,
suggestions_dict=suggestions_dict
)
@app.route('/admin/sync_suggestion/<item_name>')
@login_required
def sync_suggestion(item_name):
if not current_user.is_admin:
return redirect(url_for('index_guest'))
existing = SuggestedProduct.query.filter(func.lower(SuggestedProduct.name) == item_name.lower()).first()
if not existing:
new_suggestion = SuggestedProduct(name=item_name)
db.session.add(new_suggestion)
db.session.commit()
flash(f'Utworzono sugestię dla produktu: {item_name}', 'success')
else:
flash(f'Sugestia dla produktu "{item_name}" już istnieje.', 'info')
return redirect(url_for('list_products'))
@app.route('/admin/delete_suggestion/<int:suggestion_id>')
@login_required
def delete_suggestion(suggestion_id):
if not current_user.is_admin:
return redirect(url_for('index_guest'))
suggestion = SuggestedProduct.query.get_or_404(suggestion_id)
db.session.delete(suggestion)
db.session.commit()
flash('Sugestia została usunięta', 'success')
return redirect(url_for('list_products'))
@app.route('/admin/expenses_data')
@login_required
def admin_expenses_data():
if not current_user.is_admin:
return jsonify({'error': 'Unauthorized'}), 403
range_type = request.args.get('range', 'monthly')
start_date_str = request.args.get('start_date')
end_date_str = request.args.get('end_date')
now = datetime.utcnow()
labels = []
expenses = []
if start_date_str and end_date_str:
start_date = datetime.strptime(start_date_str, '%Y-%m-%d')
end_date = datetime.strptime(end_date_str, '%Y-%m-%d')
expenses_query = (
db.session.query(
extract('year', Expense.added_at).label('year'),
extract('month', Expense.added_at).label('month'),
func.sum(Expense.amount).label('total')
)
.filter(Expense.added_at >= start_date, Expense.added_at <= end_date)
.group_by('year', 'month')
.order_by('year', 'month')
.all()
)
for row in expenses_query:
label = f"{int(row.month):02d}/{int(row.year)}"
labels.append(label)
expenses.append(round(row.total, 2))
response = make_response(jsonify({'labels': labels, 'expenses': expenses}))
response.headers["Cache-Control"] = "no-store, no-cache, must-revalidate, max-age=0"
return response
if range_type == 'monthly':
for i in range(11, -1, -1):
year = (now - timedelta(days=i*30)).year
month = (now - timedelta(days=i*30)).month
label = f"{month:02d}/{year}"
labels.append(label)
month_sum = (
db.session.query(func.sum(Expense.amount))
.filter(extract('year', Expense.added_at) == year)
.filter(extract('month', Expense.added_at) == month)
.scalar() or 0
)
expenses.append(round(month_sum, 2))
elif range_type == 'quarterly':
for i in range(3, -1, -1):
quarter_start = now - timedelta(days=i*90)
year = quarter_start.year
quarter = (quarter_start.month - 1) // 3 + 1
label = f"Q{quarter}/{year}"
quarter_sum = (
db.session.query(func.sum(Expense.amount))
.filter(extract('year', Expense.added_at) == year)
.filter((extract('month', Expense.added_at) - 1)//3 + 1 == quarter)
.scalar() or 0
)
labels.append(label)
expenses.append(round(quarter_sum, 2))
elif range_type == 'halfyearly':
for i in range(1, -1, -1):
half_start = now - timedelta(days=i*180)
year = half_start.year
half = 1 if half_start.month <= 6 else 2
label = f"H{half}/{year}"
half_sum = (
db.session.query(func.sum(Expense.amount))
.filter(extract('year', Expense.added_at) == year)
.filter(
(extract('month', Expense.added_at) <= 6) if half == 1 else (extract('month', Expense.added_at) > 6)
)
.scalar() or 0
)
labels.append(label)
expenses.append(round(half_sum, 2))
elif range_type == 'yearly':
for i in range(4, -1, -1):
year = now.year - i
label = str(year)
year_sum = (
db.session.query(func.sum(Expense.amount))
.filter(extract('year', Expense.added_at) == year)
.scalar() or 0
)
labels.append(label)
expenses.append(round(year_sum, 2))
response = make_response(jsonify({'labels': labels, 'expenses': expenses}))
response.headers["Cache-Control"] = "no-store, no-cache"
return response
# chyba do usuniecia przeniesione na eventy socket.io
@app.route('/update-note/<int:item_id>', methods=['POST'])