zmiany w panelu
This commit is contained in:
161
app.py
161
app.py
@@ -839,15 +839,27 @@ def delete_user(user_id):
|
||||
flash('Użytkownik usunięty', 'success')
|
||||
return redirect(url_for('list_users'))
|
||||
|
||||
@app.route('/admin/receipts')
|
||||
@app.route('/admin/receipts/<id>')
|
||||
@login_required
|
||||
@admin_required
|
||||
def admin_receipts():
|
||||
def admin_receipts(id):
|
||||
all_files = os.listdir(app.config['UPLOAD_FOLDER'])
|
||||
image_files = [f for f in all_files if allowed_file(f)]
|
||||
|
||||
if id == "all":
|
||||
filtered_files = image_files
|
||||
else:
|
||||
try:
|
||||
list_id = int(id)
|
||||
receipt_prefix = f"list_{list_id}_"
|
||||
filtered_files = [f for f in image_files if f.startswith(receipt_prefix)]
|
||||
except ValueError:
|
||||
flash("Nieprawidłowe ID listy.", "danger")
|
||||
return redirect(url_for('admin_panel'))
|
||||
|
||||
return render_template(
|
||||
'admin/receipts.html',
|
||||
image_files=image_files,
|
||||
image_files=filtered_files,
|
||||
upload_folder=app.config['UPLOAD_FOLDER']
|
||||
)
|
||||
|
||||
@@ -861,6 +873,10 @@ def delete_receipt(filename):
|
||||
flash('Plik usunięty', 'success')
|
||||
else:
|
||||
flash('Plik nie istnieje', 'danger')
|
||||
|
||||
next_url = request.args.get('next')
|
||||
if next_url:
|
||||
return redirect(next_url)
|
||||
return redirect(url_for('admin_receipts'))
|
||||
|
||||
@app.route('/admin/delete_selected_lists', methods=['POST'])
|
||||
@@ -879,16 +895,6 @@ def delete_selected_lists():
|
||||
flash('Usunięto wybrane listy', 'success')
|
||||
return redirect(url_for('admin_panel'))
|
||||
|
||||
@app.route('/admin/archive_list/<int:list_id>')
|
||||
@login_required
|
||||
@admin_required
|
||||
def archive_list(list_id):
|
||||
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
|
||||
@admin_required
|
||||
@@ -905,55 +911,115 @@ def edit_list(list_id):
|
||||
l = ShoppingList.query.get_or_404(list_id)
|
||||
expenses = Expense.query.filter_by(list_id=list_id).all()
|
||||
total_expense = sum(e.amount for e in expenses)
|
||||
|
||||
users = User.query.all()
|
||||
items = Item.query.filter_by(list_id=list_id).order_by(Item.id.desc()).all()
|
||||
|
||||
# Pobranie listy plików paragonów
|
||||
receipt_pattern = f"list_{list_id}_"
|
||||
all_files = os.listdir(app.config['UPLOAD_FOLDER'])
|
||||
receipts = [f for f in all_files if f.startswith(receipt_pattern)]
|
||||
|
||||
if request.method == 'POST':
|
||||
new_title = request.form.get('title')
|
||||
new_amount_str = request.form.get('amount')
|
||||
is_archived = 'archived' in request.form
|
||||
new_owner_id = request.form.get('owner_id')
|
||||
action = request.form.get('action')
|
||||
|
||||
if new_title and new_title.strip():
|
||||
l.title = new_title.strip()
|
||||
if action == 'save':
|
||||
new_title = request.form.get('title', '').strip()
|
||||
new_amount_str = request.form.get('amount')
|
||||
is_archived = 'archived' in request.form
|
||||
is_public = 'public' in request.form
|
||||
new_owner_id = request.form.get('owner_id')
|
||||
|
||||
l.is_archived = is_archived
|
||||
if new_title:
|
||||
l.title = new_title
|
||||
|
||||
if new_owner_id:
|
||||
try:
|
||||
new_owner_id_int = int(new_owner_id)
|
||||
if User.query.get(new_owner_id_int):
|
||||
l.owner_id = new_owner_id_int
|
||||
else:
|
||||
flash('Wybrany użytkownik nie istnieje', 'danger')
|
||||
l.is_archived = is_archived
|
||||
l.is_public = is_public
|
||||
|
||||
if new_owner_id:
|
||||
try:
|
||||
new_owner_id_int = int(new_owner_id)
|
||||
if User.query.get(new_owner_id_int):
|
||||
l.owner_id = new_owner_id_int
|
||||
else:
|
||||
flash('Wybrany użytkownik nie istnieje', 'danger')
|
||||
return redirect(url_for('edit_list', list_id=list_id))
|
||||
except ValueError:
|
||||
flash('Niepoprawny ID użytkownika', 'danger')
|
||||
return redirect(url_for('edit_list', list_id=list_id))
|
||||
except ValueError:
|
||||
flash('Niepoprawny ID użytkownika', 'danger')
|
||||
return redirect(url_for('edit_list', list_id=list_id))
|
||||
|
||||
if new_amount_str:
|
||||
try:
|
||||
new_amount = float(new_amount_str)
|
||||
|
||||
if expenses:
|
||||
if new_amount_str:
|
||||
try:
|
||||
new_amount = float(new_amount_str)
|
||||
for expense in expenses:
|
||||
db.session.delete(expense)
|
||||
db.session.commit()
|
||||
new_expense = Expense(list_id=list_id, amount=new_amount)
|
||||
db.session.add(new_expense)
|
||||
db.session.commit()
|
||||
except ValueError:
|
||||
flash('Niepoprawna kwota', 'danger')
|
||||
return redirect(url_for('edit_list', list_id=list_id))
|
||||
|
||||
new_expense = Expense(list_id=list_id, amount=new_amount)
|
||||
db.session.add(new_expense)
|
||||
db.session.commit()
|
||||
flash('Zaktualizowano tytuł, właściciela, archiwizację i/lub kwotę wydatku', 'success')
|
||||
except ValueError:
|
||||
flash('Niepoprawna kwota', 'danger')
|
||||
return redirect(url_for('edit_list', list_id=list_id))
|
||||
else:
|
||||
db.session.commit()
|
||||
flash('Zaktualizowano tytuł, właściciela i/lub archiwizację', 'success')
|
||||
flash('Zapisano zmiany listy', 'success')
|
||||
return redirect(url_for('edit_list', list_id=list_id))
|
||||
|
||||
return redirect(url_for('admin_panel'))
|
||||
elif action == 'add_item':
|
||||
item_name = request.form.get('item_name', '').strip()
|
||||
quantity_str = request.form.get('quantity', '1')
|
||||
if not item_name:
|
||||
flash('Podaj nazwę produktu', 'danger')
|
||||
return redirect(url_for('edit_list', list_id=list_id))
|
||||
|
||||
return render_template('admin/edit_list.html', list=l, total_expense=total_expense, users=users)
|
||||
try:
|
||||
quantity = int(quantity_str)
|
||||
if quantity < 1:
|
||||
quantity = 1
|
||||
except ValueError:
|
||||
quantity = 1
|
||||
|
||||
new_item = Item(list_id=list_id, name=item_name, quantity=quantity, added_by=current_user.id)
|
||||
db.session.add(new_item)
|
||||
|
||||
if not SuggestedProduct.query.filter(func.lower(SuggestedProduct.name) == item_name.lower()).first():
|
||||
db.session.add(SuggestedProduct(name=item_name))
|
||||
|
||||
db.session.commit()
|
||||
flash('Dodano produkt', 'success')
|
||||
return redirect(url_for('edit_list', list_id=list_id))
|
||||
|
||||
elif action == 'delete_item':
|
||||
item_id = request.form.get('item_id')
|
||||
item = Item.query.get(item_id)
|
||||
if item and item.list_id == list_id:
|
||||
db.session.delete(item)
|
||||
db.session.commit()
|
||||
flash('Usunięto produkt', 'success')
|
||||
else:
|
||||
flash('Nie znaleziono produktu', 'danger')
|
||||
return redirect(url_for('edit_list', list_id=list_id))
|
||||
|
||||
elif action == 'toggle_purchased':
|
||||
item_id = request.form.get('item_id')
|
||||
item = Item.query.get(item_id)
|
||||
if item and item.list_id == list_id:
|
||||
item.purchased = not item.purchased
|
||||
db.session.commit()
|
||||
flash('Zmieniono status oznaczenia produktu', 'success')
|
||||
else:
|
||||
flash('Nie znaleziono produktu', 'danger')
|
||||
return redirect(url_for('edit_list', list_id=list_id))
|
||||
|
||||
# Przekazanie receipts do szablonu
|
||||
return render_template(
|
||||
'admin/edit_list.html',
|
||||
list=l,
|
||||
total_expense=total_expense,
|
||||
users=users,
|
||||
items=items,
|
||||
receipts=receipts,
|
||||
upload_folder=app.config['UPLOAD_FOLDER']
|
||||
)
|
||||
|
||||
@app.route('/admin/products')
|
||||
@login_required
|
||||
@@ -974,7 +1040,6 @@ def list_products():
|
||||
suggestions_dict=suggestions_dict
|
||||
)
|
||||
|
||||
|
||||
@app.route('/admin/sync_suggestion/<int:item_id>', methods=['POST'])
|
||||
@login_required
|
||||
def sync_suggestion_ajax(item_id):
|
||||
|
@@ -186,11 +186,12 @@ input.form-control {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
@media (max-width: 768px) {
|
||||
.info-bar-fixed {
|
||||
position: static;
|
||||
font-size: 0.85rem;
|
||||
padding: 8px 4px;
|
||||
border-radius: 10px 10px 0 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -20,7 +20,7 @@
|
||||
<a class="nav-link" href="/admin/users">👥 Zarządzanie użytkownikami</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/admin/receipts">📸 Paragony</a>
|
||||
<a class="nav-link" href="/admin/receipts/all">📸 Paragony</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/admin/products">🛍️ Produkty</a>
|
||||
@@ -141,7 +141,6 @@
|
||||
</td>
|
||||
<td class="d-flex flex-wrap gap-1">
|
||||
<a href="{{ url_for('edit_list', list_id=l.id) }}" class="btn btn-sm btn-outline-primary">✏️ Edytuj</a>
|
||||
<a href="{{ url_for('archive_list', list_id=l.id) }}" class="btn btn-sm btn-outline-secondary">📥 Archiwizuj</a>
|
||||
<a href="{{ url_for('delete_list', list_id=l.id) }}" class="btn btn-sm btn-outline-danger">🗑️ Usuń</a>
|
||||
</td>
|
||||
</tr>
|
||||
|
@@ -3,43 +3,164 @@
|
||||
{% block content %}
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center flex-wrap mb-4">
|
||||
<h2 class="mb-2">✏️ Edytuj listę #{{ list.id }}</h2>
|
||||
<h2 class="mb-2">🛠️ Edytuj listę #{{ list.id }}</h2>
|
||||
<a href="{{ url_for('admin_panel') }}" class="btn btn-outline-secondary">← Powrót</a>
|
||||
</div>
|
||||
|
||||
<form method="post">
|
||||
<div class="mb-4">
|
||||
<label for="title" class="form-label">Ustaw nazwę</label>
|
||||
<input type="text" class="form-control bg-dark text-white border-secondary rounded" id="title" name="title" value="{{ list.title }}" required>
|
||||
</div>
|
||||
<div class="card bg-dark text-white mb-5">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">📄 Podstawowe informacje</h4>
|
||||
<form method="post" class="mt-3">
|
||||
<input type="hidden" name="action" value="save">
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="title" class="form-label">Nazwa listy</label>
|
||||
<input type="text" class="form-control bg-dark text-white border-secondary rounded" id="title" name="title" value="{{ list.title }}" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="amount" class="form-label">Ustaw kwotę wydatku (PLN)</label>
|
||||
<input type="number" step="0.01" min="0" class="form-control bg-dark text-white border-secondary rounded" id="amount" name="amount" value="{{ '%.2f'|format(total_expense) }}">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="amount" class="form-label">Całkowity wydatek (PLN)</label>
|
||||
<input type="number" step="0.01" min="0" class="form-control bg-dark text-white border-secondary rounded" id="amount" name="amount" value="{{ '%.2f'|format(total_expense) }}">
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="owner_id" class="form-label">Zmień właściciela</label>
|
||||
<select class="form-select bg-dark text-white border-secondary rounded" id="owner_id" name="owner_id">
|
||||
{% for user in users %}
|
||||
<option value="{{ user.id }}" {% if list.owner_id == user.id %}selected{% endif %}>
|
||||
{{ user.username }}
|
||||
</option>
|
||||
<div class="mb-3">
|
||||
<label for="owner_id" class="form-label">Właściciel</label>
|
||||
<select class="form-select bg-dark text-white border-secondary" id="owner_id" name="owner_id">
|
||||
{% for user in users %}
|
||||
<option value="{{ user.id }}" {% if list.owner_id == user.id %}selected{% endif %}>{{ user.username }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-check form-switch mb-3">
|
||||
<input class="form-check-input" type="checkbox" id="archived" name="archived" {% if list.is_archived %}checked{% endif %}>
|
||||
<label class="form-check-label" for="archived">Archiwalna</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check form-switch mb-4">
|
||||
<input class="form-check-input" type="checkbox" id="public" name="public" {% if list.is_public %}checked{% endif %}>
|
||||
<label class="form-check-label" for="public">Publiczna</label>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="form-label">Link do udostępnienia</label>
|
||||
<input type="text" class="form-control bg-dark text-white border-secondary rounded" readonly value="{{ request.url_root }}share/{{ list.share_token }}">
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-success me-2">💾 Zapisz zmiany</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card bg-dark text-white mb-5">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">🛒 Produkty</h4>
|
||||
<form method="post" class="row g-2 mb-3">
|
||||
<input type="hidden" name="action" value="add_item">
|
||||
<div class="col-md-8">
|
||||
<input type="text" class="form-control bg-dark text-white border-secondary rounded" name="item_name" placeholder="Nazwa produktu" required>
|
||||
</div>
|
||||
<div class="col-md-1">
|
||||
<input type="number" class="form-control bg-dark text-white border-secondary rounded" name="quantity" min="1" value="1">
|
||||
</div>
|
||||
<div class="col-md-3 d-grid">
|
||||
<button type="submit" class="btn btn-outline-success">➕ Dodaj</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-dark table-bordered align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Nazwa</th>
|
||||
<th scope="col">Status</th>
|
||||
<th scope="col">Oznaczenie</th>
|
||||
<th scope="col">Usuń</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in items %}
|
||||
<tr>
|
||||
<td>
|
||||
<strong>{{ item.name }}</strong>
|
||||
<small class="text-muted">(x{{ item.quantity }})</small>
|
||||
</td>
|
||||
<td>
|
||||
{% if item.purchased %}
|
||||
<span class="badge bg-success">✔️ Kupiony</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary">Nieoznaczony</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<form method="post" action="{{ url_for('edit_list', list_id=list.id) }}" class="d-inline">
|
||||
<input type="hidden" name="action" value="toggle_purchased">
|
||||
<input type="hidden" name="item_id" value="{{ item.id }}">
|
||||
{% if item.purchased %}
|
||||
<button type="submit" class="btn btn-outline-warning btn-sm w-100">🚫 Odznacz</button>
|
||||
{% else %}
|
||||
<button type="submit" class="btn btn-outline-success btn-sm w-100">✅ Oznacz</button>
|
||||
{% endif %}
|
||||
</form>
|
||||
</td>
|
||||
<td>
|
||||
<form method="post" action="{{ url_for('edit_list', list_id=list.id) }}" class="d-inline">
|
||||
<input type="hidden" name="action" value="delete_item">
|
||||
<input type="hidden" name="item_id" value="{{ item.id }}">
|
||||
<button type="submit" class="btn btn-danger btn-sm w-100">🗑️ Usuń</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="4" class="text-center text-muted">Brak produktów.</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="form-check form-switch mb-4">
|
||||
<input class="form-check-input" type="checkbox" id="archived" name="archived" {% if list.is_archived %}checked{% endif %}>
|
||||
<label class="form-check-label" for="archived">
|
||||
Lista archiwalna
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-2">
|
||||
<button type="submit" class="btn btn-success me-2">💾 Zapisz</button>
|
||||
<a href="{{ url_for('admin_panel') }}" class="btn btn-secondary">Anuluj</a>
|
||||
<div class="card bg-dark text-white mb-5">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">🧾 Paragony</h4>
|
||||
|
||||
<div class="mb-3 text-end">
|
||||
<a href="{{ url_for('admin_receipts', id=list.id) }}" class="btn btn-sm btn-outline-light">
|
||||
📂 Otwórz widok pełny dla tej listy
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="row g-3">
|
||||
{% for img in receipts %}
|
||||
{% set file_path = upload_folder ~ '/' ~ img %}
|
||||
{% set file_size = (file_path | filesizeformat) %}
|
||||
{% set upload_time = (file_path | filemtime) %}
|
||||
<div class="col-6 col-md-4 col-lg-3">
|
||||
<div class="card bg-dark text-white h-100">
|
||||
<a href="{{ url_for('uploaded_file', filename=img) }}" data-lightbox="receipts" data-title="{{ img }}" class="glightbox">
|
||||
<img src="{{ url_for('uploaded_file', filename=img) }}" class="card-img-top" style="object-fit: cover; height: 200px;">
|
||||
</a>
|
||||
<div class="card-body text-center">
|
||||
<p class="small text-truncate mb-1">{{ img }}</p>
|
||||
<p class="small mb-1">Rozmiar: {{ file_size }}</p>
|
||||
<p class="small mb-1">Wgrano: {{ upload_time.strftime('%Y-%m-%d %H:%M') }}</p>
|
||||
<a href="{{ url_for('delete_receipt', filename=img) }}?next={{ url_for('edit_list', list_id=list.id) }}" class="btn btn-sm btn-outline-danger w-100">🗑️ Usuń</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% if not receipts %}
|
||||
<div class="alert alert-info text-center mt-3" role="alert">
|
||||
Brak paragonów.
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
@@ -10,12 +10,12 @@
|
||||
<div class="row g-3">
|
||||
{% for img in image_files %}
|
||||
{% set list_id = img.split('_')[1] if '_' in img else None %}
|
||||
{% set file_path = (upload_folder ~ '/' ~ img) %}
|
||||
{% set file_path = upload_folder ~ '/' ~ img %}
|
||||
{% set file_size = (file_path | filesizeformat) %}
|
||||
{% set upload_time = (file_path | filemtime) %}
|
||||
<div class="col-6 col-md-4 col-lg-3">
|
||||
<div class="card bg-dark text-white h-100">
|
||||
<a href="{{ url_for('uploaded_file', filename=img) }}" data-lightbox="receipts" data-title="{{ img }}">
|
||||
<a href="{{ url_for('uploaded_file', filename=img) }}" class="glightbox" data-gallery="receipts" data-title="{{ img }}">
|
||||
<img src="{{ url_for('uploaded_file', filename=img) }}" class="card-img-top" style="object-fit: cover; height: 200px;">
|
||||
</a>
|
||||
<div class="card-body text-center">
|
||||
@@ -23,9 +23,9 @@
|
||||
<p class="small mb-1">Rozmiar: {{ file_size }}</p>
|
||||
<p class="small mb-1">Wgrano: {{ upload_time.strftime('%Y-%m-%d %H:%M') }}</p>
|
||||
{% if list_id %}
|
||||
<a href="{{ url_for('view_list', list_id=list_id|int) }}" class="btn btn-sm btn-outline-light w-100 mb-2">🔗 Lista #{{ list_id }}</a>
|
||||
<a href="{{ url_for('edit_list', list_id=list_id|int) }}" class="btn btn-sm btn-outline-light w-100 mb-2">✏️ Edytuj listę #{{ list_id }}</a>
|
||||
{% endif %}
|
||||
<a href="{{ url_for('delete_receipt', filename=img) }}" class="btn btn-sm btn-outline-danger w-100">🗑️ Usuń</a>
|
||||
<a href="{{ url_for('delete_receipt', filename=img) }}?next={{ request.path }}" class="btn btn-sm btn-outline-danger w-100">🗑️ Usuń</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -33,8 +33,8 @@
|
||||
</div>
|
||||
|
||||
{% if not image_files %}
|
||||
<div class="alert alert-info text-center" role="alert">
|
||||
Nie wgrano paragonów.
|
||||
<div class="alert alert-info text-center mt-4" role="alert">
|
||||
Nie wgrano żadnych paragonów.
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
Reference in New Issue
Block a user