ux i poprawki

This commit is contained in:
Mateusz Gruszczyński
2025-07-02 12:51:30 +02:00
parent bb4fe3815f
commit 85446f950c
7 changed files with 96 additions and 27 deletions

31
app.py
View File

@ -49,14 +49,19 @@ def load_user(user_id):
@app.before_request
def require_system_password():
if 'authorized' not in request.cookies and request.endpoint != 'system_auth' and not request.endpoint.startswith('static') and not request.endpoint.startswith('login'):
return redirect(url_for('system_auth'))
if 'authorized' not in request.cookies \
and request.endpoint != 'system_auth' \
and not request.endpoint.startswith('static') \
and not request.endpoint.startswith('login'):
return redirect(url_for('system_auth', next=request.url))
@app.route('/system-auth', methods=['GET', 'POST'])
def system_auth():
DEFAULT_ADMIN_USERNAME = app.config.get('DEFAULT_ADMIN_USERNAME', 'admin')
DEFAULT_ADMIN_PASSWORD = app.config.get('DEFAULT_ADMIN_PASSWORD', 'admin123')
next_page = request.args.get('next') or url_for('index_guest')
if request.method == 'POST':
if request.form['password'] == SYSTEM_PASSWORD:
db.create_all()
@ -69,7 +74,7 @@ def system_auth():
db.session.add(admin_user)
db.session.commit()
flash(f'Utworzono konto administratora: login={DEFAULT_ADMIN_USERNAME}, hasło={DEFAULT_ADMIN_PASSWORD}')
resp = redirect(url_for('index_guest'))
resp = redirect(next_page)
resp.set_cookie('authorized', 'true')
return resp
flash('Nieprawidłowe hasło do systemu')
@ -80,6 +85,10 @@ def index_guest():
lists = ShoppingList.query.all()
return render_template('index.html', lists=lists)
@app.errorhandler(404)
def page_not_found(e):
return render_template('404.html'), 404
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
@ -128,6 +137,12 @@ def share_list(token):
items = Item.query.filter_by(list_id=shopping_list.id).all()
return render_template('list_guest.html', list=shopping_list, items=items)
@app.route('/guest-list/<int:list_id>')
def guest_list(list_id):
shopping_list = ShoppingList.query.get_or_404(list_id)
items = Item.query.filter_by(list_id=list_id).all()
return render_template('list_guest.html', list=shopping_list, items=items)
@app.route('/copy/<int:list_id>')
@login_required
def copy_list(list_id):
@ -281,6 +296,16 @@ def handle_check_item(data):
db.session.commit()
emit('item_checked', {'item_id': item.id}, to=str(item.list_id))
@socketio.on('uncheck_item')
def handle_uncheck_item(data):
item = Item.query.get(data['item_id'])
if item:
item.purchased = False
item.purchased_at = None
db.session.commit()
emit('item_unchecked', {'item_id': item.id}, to=str(item.list_id))
@app.cli.command('create_db')
def create_db():
db.create_all()

View File

@ -14,25 +14,41 @@ function setupList(listId, username) {
});
}
const itemsContainer = document.getElementById('items');
// Delegacja zdarzenia checkboxów
itemsContainer.addEventListener('change', function (e) {
if (e.target && e.target.type === 'checkbox') {
const li = e.target.closest('li');
if (li) {
const id = parseInt(li.id.replace('item-', ''), 10);
if (e.target.checked) {
socket.emit('check_item', { item_id: id });
} else {
socket.emit('uncheck_item', { item_id: id });
}
}
}
});
socket.on('user_joined', data => showToast(`${data.username} dołączył do listy`));
socket.on('item_added', data => {
showToast(`${data.added_by} dodał: ${data.name}`);
const list = document.getElementById('items');
const li = document.createElement('li');
li.className = 'list-group-item bg-dark text-white d-flex justify-content-between align-items-center';
li.className = 'list-group-item bg-dark text-white d-flex justify-content-between align-items-center flex-wrap';
li.id = `item-${data.id}`;
li.innerHTML = `
<div>
<input type="checkbox" onchange="checkItem(${data.id})">
<div class="d-flex align-items-center flex-wrap gap-2">
<input type="checkbox">
<span id="name-${data.id}">${data.name}</span>
</div>
<div>
<button class="btn btn-sm btn-warning" onclick="editItem(${data.id}, '${data.name}')">Edytuj</button>
<button class="btn btn-sm btn-danger" onclick="deleteItem(${data.id})">Usuń</button>
<div class="mt-2 mt-md-0">
<button class="btn btn-sm btn-outline-warning me-1" onclick="editItem(${data.id}, '${data.name}')">✏️ Edytuj</button>
<button class="btn btn-sm btn-outline-danger" onclick="deleteItem(${data.id})">🗑️ Usuń</button>
</div>
`;
list.appendChild(li);
itemsContainer.appendChild(li);
});
socket.on('item_checked', data => {
@ -42,6 +58,13 @@ function setupList(listId, username) {
}
});
socket.on('item_unchecked', data => {
const checkbox = document.querySelector(`#item-${data.item_id} input[type='checkbox']`);
if (checkbox) {
checkbox.checked = false;
}
});
socket.on('item_deleted', data => {
const li = document.getElementById(`item-${data.item_id}`);
if (li) {
@ -67,10 +90,6 @@ function addItem(listId) {
document.getElementById('newItem').focus();
}
function checkItem(id) {
socket.emit('check_item', { item_id: id });
}
function deleteItem(id) {
if (confirm('Na pewno usunąć produkt?')) {
socket.emit('delete_item', { item_id: id });

17
templates/404.html Normal file
View File

@ -0,0 +1,17 @@
{% extends 'base.html' %}
{% block title %}Strona nie znaleziona{% endblock %}
{% block content %}
<div class="d-flex justify-content-between align-items-center flex-wrap mb-4">
<h2 class="mb-2">404 — Strona nie znaleziona</h2>
<a href="{{ url_for('index_guest') }}" class="btn btn-outline-secondary">← Powrót na stronę główną</a>
</div>
<div class="card bg-dark text-white">
<div class="card-body">
<p class="fs-4">Ups! Podana strona nie istnieje lub została przeniesiona.</p>
<p>Sprawdź adres lub wróć na stronę główną, aby kontynuować.</p>
</div>
</div>
{% endblock %}

View File

@ -2,6 +2,7 @@
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}Live Lista Zakupów{% endblock %}</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
<script src="https://cdn.socket.io/4.6.1/socket.io.min.js"></script>
@ -9,20 +10,23 @@
</head>
<body class="bg-dark text-white">
<nav class="navbar navbar-dark bg-dark mb-4">
<div class="container-fluid">
<nav class="navbar navbar-dark bg-dark mb-3">
<div class="container-fluid d-flex justify-content-between">
<a class="navbar-brand" href="/">Live Lista Zakupów</a>
<div>
<div class="d-flex align-items-center gap-2">
{% if current_user.is_authenticated and current_user.is_admin %}
<a href="{{ url_for('admin_panel') }}" class="btn btn-outline-warning btn-sm">⚙️ Panel admina</a>
{% endif %}
{% if current_user.is_authenticated %}
<a href="{{ url_for('logout') }}" class="btn btn-outline-light">🚪 Wyloguj</a>
<a href="{{ url_for('logout') }}" class="btn btn-outline-light btn-sm">🚪 Wyloguj</a>
{% else %}
<a href="{{ url_for('login') }}" class="btn btn-outline-light">🔑 Zaloguj się</a>
<a href="{{ url_for('login') }}" class="btn btn-outline-light btn-sm">🔑 Zaloguj się</a>
{% endif %}
</div>
</div>
</nav>
<div class="container">
<div class="container px-2">
{% block content %}{% endblock %}
</div>

View File

@ -32,8 +32,12 @@
<li class="list-group-item bg-dark text-white d-flex justify-content-between align-items-center flex-wrap">
<span class="fw-bold">{{ l.title }}</span>
<div class="mt-2 mt-md-0">
{% if current_user.is_authenticated %}
<a href="/list/{{ l.id }}" class="btn btn-sm btn-outline-light me-1">📄 Otwórz</a>
<a href="/copy/{{ l.id }}" class="btn btn-sm btn-outline-secondary">📋 Kopiuj</a>
<a href="/copy/{{ l.id }}" class="btn btn-sm btn-outline-secondary">📋 Kopiuj</a>
{% else %}
<a href="/guest-list/{{ l.id }}" class="btn btn-sm btn-outline-light me-1">📄 Otwórz</a>
{% endif %}
</div>
</li>
{% endfor %}

View File

@ -23,7 +23,7 @@
{% for item in items %}
<li class="list-group-item bg-dark text-white d-flex justify-content-between align-items-center flex-wrap" id="item-{{ item.id }}">
<div class="d-flex align-items-center flex-wrap gap-2">
<input type="checkbox" onchange="checkItem({{ item.id }})" {% if item.purchased %}checked{% endif %}>
<input type="checkbox">
<span id="name-{{ item.id }}">{{ item.name }}</span>
</div>
<div class="mt-2 mt-md-0">

View File

@ -4,14 +4,14 @@
<div class="d-flex justify-content-between align-items-center flex-wrap mb-3">
<h2 class="mb-2">🛍️ {{ list.title }} <small class="text-muted">(Gość)</small></h2>
<a href="/" class="btn btn-outline-secondary">← Powrót do list</a>
<a href="/" class="btn btn-outline-secondary btn-sm">← Powrót</a>
</div>
<ul id="items" class="list-group mb-3">
{% for item in items %}
<li class="list-group-item bg-dark text-white d-flex align-items-center gap-2">
<input type="checkbox" onchange="checkItem({{ item.id }})" {% if item.purchased %}checked{% endif %}>
<span>{{ item.name }}</span>
<li class="list-group-item bg-dark text-white d-flex align-items-center gap-2 flex-wrap" id="item-{{ item.id }}">
<input type="checkbox" {% if item.purchased %}checked{% endif %}>
<span id="name-{{ item.id }}">{{ item.name }}</span>
</li>
{% endfor %}
</ul>