ux i poprawki
This commit is contained in:
31
app.py
31
app.py
@ -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()
|
||||
|
@ -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
17
templates/404.html
Normal 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 %}
|
@ -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>
|
||||
|
||||
|
@ -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 %}
|
||||
|
@ -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">
|
||||
|
@ -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>
|
||||
|
Reference in New Issue
Block a user