diff --git a/alters.txt b/alters.txt index e69de29..797bb4f 100644 --- a/alters.txt +++ b/alters.txt @@ -0,0 +1,2 @@ +ALTER TABLE zbiorka ALTER COLUMN numer_konta DROP NOT NULL; +ALTER TABLE zbiorka ALTER COLUMN numer_telefonu_blik DROP NOT NULL; \ No newline at end of file diff --git a/app.py b/app.py index 0ff37dc..f3a1e6c 100644 --- a/app.py +++ b/app.py @@ -82,8 +82,8 @@ class Zbiorka(db.Model): id = db.Column(db.Integer, primary_key=True) nazwa = db.Column(db.String(100), nullable=False) opis = db.Column(db.Text, nullable=False) - numer_konta = db.Column(db.String(50), nullable=False) - numer_telefonu_blik = db.Column(db.String(50), nullable=False) + numer_konta = db.Column(db.String(50), nullable=True) + numer_telefonu_blik = db.Column(db.String(50), nullable=True) cel = db.Column(Numeric(12, 2), nullable=False, default=0) stan = db.Column(Numeric(12, 2), default=0) ukryta = db.Column(db.Boolean, default=False) @@ -92,6 +92,8 @@ class Zbiorka(db.Model): pokaz_postep_finanse = db.Column(db.Boolean, default=True, nullable=False) pokaz_postep_pozycje = db.Column(db.Boolean, default=True, nullable=False) pokaz_postep_kwotowo = db.Column(db.Boolean, default=True, nullable=False) + uzyj_konta = db.Column(db.Boolean, default=True, nullable=False) + uzyj_blik = db.Column(db.Boolean, default=True, nullable=False) wplaty = db.relationship( "Wplata", @@ -386,88 +388,69 @@ def formularz_zbiorek(zbiorka_id=None): return redirect(url_for("index")) is_edit = zbiorka_id is not None - zb = None - if is_edit: - zb = db.session.get(Zbiorka, zbiorka_id) - if zb is None: - abort(404) + zb = db.session.get(Zbiorka, zbiorka_id) if is_edit else None + if is_edit and zb is None: + abort(404) global_settings = UstawieniaGlobalne.query.first() + def _temp_obj(): + t = zb or Zbiorka() + t.nazwa = (request.form.get("nazwa", "") or "").strip() + t.opis = (request.form.get("opis", "") or "").strip() + t.numer_konta = (request.form.get("numer_konta", "") or "").strip() + t.numer_telefonu_blik = (request.form.get("numer_telefonu_blik", "") or "").strip() + t.ukryj_kwote = "ukryj_kwote" in request.form + t.pokaz_postep_finanse = "pokaz_postep_finanse" in request.form + t.pokaz_postep_pozycje = "pokaz_postep_pozycje" in request.form + t.pokaz_postep_kwotowo = "pokaz_postep_kwotowo" in request.form + t.uzyj_konta = "uzyj_konta" in request.form + t.uzyj_blik = "uzyj_blik" in request.form + return t + if request.method == "POST": - # Pola wspólne + # Pola nazwa = (request.form.get("nazwa", "") or "").strip() opis = (request.form.get("opis", "") or "").strip() numer_konta = (request.form.get("numer_konta", "") or "").strip() numer_telefonu_blik = (request.form.get("numer_telefonu_blik", "") or "").strip() - # Widoczność kwot i poszczególnych pasków postępu + # Przełączniki płatności + uzyj_konta = "uzyj_konta" in request.form + uzyj_blik = "uzyj_blik" in request.form + + # Widoczność/metryki ukryj_kwote = "ukryj_kwote" in request.form pokaz_postep_finanse = "pokaz_postep_finanse" in request.form pokaz_postep_pozycje = "pokaz_postep_pozycje" in request.form pokaz_postep_kwotowo = "pokaz_postep_kwotowo" in request.form - # Walidacja wymaganych pól tekstowych (zanim uderzymy w bazę) + # Walidacje if not nazwa: flash("Nazwa jest wymagana", "danger") - temp_zb = zb or Zbiorka() - temp_zb.nazwa = nazwa - temp_zb.opis = opis - temp_zb.numer_konta = numer_konta - temp_zb.numer_telefonu_blik = numer_telefonu_blik - temp_zb.ukryj_kwote = ukryj_kwote - temp_zb.pokaz_postep_finanse = pokaz_postep_finanse - temp_zb.pokaz_postep_pozycje = pokaz_postep_pozycje - temp_zb.pokaz_postep_kwotowo = pokaz_postep_kwotowo - return render_template("admin/formularz_zbiorek.html", zbiorka=temp_zb, global_settings=global_settings) + return render_template("admin/formularz_zbiorek.html", zbiorka=_temp_obj(), global_settings=global_settings) if not opis: flash("Opis jest wymagany", "danger") - temp_zb = zb or Zbiorka() - temp_zb.nazwa = nazwa - temp_zb.opis = opis - temp_zb.numer_konta = numer_konta - temp_zb.numer_telefonu_blik = numer_telefonu_blik - temp_zb.ukryj_kwote = ukryj_kwote - temp_zb.pokaz_postep_finanse = pokaz_postep_finanse - temp_zb.pokaz_postep_pozycje = pokaz_postep_pozycje - temp_zb.pokaz_postep_kwotowo = pokaz_postep_kwotowo - return render_template("admin/formularz_zbiorek.html", zbiorka=temp_zb, global_settings=global_settings) + return render_template("admin/formularz_zbiorek.html", zbiorka=_temp_obj(), global_settings=global_settings) - if not numer_konta: - flash("Numer konta jest wymagany", "danger") - temp_zb = zb or Zbiorka() - temp_zb.nazwa = nazwa - temp_zb.opis = opis - temp_zb.numer_konta = numer_konta - temp_zb.numer_telefonu_blik = numer_telefonu_blik - temp_zb.ukryj_kwote = ukryj_kwote - temp_zb.pokaz_postep_finanse = pokaz_postep_finanse - temp_zb.pokaz_postep_pozycje = pokaz_postep_pozycje - temp_zb.pokaz_postep_kwotowo = pokaz_postep_kwotowo - return render_template("admin/formularz_zbiorek.html", zbiorka=temp_zb, global_settings=global_settings) + # Co najmniej jeden kanał + if not (uzyj_konta or uzyj_blik): + flash("Włącz co najmniej jeden kanał wpłat (konto lub BLIK).", "danger") + return render_template("admin/formularz_zbiorek.html", zbiorka=_temp_obj(), global_settings=global_settings) - if not numer_telefonu_blik: - flash("Numer telefonu BLIK jest wymagany", "danger") - temp_zb = zb or Zbiorka() - temp_zb.nazwa = nazwa - temp_zb.opis = opis - temp_zb.numer_konta = numer_konta - temp_zb.numer_telefonu_blik = numer_telefonu_blik - temp_zb.ukryj_kwote = ukryj_kwote - temp_zb.pokaz_postep_finanse = pokaz_postep_finanse - temp_zb.pokaz_postep_pozycje = pokaz_postep_pozycje - temp_zb.pokaz_postep_kwotowo = pokaz_postep_kwotowo - return render_template("admin/formularz_zbiorek.html", zbiorka=temp_zb, global_settings=global_settings) + # Warunkowe wartości + if uzyj_konta and not numer_konta: + flash("Numer konta jest wymagany (kanał przelewu włączony).", "danger") + return render_template("admin/formularz_zbiorek.html", zbiorka=_temp_obj(), global_settings=global_settings) - # Cel — Decimal, > 0; akceptuj przecinek i usuń spacje/nbsp + if uzyj_blik and not numer_telefonu_blik: + flash("Numer telefonu BLIK jest wymagany (kanał BLIK włączony).", "danger") + return render_template("admin/formularz_zbiorek.html", zbiorka=_temp_obj(), global_settings=global_settings) + + # Cel > 0 cel_raw = (request.form.get("cel", "") or "") - cel_norm = ( - cel_raw.replace(" ", "") - .replace("\u00A0", "") - .replace(",", ".") - .strip() - ) + cel_norm = cel_raw.replace(" ", "").replace("\u00A0", "").replace(",", ".").strip() try: if not cel_norm: raise InvalidOperation @@ -476,51 +459,41 @@ def formularz_zbiorek(zbiorka_id=None): raise InvalidOperation except (InvalidOperation, ValueError): flash("Podano nieprawidłową wartość dla celu zbiórki", "danger") - # Odtwórz stan formularza, ale nie używaj zbiorka.id w szablonie - temp_zb = zb or Zbiorka() - temp_zb.nazwa = nazwa - temp_zb.opis = opis - temp_zb.numer_konta = numer_konta - temp_zb.numer_telefonu_blik = numer_telefonu_blik - # cel pozostaw niewypełniony przy błędzie, aby input pokazał to co wpisał użytkownik - temp_zb.ukryj_kwote = ukryj_kwote - temp_zb.pokaz_postep_finanse = pokaz_postep_finanse - temp_zb.pokaz_postep_pozycje = pokaz_postep_pozycje - temp_zb.pokaz_postep_kwotowo = pokaz_postep_kwotowo - return render_template("admin/formularz_zbiorek.html", zbiorka=temp_zb, global_settings=global_settings) + return render_template("admin/formularz_zbiorek.html", zbiorka=_temp_obj(), global_settings=global_settings) - # Lista produktów + # Produkty names = request.form.getlist("item_nazwa[]") links = request.form.getlist("item_link[]") prices = request.form.getlist("item_cena[]") - def _read_price(val): - """Zwraca Decimal(>=0) albo None; akceptuje przecinek jako separator dziesiętny.""" + def _read_price(val: str): if not val or not val.strip(): return None try: d = Decimal(val.replace(",", ".")) - if d < 0: - return None - return d + return d if d >= 0 else None except Exception: return None - # --- ZAPIS ZBIÓRKI + PRODUKTÓW --- + # Zapis if is_edit: - # Aktualizacja istniejącej zbiórki zb.nazwa = nazwa zb.opis = opis - zb.numer_konta = numer_konta - zb.numer_telefonu_blik = numer_telefonu_blik + + # NOT NULL-safe: puste stringi gdy wyłączone + zb.uzyj_konta = uzyj_konta + zb.uzyj_blik = uzyj_blik + zb.numer_konta = numer_konta if uzyj_konta else "" + zb.numer_telefonu_blik = numer_telefonu_blik if uzyj_blik else "" + zb.cel = cel zb.ukryj_kwote = ukryj_kwote zb.pokaz_postep_finanse = pokaz_postep_finanse zb.pokaz_postep_pozycje = pokaz_postep_pozycje zb.pokaz_postep_kwotowo = pokaz_postep_kwotowo - db.session.commit() # zapisz bazowe pola + db.session.commit() - # Nadpisz listę produktów + # Nadpisz pozycje zb.przedmioty.clear() for i, raw_name in enumerate(names): name = (raw_name or "").strip() @@ -529,7 +502,6 @@ def formularz_zbiorek(zbiorka_id=None): link = (links[i] if i < len(links) else "").strip() or None cena_val = _read_price(prices[i] if i < len(prices) else "") kupione_val = request.form.get(f"item_kupione_val_{i}") == "1" - db.session.add(Przedmiot( zbiorka_id=zb.id, nazwa=name, @@ -537,17 +509,17 @@ def formularz_zbiorek(zbiorka_id=None): cena=cena_val, kupione=kupione_val )) - db.session.commit() flash("Zbiórka została zaktualizowana", "success") else: - # Utworzenie nowej zbiórki nowa = Zbiorka( nazwa=nazwa, opis=opis, - numer_konta=numer_konta, - numer_telefonu_blik=numer_telefonu_blik, + uzyj_konta=uzyj_konta, + uzyj_blik=uzyj_blik, + numer_konta=(numer_konta if uzyj_konta else ""), + numer_telefonu_blik=(numer_telefonu_blik if uzyj_blik else ""), cel=cel, ukryj_kwote=ukryj_kwote, pokaz_postep_finanse=pokaz_postep_finanse, @@ -557,7 +529,6 @@ def formularz_zbiorek(zbiorka_id=None): db.session.add(nowa) db.session.commit() # potrzebne ID - # Dodaj produkty do nowej zbiórki for i, raw_name in enumerate(names): name = (raw_name or "").strip() if not name: @@ -565,7 +536,6 @@ def formularz_zbiorek(zbiorka_id=None): link = (links[i] if i < len(links) else "").strip() or None cena_val = _read_price(prices[i] if i < len(prices) else "") kupione_val = request.form.get(f"item_kupione_val_{i}") == "1" - db.session.add(Przedmiot( zbiorka_id=nowa.id, nazwa=name, @@ -573,7 +543,6 @@ def formularz_zbiorek(zbiorka_id=None): cena=cena_val, kupione=kupione_val )) - db.session.commit() flash("Zbiórka została dodana", "success") diff --git a/static/css/custom.css b/static/css/custom.css index 4d68e19..25e9b83 100644 --- a/static/css/custom.css +++ b/static/css/custom.css @@ -391,34 +391,50 @@ select.form-select:focus { /* Tylko ten przycisk */ .btn.btn-outline-light.btn-opis { - color: #fff; - background-color: transparent; - border: 1px solid var(--border); - transition: none; + color: #fff; + background-color: transparent; + border: 1px solid var(--border); + transition: none; } .btn.btn-outline-light.btn-opis:hover, .btn.btn-outline-light.btn-opis:focus { - color: #fff; - background-color: #161616; - border-color: color-mix(in srgb, var(--accent) 20%, var(--border)); + color: #fff; + background-color: #161616; + border-color: color-mix(in srgb, var(--accent) 20%, var(--border)); } .btn.btn-outline-light { - color: #fff; - background-color: transparent; - border: 1px solid rgba(255,255,255,0.9); + color: #fff; + background-color: transparent; + border: 1px solid rgba(255, 255, 255, 0.9); } .btn.btn-outline-light:hover, .btn.btn-outline-light:focus { - color: #fff; - background-color: #161616; - border-color: color-mix(in srgb, var(--accent) 20%, #ffffff); + color: #fff; + background-color: #161616; + border-color: color-mix(in srgb, var(--accent) 20%, #ffffff); } .btn.btn-outline-light:active { - color: #fff; - background-color: #141414; - border-color: color-mix(in srgb, var(--accent) 24%, #ffffff); + color: #fff; + background-color: #141414; + border-color: color-mix(in srgb, var(--accent) 24%, #ffffff); } + +#kanalyWarning, +#postepyWarning { + border: 1px solid #ffc107; + background-color: #2c2c2c; + color: #fff; +} + +input:disabled, +textarea:disabled, +select:disabled { + background-color: #2b2b2b !important; + color: #bbb !important; + opacity: 1 !important; + cursor: not-allowed; +} \ No newline at end of file diff --git a/static/js/sposoby_wplat.js b/static/js/sposoby_wplat.js new file mode 100644 index 0000000..09d538d --- /dev/null +++ b/static/js/sposoby_wplat.js @@ -0,0 +1,88 @@ +(function () { + const form = document.getElementById('form-edit-zbiorka') || document.getElementById('form-add-zbiorka') || document.querySelector('form'); + + const map = [ + ['uzyj_konta', 'numer_konta'], + ['uzyj_blik', 'numer_telefonu_blik'] + ]; + + const warnBox = document.getElementById('kanalyWarning'); + + function showWarn(show) { + if (!warnBox) return; + warnBox.classList.toggle('d-none', !show); + } + + function getEl(id) { return document.getElementById(id); } + + function toggleField(chkId, inputId) { + const chk = getEl(chkId); + const inp = getEl(inputId); + if (!inp || !chk) return; + const on = chk.checked; + inp.disabled = !on; + if (on) inp.setAttribute('required', ''); + else inp.removeAttribute('required'); + } + + function atLeastOneOn() { + return map.some(([c]) => getEl(c)?.checked); + } + + function blinkInvalid(el) { + if (!el) return; + el.classList.add('is-invalid'); + setTimeout(() => el.classList.remove('is-invalid'), 400); + } + + function preventUncheckLast(e) { + const target = e.target; + if (target.checked) return; + const after = map.map(([c]) => c === target.id ? false : !!getEl(c)?.checked); + if (!after.some(Boolean)) { + e.preventDefault(); + target.checked = true; + showWarn(true); + blinkInvalid(target); + } else { + showWarn(false); + } + } + + function onToggle(chkId, inputId) { + toggleField(chkId, inputId); + showWarn(!atLeastOneOn()); + } + + map.forEach(([chkId, inputId]) => { + const chk = getEl(chkId); + if (!chk) return; + chk.addEventListener('click', preventUncheckLast); + chk.addEventListener('change', () => onToggle(chkId, inputId)); + toggleField(chkId, inputId); + }); + showWarn(!atLeastOneOn()); + + if (form) { + form.addEventListener('submit', function (e) { + if (!atLeastOneOn()) { + e.preventDefault(); + showWarn(true); + blinkInvalid(getEl('uzyj_konta') || getEl('uzyj_blik')); + (getEl('uzyj_konta') || getEl('uzyj_blik'))?.focus(); + return; + } + + for (const [chkId, inputId] of map) { + const chk = getEl(chkId), inp = getEl(inputId); + if (chk?.checked && inp && !inp.value.trim()) { + e.preventDefault(); + showWarn(true); + blinkInvalid(inp); + inp.focus(); + return; + } + } + }); + } +})(); diff --git a/templates/admin/formularz_zbiorek.html b/templates/admin/formularz_zbiorek.html index 2a9e947..cd9830d 100644 --- a/templates/admin/formularz_zbiorek.html +++ b/templates/admin/formularz_zbiorek.html @@ -17,14 +17,16 @@
{% if is_edit and zbiorka and zbiorka.id %} - ← Szczegóły zbiórki + ← Szczegóły + zbiórki {% else %} ← Panel Admina {% endif %}
-
+

{{ 'Edytuj zbiórkę' if is_edit else 'Dodaj nową zbiórkę' }}

@@ -48,19 +50,18 @@ Brakuje: {{ delta|round(2) }} PLN - {% elif delta < 0 %} - + {% elif delta < 0 %} Nadwyżka: {{ (-delta)|round(2) }} PLN - - {% endif %} - {% endif %} - {% endif %} + + {% endif %} + {% endif %} + {% endif %} - {% if zbiorka.ukryj_kwote %} - Kwoty niepubliczne - {% else %} - Kwoty widoczne - {% endif %} + {% if zbiorka.ukryj_kwote %} + Kwoty niepubliczne + {% else %} + Kwoty widoczne + {% endif %}
{% else %} Uzupełnij podstawowe dane i dane płatności @@ -77,30 +78,17 @@
- + value="{{ (zbiorka.nazwa if zbiorka else request.form.get('nazwa','')) }}" required + aria-describedby="nazwaHelp">
Krótki, zrozumiały tytuł. Max 120 znaków.
- +
Możesz używać Markdown (nagłówki, listy, linki). W edytorze włącz podgląd 👁️. @@ -139,32 +127,44 @@ {% set i = loop.index0 %} - + - + - +
- - - + + +
- + {% endfor %} {% else %} - - - + + +
@@ -173,7 +173,8 @@
- + {% endif %} @@ -183,7 +184,8 @@
- +
@@ -192,56 +194,68 @@
Dane płatności
+
-
- -
- PL - + +
+
+ +
-
Wpisz ciąg cyfr; spacje dodadzą się automatycznie dla czytelności.
+
+ + +
+
+
+ + + +
+ +
+ PL + +
+
Wpisz ciąg cyfr; spacje dodadzą się automatycznie dla + czytelności.
+
+ + +
+48 - + value="{% if zbiorka and zbiorka.numer_telefonu_blik %}{{ zbiorka.numer_telefonu_blik }}{% elif global_settings %}{{ global_settings.numer_telefonu_blik }}{% else %}{{ request.form.get('numer_telefonu_blik','') }}{% endif %}">
-
Dziewięć cyfr telefonu powiązanego z BLIK. Spacje opcjonalne.
+
Dziewięć cyfr telefonu powiązanego z BLIK. Spacje + opcjonalne.
+
{% if is_edit %}
@@ -258,7 +272,8 @@ @@ -267,82 +282,56 @@
PLN - +
Minimalnie 0,01 PLN. Można później edytować.
- +
-
-
- - +
+
+ + +
-
-
-
- - +
+
+ + +
-
-
-
- - +
+
+ + +
-

- +
@@ -365,4 +354,5 @@ -{% endblock %} + +{% endblock %} \ No newline at end of file diff --git a/templates/index.html b/templates/index.html index fb85e88..536731b 100644 --- a/templates/index.html +++ b/templates/index.html @@ -24,7 +24,7 @@ zbiórki{% endif %}{% endblock %}
{% if zbiorki and zbiorki|length > 0 %} -
+
{% for z in zbiorki %} {% set progress = (z.stan / z.cel * 100) if z.cel > 0 else 0 %} {% set progress_clamped = 100 if progress > 100 else (0 if progress < 0 else progress) %}
{# TO POWODUJE ZE BLOK JEST KLIKALNY #} - - + + diff --git a/templates/zbiorka.html b/templates/zbiorka.html index 639a1a7..a611195 100644 --- a/templates/zbiorka.html +++ b/templates/zbiorka.html @@ -62,8 +62,8 @@ {% endif %} {{ it.nazwa }} {% if it.link %} - Sklep ↗ + Sklep + ↗ {% endif %}
@@ -165,6 +165,9 @@
+ {% set show_iban = zbiorka.uzyj_konta and zbiorka.numer_konta %} + {% set show_blik = zbiorka.uzyj_blik and zbiorka.numer_telefonu_blik %} +
@@ -175,17 +178,15 @@ {% if has_cel and not zbiorka.ukryj_kwote %} {% set brak = (zbiorka.cel - zbiorka.stan) %} {% if brak > 0 %} - - Brakuje: {{ brak|round(2) }} PLN - + Brakuje: {{ brak|round(2) }} PLN {% else %} - - Zrealizowana - + Zrealizowana {% endif %} {% endif %}
+ {% if show_iban or show_blik %} + {% if show_iban %}
@@ -198,7 +199,9 @@ aria-label="Kopiuj numer konta">Kopiuj
+ {% endif %} + {% if show_blik %}
@@ -211,6 +214,12 @@ aria-label="Kopiuj numer BLIK">Kopiuj
+ {% endif %} + {% else %} +
+ Kanały płatności są wyłączone dla tej zbiórki. +
+ {% endif %} {% if not zbiorka.ukryj_kwote %}
    @@ -243,7 +252,6 @@
{% endif %} - {% if current_user.is_authenticated and current_user.czy_admin %}
@@ -271,8 +279,7 @@ Łącznie pozycji: {{ aktywnosci|length }} {% endif %} {% if current_user.is_authenticated and current_user.czy_admin %} - + Zarządzaj {% endif %} @@ -324,5 +331,4 @@ {{ super() }} - {% endblock %} \ No newline at end of file