duzo zmian ux
This commit is contained in:
186
app.py
186
app.py
@@ -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'])
|
||||
|
Reference in New Issue
Block a user