nowe funkcje i fixy
This commit is contained in:
258
app.py
258
app.py
@@ -170,7 +170,10 @@ def read_commit_and_date(filename="version.txt", root_path=None):
|
||||
|
||||
return date_str, commit
|
||||
|
||||
deploy_date, commit = read_commit_and_date("version.txt", root_path=os.path.dirname(__file__))
|
||||
|
||||
deploy_date, commit = read_commit_and_date(
|
||||
"version.txt", root_path=os.path.dirname(__file__)
|
||||
)
|
||||
if not deploy_date:
|
||||
deploy_date = datetime.now().strftime("%Y.%m.%d")
|
||||
if not commit:
|
||||
@@ -1159,19 +1162,37 @@ def save_pdf_as_webp(file, path):
|
||||
|
||||
def get_active_months_query(visible_lists_query=None):
|
||||
if db.engine.name in ("sqlite",):
|
||||
def month_expr(col): return func.strftime("%Y-%m", col)
|
||||
|
||||
def month_expr(col):
|
||||
return func.strftime("%Y-%m", col)
|
||||
|
||||
elif db.engine.name in ("mysql", "mariadb"):
|
||||
def month_expr(col): return func.date_format(col, "%Y-%m")
|
||||
|
||||
def month_expr(col):
|
||||
return func.date_format(col, "%Y-%m")
|
||||
|
||||
else: # PostgreSQL
|
||||
def month_expr(col): return func.to_char(col, "YYYY-MM")
|
||||
|
||||
def month_expr(col):
|
||||
return func.to_char(col, "YYYY-MM")
|
||||
|
||||
if visible_lists_query is not None:
|
||||
s = visible_lists_query.subquery()
|
||||
month_sel = month_expr(s.c.created_at).label("month")
|
||||
inner = db.session.query(month_sel).filter(month_sel.isnot(None)).distinct().subquery()
|
||||
inner = (
|
||||
db.session.query(month_sel)
|
||||
.filter(month_sel.isnot(None))
|
||||
.distinct()
|
||||
.subquery()
|
||||
)
|
||||
else:
|
||||
month_sel = month_expr(ShoppingList.created_at).label("month")
|
||||
inner = db.session.query(month_sel).filter(ShoppingList.created_at.isnot(None)).distinct().subquery()
|
||||
inner = (
|
||||
db.session.query(month_sel)
|
||||
.filter(ShoppingList.created_at.isnot(None))
|
||||
.distinct()
|
||||
.subquery()
|
||||
)
|
||||
|
||||
rows = db.session.query(inner.c.month).order_by(inner.c.month).all()
|
||||
return [r.month for r in rows]
|
||||
@@ -1357,7 +1378,7 @@ def load_user(user_id):
|
||||
|
||||
@app.context_processor
|
||||
def inject_version():
|
||||
return {'APP_VERSION': app.config['APP_VERSION']}
|
||||
return {"APP_VERSION": app.config["APP_VERSION"]}
|
||||
|
||||
|
||||
@app.context_processor
|
||||
@@ -1387,7 +1408,7 @@ def require_system_password():
|
||||
"static_bp.serve_css_lib",
|
||||
"favicon",
|
||||
"favicon_ico",
|
||||
"uploaded_file"
|
||||
"uploaded_file",
|
||||
):
|
||||
return
|
||||
|
||||
@@ -1401,10 +1422,7 @@ def require_system_password():
|
||||
if endpoint is None:
|
||||
return
|
||||
|
||||
if (
|
||||
"authorized" not in request.cookies
|
||||
and not endpoint.startswith("login")
|
||||
):
|
||||
if "authorized" not in request.cookies and not endpoint.startswith("login"):
|
||||
if request.path == "/":
|
||||
return redirect(url_for("system_auth"))
|
||||
|
||||
@@ -1799,6 +1817,7 @@ def edit_my_list(list_id):
|
||||
if request.method == "POST":
|
||||
action = request.form.get("action")
|
||||
|
||||
# --- Nadanie dostępu ---
|
||||
if action == "grant":
|
||||
grant_username = (request.form.get("grant_username") or "").strip().lower()
|
||||
if not grant_username:
|
||||
@@ -1829,75 +1848,82 @@ def edit_my_list(list_id):
|
||||
flash("Ten użytkownik już ma dostęp.", "info")
|
||||
return redirect(next_page or request.url)
|
||||
|
||||
else:
|
||||
revoke_user_id = request.form.get("revoke_user_id")
|
||||
|
||||
if revoke_user_id:
|
||||
try:
|
||||
uid = int(revoke_user_id)
|
||||
except ValueError:
|
||||
flash("Błędny identyfikator użytkownika.", "danger")
|
||||
return redirect(next_page or request.url)
|
||||
|
||||
ListPermission.query.filter_by(list_id=l.id, user_id=uid).delete()
|
||||
db.session.commit()
|
||||
flash("Odebrano dostęp użytkownikowi.", "success")
|
||||
# --- Odebranie dostępu ---
|
||||
revoke_user_id = request.form.get("revoke_user_id")
|
||||
if revoke_user_id:
|
||||
try:
|
||||
uid = int(revoke_user_id)
|
||||
except ValueError:
|
||||
flash("Błędny identyfikator użytkownika.", "danger")
|
||||
return redirect(next_page or request.url)
|
||||
|
||||
if "unarchive" in request.form:
|
||||
l.is_archived = False
|
||||
db.session.commit()
|
||||
flash(f"Lista „{l.title}” została przywrócona.", "success")
|
||||
return redirect(next_page or request.url)
|
||||
|
||||
move_to_month = request.form.get("move_to_month")
|
||||
if move_to_month:
|
||||
try:
|
||||
year, month = map(int, move_to_month.split("-"))
|
||||
new_created_at = datetime(year, month, 1, tzinfo=timezone.utc)
|
||||
l.created_at = new_created_at
|
||||
db.session.commit()
|
||||
flash(
|
||||
f"Zmieniono datę utworzenia listy na {new_created_at.strftime('%Y-%m-%d')}",
|
||||
"success",
|
||||
)
|
||||
return redirect(next_page or request.url)
|
||||
except ValueError:
|
||||
flash("Nieprawidłowy format miesiąca", "danger")
|
||||
return redirect(next_page or request.url)
|
||||
|
||||
new_title = (request.form.get("title") or "").strip()
|
||||
is_public = "is_public" in request.form
|
||||
is_temporary = "is_temporary" in request.form
|
||||
is_archived = "is_archived" in request.form
|
||||
expires_date = request.form.get("expires_date")
|
||||
expires_time = request.form.get("expires_time")
|
||||
|
||||
if not new_title:
|
||||
flash("Podaj poprawny tytuł", "danger")
|
||||
return redirect(next_page or request.url)
|
||||
|
||||
l.title = new_title
|
||||
l.is_public = is_public
|
||||
l.is_temporary = is_temporary
|
||||
l.is_archived = is_archived
|
||||
|
||||
if expires_date and expires_time:
|
||||
try:
|
||||
combined = f"{expires_date} {expires_time}"
|
||||
expires_dt = datetime.strptime(combined, "%Y-%m-%d %H:%M")
|
||||
l.expires_at = expires_dt.replace(tzinfo=timezone.utc)
|
||||
except ValueError:
|
||||
flash("Błędna data lub godzina wygasania", "danger")
|
||||
return redirect(next_page or request.url)
|
||||
else:
|
||||
l.expires_at = None
|
||||
|
||||
update_list_categories_from_form(l, request.form)
|
||||
ListPermission.query.filter_by(list_id=l.id, user_id=uid).delete()
|
||||
db.session.commit()
|
||||
flash("Zaktualizowano dane listy", "success")
|
||||
flash("Odebrano dostęp użytkownikowi.", "success")
|
||||
return redirect(next_page or request.url)
|
||||
|
||||
# --- Przywracanie z archiwum ---
|
||||
if "unarchive" in request.form:
|
||||
l.is_archived = False
|
||||
db.session.commit()
|
||||
flash(f"Lista „{l.title}” została przywrócona.", "success")
|
||||
return redirect(next_page or request.url)
|
||||
|
||||
# --- Główny zapis pól formularza (bez wczesnych redirectów) ---
|
||||
# Przenieś do miesiąca
|
||||
move_to_month = request.form.get("move_to_month")
|
||||
if move_to_month:
|
||||
try:
|
||||
year, month = map(int, move_to_month.split("-"))
|
||||
l.created_at = datetime(year, month, 1, tzinfo=timezone.utc)
|
||||
flash(
|
||||
f"Zmieniono datę utworzenia listy na {l.created_at.strftime('%Y-%m-%d')}",
|
||||
"success",
|
||||
)
|
||||
except ValueError:
|
||||
# Błędny format: informujemy, ale pozwalamy zapisać resztę pól
|
||||
flash(
|
||||
"Nieprawidłowy format miesiąca — zignorowano zmianę miesiąca.",
|
||||
"danger",
|
||||
)
|
||||
|
||||
# Tytuł i statusy
|
||||
new_title = (request.form.get("title") or "").strip()
|
||||
is_public = "is_public" in request.form
|
||||
is_temporary = "is_temporary" in request.form
|
||||
is_archived = "is_archived" in request.form
|
||||
expires_date = request.form.get("expires_date")
|
||||
expires_time = request.form.get("expires_time")
|
||||
|
||||
if not new_title:
|
||||
flash("Podaj poprawny tytuł", "danger")
|
||||
return redirect(next_page or request.url)
|
||||
|
||||
l.title = new_title
|
||||
l.is_public = is_public
|
||||
l.is_temporary = is_temporary
|
||||
l.is_archived = is_archived
|
||||
|
||||
# Wygasanie
|
||||
if expires_date and expires_time:
|
||||
try:
|
||||
combined = f"{expires_date} {expires_time}"
|
||||
expires_dt = datetime.strptime(combined, "%Y-%m-%d %H:%M")
|
||||
l.expires_at = expires_dt.replace(tzinfo=timezone.utc)
|
||||
except ValueError:
|
||||
flash("Błędna data lub godzina wygasania", "danger")
|
||||
return redirect(next_page or request.url)
|
||||
else:
|
||||
l.expires_at = None
|
||||
|
||||
# Kategorie
|
||||
update_list_categories_from_form(l, request.form)
|
||||
|
||||
# Jeden commit na koniec
|
||||
db.session.commit()
|
||||
flash("Zaktualizowano dane listy", "success")
|
||||
return redirect(next_page or request.url)
|
||||
|
||||
permitted_users = (
|
||||
db.session.query(User)
|
||||
.join(ListPermission, ListPermission.user_id == User.id)
|
||||
@@ -2051,6 +2077,10 @@ def view_list(list_id):
|
||||
for c in shopping_list.categories
|
||||
]
|
||||
|
||||
# dane do modala kategorii
|
||||
categories = Category.query.order_by(Category.name.asc()).all()
|
||||
selected_categories_ids = {c.id for c in shopping_list.categories}
|
||||
|
||||
return render_template(
|
||||
"list.html",
|
||||
list=shopping_list,
|
||||
@@ -2063,9 +2093,83 @@ def view_list(list_id):
|
||||
total_expense=total_expense,
|
||||
is_share=False,
|
||||
is_owner=is_owner,
|
||||
categories=categories,
|
||||
selected_categories=selected_categories_ids,
|
||||
)
|
||||
|
||||
|
||||
# proste akcje ustawień listy
|
||||
@app.route("/list/<int:list_id>/settings", methods=["POST"])
|
||||
@login_required
|
||||
def list_settings(list_id):
|
||||
l = db.session.get(ShoppingList, list_id)
|
||||
if l is None:
|
||||
abort(404)
|
||||
if l.owner_id != current_user.id:
|
||||
abort(403, description="Nie jesteś właścicielem tej listy.")
|
||||
|
||||
next_page = request.form.get("next") or url_for("view_list", list_id=list_id)
|
||||
action = (request.form.get("action") or "").strip()
|
||||
|
||||
if action == "set_category":
|
||||
cat_id = request.form.get("category_id", "").strip()
|
||||
if not cat_id:
|
||||
l.categories.clear()
|
||||
db.session.commit()
|
||||
flash("Usunięto kategorię.", "success")
|
||||
return redirect(next_page)
|
||||
|
||||
try:
|
||||
cid = int(cat_id)
|
||||
except ValueError:
|
||||
flash("Nieprawidłowa kategoria.", "danger")
|
||||
return redirect(next_page)
|
||||
|
||||
cat = db.session.get(Category, cid)
|
||||
if not cat:
|
||||
flash("Taka kategoria nie istnieje.", "danger")
|
||||
return redirect(next_page)
|
||||
|
||||
# pojedyncza kategoria
|
||||
l.categories = [cat]
|
||||
db.session.commit()
|
||||
flash(f"Ustawiono kategorię: „{cat.name}”.", "success")
|
||||
return redirect(next_page)
|
||||
|
||||
# 2) Nadanie dostępu użytkownikowi
|
||||
if action == "grant_access":
|
||||
grant_username = (request.form.get("grant_username") or "").strip().lower()
|
||||
if not grant_username:
|
||||
flash("Podaj login użytkownika.", "danger")
|
||||
return redirect(next_page)
|
||||
|
||||
u = User.query.filter(func.lower(User.username) == grant_username).first()
|
||||
if not u:
|
||||
flash("Użytkownik nie istnieje.", "danger")
|
||||
return redirect(next_page)
|
||||
if u.id == current_user.id:
|
||||
flash("Jesteś właścicielem tej listy.", "info")
|
||||
return redirect(next_page)
|
||||
|
||||
exists = (
|
||||
db.session.query(ListPermission.id)
|
||||
.filter(ListPermission.list_id == l.id, ListPermission.user_id == u.id)
|
||||
.first()
|
||||
)
|
||||
if exists:
|
||||
flash("Ten użytkownik już ma dostęp.", "info")
|
||||
return redirect(next_page)
|
||||
|
||||
db.session.add(ListPermission(list_id=l.id, user_id=u.id))
|
||||
db.session.commit()
|
||||
flash(f"Nadano dostęp użytkownikowi „{u.username}”.", "success")
|
||||
return redirect(next_page)
|
||||
|
||||
# nieznana akcja
|
||||
flash("Nieznana akcja.", "warning")
|
||||
return redirect(next_page)
|
||||
|
||||
|
||||
@app.route("/expenses")
|
||||
@login_required
|
||||
def expenses():
|
||||
@@ -4071,4 +4175,4 @@ def create_db():
|
||||
|
||||
if __name__ == "__main__":
|
||||
logging.basicConfig(level=logging.DEBUG if DEBUG_MODE else logging.INFO)
|
||||
socketio.run(app, host="0.0.0.0", port = APP_PORT, debug=False)
|
||||
socketio.run(app, host="0.0.0.0", port=APP_PORT, debug=False)
|
||||
|
@@ -12,16 +12,20 @@
|
||||
{% if list.category_badges %}
|
||||
{% for cat in list.category_badges %}
|
||||
<span class="badge rounded-pill rounded-pill text-dark ms-1" style="background-color: {{ cat.color }};
|
||||
font-size: 0.75rem;
|
||||
opacity: 0.85;">
|
||||
font-size: 0.75rem;
|
||||
opacity: 0.85;">
|
||||
{{ cat.name }}
|
||||
</span>
|
||||
{% endfor %}
|
||||
<!-- PRZYCISK DO MODALA KATEGORII -->
|
||||
<button class="btn btn-sm btn-outline-info ms-2" data-bs-toggle="modal" data-bs-target="#categoriesModal">
|
||||
✏️ Zmień kategorie
|
||||
</button>
|
||||
{% else %}
|
||||
<a href="{{ url_for('edit_my_list', list_id=list.id, next=url_for('view_list', list_id=list.id)) }}"
|
||||
class="ms-2 text-light small fw-light" style="opacity: 0.9;">
|
||||
<!-- ZAMIAST LINKU: OTWARCIE MODALA KATEGORII -->
|
||||
<button class="btn btn-sm btn-outline-info ms-2" data-bs-toggle="modal" data-bs-target="#categoriesModal">
|
||||
➕ Dodaj kategorię
|
||||
</a>
|
||||
</button>
|
||||
{% endif %}
|
||||
</h2>
|
||||
</div>
|
||||
@@ -30,15 +34,13 @@
|
||||
list.is_public %}disabled{% endif %}>
|
||||
✅ Otwórz tryb zakupowy / odznaczania produktów
|
||||
</a>
|
||||
|
||||
<div id="share-card" class="card bg-secondary bg-opacity-10 text-white mb-4">
|
||||
<div class="card-body">
|
||||
<div class="mb-2">
|
||||
<strong id="share-header">
|
||||
{% if list.is_public %}
|
||||
🔗 Udostępnij link (lista publiczna)
|
||||
{% else %}
|
||||
🔗 Udostępnij link (widoczna przez link / uprawnienia)
|
||||
{% endif %}
|
||||
{% if list.is_public %}🔗 Udostępnij link (lista publiczna){% else %}🔗 Udostępnij link (widoczna przez link /
|
||||
uprawnienia){% endif %}
|
||||
</strong>
|
||||
<span id="share-url" class="badge rounded-pill bg-secondary text-wrap" style="font-size: 0.7rem;">
|
||||
{{ request.url_root }}share/{{ list.share_token }}
|
||||
@@ -52,16 +54,13 @@
|
||||
|
||||
<button id="toggleVisibilityBtn" class="btn btn-outline-light btn-sm flex-fill"
|
||||
onclick="toggleVisibility({{ list.id }})">
|
||||
{% if list.is_public %}
|
||||
🙈 Ustaw niepubliczną
|
||||
{% else %}
|
||||
🐵 Uczyń publiczną
|
||||
{% endif %}
|
||||
{% if list.is_public %}🙈 Ustaw niepubliczną{% else %}🐵 Uczyń publiczną{% endif %}
|
||||
</button>
|
||||
<a href="{{ url_for('edit_my_list', list_id=list.id, next=url_for('view_list', list_id=list.id)) }}"
|
||||
class="btn btn-outline-info btn-sm flex-fill">
|
||||
|
||||
<!-- ZAMIAST LINKU: OTWARCIE MODALA NADAWANIA DOSTĘPU -->
|
||||
<button class="btn btn-outline-info btn-sm flex-fill" data-bs-toggle="modal" data-bs-target="#grantAccessModal">
|
||||
➕ Nadaj dostęp
|
||||
</a>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -76,14 +75,11 @@
|
||||
|
||||
<div class="progress progress-dark position-relative">
|
||||
<div id="progress-bar-purchased" class="progress-bar bg-success" role="progressbar" data-bs-toggle="tooltip"
|
||||
title="Kupione produkty">
|
||||
</div>
|
||||
title="Kupione produkty"></div>
|
||||
<div id="progress-bar-not-purchased" class="progress-bar bg-warning" role="progressbar" data-bs-toggle="tooltip"
|
||||
title="Oznaczone jako niekupione">
|
||||
</div>
|
||||
title="Oznaczone jako niekupione"></div>
|
||||
<div id="progress-bar-remaining" class="progress-bar bg-transparent" role="progressbar" data-bs-toggle="tooltip"
|
||||
title="Pozostałe do kupienia">
|
||||
</div>
|
||||
title="Pozostałe do kupienia"></div>
|
||||
<span id="progress-label" class="progress-label small fw-bold"></span>
|
||||
</div>
|
||||
|
||||
@@ -98,9 +94,8 @@
|
||||
{% endif %}
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-3 flex-wrap">
|
||||
<button id="sort-toggle-btn" class="btn btn-sm btn-outline-warning" onclick="toggleSortMode()">
|
||||
✳️ Zmień kolejność
|
||||
</button>
|
||||
<button id="sort-toggle-btn" class="btn btn-sm btn-outline-warning" onclick="toggleSortMode()">✳️ Zmień
|
||||
kolejność</button>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" id="hidePurchasedToggle">
|
||||
<label class="form-check-label ms-2" for="hidePurchasedToggle">Ukryj zaznaczone</label>
|
||||
@@ -111,14 +106,12 @@
|
||||
{% for item in items %}
|
||||
<li data-name="{{ item.name|lower }}" id="item-{{ item.id }}"
|
||||
class="list-group-item d-flex justify-content-between align-items-center flex-wrap clickable-item
|
||||
{% if item.purchased %}bg-success text-white{% elif item.not_purchased %}bg-warning text-dark{% else %}item-not-checked{% endif %}"
|
||||
{% if item.purchased %}bg-success text-white{% elif item.not_purchased %}bg-warning text-dark{% else %}item-not-checked{% endif %}"
|
||||
data-is-share="{{ 'true' if is_share else 'false' }}">
|
||||
|
||||
<div class="d-flex align-items-center gap-2 flex-grow-1">
|
||||
|
||||
<input id="checkbox-{{ item.id }}" class="large-checkbox" type="checkbox" {% if item.purchased %}checked{% endif
|
||||
%} {% if list.is_archived or item.not_purchased %}disabled{% endif %}>
|
||||
|
||||
<span id="name-{{ item.id }}" class="text-white">
|
||||
{{ item.name }}
|
||||
{% if item.quantity and item.quantity > 1 %}
|
||||
@@ -128,18 +121,12 @@
|
||||
|
||||
<div class="info-line ms-4 small d-flex flex-wrap gap-2" id="info-{{ item.id }}">
|
||||
{% set info_parts = [] %}
|
||||
{% if item.note %}
|
||||
{% set _ = info_parts.append('<span class="text-danger">[ <b>' ~ item.note ~ '</b> ]</span>') %}
|
||||
{% endif %}
|
||||
{% if item.not_purchased_reason %}
|
||||
{% set _ = info_parts.append('<span class="text-dark">[ <b>Powód: ' ~ item.not_purchased_reason ~ '</b>
|
||||
]</span>') %}
|
||||
{% endif %}
|
||||
{% if item.added_by_display %}
|
||||
{% set _ = info_parts.append('<span class="text-info">[ Dodał/a: <b>' ~ item.added_by_display ~ '</b> ]</span>')
|
||||
%}
|
||||
{% endif %}
|
||||
|
||||
{% if item.note %}{% set _ = info_parts.append('<span class="text-danger">[ <b>' ~ item.note ~ '</b> ]</span>')
|
||||
%}{% endif %}
|
||||
{% if item.not_purchased_reason %}{% set _ = info_parts.append('<span class="text-dark">[ <b>Powód: ' ~
|
||||
item.not_purchased_reason ~ '</b> ]</span>') %}{% endif %}
|
||||
{% if item.added_by_display %}{% set _ = info_parts.append('<span class="text-info">[ Dodał/a: <b>' ~
|
||||
item.added_by_display ~ '</b> ]</span>') %}{% endif %}
|
||||
{% if info_parts %}
|
||||
<div class="info-line ms-4 small d-flex flex-wrap gap-2" id="info-{{ item.id }}">
|
||||
{{ info_parts | join(' ') | safe }}
|
||||
@@ -150,34 +137,24 @@
|
||||
|
||||
<div class="btn-group btn-group-sm" role="group">
|
||||
{% if not is_share %}
|
||||
<button type="button" class="btn btn-outline-light" {% if list.is_archived %}disabled{% else %}
|
||||
onclick="editItem({{ item.id }}, '{{ item.name }}', {{ item.quantity or 1 }})" {% endif %}>
|
||||
✏️
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-light" {% if list.is_archived %}disabled{% else %}
|
||||
onclick="deleteItem({{ item.id }})" {% endif %}>
|
||||
🗑️
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-light" {% if list.is_archived %}disabled{% else
|
||||
%}onclick="editItem({{ item.id }}, '{{ item.name }}', {{ item.quantity or 1 }})" {% endif %}>✏️</button>
|
||||
<button type="button" class="btn btn-outline-light" {% if list.is_archived %}disabled{% else
|
||||
%}onclick="deleteItem({{ item.id }})" {% endif %}>🗑️</button>
|
||||
{% endif %}
|
||||
|
||||
{% if item.not_purchased %}
|
||||
<button type="button" class="btn btn-outline-light me-auto" {% if list.is_archived %}disabled{% else %}
|
||||
onclick="unmarkNotPurchased({{ item.id }})" {% endif %}>
|
||||
✅ Przywróć
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-light me-auto" {% if list.is_archived %}disabled{% else
|
||||
%}onclick="unmarkNotPurchased({{ item.id }})" {% endif %}>✅ Przywróć</button>
|
||||
{% elif not item.not_purchased %}
|
||||
<button type="button" class="btn btn-outline-light" {% if list.is_archived %}disabled{% else %}
|
||||
onclick="markNotPurchasedModal(event, {{ item.id }})" {% endif %}>
|
||||
⚠️
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-light" {% if list.is_archived %}disabled{% else
|
||||
%}onclick="markNotPurchasedModal(event, {{ item.id }})" {% endif %}>⚠️</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</li>
|
||||
|
||||
{% else %}
|
||||
<li id="empty-placeholder" class="list-group-item bg-dark text-secondary text-center w-100">
|
||||
Brak produktów w tej liście.
|
||||
</li>
|
||||
<li id="empty-placeholder" class="list-group-item bg-dark text-secondary text-center w-100">Brak produktów w tej
|
||||
liście.</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
@@ -216,12 +193,83 @@
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<div class="alert alert-info text-center w-100" role="alert">
|
||||
ℹ️ Brak wgranych paragonów do tej listy
|
||||
</div>
|
||||
<div class="alert alert-info text-center w-100" role="alert">ℹ️ Brak wgranych paragonów do tej listy</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- MODAL: KATEGORIA (pojedynczy wybór) -->
|
||||
<div class="modal fade" id="categoriesModal" tabindex="-1" aria-labelledby="categoriesModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content bg-dark text-white border-secondary">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="categoriesModalLabel">🏷️ Ustaw kategorię</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Zamknij"></button>
|
||||
</div>
|
||||
|
||||
<form method="post" action="{{ url_for('list_settings', list_id=list.id) }}">
|
||||
<div class="modal-body">
|
||||
<div class="mb-4">
|
||||
<label for="category_id" class="form-label">🏷️ Kategorie</label>
|
||||
<select id="category_id" name="category_id"
|
||||
class="form-select tom-dark bg-dark text-white border-secondary rounded">
|
||||
<option value="">– brak –</option>
|
||||
{% for cat in categories %}
|
||||
<option value="{{ cat.id }}" {% if cat.id in selected_categories %}selected{% endif %}>
|
||||
{{ cat.name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="action" value="set_category">
|
||||
<input type="hidden" name="next" value="{{ url_for('view_list', list_id=list.id) }}">
|
||||
</div>
|
||||
|
||||
<div class="modal-footer justify-content-end">
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-sm btn-outline-light" data-bs-dismiss="modal">Anuluj</button>
|
||||
<button type="submit" class="btn btn-sm btn-outline-light">Zapisz</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- MODAL: NADAWANIE DOSTĘPU -->
|
||||
<div class="modal fade" id="grantAccessModal" tabindex="-1" aria-labelledby="grantAccessModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content bg-dark text-white border-secondary">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="grantAccessModalLabel">Nadaj dostęp użytkownikowi</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Zamknij"></button>
|
||||
</div>
|
||||
|
||||
<form method="post" action="{{ url_for('list_settings', list_id=list.id) }}" class="m-0">
|
||||
<div class="modal-body">
|
||||
<label for="grant_username" class="form-label">Login użytkownika</label>
|
||||
<input type="text" name="grant_username" id="grant_username"
|
||||
class="form-control bg-dark text-white border-secondary rounded" placeholder="np. marek" required>
|
||||
|
||||
<input type="hidden" name="action" value="grant_access">
|
||||
<input type="hidden" name="next" value="{{ url_for('view_list', list_id=list.id) }}">
|
||||
</div>
|
||||
|
||||
<div class="modal-footer justify-content-end">
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-sm btn-outline-light" data-bs-dismiss="modal">Anuluj</button>
|
||||
<button type="submit" class="btn btn-sm btn-outline-light">Dodaj</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="modal fade" id="massAddModal" tabindex="-1" aria-labelledby="massAddModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg modal-dialog-scrollable">
|
||||
<div class="modal-content bg-dark text-white">
|
||||
@@ -233,17 +281,9 @@
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Zamknij"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
||||
<!-- SORTOWANIE i LICZNIK -->
|
||||
<div id="sort-bar" class="mb-2"></div>
|
||||
|
||||
<div class="mb-2">
|
||||
<span id="product-count" class="badge rounded-pill bg-primary ms-2"></span>
|
||||
</div>
|
||||
|
||||
<!-- LISTA PRODUKTÓW -->
|
||||
<div class="mb-2"><span id="product-count" class="badge rounded-pill bg-primary ms-2"></span></div>
|
||||
<ul id="mass-add-list" class="list-group"></ul>
|
||||
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline-light" data-bs-dismiss="modal">Zamknij</button>
|
||||
@@ -263,6 +303,7 @@
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='mass_add.js') }}?v={{ APP_VERSION }}"></script>
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='receipt_upload.js') }}?v={{ APP_VERSION }}"></script>
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='sort_mode.js') }}?v={{ APP_VERSION }}"></script>
|
||||
|
||||
<script>
|
||||
setupList({{ list.id }}, '{{ current_user.username if current_user.is_authenticated else 'Gość' }}');
|
||||
</script>
|
||||
|
Reference in New Issue
Block a user