diff --git a/README.md b/README.md index 284ef41..d3f28f4 100644 --- a/README.md +++ b/README.md @@ -89,3 +89,47 @@ Dodatkowe przyklady: docker compose -f docker/compose.yml exec app sh -c 'flask lists create-from-template --template-id 7 --owner admin --when "2026-03-24 09:15" --title "Poranna lista"' ``` + +## Uproszczenie UI i skalowanie + +Interfejs korzysta z jednokolorowych, konturowych ikon SVG zapisanych lokalnie +w `shopping_app/static/icons/tabler/`. Ikony są wykorzystywane także przez +JavaScript (`shopping_app/static/js/ui_icons.js`). Nie wymagają CDN. + +W `.env` można ustawić: + +```dotenv +UI_SCALE=100 +``` + +Obsługiwane wartości: `100` (domyślnie), `90`, `80`, `70`. Pozostałe wartości +są traktowane jako `100`. Zmiana wymaga ponownego uruchomienia procesu Flask +(np. odtworzenia kontenera aplikacji). Przy `100` globalne skalowanie nie jest +włączane. Niższe wartości pomniejszają również tekst i obszary dotykowe, +dlatego na telefonach rekomendowane jest `100`. + +**Opcjonalnie:** aby zastąpić dołączone konturowe SVG oryginalnymi plikami +projektu [Tabler Icons](https://github.com/tabler/tabler-icons) (licencja MIT), +wykonaj przy dostępie do Internetu: + +```bash +python _tools/sync_tabler_icons.py +``` + +Skrypt pobierze komplet oryginalnych ikon z repozytorium Tabler, odtworzy +lokalny `sprite.svg`, bez wprowadzania zależności sieciowej w czasie działania +aplikacji. Dołączone SVG mają spójny styl Tabler, ale bez uruchomienia tego +skryptu nie należy traktować ich jako zweryfikowanych oryginalnych plików +Tabler Icons. + +Aktualizacja istniejącej instalacji: skopiuj zmodyfikowane pliki do katalogu +projektu, zachowaj lokalne `.env`, a następnie odtwórz kontener `app` np.: + +```bash +docker compose --env-file .env -f docker/compose.yml up -d --force-recreate app +``` + +Nie używaj trybu `deploy` skryptu `deploy_docker.sh` do wdrażania zmian tylko +z rozpakowanego ZIP-a, ponieważ uruchamia on `git pull` i może nadpisać lokalny +kod. Skalowanie przetestowano w Chromium; na innych silnikach oraz na +fizycznych telefonach należy dodatkowo zweryfikować działanie modali i układ. diff --git a/_tools/sync_tabler_icons.py b/_tools/sync_tabler_icons.py new file mode 100644 index 0000000..cd92d8a --- /dev/null +++ b/_tools/sync_tabler_icons.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +"""Opcjonalnie podmien lokalny zestaw konturowych SVG na oryginalne Tabler Icons. + +Pobranie jest jednorazowe podczas aktualizacji projektu; przegladarka +korzysta wylacznie z lokalnych plikow oraz sprite SVG. + +Uzycie: python _tools/sync_tabler_icons.py +Zrodlo: https://github.com/tabler/tabler-icons (MIT) +""" +from __future__ import annotations + +import copy +from pathlib import Path +from urllib.request import Request, urlopen +import xml.etree.ElementTree as ET + +BASE = Path(__file__).resolve().parents[1] +ICON_DIR = BASE / 'shopping_app/static/icons/tabler' +UPSTREAM = 'https://raw.githubusercontent.com/tabler/tabler-icons/main/icons/outline/' +SVG_NS = 'http://www.w3.org/2000/svg' +ET.register_namespace('', SVG_NS) + + +def main() -> None: + names = sorted(x.stem for x in ICON_DIR.glob('*.svg') if x.name != 'sprite.svg') + if not names: + raise SystemExit('Nie znaleziono ikon do synchronizacji.') + originals = {} + for name in names: + url = UPSTREAM + name + '.svg' + try: + with urlopen(Request(url, headers={'User-Agent': 'shopping-ui-icons/1.0'}), timeout=20) as result: + source = result.read() + element = ET.fromstring(source) + if element.tag != '{%s}svg' % SVG_NS: + raise ValueError('brak elementu svg') + originals[name] = (source, element) + print('OK', name) + except Exception as exc: + raise SystemExit(f'Nie pobrano {name}: {exc}. Nie zmieniono lokalnych ikon.') from exc + + root = ET.Element('{%s}svg' % SVG_NS, { + 'class': 'ui-icon-sprite', 'width': '0', 'height': '0', + 'aria-hidden': 'true', 'focusable': 'false', + }) + defs = ET.SubElement(root, '{%s}defs' % SVG_NS) + for name in names: + element = originals[name][1] + symbol = ET.SubElement(defs, '{%s}symbol' % SVG_NS, { + 'id': 'ti-' + name, 'viewBox': element.get('viewBox', '0 0 24 24'), + }) + for child in element: + symbol.append(copy.deepcopy(child)) + ET.indent(root, space=' ') + sprite_bytes = ET.tostring(root, encoding='utf-8') + + # Najpierw pobierz i zwaliduj caly zestaw; zapis wykonaj dopiero po sukcesie. + for name in names: + (ICON_DIR / (name + '.svg')).write_bytes(originals[name][0]) + (ICON_DIR / 'sprite.svg').write_bytes(sprite_bytes) + print(f'Zaktualizowano {len(names)} lokalnych SVG i sprite.svg.') + + +if __name__ == '__main__': + main() diff --git a/config.py b/config.py index 3f31f78..b0004ff 100644 --- a/config.py +++ b/config.py @@ -34,6 +34,10 @@ class Config: APP_PORT = env_int("APP_PORT", 8000) + # 100% jest neutralne; inne wartości tylko z białej listy. + _ui_scale = env_int("UI_SCALE", 100) + UI_SCALE = _ui_scale if _ui_scale in (100, 90, 80, 70) else 100 + DB_ENGINE = env_str("DB_ENGINE", "sqlite").lower() if DB_ENGINE == "sqlite": diff --git a/shopping_app/static/css/split/buttons.css b/shopping_app/static/css/split/buttons.css index 07d1593..fd643c4 100644 --- a/shopping_app/static/css/split/buttons.css +++ b/shopping_app/static/css/split/buttons.css @@ -5,7 +5,7 @@ font-weight: 600; letter-spacing: 0.01em; box-shadow: none; - transition: transform .18s ease, box-shadow .18s ease, background-color .18s ease, border-color .18s ease; + transition: background-color .18s ease, border-color .18s ease; } .btn-sm { @@ -20,11 +20,6 @@ box-shadow: 0 0 0 0.2rem rgba(25, 135, 84, 0.16) !important; } -.btn:hover, -.btn:focus-visible { - transform: translateY(-1px); -} - .btn-primary { background-color: var(--primary) !important; border-color: var(--primary-border) !important; @@ -38,10 +33,9 @@ } .btn-success { - background: linear-gradient(135deg, #29d17d, #1ea860) !important; + background: #1d975b !important; border-color: rgba(41, 209, 125, 0.9) !important; color: #fff !important; - box-shadow: 0 10px 24px rgba(0,0,0,0.16); } .btn-success:hover, @@ -85,7 +79,6 @@ background-color: rgba(255,255,255,0.1) !important; color: #fff !important; border-color: #6c757d !important; - box-shadow: 0 10px 24px rgba(0,0,0,0.16); } .btn-outline-light:active, @@ -100,7 +93,6 @@ .btn-outline-secondary:focus, .btn-outline-secondary:focus-visible { background: rgba(108, 117, 125, 0.18) !important; - box-shadow: 0 10px 24px rgba(0,0,0,0.16); } .btn-outline-success { @@ -115,7 +107,6 @@ background: var(--ui-success-soft) !important; border-color: var(--success-border) !important; color: #fff !important; - box-shadow: 0 10px 24px rgba(0,0,0,0.16); } .btn-outline-warning { diff --git a/shopping_app/static/css/split/components.css b/shopping_app/static/css/split/components.css index f1024d2..a78476d 100644 --- a/shopping_app/static/css/split/components.css +++ b/shopping_app/static/css/split/components.css @@ -249,8 +249,7 @@ width: 2.6rem; height: 2.6rem; border-radius: 16px; - background: linear-gradient(135deg, rgba(39,208,125,0.22), rgba(74,144,226,0.18)); - box-shadow: inset 0 0 0 1px rgba(255,255,255,0.08); + background: rgba(39, 208, 125, 0.16); } .app-brand__title { @@ -394,7 +393,7 @@ min-width: 14rem; padding: .4rem; border-radius: 16px; - background: linear-gradient(180deg, rgba(14,22,35,0.98), rgba(9,15,26,0.98)) !important; + background: rgba(14, 22, 35, 0.98) !important; border: 1px solid rgba(255,255,255,0.1); box-shadow: 0 18px 36px rgba(0,0,0,0.28); } @@ -889,10 +888,7 @@ html, body { min-height: 100%; background-color: var(--app-bg); - background-image: - radial-gradient(circle at top left, rgba(39, 208, 125, 0.18), transparent 24%), - radial-gradient(circle at top right, rgba(74, 144, 226, 0.16), transparent 22%), - linear-gradient(180deg, #09111d 0%, #08121f 38%, #060d18 100%); + background-image: none; background-repeat: no-repeat; color: var(--app-text); } @@ -923,7 +919,7 @@ body.app-body { position: fixed; inset: 0; pointer-events: none; - background: linear-gradient(180deg, rgba(255,255,255,0.02), transparent 28%); + background: transparent; } .app-header { @@ -1023,7 +1019,7 @@ h1, h2, h3, h4, h5, h6 { .card.bg-secondary.bg-opacity-10, #share-card { - background: linear-gradient(180deg, rgba(16, 29, 49, 0.96), rgba(10, 20, 36, 0.94)) !important; + background: rgba(16, 29, 49, 0.96) !important; } .table-dark { @@ -1050,11 +1046,11 @@ h1, h2, h3, h4, h5, h6 { } .list-group-item.bg-success { - background: linear-gradient(135deg, rgba(39,208,125,0.92), rgba(22,150,91,0.96)) !important; + background: rgba(22, 150, 91, 0.96) !important; } .list-group-item.bg-warning { - background: linear-gradient(135deg, rgba(246,196,83,0.96), rgba(224,164,26,0.96)) !important; + background: rgba(224, 164, 26, 0.96) !important; color: #1c1b17 !important; } diff --git a/shopping_app/static/css/split/layout.css b/shopping_app/static/css/split/layout.css index 81bad2f..4717ffb 100644 --- a/shopping_app/static/css/split/layout.css +++ b/shopping_app/static/css/split/layout.css @@ -311,13 +311,13 @@ textarea.form-control { .dropdown-menu-dark, .list-group-item.bg-dark, .table-dark { - background: linear-gradient(180deg, var(--ui-surface-2), rgba(8, 12, 20, 0.96)) !important; + background: var(--ui-surface-2) !important; } .card.bg-secondary.bg-opacity-10, .admin-shortcuts, #bulkBar .card { - background: linear-gradient(180deg, rgba(22, 29, 45, 0.88), rgba(12, 18, 30, 0.88)) !important; + background: rgba(22, 29, 45, 0.88) !important; } .form-label, @@ -1592,11 +1592,11 @@ input[type="checkbox"].form-check-input, } #progress-bar-purchased { - background: linear-gradient(135deg, rgba(39,208,125,0.96), rgba(22,150,91,0.98)) !important; + background: rgba(22, 150, 91, 0.96) !important; } #progress-bar-not-purchased { - background: linear-gradient(135deg, rgba(246,196,83,0.96), rgba(224,164,26,0.98)) !important; + background: rgba(224, 164, 26, 0.96) !important; } #progress-bar-remaining { @@ -2100,11 +2100,10 @@ body:not(.sorting-active) .drag-handle { .endpoint-list_share .shopping-entry-card, .endpoint-shared_list .shopping-entry-card, .endpoint-view_list .shopping-entry-card { - background: linear-gradient(180deg, rgba(25, 135, 84, 0.16), rgba(13, 17, 23, 0.92)); + background: rgba(25, 135, 84, 0.16); border: 1px solid rgba(25, 135, 84, 0.42); border-radius: 1rem; padding: .9rem; - box-shadow: 0 .5rem 1.2rem rgba(0, 0, 0, 0.18); } .endpoint-list .shopping-entry-card__label, @@ -2189,7 +2188,7 @@ body:not(.sorting-active) .drag-handle { .endpoint-list_share .shopping-entry-card--expense, .endpoint-shared_list .shopping-entry-card--expense, .endpoint-view_list .shopping-entry-card--expense { - background: linear-gradient(180deg, rgba(13, 110, 253, 0.16), rgba(13, 17, 23, 0.92)); + background: rgba(13, 110, 253, 0.16); border-color: rgba(13, 110, 253, 0.42); } @@ -2258,8 +2257,7 @@ body:not(.sorting-active) .drag-handle { padding: 1rem; border: 1px solid rgba(255,255,255,.08); border-radius: 1rem; - background: linear-gradient(180deg, rgba(255,255,255,.04), rgba(255,255,255,.02)); - box-shadow: 0 .5rem 1.25rem rgba(0,0,0,.14); + background: rgba(255, 255, 255, 0.03); } .list-quick-actions__header { diff --git a/shopping_app/static/css/split/pages.css b/shopping_app/static/css/split/pages.css index ff9b072..3ca5720 100644 --- a/shopping_app/static/css/split/pages.css +++ b/shopping_app/static/css/split/pages.css @@ -39,7 +39,7 @@ margin: 0 !important; border-radius: 16px !important; border: 1px solid rgba(255,255,255,0.08) !important; - background: linear-gradient(180deg, rgba(11,22,40,0.92) 0%, rgba(8,16,30,0.92) 100%) !important; + background: rgba(11, 22, 40, 0.92) !important; box-shadow: inset 0 1px 0 rgba(255,255,255,0.03); } @@ -67,22 +67,20 @@ cursor: pointer; border: 1px solid rgba(255, 255, 255, 0.08); border-radius: 20px; - background: linear-gradient(135deg, rgba(255, 255, 255, 0.04), rgba(255, 255, 255, 0.02)); + background: rgba(255, 255, 255, 0.03); box-shadow: 0 12px 32px rgba(0, 0, 0, 0.18); transition: transform 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease, background 0.2s ease; } .receipt-disclosure:hover, .receipt-disclosure:focus-visible { - transform: translateY(-1px); border-color: rgba(255, 255, 255, 0.18); - box-shadow: 0 16px 36px rgba(0, 0, 0, 0.24); outline: none; } .receipt-disclosure.is-open { border-color: rgba(24, 64, 118, 0.9); - background: linear-gradient(135deg, rgba(24, 64, 118, 0.22), rgba(255, 255, 255, 0.03)); + background: rgba(24, 64, 118, 0.20); } .receipt-disclosure__content { @@ -198,7 +196,7 @@ .share-hub { border: 1px solid rgba(79, 142, 255, 0.18); - background: linear-gradient(180deg, rgba(11, 24, 43, 0.98), rgba(8, 17, 31, 0.96)) !important; + background: rgba(11, 24, 43, 0.98) !important; } .share-hub .card-body { @@ -319,7 +317,7 @@ border-top-left-radius: 24px; border-top-right-radius: 24px; border: 1px solid rgba(255, 255, 255, 0.06); - background: linear-gradient(180deg, rgba(8, 18, 33, 0.995), rgba(6, 13, 24, 0.99)) !important; + background: rgba(8, 18, 33, 0.995) !important; box-shadow: 0 -24px 60px rgba(0, 0, 0, 0.42); } @@ -374,7 +372,7 @@ position: sticky; bottom: 0; padding-top: 0.3rem; - background: linear-gradient(180deg, rgba(6, 13, 24, 0), rgba(6, 13, 24, 0.96) 28%); + background: rgba(6, 13, 24, 0.96); } @media (min-width: 576px) { diff --git a/shopping_app/static/css/split/responsive.css b/shopping_app/static/css/split/responsive.css index 4fdb2d2..e62e0db 100644 --- a/shopping_app/static/css/split/responsive.css +++ b/shopping_app/static/css/split/responsive.css @@ -149,20 +149,18 @@ height: 16px; margin-top: 0 !important; border: 1px solid rgba(255, 255, 255, 0.08); - background: - linear-gradient(180deg, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.02)), - var(--dark-700) !important; + background: var(--dark-700) !important; box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05), 0 4px 10px rgba(0, 0, 0, 0.18); } .endpoint-main_page .main-list-progress .progress-bar.bg-success { - background: linear-gradient(135deg, rgba(40, 199, 111, 0.98), rgba(22, 163, 74, 0.98)) !important; + background: rgba(40, 199, 111, 0.98) !important; } .endpoint-main_page .main-list-progress .progress-bar.bg-warning { - background: linear-gradient(135deg, rgba(245, 189, 65, 0.98), rgba(217, 119, 6, 0.98)) !important; + background: rgba(245, 189, 65, 0.98) !important; } .endpoint-main_page .main-list-progress .progress-bar.bg-transparent { @@ -199,7 +197,7 @@ padding: 1rem 1rem 1.05rem; border-radius: 1rem; border: 1px solid rgba(255, 255, 255, 0.08); - background: linear-gradient(180deg, rgba(255,255,255,0.04), rgba(255,255,255,0.02)), rgba(9, 16, 28, 0.88); + background: rgba(9, 16, 28, 0.88); box-shadow: 0 12px 28px rgba(0, 0, 0, 0.2); } diff --git a/shopping_app/static/css/split/ui_clean.css b/shopping_app/static/css/split/ui_clean.css new file mode 100644 index 0000000..858ff58 --- /dev/null +++ b/shopping_app/static/css/split/ui_clean.css @@ -0,0 +1,49 @@ +/* Monochromatyczne ikony z lokalnego sprite SVG w . */ +.ui-icon-sprite { + position: absolute !important; + left: 0; top: 0; + width: 0 !important; height: 0 !important; + overflow: hidden; pointer-events: none; +} +.ui-icon { + display: inline-block; + vertical-align: -0.13em; + width: 1em; height: 1em; + flex: none; + fill: none; + stroke: currentColor; + stroke-width: 2; + stroke-linecap: round; + stroke-linejoin: round; + pointer-events: none; +} +.app-brand__icon .ui-icon { width: 1.15rem; height: 1.15rem; } + +/* Wyciszenie oznaczeń wizualnych bez usuwania informacji i akcji. */ +.share-state-badge--link, +.share-state-badge--people { background-color: rgba(255, 255, 255, 0.04); } +.list-quick-actions__icon { color: inherit; } +.receipt-disclosure { box-shadow: none; } + +/* zoom zachowuje układ blokowy, w przeciwieństwie do transform: scale(). + Wartości 100 nie nadpisujemy, aby domyślny UI pozostał bez zmian. + Pomniejszanie dotyczy także tekstu i obszarów dotykowych - patrz testy. */ +html[data-ui-scale="90"] body.app-body { zoom: 0.9; } +html[data-ui-scale="80"] body.app-body { zoom: 0.8; } +html[data-ui-scale="70"] body.app-body { zoom: 0.7; } + +/* Bootstrap dodaje backdrop do body i nadaje mu 100vw/100vh. + Przy body { zoom } jednostki vw/vh wymagaja kompensacji, inaczej + przy 70-90% pozostaje nieprzyciemniona czesc ekranu. */ +html[data-ui-scale="90"] :is(.modal-backdrop, .offcanvas-backdrop) { + width: calc(100vw / 0.9); + height: calc(100vh / 0.9); +} +html[data-ui-scale="80"] :is(.modal-backdrop, .offcanvas-backdrop) { + width: calc(100vw / 0.8); + height: calc(100vh / 0.8); +} +html[data-ui-scale="70"] :is(.modal-backdrop, .offcanvas-backdrop) { + width: calc(100vw / 0.7); + height: calc(100vh / 0.7); +} diff --git a/shopping_app/static/icons/tabler/alert-triangle.svg b/shopping_app/static/icons/tabler/alert-triangle.svg new file mode 100644 index 0000000..279f65e --- /dev/null +++ b/shopping_app/static/icons/tabler/alert-triangle.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/archive.svg b/shopping_app/static/icons/tabler/archive.svg new file mode 100644 index 0000000..66e19c9 --- /dev/null +++ b/shopping_app/static/icons/tabler/archive.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/arrow-down.svg b/shopping_app/static/icons/tabler/arrow-down.svg new file mode 100644 index 0000000..ded72db --- /dev/null +++ b/shopping_app/static/icons/tabler/arrow-down.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/arrow-up.svg b/shopping_app/static/icons/tabler/arrow-up.svg new file mode 100644 index 0000000..d8a9524 --- /dev/null +++ b/shopping_app/static/icons/tabler/arrow-up.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/arrows-sort.svg b/shopping_app/static/icons/tabler/arrows-sort.svg new file mode 100644 index 0000000..058fe5b --- /dev/null +++ b/shopping_app/static/icons/tabler/arrows-sort.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/ban.svg b/shopping_app/static/icons/tabler/ban.svg new file mode 100644 index 0000000..0a44075 --- /dev/null +++ b/shopping_app/static/icons/tabler/ban.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/book.svg b/shopping_app/static/icons/tabler/book.svg new file mode 100644 index 0000000..233c736 --- /dev/null +++ b/shopping_app/static/icons/tabler/book.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/box.svg b/shopping_app/static/icons/tabler/box.svg new file mode 100644 index 0000000..a3f2f85 --- /dev/null +++ b/shopping_app/static/icons/tabler/box.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/bulb.svg b/shopping_app/static/icons/tabler/bulb.svg new file mode 100644 index 0000000..a857203 --- /dev/null +++ b/shopping_app/static/icons/tabler/bulb.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/calendar.svg b/shopping_app/static/icons/tabler/calendar.svg new file mode 100644 index 0000000..7482c2e --- /dev/null +++ b/shopping_app/static/icons/tabler/calendar.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/camera.svg b/shopping_app/static/icons/tabler/camera.svg new file mode 100644 index 0000000..9a0eb6e --- /dev/null +++ b/shopping_app/static/icons/tabler/camera.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/chart-bar.svg b/shopping_app/static/icons/tabler/chart-bar.svg new file mode 100644 index 0000000..21b1710 --- /dev/null +++ b/shopping_app/static/icons/tabler/chart-bar.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/chart-line.svg b/shopping_app/static/icons/tabler/chart-line.svg new file mode 100644 index 0000000..22db738 --- /dev/null +++ b/shopping_app/static/icons/tabler/chart-line.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/check.svg b/shopping_app/static/icons/tabler/check.svg new file mode 100644 index 0000000..95a7b96 --- /dev/null +++ b/shopping_app/static/icons/tabler/check.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/clipboard.svg b/shopping_app/static/icons/tabler/clipboard.svg new file mode 100644 index 0000000..8682cb3 --- /dev/null +++ b/shopping_app/static/icons/tabler/clipboard.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/clock.svg b/shopping_app/static/icons/tabler/clock.svg new file mode 100644 index 0000000..cdf894b --- /dev/null +++ b/shopping_app/static/icons/tabler/clock.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/coins.svg b/shopping_app/static/icons/tabler/coins.svg new file mode 100644 index 0000000..7e99488 --- /dev/null +++ b/shopping_app/static/icons/tabler/coins.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/copy.svg b/shopping_app/static/icons/tabler/copy.svg new file mode 100644 index 0000000..dbb3918 --- /dev/null +++ b/shopping_app/static/icons/tabler/copy.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/device-floppy.svg b/shopping_app/static/icons/tabler/device-floppy.svg new file mode 100644 index 0000000..d0067ce --- /dev/null +++ b/shopping_app/static/icons/tabler/device-floppy.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/download.svg b/shopping_app/static/icons/tabler/download.svg new file mode 100644 index 0000000..1e23318 --- /dev/null +++ b/shopping_app/static/icons/tabler/download.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/eraser.svg b/shopping_app/static/icons/tabler/eraser.svg new file mode 100644 index 0000000..58e2645 --- /dev/null +++ b/shopping_app/static/icons/tabler/eraser.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/eye-off.svg b/shopping_app/static/icons/tabler/eye-off.svg new file mode 100644 index 0000000..4a02b83 --- /dev/null +++ b/shopping_app/static/icons/tabler/eye-off.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/eye.svg b/shopping_app/static/icons/tabler/eye.svg new file mode 100644 index 0000000..053eb28 --- /dev/null +++ b/shopping_app/static/icons/tabler/eye.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/file-text.svg b/shopping_app/static/icons/tabler/file-text.svg new file mode 100644 index 0000000..70a6f60 --- /dev/null +++ b/shopping_app/static/icons/tabler/file-text.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/flame.svg b/shopping_app/static/icons/tabler/flame.svg new file mode 100644 index 0000000..d2de64c --- /dev/null +++ b/shopping_app/static/icons/tabler/flame.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/folder.svg b/shopping_app/static/icons/tabler/folder.svg new file mode 100644 index 0000000..69b62df --- /dev/null +++ b/shopping_app/static/icons/tabler/folder.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/history.svg b/shopping_app/static/icons/tabler/history.svg new file mode 100644 index 0000000..0a60e9d --- /dev/null +++ b/shopping_app/static/icons/tabler/history.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/hourglass.svg b/shopping_app/static/icons/tabler/hourglass.svg new file mode 100644 index 0000000..a422a19 --- /dev/null +++ b/shopping_app/static/icons/tabler/hourglass.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/info-circle.svg b/shopping_app/static/icons/tabler/info-circle.svg new file mode 100644 index 0000000..f77ac39 --- /dev/null +++ b/shopping_app/static/icons/tabler/info-circle.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/link.svg b/shopping_app/static/icons/tabler/link.svg new file mode 100644 index 0000000..039e2dd --- /dev/null +++ b/shopping_app/static/icons/tabler/link.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/lock-open.svg b/shopping_app/static/icons/tabler/lock-open.svg new file mode 100644 index 0000000..64ae032 --- /dev/null +++ b/shopping_app/static/icons/tabler/lock-open.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/lock.svg b/shopping_app/static/icons/tabler/lock.svg new file mode 100644 index 0000000..8965cd5 --- /dev/null +++ b/shopping_app/static/icons/tabler/lock.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/login.svg b/shopping_app/static/icons/tabler/login.svg new file mode 100644 index 0000000..8ba9c3b --- /dev/null +++ b/shopping_app/static/icons/tabler/login.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/logout.svg b/shopping_app/static/icons/tabler/logout.svg new file mode 100644 index 0000000..25a6d63 --- /dev/null +++ b/shopping_app/static/icons/tabler/logout.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/maximize.svg b/shopping_app/static/icons/tabler/maximize.svg new file mode 100644 index 0000000..3661701 --- /dev/null +++ b/shopping_app/static/icons/tabler/maximize.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/menu-2.svg b/shopping_app/static/icons/tabler/menu-2.svg new file mode 100644 index 0000000..fcd0750 --- /dev/null +++ b/shopping_app/static/icons/tabler/menu-2.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/notes.svg b/shopping_app/static/icons/tabler/notes.svg new file mode 100644 index 0000000..34dd9d2 --- /dev/null +++ b/shopping_app/static/icons/tabler/notes.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/number.svg b/shopping_app/static/icons/tabler/number.svg new file mode 100644 index 0000000..ceab9fc --- /dev/null +++ b/shopping_app/static/icons/tabler/number.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/palette.svg b/shopping_app/static/icons/tabler/palette.svg new file mode 100644 index 0000000..79600ce --- /dev/null +++ b/shopping_app/static/icons/tabler/palette.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/pencil.svg b/shopping_app/static/icons/tabler/pencil.svg new file mode 100644 index 0000000..59bbeb5 --- /dev/null +++ b/shopping_app/static/icons/tabler/pencil.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/photo.svg b/shopping_app/static/icons/tabler/photo.svg new file mode 100644 index 0000000..2e8fbeb --- /dev/null +++ b/shopping_app/static/icons/tabler/photo.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/player-pause.svg b/shopping_app/static/icons/tabler/player-pause.svg new file mode 100644 index 0000000..c8ecac2 --- /dev/null +++ b/shopping_app/static/icons/tabler/player-pause.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/player-play.svg b/shopping_app/static/icons/tabler/player-play.svg new file mode 100644 index 0000000..2babcaf --- /dev/null +++ b/shopping_app/static/icons/tabler/player-play.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/plus.svg b/shopping_app/static/icons/tabler/plus.svg new file mode 100644 index 0000000..3cf8e8e --- /dev/null +++ b/shopping_app/static/icons/tabler/plus.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/receipt.svg b/shopping_app/static/icons/tabler/receipt.svg new file mode 100644 index 0000000..03cb421 --- /dev/null +++ b/shopping_app/static/icons/tabler/receipt.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/refresh.svg b/shopping_app/static/icons/tabler/refresh.svg new file mode 100644 index 0000000..2042da8 --- /dev/null +++ b/shopping_app/static/icons/tabler/refresh.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/rotate-clockwise.svg b/shopping_app/static/icons/tabler/rotate-clockwise.svg new file mode 100644 index 0000000..d9ddf5e --- /dev/null +++ b/shopping_app/static/icons/tabler/rotate-clockwise.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/scissors.svg b/shopping_app/static/icons/tabler/scissors.svg new file mode 100644 index 0000000..32a39c4 --- /dev/null +++ b/shopping_app/static/icons/tabler/scissors.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/search.svg b/shopping_app/static/icons/tabler/search.svg new file mode 100644 index 0000000..a39d0f0 --- /dev/null +++ b/shopping_app/static/icons/tabler/search.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/settings.svg b/shopping_app/static/icons/tabler/settings.svg new file mode 100644 index 0000000..da9dcf6 --- /dev/null +++ b/shopping_app/static/icons/tabler/settings.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/share.svg b/shopping_app/static/icons/tabler/share.svg new file mode 100644 index 0000000..af3f0cd --- /dev/null +++ b/shopping_app/static/icons/tabler/share.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/shield-lock.svg b/shopping_app/static/icons/tabler/shield-lock.svg new file mode 100644 index 0000000..36d5327 --- /dev/null +++ b/shopping_app/static/icons/tabler/shield-lock.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/shopping-bag.svg b/shopping_app/static/icons/tabler/shopping-bag.svg new file mode 100644 index 0000000..4ee5f39 --- /dev/null +++ b/shopping_app/static/icons/tabler/shopping-bag.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/shopping-cart.svg b/shopping_app/static/icons/tabler/shopping-cart.svg new file mode 100644 index 0000000..487c89c --- /dev/null +++ b/shopping_app/static/icons/tabler/shopping-cart.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/sprite.svg b/shopping_app/static/icons/tabler/sprite.svg new file mode 100644 index 0000000..d9871af --- /dev/null +++ b/shopping_app/static/icons/tabler/sprite.svg @@ -0,0 +1,67 @@ + diff --git a/shopping_app/static/icons/tabler/tag.svg b/shopping_app/static/icons/tabler/tag.svg new file mode 100644 index 0000000..36aa9a9 --- /dev/null +++ b/shopping_app/static/icons/tabler/tag.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/target.svg b/shopping_app/static/icons/tabler/target.svg new file mode 100644 index 0000000..beb0995 --- /dev/null +++ b/shopping_app/static/icons/tabler/target.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/template.svg b/shopping_app/static/icons/tabler/template.svg new file mode 100644 index 0000000..ed6c2a9 --- /dev/null +++ b/shopping_app/static/icons/tabler/template.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/tool.svg b/shopping_app/static/icons/tabler/tool.svg new file mode 100644 index 0000000..3aad29e --- /dev/null +++ b/shopping_app/static/icons/tabler/tool.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/trash.svg b/shopping_app/static/icons/tabler/trash.svg new file mode 100644 index 0000000..fddb5a1 --- /dev/null +++ b/shopping_app/static/icons/tabler/trash.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/user.svg b/shopping_app/static/icons/tabler/user.svg new file mode 100644 index 0000000..0f3e170 --- /dev/null +++ b/shopping_app/static/icons/tabler/user.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/users.svg b/shopping_app/static/icons/tabler/users.svg new file mode 100644 index 0000000..e651a59 --- /dev/null +++ b/shopping_app/static/icons/tabler/users.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/world.svg b/shopping_app/static/icons/tabler/world.svg new file mode 100644 index 0000000..bf5f478 --- /dev/null +++ b/shopping_app/static/icons/tabler/world.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/icons/tabler/x.svg b/shopping_app/static/icons/tabler/x.svg new file mode 100644 index 0000000..69f1915 --- /dev/null +++ b/shopping_app/static/icons/tabler/x.svg @@ -0,0 +1,4 @@ + + + + diff --git a/shopping_app/static/js/access_users.js b/shopping_app/static/js/access_users.js index 73937c3..fee9537 100644 --- a/shopping_app/static/js/access_users.js +++ b/shopping_app/static/js/access_users.js @@ -36,7 +36,7 @@ if (sheetBadge) sheetBadge.textContent = String(count); if (cardBadge) { - cardBadge.textContent = `👥 ${count} ${pluralizePeople(count)}`; + setUiIconLabel(cardBadge, 'users', `${count} ${pluralizePeople(count)}`); cardBadge.classList.toggle('d-none', count === 0); } } diff --git a/shopping_app/static/js/app_ui.js b/shopping_app/static/js/app_ui.js index db3caea..08593b2 100644 --- a/shopping_app/static/js/app_ui.js +++ b/shopping_app/static/js/app_ui.js @@ -19,11 +19,11 @@ function initPasswordField(input) { btn.setAttribute('aria-label', 'Pokaż lub ukryj hasło'); btn.setAttribute('aria-pressed', 'false'); btn.title = 'Pokaż hasło'; - btn.innerHTML = ''; + btn.innerHTML = uiIcon('eye'); const syncState = function () { const visible = input.type === 'text'; - btn.innerHTML = visible ? '' : ''; + btn.innerHTML = uiIcon(visible ? 'eye-off' : 'eye'); btn.classList.toggle('is-active', visible); btn.setAttribute('aria-pressed', visible ? 'true' : 'false'); btn.title = visible ? 'Ukryj hasło' : 'Pokaż hasło'; @@ -99,7 +99,7 @@ function enhanceSearchableTables() { toolbar.className = 'table-toolbar'; toolbar.innerHTML = [ '', @@ -149,10 +149,10 @@ function wireCopyButtons() { const text = target.value || target.textContent || ''; try { await navigator.clipboard.writeText(text.trim()); - const original = button.textContent; - button.textContent = '✅ Skopiowano'; + const original = button.innerHTML; + setUiIconLabel(button, 'check', 'Skopiowano'); setTimeout(function () { - button.textContent = original; + button.innerHTML = original; }, 1800); } catch (err) { console.warn('Copy failed', err); diff --git a/shopping_app/static/js/functions.js b/shopping_app/static/js/functions.js index d9f6a2d..76b4a70 100644 --- a/shopping_app/static/js/functions.js +++ b/shopping_app/static/js/functions.js @@ -326,11 +326,11 @@ function formatShareUrlPreview(url) { return String(url || '').replace(/^https?:\/\//, ''); } -function setVisibilityBadgeState(el, isPublic, publicLabel = '🌍 Publiczna', privateLabel = '🔒 Prywatna') { +function setVisibilityBadgeState(el, isPublic, publicLabel = 'Publiczna', privateLabel = 'Prywatna') { if (!el) return; el.classList.remove('share-state-badge--public', 'share-state-badge--private'); el.classList.add(isPublic ? 'share-state-badge--public' : 'share-state-badge--private'); - el.textContent = isPublic ? publicLabel : privateLabel; + setUiIconLabel(el, isPublic ? 'world' : 'lock', isPublic ? publicLabel : privateLabel); } function updateShareVisibilityUI(data) { @@ -368,7 +368,7 @@ function updateShareVisibilityUI(data) { } if (toggleBtn) { - toggleBtn.innerHTML = isPublic ? '🙈 Ustaw jako prywatną' : '🌍 Uczyń publiczną'; + setUiIconLabel(toggleBtn, isPublic ? 'eye-off' : 'world', isPublic ? 'Ustaw jako prywatną' : 'Uczyń publiczną'); } } @@ -485,7 +485,7 @@ function renderItem(item, isShare = window.IS_SHARE, optionsOrShowEditOnly = fal const iconBtn = 'btn btn-outline-light btn-sm shopping-action-btn'; const wideBtn = 'btn btn-outline-light btn-sm shopping-action-btn shopping-action-btn--wide'; const dragHandleButton = canEditListItem - ? `` + ? `` : ''; const itemNameHtml = canEditListItem ? ` - `; + + `; } if (item.not_purchased) { @@ -510,17 +510,17 @@ function renderItem(item, isShare = window.IS_SHARE, optionsOrShowEditOnly = fal `; } else if (!isShare || canShowShareActions || isOwner) { actionButtons += ` - `; + `; } if (temporaryShareUndo) { actionButtons += ` - - `; + + `; } else if (canShowShareActions || (!isShare && isOwner)) { actionButtons += ` - `; + `; } li.innerHTML = ` diff --git a/shopping_app/static/js/lists_access.js b/shopping_app/static/js/lists_access.js index bf42bd9..12988a1 100644 --- a/shopping_app/static/js/lists_access.js +++ b/shopping_app/static/js/lists_access.js @@ -9,7 +9,7 @@ function applyFilter(){const q=(filterInput?.value||'').trim().toLowerCase();let filterInput?.addEventListener('input',applyFilter);applyFilter(); selectAll?.addEventListener('change',()=>{visibleRows().forEach(tr=>{const cb=tr.querySelector('.row-check'); if(cb) cb.checked=selectAll.checked;});}); $$('.copy-share').forEach(btn=>btn.addEventListener('click',async()=>{const url=btn.dataset.url;try{await navigator.clipboard.writeText(url);}catch{const ta=Object.assign(document.createElement('textarea'),{value:url});document.body.appendChild(ta);ta.select();document.execCommand('copy');ta.remove();}showToast('Skopiowano link udostępnienia','success');})); -function addGlobalToken(username){if(!username) return;const exists=$(`.user-token[data-user="${username}"]`,bulkTokens);if(exists) return;const token=document.createElement('span');token.className='badge rounded-pill text-bg-secondary user-token';token.dataset.user=username;token.innerHTML=`@${username} `;token.querySelector('button').addEventListener('click',()=>token.remove());bulkTokens.appendChild(token);} +function addGlobalToken(username){if(!username) return;const exists=$(`.user-token[data-user="${username}"]`,bulkTokens);if(exists) return;const token=document.createElement('span');token.className='badge rounded-pill text-bg-secondary user-token';token.dataset.user=username;token.innerHTML=`@${username} `;token.querySelector('button').addEventListener('click',()=>token.remove());bulkTokens.appendChild(token);} bulkInput?.addEventListener('keydown',e=>{if(e.key==='Enter'){e.preventDefault();parseUserText(bulkInput.value).forEach(addGlobalToken);bulkInput.value='';}}); bulkInput?.addEventListener('change',()=>{parseUserText(bulkInput.value).forEach(addGlobalToken);bulkInput.value='';}); let hintCtrl=null; @@ -17,6 +17,6 @@ function renderBulkHints(users){const dl=$('#userHints'); if(!dl) return; dl.inn async function fetchBulkHints(q=''){const normalized=String(q||'').trim().replace(/^@/,'');try{hintCtrl?.abort();hintCtrl=new AbortController();const res=await fetch(`/admin/user-suggestions?q=${encodeURIComponent(normalized)}`,{credentials:'same-origin',signal:hintCtrl.signal});if(!res.ok) return renderBulkHints([]);const data=await res.json().catch(()=>({users:[]}));renderBulkHints(data.users||[]);}catch(e){renderBulkHints([]);}} bulkInput?.addEventListener('focus',()=>fetchBulkHints(bulkInput.value)); bulkInput?.addEventListener('input',()=>fetchBulkHints(bulkInput.value)); -async function bulkGrant(){const lists=selectedListIds(), users=$$('.user-token',bulkTokens).map(t=>t.dataset.user);if(!lists.length) return showToast('Zaznacz przynajmniej jedną listę','warning');if(!users.length) return showToast('Dodaj przynajmniej jednego użytkownika','warning');bulkBtn.disabled=true;bulkBtn.textContent='Pracuję…';const url=location.pathname+location.search;let ok=0,fail=0;for(const lid of lists){for(const u of users){const form=new FormData();form.set('action','grant');form.set('target_list_id',lid);form.set('grant_username',u);try{const res=await fetch(url,{method:'POST',body:form,credentials:'same-origin',headers:{'Accept':'application/json','X-Requested-With':'fetch'}});if(res.ok) ok++; else fail++;}catch{fail++;}}}bulkBtn.disabled=false;bulkBtn.textContent='➕ Nadaj dostęp';showToast(`Gotowe. Sukcesy: ${ok}${fail?`, błędy: ${fail}`:''}`,fail?'danger':'success');if(ok) location.reload();} +async function bulkGrant(){const lists=selectedListIds(), users=$$('.user-token',bulkTokens).map(t=>t.dataset.user);if(!lists.length) return showToast('Zaznacz przynajmniej jedną listę','warning');if(!users.length) return showToast('Dodaj przynajmniej jednego użytkownika','warning');bulkBtn.disabled=true;bulkBtn.textContent='Pracuję…';const url=location.pathname+location.search;let ok=0,fail=0;for(const lid of lists){for(const u of users){const form=new FormData();form.set('action','grant');form.set('target_list_id',lid);form.set('grant_username',u);try{const res=await fetch(url,{method:'POST',body:form,credentials:'same-origin',headers:{'Accept':'application/json','X-Requested-With':'fetch'}});if(res.ok) ok++; else fail++;}catch{fail++;}}}bulkBtn.disabled=false;setUiIconLabel(bulkBtn,'plus','Nadaj dostęp');showToast(`Gotowe. Sukcesy: ${ok}${fail?`, błędy: ${fail}`:''}`,fail?'danger':'success');if(ok) location.reload();} bulkBtn?.addEventListener('click',bulkGrant); })(); \ No newline at end of file diff --git a/shopping_app/static/js/live.js b/shopping_app/static/js/live.js index 1b1c244..cc47a43 100644 --- a/shopping_app/static/js/live.js +++ b/shopping_app/static/js/live.js @@ -149,7 +149,7 @@ function setupList(listId, username) { socket.on('expense_added', data => { const badgeEl = document.getElementById('total-expense1'); if (badgeEl) { - badgeEl.innerHTML = `💸 ${formatCurrencyAmount(data.total)}`; + badgeEl.innerHTML = `${uiIcon('coins')} ${formatCurrencyAmount(data.total)}`; badgeEl.classList.remove('bg-secondary'); badgeEl.classList.add('bg-success'); badgeEl.style.display = ''; @@ -157,7 +157,7 @@ function setupList(listId, username) { const summaryEl = document.getElementById('total-expense2'); if (summaryEl) { - summaryEl.innerHTML = `💸 Łącznie wydano: ${formatCurrencyAmount(data.total)}`; + summaryEl.innerHTML = `${uiIcon('coins')} Łącznie wydano: ${formatCurrencyAmount(data.total)}`; } showToast(`Dodano wydatek: ${formatCurrencyAmount(data.amount)}`, 'info'); diff --git a/shopping_app/static/js/mass_add.js b/shopping_app/static/js/mass_add.js index 5a069db..ddcd38a 100644 --- a/shopping_app/static/js/mass_add.js +++ b/shopping_app/static/js/mass_add.js @@ -306,7 +306,7 @@ document.addEventListener('DOMContentLoaded', function () { const undoBtn = document.createElement('button'); undoBtn.className = 'btn btn-outline-warning'; - undoBtn.innerHTML = '⟳ Cofnij'; + setUiIconLabel(undoBtn, 'refresh', 'Cofnij'); const timerBtn = document.createElement('button'); timerBtn.className = 'btn btn-outline-secondary disabled'; diff --git a/shopping_app/static/js/preview_list_modal.js b/shopping_app/static/js/preview_list_modal.js index 77c66fa..498d632 100644 --- a/shopping_app/static/js/preview_list_modal.js +++ b/shopping_app/static/js/preview_list_modal.js @@ -76,7 +76,7 @@ document.addEventListener("DOMContentLoaded", function () { const listId = btn.dataset.listId; modalTitle.textContent = "Ładowanie..."; - renderState("⏳ Ładowanie produktów..."); + renderState("Ładowanie produktów..."); modal.show(); try { @@ -91,19 +91,19 @@ document.addEventListener("DOMContentLoaded", function () { const totalExpense = Number(data.total_expense || 0); const percent = totalCount > 0 ? Math.round((purchasedCount / totalCount) * 100) : 0; - modalTitle.textContent = `🛒 ${data.title}`; + setUiIconLabel(modalTitle, 'shopping-cart', data.title); productList.innerHTML = ""; const summary = document.createElement("div"); summary.className = "preview-product-summary"; summary.innerHTML = ` -

📦 ${totalCount} produktów

-

✅ Kupione: ${purchasedCount} (${percent}%)

-

💸 Wydatek: ${formatCurrencyAmount(totalExpense)}

`; +

${uiIcon('box')} ${totalCount} produktów

+

${uiIcon('check')} Kupione: ${purchasedCount} (${percent}%)

+

${uiIcon('coins')} Wydatek: ${formatCurrencyAmount(totalExpense)}

`; productList.appendChild(summary); - const purchased = createSection("✔️ Kupione"); - const pending = createSection("🚫 Niekupione / Nieoznaczone"); + const purchased = createSection("Kupione"); + const pending = createSection("Niekupione / Nieoznaczone"); let hasPurchased = false; let hasPending = false; @@ -133,7 +133,7 @@ document.addEventListener("DOMContentLoaded", function () { } } catch (error) { modalTitle.textContent = "Błąd"; - renderState("❌ Błąd podczas ładowania", "text-danger"); + renderState("Błąd podczas ładowania", "text-danger"); } }); }); diff --git a/shopping_app/static/js/product_suggestion.js b/shopping_app/static/js/product_suggestion.js index 4d3b0c8..567d23d 100644 --- a/shopping_app/static/js/product_suggestion.js +++ b/shopping_app/static/js/product_suggestion.js @@ -16,7 +16,7 @@ function bindSyncButton(button) { showToast(data.message, data.success ? 'success' : 'danger'); if (data.success) { - button.innerText = '✅ Zsynchronizowano'; + setUiIconLabel(button, 'check', 'Zsynchronizowano'); button.classList.remove('btn-outline-primary'); button.classList.add('btn-success'); } else { @@ -62,7 +62,7 @@ function bindDeleteButton(button) { if (!cell) return; if (isProductRow) { - cell.innerHTML = ``; + cell.innerHTML = ``; const syncBtn = cell.querySelector('.sync-btn'); if (syncBtn) bindSyncButton(syncBtn); } else { diff --git a/shopping_app/static/js/receipt_analysis.js b/shopping_app/static/js/receipt_analysis.js index d939dd3..5d65867 100644 --- a/shopping_app/static/js/receipt_analysis.js +++ b/shopping_app/static/js/receipt_analysis.js @@ -21,8 +21,8 @@ async function analyzeReceipts(listId) { const duration = ((performance.now() - start) / 1000).toFixed(2); let html = `
`; - html += `

⏱ Czas analizy OCR: ${duration} sek.

`; - html += `

📊 Łącznie wykryto: ${formatCurrencyAmount(data.total)}

`; + html += `

${uiIcon('clock')} Czas analizy OCR: ${duration} sek.

`; + html += `

${uiIcon('chart-bar')} Łącznie wykryto: ${formatCurrencyAmount(data.total)}

`; data.results.forEach((r, i) => { const disabled = r.already_added ? "disabled" : ""; @@ -31,7 +31,7 @@ async function analyzeReceipts(listId) { const button = r.already_added ? `Dodano` - : ``; + : ``; html += `
@@ -43,7 +43,7 @@ async function analyzeReceipts(listId) { if (data.results.length > 1) { - html += ``; + html += ``; } html += `
`; @@ -51,7 +51,7 @@ async function analyzeReceipts(listId) { window._ocr_results = data.results; } catch (err) { - resultsDiv.innerHTML = `
❌ Wystąpił błąd podczas analizy.
`; + resultsDiv.innerHTML = `
${uiIcon('x')} Wystąpił błąd podczas analizy.
`; console.error(err); } } @@ -73,7 +73,7 @@ function emitExpense(i) { btn.disabled = true; btn.classList.remove('btn-outline-success'); btn.classList.add('btn-success'); - btn.textContent = '✅ Dodano'; + setUiIconLabel(btn, 'check', 'Dodano'); } } } @@ -91,7 +91,7 @@ function emitAllExpenses(n) { setTimeout(() => { if (btnAll) { - btnAll.innerHTML = '✅ Wszystko dodano'; + setUiIconLabel(btnAll, 'check', 'Wszystko dodano'); btnAll.classList.remove('btn-success'); btnAll.classList.add('btn-outline-success'); } diff --git a/shopping_app/static/js/receipt_upload.js b/shopping_app/static/js/receipt_upload.js index 29f8488..df4fa45 100644 --- a/shopping_app/static/js/receipt_upload.js +++ b/shopping_app/static/js/receipt_upload.js @@ -20,7 +20,7 @@ if (!window.receiptUploaderInitialized) { if (isDesktop) { if (cameraBtn) cameraBtn.remove(); if (inputCamera) inputCamera.remove(); - if (galleryBtnText) galleryBtnText.textContent = "➕ Dodaj paragon"; + if (galleryBtnText) setUiIconLabel(galleryBtnText, 'plus', 'Dodaj paragon'); } function handleFileUpload(inputElement) { diff --git a/shopping_app/static/js/sort_mode.js b/shopping_app/static/js/sort_mode.js index dcad781..efb0ee6 100644 --- a/shopping_app/static/js/sort_mode.js +++ b/shopping_app/static/js/sort_mode.js @@ -10,11 +10,11 @@ function syncSortModeUI() { if (btn) { if (active) { - btn.textContent = '✔️ Zakończ sortowanie'; + setUiIconLabel(btn, 'check', 'Zakończ sortowanie'); btn.classList.remove('btn-outline-warning'); btn.classList.add('btn-outline-success'); } else { - btn.textContent = '✳️ Zmień kolejność'; + setUiIconLabel(btn, 'arrows-sort', 'Zmień kolejność'); btn.classList.remove('btn-outline-success'); btn.classList.add('btn-outline-warning'); } diff --git a/shopping_app/static/js/toggle_button.js b/shopping_app/static/js/toggle_button.js index 9f5b6a2..7d208ab 100644 --- a/shopping_app/static/js/toggle_button.js +++ b/shopping_app/static/js/toggle_button.js @@ -9,7 +9,7 @@ document.addEventListener("DOMContentLoaded", function () { function updateToggle(isActive) { toggleBtn.classList.toggle("is-active", isActive); - toggleBtn.textContent = isActive ? "Tymczasowa ✔" : "Tymczasowa"; + setUiIconLabel(toggleBtn, isActive ? 'check' : 'hourglass', 'Tymczasowa'); toggleBtn.setAttribute("aria-pressed", isActive ? "true" : "false"); toggleBtn.setAttribute("title", isActive ? "Lista tymczasowa będzie ważna przez 7 dni" @@ -57,7 +57,7 @@ document.addEventListener("DOMContentLoaded", function () { const value = dateHidden.value; const hasDate = Boolean(value); dateToggle.classList.toggle("is-active", hasDate); - dateToggle.textContent = hasDate ? "📅 Data ✓" : "📅 Data"; + setUiIconLabel(dateToggle, 'calendar', hasDate ? 'Data' : 'Data'); dateToggle.setAttribute("aria-pressed", hasDate ? "true" : "false"); dateToggle.setAttribute( "aria-label", diff --git a/shopping_app/static/js/ui_icons.js b/shopping_app/static/js/ui_icons.js new file mode 100644 index 0000000..7400839 --- /dev/null +++ b/shopping_app/static/js/ui_icons.js @@ -0,0 +1,14 @@ +// Jedna funkcja renderowania ikon SVG także dla dynamicznych elementów DOM. +// Nazwa ikony jest walidowana, dlatego nie może zostać interpolowana z HTML użytkownika. +(function () { + const names = new Set(["alert-triangle", "archive", "arrow-down", "arrow-up", "arrows-sort", "ban", "book", "box", "bulb", "calendar", "camera", "chart-bar", "chart-line", "check", "clipboard", "clock", "coins", "copy", "device-floppy", "download", "eraser", "eye", "eye-off", "file-text", "flame", "folder", "history", "hourglass", "info-circle", "link", "lock", "lock-open", "login", "logout", "maximize", "menu-2", "notes", "number", "palette", "pencil", "photo", "player-pause", "player-play", "plus", "receipt", "refresh", "rotate-clockwise", "scissors", "search", "settings", "share", "shield-lock", "shopping-bag", "shopping-cart", "tag", "target", "template", "tool", "trash", "user", "users", "world", "x"]); + window.uiIcon = function (name) { + if (!names.has(name)) return ''; + return ``; + }; + window.setUiIconLabel = function (element, icon, label) { + if (!element) return; + element.textContent = String(label ?? ''); + if (names.has(icon)) element.insertAdjacentHTML('afterbegin', uiIcon(icon) + ' '); + }; +})(); diff --git a/shopping_app/templates/_list_progress.html b/shopping_app/templates/_list_progress.html index 817a784..bf531c3 100644 --- a/shopping_app/templates/_list_progress.html +++ b/shopping_app/templates/_list_progress.html @@ -27,7 +27,7 @@ Produkty: {{ purchased_count }}/{{ total_count }} ({{ percent|round(0)|int }}%) - {% if total_expense > 0 %} — 💸 {{ format_currency(total_expense) }}{% endif %} + {% if total_expense > 0 %} — {{ ui_icon('coins') }} {{ format_currency(total_expense) }}{% endif %}
\ No newline at end of file diff --git a/shopping_app/templates/admin/_nav.html b/shopping_app/templates/admin/_nav.html index 67cb8da..1fd373c 100644 --- a/shopping_app/templates/admin/_nav.html +++ b/shopping_app/templates/admin/_nav.html @@ -1,18 +1,18 @@
- +
diff --git a/shopping_app/templates/admin/admin_panel.html b/shopping_app/templates/admin/admin_panel.html index 1885ee4..c65f3d2 100644 --- a/shopping_app/templates/admin/admin_panel.html +++ b/shopping_app/templates/admin/admin_panel.html @@ -4,7 +4,7 @@
-

⚙️ Panel administratora

+

{{ ui_icon('settings') }} Panel administratora

Wgląd w użytkowników, listy, paragony, wydatki i ustawienia aplikacji.

← Powrót do strony głównej @@ -18,49 +18,49 @@
-
📊 Statystyki ogólne
+
{{ ui_icon('chart-bar') }} Statystyki ogólne
- + - + - + - + - + - + - + - +
👤 Użytkownicy{{ ui_icon('user') }} Użytkownicy {{ user_count }}
📝 Listy zakupowe{{ ui_icon('notes') }} Listy zakupowe {{ list_count }}
🛒 Produkty na listach{{ ui_icon('shopping-cart') }} Produkty na listach {{ item_count }}
✅ Zakupione{{ ui_icon('check') }} Zakupione {{ purchased_items_count }}
🚫 Nieoznaczone jako kupione{{ ui_icon('ban') }} Nieoznaczone jako kupione {{ not_purchased_count }}
✍️ Produkty z notatkami{{ ui_icon('notes') }} Produkty z notatkami {{ items_with_notes }}
🕓 Śr. czas do zakupu (h){{ ui_icon('clock') }} Śr. czas do zakupu (h) {{ avg_hours_to_purchase }}
💸 Średnia kwota na listę{{ ui_icon('coins') }} Średnia kwota na listę {{ format_currency(avg_list_expense) }}

-
📈 Średnie tempo tworzenia list:
+
{{ ui_icon('chart-line') }} Średnie tempo tworzenia list:
    -
  • 📆 Tygodniowo: {{ avg_per_week }}
  • -
  • 🗓️ Miesięcznie: {{ avg_per_month }}
  • - +
  • {{ ui_icon('calendar') }} Tygodniowo: {{ avg_per_week }}
  • +
  • {{ ui_icon('calendar') }} Miesięcznie: {{ avg_per_month }}
  • +
@@ -71,7 +71,7 @@
-
🔥 Najczęściej kupowane produkty
+
{{ ui_icon('flame') }} Najczęściej kupowane produkty
{% if top_products %} {% set max_count = top_products[0][1] %} {% for name, count in top_products %} @@ -100,7 +100,7 @@
-
💸 Podsumowanie wydatków
+
{{ ui_icon('coins') }} Podsumowanie wydatków
@@ -145,13 +145,13 @@
- 📊 Pokaż wykres wydatków + {{ ui_icon('chart-bar') }} Pokaż wykres wydatków
- {% if expiring_lists %}
⏰ Listy tymczasowe wygasające w ciągu 24h
    {% for l in expiring_lists %}
  • #{{ l.id }} {{ l.title }} — {{ l.owner.username if l.owner else '—' }} — {{ l.expires_at.strftime('%Y-%m-%d %H:%M') }}
  • {% endfor %}
{% endif %}
+ {% if expiring_lists %}
{{ ui_icon('clock') }} Listy tymczasowe wygasające w ciągu 24h
    {% for l in expiring_lists %}
  • #{{ l.id }} {{ l.title }} — {{ l.owner.username if l.owner else '—' }} — {{ l.expires_at.strftime('%Y-%m-%d %H:%M') }}
  • {% endfor %}
{% endif %}
{# panel wyboru miesiąca zawsze widoczny #} @@ -182,7 +182,7 @@ {% else %} {# Tryb wszystkie miesiące — możemy pokazać skrót do bieżącego miesiąca #} - 📅 Przejdź do bieżącego miesiąca + {{ ui_icon('calendar') }} Przejdź do bieżącego miesiąca {% endif %}
@@ -190,7 +190,7 @@ {# PRAWA STRONA — picker miesięcy zawsze widoczny #}
- 📅 + {{ ui_icon('calendar') }} - +
Pełna wartość jest widoczna tylko teraz. Po odświeżeniu zostanie ukryta.
@@ -30,7 +30,7 @@
-
➕ Utwórz token
+
{{ ui_icon('plus') }} Utwórz token
@@ -58,7 +58,7 @@
- +
@@ -68,7 +68,7 @@
-
📘 Dokumentacja API
+
{{ ui_icon('book') }} Dokumentacja API
Otwórz TXT
Autoryzacja: Authorization: Bearer TWOJ_TOKEN lub X-API-Token. Endpoint i zakres muszą być jednocześnie dozwolone na tokenie. Parametr limit jest przycinany do wartości ustawionej w tokenie.
@@ -95,7 +95,7 @@
-
📋 Aktywne i historyczne tokeny
+
{{ ui_icon('clipboard') }} Aktywne i historyczne tokeny
{{ api_tokens|length }} szt.
@@ -133,19 +133,19 @@
- +
{% else %}
- +
{% endif %}
- +
diff --git a/shopping_app/templates/admin/edit_categories.html b/shopping_app/templates/admin/edit_categories.html index f259107..c54c7c0 100644 --- a/shopping_app/templates/admin/edit_categories.html +++ b/shopping_app/templates/admin/edit_categories.html @@ -3,7 +3,7 @@ {% block content %}
-

🗂 Masowa edycja kategorii

+

{{ ui_icon('folder') }} Masowa edycja kategorii

@@ -14,7 +14,7 @@
@@ -44,7 +44,7 @@ {% if l.owner %} - 👤 {{ l.owner.username }} ({{ l.owner.id }}) + {{ ui_icon('user') }} {{ l.owner.username }} ({{ l.owner.id }}) {% else %}-{% endif %} {{ l.created_at.strftime('%Y-%m-%d %H:%M') if l.created_at else '-' }} @@ -60,7 +60,7 @@ @@ -90,7 +90,7 @@
-
+
@@ -98,7 +98,7 @@
- +
- +
@@ -25,13 +25,13 @@
- +
- + - +
- +
- +
@@ -70,12 +70,12 @@
- +
- +
@@ -84,7 +84,7 @@
- +
{{ list.created_at.strftime('%Y-%m-%d') }} @@ -92,7 +92,7 @@
- +
@@ -100,7 +100,7 @@
- +
@@ -122,10 +122,10 @@
-
🔐 Użytkownicy z dostępem
+
{{ ui_icon('shield-lock') }} Użytkownicy z dostępem
- ⚙️ Edytuj uprawnienia + {{ ui_icon('settings') }} Edytuj uprawnienia {% if permitted_users %} @@ -144,7 +144,7 @@ {% endif %}
- +
@@ -152,7 +152,7 @@
-

📝 Produkty

+

{{ ui_icon('notes') }} Produkty

@@ -165,7 +165,7 @@ value="1">
- +
@@ -202,15 +202,15 @@
- +
{% if item.purchased %} - ✔️ Kupiony + {{ ui_icon('check') }} Kupiony {% elif item.not_purchased %} - ⚠️ Nie kupiony + {{ ui_icon('alert-triangle') }} Nie kupiony {% else %} Nieoznaczony {% endif %} @@ -221,14 +221,14 @@
{% if not item.not_purchased %} - {% endif %} {% if item.not_purchased %} {% endif %}
@@ -238,7 +238,7 @@
- +
@@ -255,11 +255,11 @@
-

🧾 Paragony

+

{{ ui_icon('receipt') }} Paragony

@@ -274,11 +274,11 @@
-
📄 {{ r.filename }}
-
📅 {{ r.uploaded_at.strftime('%Y-%m-%d %H:%M') }}
-
👤 {{ r.uploaded_by_user.username if r.uploaded_by_user else "?" }}
+
{{ ui_icon('file-text') }} {{ r.filename }}
+
{{ ui_icon('calendar') }} {{ r.uploaded_at.strftime('%Y-%m-%d %H:%M') }}
+
{{ ui_icon('user') }} {{ r.uploaded_by_user.username if r.uploaded_by_user else "?" }}
- 💾 + {{ ui_icon('device-floppy') }} {% if r.filesize and r.filesize >= 1024 * 1024 %} {{ (r.filesize / 1024 / 1024) | round(2) }} MB {% elif r.filesize %} @@ -295,7 +295,7 @@ {% if not receipts %} {% endif %}
diff --git a/shopping_app/templates/admin/list_products.html b/shopping_app/templates/admin/list_products.html index 89f2328..0286473 100644 --- a/shopping_app/templates/admin/list_products.html +++ b/shopping_app/templates/admin/list_products.html @@ -3,7 +3,7 @@ {% block content %}
-

🛍️ Produkty i sugestie

+

{{ ui_icon('shopping-bag') }} Produkty i sugestie

← Powrót do panelu
@@ -14,22 +14,22 @@
- +
- +

- +
- +
@@ -38,7 +38,7 @@
-

📦 Produkty (z synchronizacją sugestii o unikalnych nazwach)

+

{{ ui_icon('box') }} Produkty (z synchronizacją sugestii o unikalnych nazwach)

{{ total_items }} produktów
@@ -59,7 +59,7 @@ {{ item.name }} {% if item.added_by and users_dict.get(item.added_by) %} - 👤 {{ users_dict[item.added_by] }} ({{ item.added_by }}) + {{ ui_icon('user') }} {{ users_dict[item.added_by] }} ({{ item.added_by }}) {% else %} - {% endif %} @@ -69,11 +69,11 @@ {% set clean_name = item.name | replace('\xa0', ' ') | trim | lower %} {% set suggestion = suggestions_dict.get(clean_name) %} {% if suggestion %} - ✅ Istnieje (ID: {{ suggestion.id }}) + {{ ui_icon('check') }} Istnieje (ID: {{ suggestion.id }}) + data-suggestion-id="{{ suggestion.id }}">{{ ui_icon('trash') }} Usuń {% else %} - {% endif %} @@ -96,7 +96,7 @@
-

💡 Wszystkie sugestie (poza powiązanymi)

+

{{ ui_icon('bulb') }} Wszystkie sugestie (poza powiązanymi)

{{ orphan_suggestions|length }} sugestii
@@ -117,7 +117,7 @@ {{ suggestion.name }} + data-suggestion-id="{{ suggestion.id }}">{{ ui_icon('trash') }} Usuń {% endif %} @@ -138,7 +138,7 @@
- + - +
@@ -84,7 +84,7 @@ {% if l.owner %} - 👤 @{{ l.owner.username }} + {{ ui_icon('user') }} @{{ l.owner.username }} ({{ l.owner.id }}) {% else %}-{% endif %} @@ -96,7 +96,7 @@
{{ share_url }}
+ aria-label="Kopiuj link">{{ ui_icon('clipboard') }}
{% if l.is_public %}Lista widoczna publicznie{% else %}Dostęp przez link / uprawnienia{% endif %} @@ -127,7 +127,7 @@ class="form-control form-control-sm bg-dark text-white border-secondary access-input" placeholder="Dodaj użytkownika (wiele: przecinki/enter)" list="userHints" autocomplete="off" aria-label="Dodaj użytkowników"> - +
Kliknij token, aby odebrać dostęp. Zmiana zapisuje się od razu.
@@ -151,7 +151,7 @@
- + @@ -181,7 +181,7 @@
Brak powiązania z listą!
- 🗑 Usuń plik z serwera + {{ ui_icon('trash') }} Usuń plik z serwera
@@ -194,7 +194,7 @@
@@ -68,10 +68,10 @@
@@ -96,16 +96,16 @@
{% if total_expense > 0 %}
- 💸 Łącznie wydano: {{ format_currency(total_expense) }} + {{ ui_icon('coins') }} Łącznie wydano: {{ format_currency(total_expense) }}
{% else %}
- 💸 Łącznie wydano: {{ format_currency(0) }} + {{ ui_icon('coins') }} Łącznie wydano: {{ format_currency(0) }}
{% endif %}
- +
@@ -121,7 +121,7 @@
{% if not is_share %} - + {% endif %} @@ -152,10 +152,10 @@
{% if not is_share %} + %}onclick='openEditItemModal(event, {{ item.id }}, {{ item.name|tojson }}, {{ item.quantity_value or item.quantity or 1 }}, {{ (item.quantity_unit or "szt")|tojson }})' {% endif %} aria-label="Edytuj produkt" title="Edytuj produkt">{{ ui_icon('pencil') }} + %}onclick="deleteItem({{ item.id }})" {% endif %} aria-label="Usuń produkt" title="Usuń produkt">{{ ui_icon('trash') }} {% endif %} {% if item.not_purchased %} @@ -163,11 +163,11 @@ %}onclick="unmarkNotPurchased({{ item.id }})" {% endif %}>Przywróć {% elif not item.not_purchased %} + %}onclick="markNotPurchasedModal(event, {{ item.id }})" {% endif %} aria-label="Oznacz jako niekupiony" title="Oznacz jako niekupiony">{{ ui_icon('alert-triangle') }} {% endif %} + %}onclick="openNoteModal(event, {{ item.id }})" {% endif %} aria-label="Notatka produktu" title="Notatka produktu">{{ ui_icon('notes') }}
@@ -180,8 +180,8 @@ {% elif list.is_archived %}
-
📤 Dodawanie zablokowane
+
{{ ui_icon('share') }} Dodawanie zablokowane

Ta lista jest archiwalna, więc nie można już dodawać nowych paragonów.

{% elif not current_user.is_authenticated %}
-
🔐 Dodawanie wymaga logowania
+
{{ ui_icon('shield-lock') }} Dodawanie wymaga logowania

Zaloguj się, aby dodawać paragony i uruchamiać analizę OCR.

{% endif %} @@ -339,8 +339,8 @@
@@ -363,8 +363,8 @@
- +
diff --git a/shopping_app/templates/main.html b/shopping_app/templates/main.html index 6f80900..17f3590 100644 --- a/shopping_app/templates/main.html +++ b/shopping_app/templates/main.html @@ -4,14 +4,14 @@ {% if not current_user.is_authenticated %} {% endif %} {% if current_user.is_authenticated %} {% if expiring_lists %} - +
@@ -54,7 +54,7 @@