deslop
No files matched your search
@@ -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.
|
||||
@@ -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()
|
||||
@@ -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":
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/* Monochromatyczne ikony z lokalnego sprite SVG w <body>. */
|
||||
.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);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-alert-triangle" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M11 3a2 2 0 0 1 2 0l9 16H2zM12 9v4M12 17v.1"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 353 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-archive" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<rect x="3" y="4" width="18" height="4" rx="1"/><path d="M5 8v12h14V8M10 12h4"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 371 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-arrow-down" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M12 4v16M5 13l7 7l7-7"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 327 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-arrow-up" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M12 20V4M5 11l7-7l7 7"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 325 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-arrows-sort" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M7 3v18M4 6l3-3l3 3M17 21V3M14 18l3 3l3-3"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 348 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-ban" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<circle cx="12" cy="12" r="9"/><path d="M5.6 5.6l12.8 12.8"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 348 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-book" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M4 4h8a3 3 0 0 1 3 3v14H7a3 3 0 0 0-3 0zM15 7a3 3 0 0 1 3-3h2v17h-2a3 3 0 0 0-3 0"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 381 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-box" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M3 7l9-4l9 4v10l-9 4l-9-4zM3 7l9 4l9-4M12 11v10"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 346 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-bulb" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M9 18h6M10 21h4M8 14a7 7 0 1 1 8 0l-1 4H9z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 342 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-calendar" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<rect x="3" y="5" width="18" height="16" rx="2"/><path d="M7 3v4M17 3v4M3 10h18"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 374 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-camera" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M4 7h4l2-3h4l2 3h4a1 1 0 0 1 1 1v11H3V8a1 1 0 0 1 1-1z"/><circle cx="12" cy="13" r="4"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 387 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-chart-bar" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M3 3v18h18M7 17v-6h3v6M13 17V6h3v11M19 17v-8h2"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 351 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-chart-line" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M3 3v18h18M6 16l5-6l4 3l5-7"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 333 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-check" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M5 12l5 5L20 7"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 315 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-clipboard" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<rect x="5" y="5" width="14" height="17" rx="2"/><path d="M9 5a3 3 0 0 1 6 0M8 11h8M8 15h8"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 386 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-clock" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 343 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-coins" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<circle cx="12" cy="12" r="9"/><path d="M15 8c-1-.9-5-1.1-6.2 0C7.2 10 9 12 12 12s4.8 1.8 3.2 4c-1.2 1.1-5.2.9-6.2 0M12 6v12"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 416 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-copy" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<rect x="8" y="8" width="13" height="13" rx="2"/><path d="M16 8V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h3"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 404 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-device-floppy" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M5 3h13l3 3v15H3V3zM7 3v7h10V3M7 21v-8h10v8M14 5v3"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 359 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-download" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M12 3v13M7 11l5 5l5-5M4 20h16"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 333 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-eraser" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M3 14l10-10a3 3 0 0 1 4 0l3 3a3 3 0 0 1 0 4l-9 9H6zM10 11l7 7M11 20h10"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 372 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-eye-off" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M3 3l18 18M10 5.2A12 12 0 0 1 12 5c6 0 10 7 10 7a16 16 0 0 1-3 3.6M6 6.4C3.5 8.2 2 12 2 12s4 7 10 7a10 10 0 0 0 4-1"/><path d="M10 10a3 3 0 0 0 4 4"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 450 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-eye" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M2 12s4-7 10-7s10 7 10 7s-4 7-10 7S2 12 2 12z"/><circle cx="12" cy="12" r="3"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 375 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-file-text" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M6 3h9l5 5v13H6a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2zM15 3v6h5M8 13h8M8 17h8"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 374 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-flame" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M12 22a8 8 0 0 0 8-8c0-4-3-6-5-9c-1 4-3 4-4 5c-2-3-2-5-3-7C6 8 4 10 4 14a8 8 0 0 0 8 8z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 388 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-folder" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M3 7a2 2 0 0 1 2-2h5l2 2h7a2 2 0 0 1 2 2v10H3z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 348 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-history" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M3 12a9 9 0 1 0 3-7M3 4v5h5M12 7v5l4 2"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 341 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-hourglass" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M6 3h12M6 21h12M7 3c0 5 2 6 5 9c-3 3-5 4-5 9M17 3c0 5-2 6-5 9c3 3 5 4 5 9"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 378 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-info-circle" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<circle cx="12" cy="12" r="9"/><path d="M12 11v6M12 7v.1"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 354 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-link" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M10 13a5 5 0 0 0 7 .2l2-2a5 5 0 0 0-7-7l-1.1 1.1M14 11a5 5 0 0 0-7-.2l-2 2a5 5 0 0 0 7 7l1.1-1.1"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 396 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-lock-open" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<rect x="5" y="11" width="14" height="10" rx="2"/><path d="M8 11V8a4 4 0 0 1 7-3M12 15v2"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 384 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-lock" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<rect x="5" y="11" width="14" height="10" rx="2"/><path d="M8 11V8a4 4 0 1 1 8 0v3M12 15v2"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 381 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-login" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M10 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h5M14 7l5 5l-5 5M19 12H8"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 363 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-logout" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M10 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h5M15 7l5 5l-5 5M20 12H9"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 364 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-maximize" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M4 9V4h5M15 4h5v5M20 15v5h-5M9 20H4v-5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 342 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-menu-2" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M4 7h16M4 12h16M4 17h16"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 325 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-notes" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M5 4h14v16H5zM8 8h8M8 12h7M8 16h5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 334 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-number" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M9 3L6 21M18 3l-3 18M4 9h17M3 16h17"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 337 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-palette" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M12 3a9 9 0 0 0 0 18h2a2 2 0 0 0 1-4c-1.5-1-.8-3 1-3h3a3 3 0 0 0 3-3a9 9 0 0 0-10-8z"/><circle cx="7" cy="10" r="1"/><circle cx="11" cy="6" r="1"/><circle cx="16" cy="8" r="1"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 477 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-pencil" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M4 20l4.5-1l11-11a2 2 0 0 0-3-3l-11 11zM14 7l3 3"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 350 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-photo" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<rect x="3" y="4" width="18" height="16" rx="2"/><circle cx="8" cy="9" r="2"/><path d="M3 17l5-5l4 4l3-3l6 5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 400 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-player-pause" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M8 5v14M16 5v14"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 323 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-player-play" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M7 4l13 8l-13 8z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 323 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-plus" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M12 5v14M5 12h14"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 316 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-receipt" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M5 3h14v18l-3-2l-4 2l-4-2l-3 2zM8 8h8M8 12h8M8 16h5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 354 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-refresh" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M20 8a8 8 0 0 0-14-3L3 8M3 4v4h5M4 16a8 8 0 0 0 14 3l3-3M21 20v-4h-5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 371 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-rotate-clockwise" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M20 9V4h-5M20 4a9 9 0 1 0 2 10"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 342 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-scissors" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<circle cx="6" cy="7" r="3"/><circle cx="6" cy="17" r="3"/><path d="M9 9l12 12M9 15L21 3"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 383 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-search" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<circle cx="10.5" cy="10.5" r="7.5"/><path d="M16 16l5 5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 349 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-settings" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<circle cx="12" cy="12" r="3"/><path d="M10 2h4l.6 3l2.3 1l2.5-1.6l2.2 3.3l-2.1 2l.1 2.5l2.4 1.8l-2.1 3.5l-2.9-.8l-2.1 1.5L14 22h-4l-.6-3l-2.3-1l-2.5 1.6L2.4 16.3l2.1-2l-.1-2.5L2 10l2.1-3.5l2.9.8l2.1-1.5z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 499 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-share" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<circle cx="18" cy="5" r="2"/><circle cx="6" cy="12" r="2"/><circle cx="18" cy="19" r="2"/><path d="M8 11l8-5M8 13l8 5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 410 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-shield-lock" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M12 2l9 4v5c0 6-4 9-9 11c-5-2-9-5-9-11V6z"/><rect x="9" y="11" width="6" height="6" rx="1"/><path d="M10 11V9a2 2 0 0 1 4 0v2"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 432 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-shopping-bag" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<rect x="4" y="7" width="16" height="14" rx="2"/><path d="M8 9V6a4 4 0 0 1 8 0v3"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 379 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-shopping-cart" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<circle cx="9" cy="20" r="1"/><circle cx="19" cy="20" r="1"/><path d="M2 3h2l3 13h13l2-9H5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 390 B |
@@ -0,0 +1,67 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="ui-icon-sprite" width="0" height="0" aria-hidden="true" focusable="false">
|
||||
<defs>
|
||||
<symbol id="ti-alert-triangle" viewBox="0 0 24 24"><path d="M11 3a2 2 0 0 1 2 0l9 16H2zM12 9v4M12 17v.1"/></symbol>
|
||||
<symbol id="ti-archive" viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="4" rx="1"/><path d="M5 8v12h14V8M10 12h4"/></symbol>
|
||||
<symbol id="ti-arrow-down" viewBox="0 0 24 24"><path d="M12 4v16M5 13l7 7l7-7"/></symbol>
|
||||
<symbol id="ti-arrow-up" viewBox="0 0 24 24"><path d="M12 20V4M5 11l7-7l7 7"/></symbol>
|
||||
<symbol id="ti-arrows-sort" viewBox="0 0 24 24"><path d="M7 3v18M4 6l3-3l3 3M17 21V3M14 18l3 3l3-3"/></symbol>
|
||||
<symbol id="ti-ban" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M5.6 5.6l12.8 12.8"/></symbol>
|
||||
<symbol id="ti-book" viewBox="0 0 24 24"><path d="M4 4h8a3 3 0 0 1 3 3v14H7a3 3 0 0 0-3 0zM15 7a3 3 0 0 1 3-3h2v17h-2a3 3 0 0 0-3 0"/></symbol>
|
||||
<symbol id="ti-box" viewBox="0 0 24 24"><path d="M3 7l9-4l9 4v10l-9 4l-9-4zM3 7l9 4l9-4M12 11v10"/></symbol>
|
||||
<symbol id="ti-bulb" viewBox="0 0 24 24"><path d="M9 18h6M10 21h4M8 14a7 7 0 1 1 8 0l-1 4H9z"/></symbol>
|
||||
<symbol id="ti-calendar" viewBox="0 0 24 24"><rect x="3" y="5" width="18" height="16" rx="2"/><path d="M7 3v4M17 3v4M3 10h18"/></symbol>
|
||||
<symbol id="ti-camera" viewBox="0 0 24 24"><path d="M4 7h4l2-3h4l2 3h4a1 1 0 0 1 1 1v11H3V8a1 1 0 0 1 1-1z"/><circle cx="12" cy="13" r="4"/></symbol>
|
||||
<symbol id="ti-chart-bar" viewBox="0 0 24 24"><path d="M3 3v18h18M7 17v-6h3v6M13 17V6h3v11M19 17v-8h2"/></symbol>
|
||||
<symbol id="ti-chart-line" viewBox="0 0 24 24"><path d="M3 3v18h18M6 16l5-6l4 3l5-7"/></symbol>
|
||||
<symbol id="ti-check" viewBox="0 0 24 24"><path d="M5 12l5 5L20 7"/></symbol>
|
||||
<symbol id="ti-clipboard" viewBox="0 0 24 24"><rect x="5" y="5" width="14" height="17" rx="2"/><path d="M9 5a3 3 0 0 1 6 0M8 11h8M8 15h8"/></symbol>
|
||||
<symbol id="ti-clock" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></symbol>
|
||||
<symbol id="ti-coins" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M15 8c-1-.9-5-1.1-6.2 0C7.2 10 9 12 12 12s4.8 1.8 3.2 4c-1.2 1.1-5.2.9-6.2 0M12 6v12"/></symbol>
|
||||
<symbol id="ti-copy" viewBox="0 0 24 24"><rect x="8" y="8" width="13" height="13" rx="2"/><path d="M16 8V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h3"/></symbol>
|
||||
<symbol id="ti-device-floppy" viewBox="0 0 24 24"><path d="M5 3h13l3 3v15H3V3zM7 3v7h10V3M7 21v-8h10v8M14 5v3"/></symbol>
|
||||
<symbol id="ti-download" viewBox="0 0 24 24"><path d="M12 3v13M7 11l5 5l5-5M4 20h16"/></symbol>
|
||||
<symbol id="ti-eraser" viewBox="0 0 24 24"><path d="M3 14l10-10a3 3 0 0 1 4 0l3 3a3 3 0 0 1 0 4l-9 9H6zM10 11l7 7M11 20h10"/></symbol>
|
||||
<symbol id="ti-eye" viewBox="0 0 24 24"><path d="M2 12s4-7 10-7s10 7 10 7s-4 7-10 7S2 12 2 12z"/><circle cx="12" cy="12" r="3"/></symbol>
|
||||
<symbol id="ti-eye-off" viewBox="0 0 24 24"><path d="M3 3l18 18M10 5.2A12 12 0 0 1 12 5c6 0 10 7 10 7a16 16 0 0 1-3 3.6M6 6.4C3.5 8.2 2 12 2 12s4 7 10 7a10 10 0 0 0 4-1"/><path d="M10 10a3 3 0 0 0 4 4"/></symbol>
|
||||
<symbol id="ti-file-text" viewBox="0 0 24 24"><path d="M6 3h9l5 5v13H6a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2zM15 3v6h5M8 13h8M8 17h8"/></symbol>
|
||||
<symbol id="ti-flame" viewBox="0 0 24 24"><path d="M12 22a8 8 0 0 0 8-8c0-4-3-6-5-9c-1 4-3 4-4 5c-2-3-2-5-3-7C6 8 4 10 4 14a8 8 0 0 0 8 8z"/></symbol>
|
||||
<symbol id="ti-folder" viewBox="0 0 24 24"><path d="M3 7a2 2 0 0 1 2-2h5l2 2h7a2 2 0 0 1 2 2v10H3z"/></symbol>
|
||||
<symbol id="ti-history" viewBox="0 0 24 24"><path d="M3 12a9 9 0 1 0 3-7M3 4v5h5M12 7v5l4 2"/></symbol>
|
||||
<symbol id="ti-hourglass" viewBox="0 0 24 24"><path d="M6 3h12M6 21h12M7 3c0 5 2 6 5 9c-3 3-5 4-5 9M17 3c0 5-2 6-5 9c3 3 5 4 5 9"/></symbol>
|
||||
<symbol id="ti-info-circle" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 11v6M12 7v.1"/></symbol>
|
||||
<symbol id="ti-link" viewBox="0 0 24 24"><path d="M10 13a5 5 0 0 0 7 .2l2-2a5 5 0 0 0-7-7l-1.1 1.1M14 11a5 5 0 0 0-7-.2l-2 2a5 5 0 0 0 7 7l1.1-1.1"/></symbol>
|
||||
<symbol id="ti-lock" viewBox="0 0 24 24"><rect x="5" y="11" width="14" height="10" rx="2"/><path d="M8 11V8a4 4 0 1 1 8 0v3M12 15v2"/></symbol>
|
||||
<symbol id="ti-lock-open" viewBox="0 0 24 24"><rect x="5" y="11" width="14" height="10" rx="2"/><path d="M8 11V8a4 4 0 0 1 7-3M12 15v2"/></symbol>
|
||||
<symbol id="ti-login" viewBox="0 0 24 24"><path d="M10 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h5M14 7l5 5l-5 5M19 12H8"/></symbol>
|
||||
<symbol id="ti-logout" viewBox="0 0 24 24"><path d="M10 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h5M15 7l5 5l-5 5M20 12H9"/></symbol>
|
||||
<symbol id="ti-maximize" viewBox="0 0 24 24"><path d="M4 9V4h5M15 4h5v5M20 15v5h-5M9 20H4v-5"/></symbol>
|
||||
<symbol id="ti-menu-2" viewBox="0 0 24 24"><path d="M4 7h16M4 12h16M4 17h16"/></symbol>
|
||||
<symbol id="ti-notes" viewBox="0 0 24 24"><path d="M5 4h14v16H5zM8 8h8M8 12h7M8 16h5"/></symbol>
|
||||
<symbol id="ti-number" viewBox="0 0 24 24"><path d="M9 3L6 21M18 3l-3 18M4 9h17M3 16h17"/></symbol>
|
||||
<symbol id="ti-palette" viewBox="0 0 24 24"><path d="M12 3a9 9 0 0 0 0 18h2a2 2 0 0 0 1-4c-1.5-1-.8-3 1-3h3a3 3 0 0 0 3-3a9 9 0 0 0-10-8z"/><circle cx="7" cy="10" r="1"/><circle cx="11" cy="6" r="1"/><circle cx="16" cy="8" r="1"/></symbol>
|
||||
<symbol id="ti-pencil" viewBox="0 0 24 24"><path d="M4 20l4.5-1l11-11a2 2 0 0 0-3-3l-11 11zM14 7l3 3"/></symbol>
|
||||
<symbol id="ti-photo" viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="16" rx="2"/><circle cx="8" cy="9" r="2"/><path d="M3 17l5-5l4 4l3-3l6 5"/></symbol>
|
||||
<symbol id="ti-player-pause" viewBox="0 0 24 24"><path d="M8 5v14M16 5v14"/></symbol>
|
||||
<symbol id="ti-player-play" viewBox="0 0 24 24"><path d="M7 4l13 8l-13 8z"/></symbol>
|
||||
<symbol id="ti-plus" viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"/></symbol>
|
||||
<symbol id="ti-receipt" viewBox="0 0 24 24"><path d="M5 3h14v18l-3-2l-4 2l-4-2l-3 2zM8 8h8M8 12h8M8 16h5"/></symbol>
|
||||
<symbol id="ti-refresh" viewBox="0 0 24 24"><path d="M20 8a8 8 0 0 0-14-3L3 8M3 4v4h5M4 16a8 8 0 0 0 14 3l3-3M21 20v-4h-5"/></symbol>
|
||||
<symbol id="ti-rotate-clockwise" viewBox="0 0 24 24"><path d="M20 9V4h-5M20 4a9 9 0 1 0 2 10"/></symbol>
|
||||
<symbol id="ti-scissors" viewBox="0 0 24 24"><circle cx="6" cy="7" r="3"/><circle cx="6" cy="17" r="3"/><path d="M9 9l12 12M9 15L21 3"/></symbol>
|
||||
<symbol id="ti-search" viewBox="0 0 24 24"><circle cx="10.5" cy="10.5" r="7.5"/><path d="M16 16l5 5"/></symbol>
|
||||
<symbol id="ti-settings" viewBox="0 0 24 24"><circle cx="12" cy="12" r="3"/><path d="M10 2h4l.6 3l2.3 1l2.5-1.6l2.2 3.3l-2.1 2l.1 2.5l2.4 1.8l-2.1 3.5l-2.9-.8l-2.1 1.5L14 22h-4l-.6-3l-2.3-1l-2.5 1.6L2.4 16.3l2.1-2l-.1-2.5L2 10l2.1-3.5l2.9.8l2.1-1.5z"/></symbol>
|
||||
<symbol id="ti-share" viewBox="0 0 24 24"><circle cx="18" cy="5" r="2"/><circle cx="6" cy="12" r="2"/><circle cx="18" cy="19" r="2"/><path d="M8 11l8-5M8 13l8 5"/></symbol>
|
||||
<symbol id="ti-shield-lock" viewBox="0 0 24 24"><path d="M12 2l9 4v5c0 6-4 9-9 11c-5-2-9-5-9-11V6z"/><rect x="9" y="11" width="6" height="6" rx="1"/><path d="M10 11V9a2 2 0 0 1 4 0v2"/></symbol>
|
||||
<symbol id="ti-shopping-bag" viewBox="0 0 24 24"><rect x="4" y="7" width="16" height="14" rx="2"/><path d="M8 9V6a4 4 0 0 1 8 0v3"/></symbol>
|
||||
<symbol id="ti-shopping-cart" viewBox="0 0 24 24"><circle cx="9" cy="20" r="1"/><circle cx="19" cy="20" r="1"/><path d="M2 3h2l3 13h13l2-9H5"/></symbol>
|
||||
<symbol id="ti-tag" viewBox="0 0 24 24"><path d="M3 12V5a2 2 0 0 1 2-2h7l9 9l-9 9z"/><circle cx="8" cy="8" r="1"/></symbol>
|
||||
<symbol id="ti-target" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="5"/><circle cx="12" cy="12" r="1"/></symbol>
|
||||
<symbol id="ti-template" viewBox="0 0 24 24"><path d="M4 3h16v18H4zM4 9h16M9 9v12M13 13h4M13 17h4"/></symbol>
|
||||
<symbol id="ti-tool" viewBox="0 0 24 24"><path d="M14 6a6 6 0 0 0-8 8L3 17l4 4l3-3a6 6 0 0 0 8-8l-4 4l-4-4z"/></symbol>
|
||||
<symbol id="ti-trash" viewBox="0 0 24 24"><path d="M4 7h16M10 4h4M6 7l1 14h10l1-14M10 11v6M14 11v6"/></symbol>
|
||||
<symbol id="ti-user" viewBox="0 0 24 24"><circle cx="12" cy="8" r="4"/><path d="M4 21v-2a8 8 0 0 1 16 0v2"/></symbol>
|
||||
<symbol id="ti-users" viewBox="0 0 24 24"><circle cx="9" cy="8" r="3"/><path d="M3 20v-2a6 6 0 0 1 12 0v2H3M16 5a3 3 0 0 1 0 6M18 14a5 5 0 0 1 3 5v1h-4"/></symbol>
|
||||
<symbol id="ti-world" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3c-6 5-6 13 0 18M12 3c6 5 6 13 0 18"/></symbol>
|
||||
<symbol id="ti-x" viewBox="0 0 24 24"><path d="M5 5l14 14M19 5L5 19"/></symbol>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8.2 KiB |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-tag" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M3 12V5a2 2 0 0 1 2-2h7l9 9l-9 9z"/><circle cx="8" cy="8" r="1"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 361 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-target" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="5"/><circle cx="12" cy="12" r="1"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 383 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-template" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M4 3h16v18H4zM4 9h16M9 9v12M13 13h4M13 17h4"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 347 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-tool" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M14 6a6 6 0 0 0-8 8L3 17l4 4l3-3a6 6 0 0 0 8-8l-4 4l-4-4z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 357 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-trash" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M4 7h16M10 4h4M6 7l1 14h10l1-14M10 11v6M14 11v6"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 348 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-user" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<circle cx="12" cy="8" r="4"/><path d="M4 21v-2a8 8 0 0 1 16 0v2"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 355 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-users" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<circle cx="9" cy="8" r="3"/><path d="M3 20v-2a6 6 0 0 1 12 0v2H3M16 5a3 3 0 0 1 0 6M18 14a5 5 0 0 1 3 5v1h-4"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 401 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-world" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3c-6 5-6 13 0 18M12 3c6 5 6 13 0 18"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 379 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-x" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M5 5l14 14M19 5L5 19"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 317 B |
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 = '<span aria-hidden="true">👁</span>';
|
||||
btn.innerHTML = uiIcon('eye');
|
||||
|
||||
const syncState = function () {
|
||||
const visible = input.type === 'text';
|
||||
btn.innerHTML = visible ? '<span aria-hidden="true">🙈</span>' : '<span aria-hidden="true">👁</span>';
|
||||
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 = [
|
||||
'<div class="input-group input-group-sm table-toolbar__search">',
|
||||
' <span class="input-group-text">🔎</span>',
|
||||
' <span class="input-group-text">' + uiIcon('search') + '</span>',
|
||||
' <input type="search" class="form-control" placeholder="Filtruj tabelę…" aria-label="Filtruj tabelę">',
|
||||
' <button type="button" class="btn btn-outline-light">Wyczyść</button>',
|
||||
'</div>',
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
? `<button type="button" class="drag-handle" title="Przeciągnij produkt" aria-label="Przeciągnij produkt" ${isArchived ? 'disabled' : ''}>⠿</button>`
|
||||
? `<button type="button" class="drag-handle" title="Przeciągnij produkt" aria-label="Przeciągnij produkt" ${isArchived ? 'disabled' : ''}>${uiIcon('arrows-sort')}</button>`
|
||||
: '';
|
||||
const itemNameHtml = canEditListItem
|
||||
? `<button type="button"
|
||||
@@ -501,8 +501,8 @@ function renderItem(item, isShare = window.IS_SHARE, optionsOrShowEditOnly = fal
|
||||
|
||||
if (canEditListItem) {
|
||||
actionButtons += `
|
||||
<button type="button" class="${iconBtn}" ${isArchived ? 'disabled' : `onclick='openEditItemModal(event, ${item.id}, ${JSON.stringify(String(item.name || ''))}, ${quantityValue}, ${JSON.stringify(quantityUnit)})'`}>✏️</button>
|
||||
<button type="button" class="${iconBtn}" ${isArchived ? 'disabled' : `onclick="deleteItem(${item.id})"`}>🗑️</button>`;
|
||||
<button type="button" class="${iconBtn}" ${isArchived ? 'disabled' : `onclick='openEditItemModal(event, ${item.id}, ${JSON.stringify(String(item.name || ''))}, ${quantityValue}, ${JSON.stringify(quantityUnit)})'`} aria-label="Edytuj produkt" title="Edytuj produkt">${uiIcon('pencil')}</button>
|
||||
<button type="button" class="${iconBtn}" ${isArchived ? 'disabled' : `onclick="deleteItem(${item.id})"`} aria-label="Usuń produkt" title="Usuń produkt">${uiIcon('trash')}</button>`;
|
||||
}
|
||||
|
||||
if (item.not_purchased) {
|
||||
@@ -510,17 +510,17 @@ function renderItem(item, isShare = window.IS_SHARE, optionsOrShowEditOnly = fal
|
||||
<button type="button" class="${wideBtn}" ${isArchived ? 'disabled' : `onclick="unmarkNotPurchased(${item.id})"`}>Przywróć</button>`;
|
||||
} else if (!isShare || canShowShareActions || isOwner) {
|
||||
actionButtons += `
|
||||
<button type="button" class="${iconBtn}" ${canMarkNotPurchased ? `onclick="markNotPurchasedModal(event, ${item.id})"` : 'disabled'}>⚠️</button>`;
|
||||
<button type="button" class="${iconBtn}" ${canMarkNotPurchased ? `onclick="markNotPurchasedModal(event, ${item.id})"` : 'disabled'} aria-label="Oznacz jako niekupiony" title="Oznacz jako niekupiony">${uiIcon('alert-triangle')}</button>`;
|
||||
}
|
||||
|
||||
if (temporaryShareUndo) {
|
||||
actionButtons += `
|
||||
<button type="button" class="${iconBtn} shopping-action-btn--countdown" disabled data-countdown-for="${item.id}">${countdownSeconds}s</button>
|
||||
<button type="button" class="${iconBtn}" ${isArchived ? 'disabled' : `onclick='openEditItemModal(event, ${item.id}, ${nameForEdit}, ${quantityValue}, ${JSON.stringify(quantityUnit)})'`}>✏️</button>
|
||||
<button type="button" class="${iconBtn}" ${isArchived ? 'disabled' : `onclick="deleteItem(${item.id})"`}>🗑️</button>`;
|
||||
<button type="button" class="${iconBtn}" ${isArchived ? 'disabled' : `onclick='openEditItemModal(event, ${item.id}, ${nameForEdit}, ${quantityValue}, ${JSON.stringify(quantityUnit)})'`} aria-label="Edytuj produkt" title="Edytuj produkt">${uiIcon('pencil')}</button>
|
||||
<button type="button" class="${iconBtn}" ${isArchived ? 'disabled' : `onclick="deleteItem(${item.id})"`} aria-label="Usuń produkt" title="Usuń produkt">${uiIcon('trash')}</button>`;
|
||||
} else if (canShowShareActions || (!isShare && isOwner)) {
|
||||
actionButtons += `
|
||||
<button type="button" class="${iconBtn}" ${isArchived ? 'disabled' : `onclick="openNoteModal(event, ${item.id})"`}>📝</button>`;
|
||||
<button type="button" class="${iconBtn}" ${isArchived ? 'disabled' : `onclick="openNoteModal(event, ${item.id})"`} aria-label="Notatka produktu" title="Notatka produktu">${uiIcon('notes')}</button>`;
|
||||
}
|
||||
|
||||
li.innerHTML = `
|
||||
|
||||
@@ -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} <button type="button" class="btn btn-sm btn-link p-0 ms-1 text-white">✕</button>`;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} <button type="button" class="btn btn-sm btn-link p-0 ms-1 text-white">${uiIcon('x')}</button>`;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);
|
||||
})();
|
||||
@@ -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 = `<b>💸 Łącznie wydano:</b> ${formatCurrencyAmount(data.total)}`;
|
||||
summaryEl.innerHTML = `<b>${uiIcon('coins')} Łącznie wydano:</b> ${formatCurrencyAmount(data.total)}`;
|
||||
}
|
||||
|
||||
showToast(`Dodano wydatek: ${formatCurrencyAmount(data.amount)}`, 'info');
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 = `
|
||||
<p class="mb-1">📦 <strong>${totalCount}</strong> produktów</p>
|
||||
<p class="mb-1">✅ Kupione: <strong>${purchasedCount}</strong> (${percent}%)</p>
|
||||
<p class="mb-0">💸 Wydatek: <strong>${formatCurrencyAmount(totalExpense)}</strong></p>`;
|
||||
<p class="mb-1">${uiIcon('box')} <strong>${totalCount}</strong> produktów</p>
|
||||
<p class="mb-1">${uiIcon('check')} Kupione: <strong>${purchasedCount}</strong> (${percent}%)</p>
|
||||
<p class="mb-0">${uiIcon('coins')} Wydatek: <strong>${formatCurrencyAmount(totalExpense)}</strong></p>`;
|
||||
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");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 = `<button class="btn btn-sm btn-outline-light sync-btn" data-item-id="${itemId}">🔄 Synchronizuj</button>`;
|
||||
cell.innerHTML = `<button class="btn btn-sm btn-outline-light sync-btn" data-item-id="${itemId}">${uiIcon('refresh')} Synchronizuj</button>`;
|
||||
const syncBtn = cell.querySelector('.sync-btn');
|
||||
if (syncBtn) bindSyncButton(syncBtn);
|
||||
} else {
|
||||
|
||||
@@ -21,8 +21,8 @@ async function analyzeReceipts(listId) {
|
||||
const duration = ((performance.now() - start) / 1000).toFixed(2);
|
||||
|
||||
let html = `<div class="card bg-dark text-white border-secondary p-3">`;
|
||||
html += `<p class="text-secondary"><small>⏱ Czas analizy OCR: ${duration} sek.</small></p>`;
|
||||
html += `<p><b>📊 Łącznie wykryto:</b> ${formatCurrencyAmount(data.total)}</p>`;
|
||||
html += `<p class="text-secondary"><small>${uiIcon('clock')} Czas analizy OCR: ${duration} sek.</small></p>`;
|
||||
html += `<p><b>${uiIcon('chart-bar')} Łącznie wykryto:</b> ${formatCurrencyAmount(data.total)}</p>`;
|
||||
|
||||
data.results.forEach((r, i) => {
|
||||
const disabled = r.already_added ? "disabled" : "";
|
||||
@@ -31,7 +31,7 @@ async function analyzeReceipts(listId) {
|
||||
|
||||
const button = r.already_added
|
||||
? `<span class="badge rounded-pill bg-secondary ms-2">Dodano</span>`
|
||||
: `<button id="add-btn-${i}" onclick="emitExpense(${i})" class="btn btn-outline-light ms-2">➕ Dodaj</button>`;
|
||||
: `<button id="add-btn-${i}" onclick="emitExpense(${i})" class="btn btn-outline-light ms-2">${uiIcon('plus')} Dodaj</button>`;
|
||||
|
||||
html += `
|
||||
<div class="mb-2 d-flex align-items-center gap-2 flex-wrap">
|
||||
@@ -43,7 +43,7 @@ async function analyzeReceipts(listId) {
|
||||
|
||||
|
||||
if (data.results.length > 1) {
|
||||
html += `<button id="addAllBtn" onclick="emitAllExpenses(${data.results.length})" class="btn btn-sm btn-outline-light mt-3 w-100">➕ Dodaj wszystkie</button>`;
|
||||
html += `<button id="addAllBtn" onclick="emitAllExpenses(${data.results.length})" class="btn btn-sm btn-outline-light mt-3 w-100">${uiIcon('plus')} Dodaj wszystkie</button>`;
|
||||
}
|
||||
|
||||
html += `</div>`;
|
||||
@@ -51,7 +51,7 @@ async function analyzeReceipts(listId) {
|
||||
window._ocr_results = data.results;
|
||||
|
||||
} catch (err) {
|
||||
resultsDiv.innerHTML = `<div class="text-danger">❌ Wystąpił błąd podczas analizy.</div>`;
|
||||
resultsDiv.innerHTML = `<div class="text-danger">${uiIcon('x')} Wystąpił błąd podczas analizy.</div>`;
|
||||
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');
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 `<svg class="ui-icon icon-tabler" aria-hidden="true" focusable="false" viewBox="0 0 24 24"><use href="#ti-${name}"></use></svg>`;
|
||||
};
|
||||
window.setUiIconLabel = function (element, icon, label) {
|
||||
if (!element) return;
|
||||
element.textContent = String(label ?? '');
|
||||
if (names.has(icon)) element.insertAdjacentHTML('afterbegin', uiIcon(icon) + ' ');
|
||||
};
|
||||
})();
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
<span class="progress-label main-list-progress__label small fw-bold {% if percent < 51 %}text-white{% else %}text-dark{% endif %}">
|
||||
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 %}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,18 +1,18 @@
|
||||
<div class="card bg-secondary bg-opacity-10 text-white mb-4 admin-shortcuts">
|
||||
<div class="card-body p-2">
|
||||
<div class="d-md-none mb-2">
|
||||
<button type="button" class="btn btn-outline-light w-100" data-admin-nav-toggle aria-expanded="false">☰ Menu admina</button>
|
||||
<button type="button" class="btn btn-outline-light w-100" data-admin-nav-toggle aria-expanded="false">{{ ui_icon('menu-2') }} Menu admina</button>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap gap-2" data-admin-nav-body>
|
||||
<a href="{{ url_for('admin_panel') }}" class="btn btn-sm {% if request.endpoint == 'admin_panel' %}btn-success{% else %}btn-outline-light{% endif %}">📊 Dashboard</a>
|
||||
<a href="{{ url_for('list_users') }}" class="btn btn-sm {% if request.endpoint == 'list_users' %}btn-success{% else %}btn-outline-light{% endif %}">👥 Użytkownicy</a>
|
||||
<a href="{{ url_for('admin_receipts') }}" class="btn btn-sm {% if request.endpoint == 'admin_receipts' %}btn-success{% else %}btn-outline-light{% endif %}">📸 Paragony</a>
|
||||
<a href="{{ url_for('list_products') }}" class="btn btn-sm {% if request.endpoint == 'list_products' %}btn-success{% else %}btn-outline-light{% endif %}">🛍️ Produkty</a>
|
||||
<a href="{{ url_for('admin_edit_categories') }}" class="btn btn-sm {% if request.endpoint == 'admin_edit_categories' %}btn-success{% else %}btn-outline-light{% endif %}">🗂 Kategorie</a>
|
||||
<a href="{{ url_for('admin_lists_access') }}" class="btn btn-sm {% if request.endpoint == 'admin_lists_access' %}btn-success{% else %}btn-outline-light{% endif %}">🔐 Uprawnienia</a>
|
||||
<a href="{{ url_for('admin_api_tokens') }}" class="btn btn-sm {% if request.endpoint in ['admin_api_tokens', 'admin_api_docs'] %}btn-success{% else %}btn-outline-light{% endif %}">🔑 Tokeny API</a>
|
||||
<a href="{{ url_for('admin_templates') }}" class="btn btn-sm {% if request.endpoint == 'admin_templates' %}btn-success{% else %}btn-outline-light{% endif %}">🧩 Szablony</a>
|
||||
<a href="{{ url_for('admin_settings') }}" class="btn btn-sm {% if request.endpoint == 'admin_settings' %}btn-success{% else %}btn-outline-light{% endif %}">⚙️ Ustawienia</a>
|
||||
<a href="{{ url_for('admin_panel') }}" class="btn btn-sm {% if request.endpoint == 'admin_panel' %}btn-success{% else %}btn-outline-light{% endif %}">{{ ui_icon('chart-bar') }} Dashboard</a>
|
||||
<a href="{{ url_for('list_users') }}" class="btn btn-sm {% if request.endpoint == 'list_users' %}btn-success{% else %}btn-outline-light{% endif %}">{{ ui_icon('users') }} Użytkownicy</a>
|
||||
<a href="{{ url_for('admin_receipts') }}" class="btn btn-sm {% if request.endpoint == 'admin_receipts' %}btn-success{% else %}btn-outline-light{% endif %}">{{ ui_icon('camera') }} Paragony</a>
|
||||
<a href="{{ url_for('list_products') }}" class="btn btn-sm {% if request.endpoint == 'list_products' %}btn-success{% else %}btn-outline-light{% endif %}">{{ ui_icon('shopping-bag') }} Produkty</a>
|
||||
<a href="{{ url_for('admin_edit_categories') }}" class="btn btn-sm {% if request.endpoint == 'admin_edit_categories' %}btn-success{% else %}btn-outline-light{% endif %}">{{ ui_icon('folder') }} Kategorie</a>
|
||||
<a href="{{ url_for('admin_lists_access') }}" class="btn btn-sm {% if request.endpoint == 'admin_lists_access' %}btn-success{% else %}btn-outline-light{% endif %}">{{ ui_icon('shield-lock') }} Uprawnienia</a>
|
||||
<a href="{{ url_for('admin_api_tokens') }}" class="btn btn-sm {% if request.endpoint in ['admin_api_tokens', 'admin_api_docs'] %}btn-success{% else %}btn-outline-light{% endif %}">{{ ui_icon('login') }} Tokeny API</a>
|
||||
<a href="{{ url_for('admin_templates') }}" class="btn btn-sm {% if request.endpoint == 'admin_templates' %}btn-success{% else %}btn-outline-light{% endif %}">{{ ui_icon('template') }} Szablony</a>
|
||||
<a href="{{ url_for('admin_settings') }}" class="btn btn-sm {% if request.endpoint == 'admin_settings' %}btn-success{% else %}btn-outline-light{% endif %}">{{ ui_icon('settings') }} Ustawienia</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
<div class="admin-page-head mb-4">
|
||||
<div>
|
||||
<h2 class="mb-2">⚙️ Panel administratora</h2>
|
||||
<h2 class="mb-2">{{ ui_icon('settings') }} Panel administratora</h2>
|
||||
<p class="text-secondary mb-0">Wgląd w użytkowników, listy, paragony, wydatki i ustawienia aplikacji.</p>
|
||||
</div>
|
||||
<a href="{{ url_for('main_page') }}" class="btn btn-outline-secondary">← Powrót do strony głównej</a>
|
||||
@@ -18,49 +18,49 @@
|
||||
<div class="col-md-4">
|
||||
<div class="card bg-dark text-white h-100">
|
||||
<div class="card-body">
|
||||
<h5 class="mb-3">📊 Statystyki ogólne</h5>
|
||||
<h5 class="mb-3">{{ ui_icon('chart-bar') }} Statystyki ogólne</h5>
|
||||
<table class="table table-dark table-sm mb-0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>👤 Użytkownicy</td>
|
||||
<td>{{ ui_icon('user') }} Użytkownicy</td>
|
||||
<td class="text-end fw-bold">{{ user_count }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>📝 Listy zakupowe</td>
|
||||
<td>{{ ui_icon('notes') }} Listy zakupowe</td>
|
||||
<td class="text-end fw-bold">{{ list_count }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>🛒 Produkty na listach</td>
|
||||
<td>{{ ui_icon('shopping-cart') }} Produkty na listach</td>
|
||||
<td class="text-end fw-bold">{{ item_count }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>✅ Zakupione</td>
|
||||
<td>{{ ui_icon('check') }} Zakupione</td>
|
||||
<td class="text-end fw-bold">{{ purchased_items_count }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>🚫 Nieoznaczone jako kupione</td>
|
||||
<td>{{ ui_icon('ban') }} Nieoznaczone jako kupione</td>
|
||||
<td class="text-end fw-bold">{{ not_purchased_count }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>✍️ Produkty z notatkami</td>
|
||||
<td>{{ ui_icon('notes') }} Produkty z notatkami</td>
|
||||
<td class="text-end fw-bold">{{ items_with_notes }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>🕓 Śr. czas do zakupu (h)</td>
|
||||
<td>{{ ui_icon('clock') }} Śr. czas do zakupu (h)</td>
|
||||
<td class="text-end fw-bold">{{ avg_hours_to_purchase }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>💸 Średnia kwota na listę</td>
|
||||
<td>{{ ui_icon('coins') }} Średnia kwota na listę</td>
|
||||
<td class="text-end fw-bold">{{ format_currency(avg_list_expense) }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr>
|
||||
<div class="small text-uppercase mb-1">📈 Średnie tempo tworzenia list:</div>
|
||||
<div class="small text-uppercase mb-1">{{ ui_icon('chart-line') }} Średnie tempo tworzenia list:</div>
|
||||
<ul class="list-unstyled small mb-0">
|
||||
<li>📆 Tygodniowo: <strong>{{ avg_per_week }}</strong></li>
|
||||
<li>🗓️ Miesięcznie: <strong>{{ avg_per_month }}</strong></li>
|
||||
<!--< li>📅 Rocznie: <strong>{{ avg_per_year }}</strong></li> -->
|
||||
<li>{{ ui_icon('calendar') }} Tygodniowo: <strong>{{ avg_per_week }}</strong></li>
|
||||
<li>{{ ui_icon('calendar') }} Miesięcznie: <strong>{{ avg_per_month }}</strong></li>
|
||||
<!--< li>{{ ui_icon('calendar') }} Rocznie: <strong>{{ avg_per_year }}</strong></li> -->
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -71,7 +71,7 @@
|
||||
<div class="col-md-4">
|
||||
<div class="card bg-dark text-white h-100">
|
||||
<div class="card-body">
|
||||
<h5 class="mb-3">🔥 Najczęściej kupowane produkty</h5>
|
||||
<h5 class="mb-3">{{ ui_icon('flame') }} Najczęściej kupowane produkty</h5>
|
||||
{% if top_products %}
|
||||
{% set max_count = top_products[0][1] %}
|
||||
{% for name, count in top_products %}
|
||||
@@ -100,7 +100,7 @@
|
||||
<div class="col-md-4">
|
||||
<div class="card bg-dark text-white h-100 shadow-sm">
|
||||
<div class="card-body">
|
||||
<h5 class="mb-3">💸 Podsumowanie wydatków</h5>
|
||||
<h5 class="mb-3">{{ ui_icon('coins') }} Podsumowanie wydatków</h5>
|
||||
|
||||
<table class="table table-dark table-sm mb-3 align-middle">
|
||||
<thead class="text-muted small">
|
||||
@@ -145,13 +145,13 @@
|
||||
</table>
|
||||
|
||||
<a href="{{ url_for('expenses') }}#chartTab" class="btn btn-outline-light w-100">
|
||||
📊 Pokaż wykres wydatków
|
||||
{{ ui_icon('chart-bar') }} Pokaż wykres wydatków
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if expiring_lists %}<div class="alert alert-warning mb-4"><div class="fw-semibold mb-2">⏰ Listy tymczasowe wygasające w ciągu 24h</div><ul class="mb-0 ps-3">{% for l in expiring_lists %}<li>#{{ l.id }} {{ l.title }} — {{ l.owner.username if l.owner else '—' }} — {{ l.expires_at.strftime('%Y-%m-%d %H:%M') }}</li>{% endfor %}</ul></div>{% endif %}<div class="card bg-dark text-white mb-5">
|
||||
{% if expiring_lists %}<div class="alert alert-warning mb-4"><div class="fw-semibold mb-2">{{ ui_icon('clock') }} Listy tymczasowe wygasające w ciągu 24h</div><ul class="mb-0 ps-3">{% for l in expiring_lists %}<li>#{{ l.id }} {{ l.title }} — {{ l.owner.username if l.owner else '—' }} — {{ l.expires_at.strftime('%Y-%m-%d %H:%M') }}</li>{% endfor %}</ul></div>{% endif %}<div class="card bg-dark text-white mb-5">
|
||||
<div class="card-body">
|
||||
|
||||
{# 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 #}
|
||||
<a href="{{ url_for('admin_panel', m=now.strftime('%Y-%m')) }}" class="btn btn-outline-light btn-sm">
|
||||
📅 Przejdź do bieżącego miesiąca
|
||||
{{ ui_icon('calendar') }} Przejdź do bieżącego miesiąca
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -190,7 +190,7 @@
|
||||
{# PRAWA STRONA — picker miesięcy zawsze widoczny #}
|
||||
<form method="get" class="m-0">
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="input-group-text bg-secondary text-white">📅</span>
|
||||
<span class="input-group-text bg-secondary text-white">{{ ui_icon('calendar') }}</span>
|
||||
<select name="m" class="form-select bg-dark text-white border-secondary" onchange="this.form.submit()">
|
||||
<option value="all" {% if show_all %}selected{% endif %}>Wszystkie miesiące</option>
|
||||
{% for val in month_options %}
|
||||
@@ -205,7 +205,7 @@
|
||||
</div>
|
||||
|
||||
<h3 class="mt-4">
|
||||
📄 Listy zakupowe
|
||||
{{ ui_icon('file-text') }} Listy zakupowe
|
||||
{% if show_all %}
|
||||
— <strong>wszystkie miesiące</strong>
|
||||
{% else %}
|
||||
@@ -243,7 +243,7 @@
|
||||
{% if l.categories %}
|
||||
<span class="ms-1 text-info" data-bs-toggle="tooltip"
|
||||
title="{{ l.categories | map(attribute='name') | join(', ') }}">
|
||||
🏷
|
||||
{{ ui_icon('tag') }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
@@ -260,7 +260,7 @@
|
||||
<td>{{ l.created_at.strftime('%Y-%m-%d %H:%M') if l.created_at else '-' }}</td>
|
||||
<td>
|
||||
{% if l.owner %}
|
||||
👤 {{ l.owner.username }} ({{ l.owner.id }})
|
||||
{{ ui_icon('user') }} {{ l.owner.username }} ({{ l.owner.id }})
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
@@ -289,8 +289,8 @@
|
||||
</td>
|
||||
<td>
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<a href="{{ url_for('edit_list', list_id=l.id) }}" class="btn btn-sm btn-outline-light" title="Edytuj">✏️</a>
|
||||
<button type="button" class="btn btn-sm btn-outline-light preview-btn" data-list-id="{{ l.id }}" title="Podgląd produktów">👁️</button>
|
||||
<a href="{{ url_for('edit_list', list_id=l.id) }}" class="btn btn-sm btn-outline-light" title="Edytuj">{{ ui_icon('pencil') }}</a>
|
||||
<button type="button" class="btn btn-sm btn-outline-light preview-btn" data-list-id="{{ l.id }}" title="Podgląd produktów">{{ ui_icon('eye') }}</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -306,7 +306,7 @@
|
||||
</table>
|
||||
</div>
|
||||
<div class="d-flex justify-content-end mt-2">
|
||||
<button type="submit" class="btn btn-outline-light btn-sm">🗑️ Usuń zaznaczone listy</button>
|
||||
<button type="submit" class="btn btn-outline-light btn-sm">{{ ui_icon('trash') }} Usuń zaznaczone listy</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center flex-wrap mb-4 gap-2">
|
||||
<div>
|
||||
<h2 class="mb-2">🔑 Tokeny API</h2>
|
||||
<h2 class="mb-2">{{ ui_icon('login') }} Tokeny API</h2>
|
||||
<p class="text-secondary mb-0">Administrator może utworzyć wiele tokenów, ograniczyć ich zakres i endpointy oraz w każdej chwili je wyłączyć albo usunąć.</p>
|
||||
</div>
|
||||
<div class="d-flex gap-2 flex-wrap">
|
||||
<a href="{{ url_for('admin_api_docs') }}" class="btn btn-outline-light" target="_blank">📄 Zobacz opis API</a>
|
||||
<a href="{{ url_for('admin_api_docs') }}" class="btn btn-outline-light" target="_blank">{{ ui_icon('file-text') }} Zobacz opis API</a>
|
||||
<a href="{{ url_for('admin_panel') }}" class="btn btn-outline-secondary">← Powrót do panelu</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -21,7 +21,7 @@
|
||||
<div><strong>Nowy token:</strong> {{ latest_api_token_name or 'API' }}</div>
|
||||
<div class="input-group">
|
||||
<input type="text" id="latestApiToken" class="form-control" readonly value="{{ latest_plain_token }}">
|
||||
<button type="button" class="btn btn-outline-light" data-copy-target="#latestApiToken">📋 Kopiuj</button>
|
||||
<button type="button" class="btn btn-outline-light" data-copy-target="#latestApiToken">{{ ui_icon('clipboard') }} Kopiuj</button>
|
||||
</div>
|
||||
<div class="small text-warning">Pełna wartość jest widoczna tylko teraz. Po odświeżeniu zostanie ukryta.</div>
|
||||
</div>
|
||||
@@ -30,7 +30,7 @@
|
||||
|
||||
<div class="card bg-dark text-white">
|
||||
<div class="card-body">
|
||||
<h5 class="mb-3">➕ Utwórz token</h5>
|
||||
<h5 class="mb-3">{{ ui_icon('plus') }} Utwórz token</h5>
|
||||
<form method="post" data-unsaved-warning="true" class="stack-form">
|
||||
<input type="hidden" name="action" value="create">
|
||||
<div class="row g-4">
|
||||
@@ -58,7 +58,7 @@
|
||||
<input type="number" id="max_limit" name="max_limit" min="1" max="500" value="100" class="form-control">
|
||||
</div>
|
||||
<div class="col-sm-6 col-xl-3 d-flex align-items-end">
|
||||
<button type="submit" class="btn btn-success w-100">🔑 Wygeneruj token</button>
|
||||
<button type="submit" class="btn btn-success w-100">{{ ui_icon('login') }} Wygeneruj token</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -68,7 +68,7 @@
|
||||
<div class="card bg-dark text-white mt-4">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center flex-wrap gap-2 mb-3">
|
||||
<h5 class="mb-0">📘 Dokumentacja API</h5>
|
||||
<h5 class="mb-0">{{ ui_icon('book') }} Dokumentacja API</h5>
|
||||
<a href="{{ url_for('admin_api_docs') }}" class="btn btn-sm btn-outline-light" target="_blank">Otwórz TXT</a>
|
||||
</div>
|
||||
<div class="small text-secondary mb-3">Autoryzacja: <code>Authorization: Bearer TWOJ_TOKEN</code> lub <code>X-API-Token</code>. Endpoint i zakres muszą być jednocześnie dozwolone na tokenie. Parametr <code>limit</code> jest przycinany do wartości ustawionej w tokenie.</div>
|
||||
@@ -95,7 +95,7 @@
|
||||
<div class="card bg-dark text-white mt-4">
|
||||
<div class="card-body">
|
||||
<div class="admin-page-head mb-3">
|
||||
<h5 class="mb-0">📋 Aktywne i historyczne tokeny</h5>
|
||||
<h5 class="mb-0">{{ ui_icon('clipboard') }} Aktywne i historyczne tokeny</h5>
|
||||
<span class="badge rounded-pill bg-secondary">{{ api_tokens|length }} szt.</span>
|
||||
</div>
|
||||
<div class="table-responsive admin-table-responsive admin-table-responsive--wide">
|
||||
@@ -133,19 +133,19 @@
|
||||
<form method="post" class="d-inline">
|
||||
<input type="hidden" name="action" value="deactivate">
|
||||
<input type="hidden" name="token_id" value="{{ token.id }}">
|
||||
<button type="submit" class="btn btn-sm btn-outline-warning">⏸ Wyłącz</button>
|
||||
<button type="submit" class="btn btn-sm btn-outline-warning">{{ ui_icon('player-pause') }} Wyłącz</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<form method="post" class="d-inline">
|
||||
<input type="hidden" name="action" value="activate">
|
||||
<input type="hidden" name="token_id" value="{{ token.id }}">
|
||||
<button type="submit" class="btn btn-sm btn-outline-success">▶ Włącz</button>
|
||||
<button type="submit" class="btn btn-sm btn-outline-success">{{ ui_icon('player-play') }} Włącz</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
<form method="post" class="d-inline" onsubmit="return confirm('Usunąć ten token API?')">
|
||||
<input type="hidden" name="action" value="delete">
|
||||
<input type="hidden" name="token_id" value="{{ token.id }}">
|
||||
<button type="submit" class="btn btn-sm btn-outline-danger">🗑 Usuń</button>
|
||||
<button type="submit" class="btn btn-sm btn-outline-danger">{{ ui_icon('trash') }} Usuń</button>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
{% block content %}
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center flex-wrap mb-4">
|
||||
<h2 class="mb-2">🗂 Masowa edycja kategorii</h2>
|
||||
<h2 class="mb-2">{{ ui_icon('folder') }} Masowa edycja kategorii</h2>
|
||||
<div>
|
||||
<a href="{{ url_for('admin_panel') }}" class="btn btn-outline-secondary">← Powrót do panelu</a>
|
||||
</div>
|
||||
@@ -14,7 +14,7 @@
|
||||
<div class="card bg-dark text-white mb-5">
|
||||
<div class="card-body">
|
||||
<div class="alert alert-warning border-warning text-dark" role="alert">
|
||||
⚠️ <strong>Uwaga!</strong> Przypisanie więcej niż jednej kategorii do listy może zaburzyć poprawne zliczanie
|
||||
{{ ui_icon('alert-triangle') }} <strong>Uwaga!</strong> Przypisanie więcej niż jednej kategorii do listy może zaburzyć poprawne zliczanie
|
||||
wydatków.
|
||||
</div>
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
</td>
|
||||
<td>
|
||||
{% if l.owner %}
|
||||
👤 {{ l.owner.username }} ({{ l.owner.id }})
|
||||
{{ ui_icon('user') }} {{ l.owner.username }} ({{ l.owner.id }})
|
||||
{% else %}-{% endif %}
|
||||
</td>
|
||||
<td>{{ l.created_at.strftime('%Y-%m-%d %H:%M') if l.created_at else '-' }}</td>
|
||||
@@ -60,7 +60,7 @@
|
||||
<td>
|
||||
<button type="button" class="btn btn-sm btn-outline-light preview-btn"
|
||||
data-list-id="{{ l.id }}">
|
||||
🔍 Zobacz
|
||||
{{ ui_icon('search') }} Zobacz
|
||||
</button>
|
||||
</td>
|
||||
<td>
|
||||
@@ -90,7 +90,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-end mt-3"><button type="submit" class="btn btn-sm btn-outline-light">💾 Zapisz wszystkie zmiany</button></div>
|
||||
<div class="d-flex justify-content-end mt-3"><button type="submit" class="btn btn-sm btn-outline-light">{{ ui_icon('device-floppy') }} Zapisz wszystkie zmiany</button></div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@@ -98,7 +98,7 @@
|
||||
<hr>
|
||||
<div class="d-flex justify-content-between align-items-center mt-4">
|
||||
<form method="get" class="d-flex align-items-center">
|
||||
<label for="per_page" class="me-2">🔢 Pozycji na stronę:</label>
|
||||
<label for="per_page" class="me-2">{{ ui_icon('number') }} Pozycji na stronę:</label>
|
||||
<select id="per_page" name="per_page" class="form-select form-select-sm me-2"
|
||||
onchange="this.form.page.value = 1; this.form.submit();">
|
||||
<option value="25" {% if per_page==25 %}selected{% endif %}>25</option>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
{% block content %}
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center flex-wrap mb-4">
|
||||
<h2 class="mb-2">🛠️ Edytuj listę #{{ list.id }}</h2>
|
||||
<h2 class="mb-2">{{ ui_icon('tool') }} Edytuj listę #{{ list.id }}</h2>
|
||||
<a href="{{ url_for('admin_panel') }}" class="btn btn-outline-secondary">← Powrót do panelu</a>
|
||||
</div>
|
||||
|
||||
@@ -11,13 +11,13 @@
|
||||
|
||||
<div class="card bg-secondary bg-opacity-10 text-white mb-5">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">📄 Podstawowe informacje</h4>
|
||||
<h4 class="card-title">{{ ui_icon('file-text') }} Podstawowe informacje</h4>
|
||||
<form method="post" class="mt-3" data-unsaved-warning="true">
|
||||
<input type="hidden" name="action" value="save">
|
||||
|
||||
<!-- Nazwa listy -->
|
||||
<div class="mb-3">
|
||||
<label for="title" class="form-label">📝 Nazwa listy</label>
|
||||
<label for="title" class="form-label">{{ ui_icon('notes') }} Nazwa listy</label>
|
||||
<input type="text" class="form-control bg-dark text-white border-secondary ui-consistent-input" id="title" name="title"
|
||||
value="{{ list.title }}" required>
|
||||
</div>
|
||||
@@ -25,13 +25,13 @@
|
||||
<!-- Wydatek i właściciel -->
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<label for="amount" class="form-label">💰 Całkowity wydatek ({{ CURRENCY_CODE }})</label>
|
||||
<label for="amount" class="form-label">{{ ui_icon('coins') }} Całkowity wydatek ({{ CURRENCY_CODE }})</label>
|
||||
<input type="number" step="0.01" min="0" class="form-control bg-dark text-white border-secondary ui-consistent-input"
|
||||
id="amount" name="amount" value="{{ '%.2f'|format(total_expense) }}">
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="owner_id" class="form-label">👤 Właściciel</label>
|
||||
<label for="owner_id" class="form-label">{{ ui_icon('user') }} Właściciel</label>
|
||||
<select class="form-select bg-dark text-white border-secondary ui-consistent-input" id="owner_id" name="owner_id">
|
||||
{% for user in users %}
|
||||
<option value="{{ user.id }}" {% if list.owner_id==user.id %}selected{% endif %}>
|
||||
@@ -44,24 +44,24 @@
|
||||
|
||||
<!-- Statusy -->
|
||||
<div class="mb-4">
|
||||
<label class="form-label">⚙️ Statusy listy</label>
|
||||
<label class="form-label">{{ ui_icon('settings') }} Statusy listy</label>
|
||||
<div class="switch-grid">
|
||||
<div class="form-check form-switch app-switch">
|
||||
<input class="form-check-input" type="checkbox" id="archived" name="archived" {% if list.is_archived
|
||||
%}checked{% endif %}>
|
||||
<label class="form-check-label" for="archived">📦 Archiwalna</label>
|
||||
<label class="form-check-label" for="archived">{{ ui_icon('box') }} Archiwalna</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check form-switch app-switch">
|
||||
<input class="form-check-input" type="checkbox" id="public" name="public" {% if list.is_public %}checked{%
|
||||
endif %}>
|
||||
<label class="form-check-label" for="public">🌐 Publiczna</label>
|
||||
<label class="form-check-label" for="public">{{ ui_icon('world') }} Publiczna</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check form-switch app-switch">
|
||||
<input class="form-check-input" type="checkbox" id="temporary" name="temporary" {% if list.is_temporary
|
||||
%}checked{% endif %}>
|
||||
<label class="form-check-label" for="temporary">⏳ Tymczasowa (podaj date i godzine wygasania)</label>
|
||||
<label class="form-check-label" for="temporary">{{ ui_icon('hourglass') }} Tymczasowa (podaj date i godzine wygasania)</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -70,12 +70,12 @@
|
||||
<!-- Data/godzina wygaśnięcia -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6">
|
||||
<label for="expires_date" class="form-label">📅 Data wygaśnięcia</label>
|
||||
<label for="expires_date" class="form-label">{{ ui_icon('calendar') }} Data wygaśnięcia</label>
|
||||
<input type="date" class="form-control bg-dark text-white border-secondary ui-consistent-input" id="expires_date"
|
||||
name="expires_date" value="{{ list.expires_at.strftime('%Y-%m-%d') if list.expires_at else '' }}">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="expires_time" class="form-label">⏰ Godzina wygaśnięcia</label>
|
||||
<label for="expires_time" class="form-label">{{ ui_icon('clock') }} Godzina wygaśnięcia</label>
|
||||
<input type="time" class="form-control bg-dark text-white border-secondary ui-consistent-input" id="expires_time"
|
||||
name="expires_time" value="{{ list.expires_at.strftime('%H:%M') if list.expires_at else '' }}">
|
||||
</div>
|
||||
@@ -84,7 +84,7 @@
|
||||
<!-- Utworzono / Zmień miesiąc -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">📆 Utworzono</label>
|
||||
<label class="form-label">{{ ui_icon('calendar') }} Utworzono</label>
|
||||
<div>
|
||||
<span class="badge rounded-pill bg-success rounded-pill text-dark ms-1">
|
||||
{{ list.created_at.strftime('%Y-%m-%d') }}
|
||||
@@ -92,7 +92,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">📁 Przenieś do miesiąca (format: rok-miesiąc np 2026-01)</label>
|
||||
<label class="form-label">{{ ui_icon('folder') }} Przenieś do miesiąca (format: rok-miesiąc np 2026-01)</label>
|
||||
<input type="month" id="created_month" name="created_month"
|
||||
class="form-control bg-dark text-white border-secondary ui-consistent-input">
|
||||
</div>
|
||||
@@ -100,7 +100,7 @@
|
||||
|
||||
<!-- Kategorie -->
|
||||
<div class="mb-4">
|
||||
<label for="categories" class="form-label">🏷️ Kategorie</label>
|
||||
<label for="categories" class="form-label">{{ ui_icon('tag') }} Kategorie</label>
|
||||
<select id="categories" name="categories"
|
||||
class="form-select tom-dark bg-dark text-white border-secondary ui-consistent-input">
|
||||
<option value="">– brak –</option>
|
||||
@@ -114,7 +114,7 @@
|
||||
|
||||
<!-- Link udostępnienia -->
|
||||
<div class="mb-4">
|
||||
<label class="form-label">🔗 Link do udostępnienia</label>
|
||||
<label class="form-label">{{ ui_icon('link') }} Link do udostępnienia</label>
|
||||
<input type="text" class="form-control bg-dark text-white border-secondary ui-consistent-input" readonly
|
||||
value="{{ request.url_root }}share/{{ list.share_token }}">
|
||||
</div>
|
||||
@@ -122,10 +122,10 @@
|
||||
|
||||
<!-- Dostęp / uprawnienia -->
|
||||
<div class="mb-4 border-top pt-3 mt-4">
|
||||
<h5 class="mb-3">🔐 Użytkownicy z dostępem</h5>
|
||||
<h5 class="mb-3">{{ ui_icon('shield-lock') }} Użytkownicy z dostępem</h5>
|
||||
|
||||
<a class="btn btn-outline-warning btn-sm mb-3" href="{{ url_for('admin_lists_access', list_id=list.id) }}">
|
||||
⚙️ Edytuj uprawnienia
|
||||
{{ ui_icon('settings') }} Edytuj uprawnienia
|
||||
</a>
|
||||
|
||||
{% if permitted_users %}
|
||||
@@ -144,7 +144,7 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-outline-light btn-sm me-2">💾 Zapisz zmiany</button>
|
||||
<button type="submit" class="btn btn-outline-light btn-sm me-2">{{ ui_icon('device-floppy') }} Zapisz zmiany</button>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
@@ -152,7 +152,7 @@
|
||||
|
||||
<div class="card bg-dark text-white mb-5">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">📝 Produkty</h4>
|
||||
<h4 class="card-title">{{ ui_icon('notes') }} Produkty</h4>
|
||||
|
||||
<form method="post" class="row g-2 mb-3">
|
||||
<input type="hidden" name="action" value="add_item">
|
||||
@@ -165,7 +165,7 @@
|
||||
value="1">
|
||||
</div>
|
||||
<div class="col-md-3 d-grid">
|
||||
<button type="submit" class="btn btn-outline-light">➕ Dodaj</button>
|
||||
<button type="submit" class="btn btn-outline-light">{{ ui_icon('plus') }} Dodaj</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -202,15 +202,15 @@
|
||||
<div class="input-group input-group-sm w-auto">
|
||||
<input type="number" name="quantity" class="form-control bg-dark text-white border-secondary" min="1"
|
||||
value="{{ item.quantity }}">
|
||||
<button type="submit" class="btn btn-outline-light btn-sm">💾</button>
|
||||
<button type="submit" class="btn btn-outline-light btn-sm" aria-label="Zapisz ilość" title="Zapisz ilość">{{ ui_icon('device-floppy') }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</td>
|
||||
<td>
|
||||
{% if item.purchased %}
|
||||
<span class="badge bg-success">✔️ Kupiony</span>
|
||||
<span class="badge bg-success">{{ ui_icon('check') }} Kupiony</span>
|
||||
{% elif item.not_purchased %}
|
||||
<span class="badge bg-warning text-dark">⚠️ Nie kupiony</span>
|
||||
<span class="badge bg-warning text-dark">{{ ui_icon('alert-triangle') }} Nie kupiony</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary">Nieoznaczony</span>
|
||||
{% endif %}
|
||||
@@ -221,14 +221,14 @@
|
||||
<div class="btn-group btn-group-sm d-flex gap-1">
|
||||
{% if not item.not_purchased %}
|
||||
<button type="submit" name="action" value="toggle_purchased" class="btn btn-outline-light btn-sm">
|
||||
{{ '🚫 Odznacz' if item.purchased else '✅ Kupiony' }}
|
||||
{% if item.purchased %}{{ ui_icon('ban') }} Odznacz{% else %}{{ ui_icon('check') }} Kupiony{% endif %}
|
||||
</button>
|
||||
<button type="submit" name="action" value="mark_not_purchased" class="btn btn-outline-light btn-sm">⚠️
|
||||
<button type="submit" name="action" value="mark_not_purchased" class="btn btn-outline-light btn-sm">{{ ui_icon('alert-triangle') }}
|
||||
Nie kupiony</button>
|
||||
{% endif %}
|
||||
{% if item.not_purchased %}
|
||||
<button type="submit" name="action" value="unmark_not_purchased"
|
||||
class="btn btn-outline-light btn-sm">✅
|
||||
class="btn btn-outline-light btn-sm">{{ ui_icon('check') }}
|
||||
Przywróć jako nieoznaczone</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -238,7 +238,7 @@
|
||||
<form method="post" action="{{ url_for('edit_list', list_id=list.id) }}">
|
||||
<input type="hidden" name="action" value="delete_item">
|
||||
<input type="hidden" name="item_id" value="{{ item.id }}">
|
||||
<button type="submit" class="btn btn-outline-light btn-sm">🗑️</button>
|
||||
<button type="submit" class="btn btn-outline-light btn-sm" aria-label="Usuń produkt" title="Usuń produkt">{{ ui_icon('trash') }}</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -255,11 +255,11 @@
|
||||
|
||||
<div class="card bg-dark text-white mb-5">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">🧾 Paragony</h4>
|
||||
<h4 class="card-title">{{ ui_icon('receipt') }} Paragony</h4>
|
||||
|
||||
<div class="mb-3 text-end">
|
||||
<a href="{{ url_for('admin_receipts', id=list.id) }}" class="btn btn-sm btn-outline-light">
|
||||
📂 Otwórz zarządzanie paragonami
|
||||
{{ ui_icon('folder') }} Otwórz zarządzanie paragonami
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -274,11 +274,11 @@
|
||||
</a>
|
||||
|
||||
<div class="card-body text-center p-2 small">
|
||||
<div class="text-truncate fw-semibold" title="{{ r.filename }}">📄 {{ r.filename }}</div>
|
||||
<div>📅 {{ r.uploaded_at.strftime('%Y-%m-%d %H:%M') }}</div>
|
||||
<div>👤 {{ r.uploaded_by_user.username if r.uploaded_by_user else "?" }}</div>
|
||||
<div class="text-truncate fw-semibold" title="{{ r.filename }}">{{ ui_icon('file-text') }} {{ r.filename }}</div>
|
||||
<div>{{ ui_icon('calendar') }} {{ r.uploaded_at.strftime('%Y-%m-%d %H:%M') }}</div>
|
||||
<div>{{ ui_icon('user') }} {{ r.uploaded_by_user.username if r.uploaded_by_user else "?" }}</div>
|
||||
<div>
|
||||
💾
|
||||
{{ 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 %}
|
||||
<div class="alert alert-info text-center mt-3" role="alert">
|
||||
ℹ️ Brak paragonów
|
||||
{{ ui_icon('info-circle') }} Brak paragonów
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
{% block content %}
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center flex-wrap mb-4">
|
||||
<h2 class="mb-2">🛍️ Produkty i sugestie</h2>
|
||||
<h2 class="mb-2">{{ ui_icon('shopping-bag') }} Produkty i sugestie</h2>
|
||||
<a href="{{ url_for('admin_panel') }}" class="btn btn-outline-secondary">← Powrót do panelu</a>
|
||||
</div>
|
||||
|
||||
@@ -14,22 +14,22 @@
|
||||
|
||||
<!-- Formularz dodawania sugestii -->
|
||||
<form action="{{ url_for('add_suggestion') }}" method="POST" class="mb-4">
|
||||
<label class="form-label fw-bold mb-2">➕ Dodaj nową sugestię:</label>
|
||||
<label class="form-label fw-bold mb-2">{{ ui_icon('plus') }} Dodaj nową sugestię:</label>
|
||||
<div class="input-group">
|
||||
<input type="text" name="suggestion_name" class="form-control bg-dark text-white border-secondary"
|
||||
placeholder="Nowa sugestia produktu…" required>
|
||||
<button type="submit" class="btn btn-outline-light">➕ Dodaj</button>
|
||||
<button type="submit" class="btn btn-outline-light">{{ ui_icon('plus') }} Dodaj</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<hr class="border-secondary opacity-50 mb-4 mt-2">
|
||||
|
||||
<!-- Szukajka z przyciskiem wyczyść -->
|
||||
<label for="search-table" class="form-label fw-bold mb-2">🔍 Przeszukaj tabelę produktów i sugestii:</label>
|
||||
<label for="search-table" class="form-label fw-bold mb-2">{{ ui_icon('search') }} Przeszukaj tabelę produktów i sugestii:</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="search-table" class="form-control bg-dark text-white border-secondary"
|
||||
placeholder="Wpisz frazę, np. 'mleko'">
|
||||
<button type="button" id="clear-search" class="btn btn-outline-light">🧹 Wyczyść</button>
|
||||
<button type="button" id="clear-search" class="btn btn-outline-light">{{ ui_icon('eraser') }} Wyczyść</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -38,7 +38,7 @@
|
||||
<div class="card bg-dark text-white mb-5">
|
||||
<div class="card-body">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h4 class="m-0">📦 Produkty (z synchronizacją sugestii o unikalnych nazwach)</h4>
|
||||
<h4 class="m-0">{{ ui_icon('box') }} Produkty (z synchronizacją sugestii o unikalnych nazwach)</h4>
|
||||
<span class="badge rounded-pill bg-info">{{ total_items }} produktów</span>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
@@ -59,7 +59,7 @@
|
||||
<td class="fw-bold"><span class="badge rounded-pill bg-primary">{{ item.name }}</span></td>
|
||||
<td>
|
||||
{% 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 }})
|
||||
<button class="btn btn-sm btn-outline-light ms-1 delete-suggestion-btn"
|
||||
data-suggestion-id="{{ suggestion.id }}">🗑️ Usuń</button>
|
||||
data-suggestion-id="{{ suggestion.id }}">{{ ui_icon('trash') }} Usuń</button>
|
||||
{% else %}
|
||||
<button class="btn btn-sm btn-outline-light sync-btn" data-item-id="{{ item.id }}">🔄
|
||||
<button class="btn btn-sm btn-outline-light sync-btn" data-item-id="{{ item.id }}">{{ ui_icon('refresh') }}
|
||||
Synchronizuj</button>
|
||||
{% endif %}
|
||||
</td>
|
||||
@@ -96,7 +96,7 @@
|
||||
<div class="card bg-dark text-white mb-5">
|
||||
<div class="card-body">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h4 class="m-0">💡 Wszystkie sugestie (poza powiązanymi)</h4>
|
||||
<h4 class="m-0">{{ ui_icon('bulb') }} Wszystkie sugestie (poza powiązanymi)</h4>
|
||||
<span class="badge rounded-pill bg-info">{{ orphan_suggestions|length }} sugestii</span>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
@@ -117,7 +117,7 @@
|
||||
<td class="fw-bold"><span class="badge rounded-pill bg-primary">{{ suggestion.name }}</span></td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-outline-light delete-suggestion-btn"
|
||||
data-suggestion-id="{{ suggestion.id }}">🗑️ Usuń</button>
|
||||
data-suggestion-id="{{ suggestion.id }}">{{ ui_icon('trash') }} Usuń</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
@@ -138,7 +138,7 @@
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mt-4">
|
||||
<form method="get" class="d-flex align-items-center">
|
||||
<label for="per_page" class="me-2">🔢 Pozycji na stronę:</label>
|
||||
<label for="per_page" class="me-2">{{ ui_icon('number') }} Pozycji na stronę:</label>
|
||||
<select id="per_page" name="per_page" class="form-select form-select-sm me-2"
|
||||
onchange="this.form.page.value = 1; this.form.submit();">
|
||||
<option value="100" {% if per_page==25 %}selected{% endif %}>100</option>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center flex-wrap mb-3">
|
||||
<h2 class="mb-2">🔐{% if list_id %} Dostęp do listy #{{ list_id }}{% else %} Zarządzanie dostępem do list{% endif %}</h2>
|
||||
<h2 class="mb-2">{{ ui_icon('shield-lock') }}{% if list_id %} Dostęp do listy #{{ list_id }}{% else %} Zarządzanie dostępem do list{% endif %}</h2>
|
||||
<div class="d-flex gap-2">
|
||||
{% if list_id %}
|
||||
<a href="{{ url_for('admin_lists_access') }}" class="btn btn-outline-light">Powrót do wszystkich list</a>
|
||||
@@ -38,7 +38,7 @@
|
||||
<div class="input-group input-group-sm">
|
||||
<input id="bulkUsersInput" class="form-control bg-dark text-white border-secondary"
|
||||
placeholder="Podaj użytkowników (po przecinku lub enterach)" list="userHints" autocomplete="off">
|
||||
<button id="bulkAddBtn" class="btn btn-outline-light" type="button">➕ Nadaj dostęp</button>
|
||||
<button id="bulkAddBtn" class="btn btn-outline-light" type="button">{{ ui_icon('plus') }} Nadaj dostęp</button>
|
||||
</div>
|
||||
<div id="bulkTokens" class="d-flex flex-wrap gap-2 mt-2"></div>
|
||||
</div>
|
||||
@@ -84,7 +84,7 @@
|
||||
|
||||
<td>
|
||||
{% if l.owner %}
|
||||
👤 <span class="owner-username" data-username="{{ l.owner.username }}">@{{ l.owner.username }}</span>
|
||||
{{ ui_icon('user') }} <span class="owner-username" data-username="{{ l.owner.username }}">@{{ l.owner.username }}</span>
|
||||
({{ l.owner.id }})
|
||||
{% else %}-{% endif %}
|
||||
</td>
|
||||
@@ -96,7 +96,7 @@
|
||||
<div class="d-flex align-items-center gap-2 w-100">
|
||||
<div class="flex-grow-1 text-truncate mono small" title="{{ share_url }}">{{ share_url }}</div>
|
||||
<button class="btn btn-sm btn-outline-secondary copy-share" type="button" data-url="{{ share_url }}"
|
||||
aria-label="Kopiuj link">📋</button>
|
||||
aria-label="Kopiuj link">{{ ui_icon('clipboard') }}</button>
|
||||
</div>
|
||||
<div class="text-info small mt-1">
|
||||
{% 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">
|
||||
<button type="button" class="btn btn-sm btn-outline-light access-add">💾 Zapisz dostęp</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-light access-add">{{ ui_icon('device-floppy') }} Zapisz dostęp</button>
|
||||
</div>
|
||||
<div class="text-secondary small mt-1">Kliknij token, aby odebrać dostęp. Zmiana zapisuje się od razu.</div>
|
||||
</div>
|
||||
@@ -151,7 +151,7 @@
|
||||
<hr>
|
||||
<div class="d-flex justify-content-between align-items-center mt-4">
|
||||
<form method="get" class="d-flex align-items-center">
|
||||
<label for="per_page" class="me-2">🔢 Pozycji na stronę:</label>
|
||||
<label for="per_page" class="me-2">{{ ui_icon('number') }} Pozycji na stronę:</label>
|
||||
<select id="per_page" name="per_page" class="form-select form-select-sm me-2"
|
||||
onchange="this.form.page.value = 1; this.form.submit();">
|
||||
<option value="25" {% if per_page==25 %}selected{% endif %}>25</option>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center flex-wrap mb-4">
|
||||
<h2 class="mb-2">
|
||||
📸 {% if id == 'all' %}Wszystkie paragony{% else %}Paragony dla listy #{{ id }}{% endif %}
|
||||
{{ ui_icon('camera') }} {% if id == 'all' %}Wszystkie paragony{% else %}Paragony dla listy #{{ id }}{% endif %}
|
||||
</h2>
|
||||
|
||||
<p class="text-white-50 small mt-1">
|
||||
@@ -62,11 +62,11 @@
|
||||
</a>
|
||||
|
||||
<div class="card-body text-center p-2 small">
|
||||
<div class="text-truncate fw-semibold" title="{{ r.filename }}">📄 {{ r.filename }}</div>
|
||||
<div>📅 {{ r.uploaded_at.strftime('%Y-%m-%d %H:%M') }}</div>
|
||||
<div>👤 {{ r.uploaded_by_user.username if r.uploaded_by_user else "?" }}</div>
|
||||
<div class="text-truncate fw-semibold" title="{{ r.filename }}">{{ ui_icon('file-text') }} {{ r.filename }}</div>
|
||||
<div>{{ ui_icon('calendar') }} {{ r.uploaded_at.strftime('%Y-%m-%d %H:%M') }}</div>
|
||||
<div>{{ ui_icon('user') }} {{ r.uploaded_by_user.username if r.uploaded_by_user else "?" }}</div>
|
||||
<div>
|
||||
💾
|
||||
{{ ui_icon('device-floppy') }}
|
||||
{% if r.filesize and r.filesize >= 1024 * 1024 %}
|
||||
{{ (r.filesize / 1024 / 1024) | round(2) }} MB
|
||||
{% elif r.filesize %}
|
||||
@@ -83,35 +83,35 @@
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-dark w-100 text-start">
|
||||
<li>
|
||||
<a class="dropdown-item" href="{{ url_for('rotate_receipt', receipt_id=r.id) }}">🔄 Obróć o 90°</a>
|
||||
<a class="dropdown-item" href="{{ url_for('rotate_receipt', receipt_id=r.id) }}">{{ ui_icon('refresh') }} Obróć o 90°</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item" href="#" data-bs-toggle="modal" data-bs-target="#adminCropModal"
|
||||
data-img-src="{{ url_for('uploaded_file', filename=r.filename) }}?v={{ r.version_token or '0' }}"
|
||||
data-receipt-id="{{ r.id }}" data-crop-endpoint="{{ url_for('crop_receipt_admin') }}"
|
||||
data-version="{{ r.version_token or '0' }}">
|
||||
✂️ Przytnij
|
||||
{{ ui_icon('scissors') }} Przytnij
|
||||
</a>
|
||||
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item" href="{{ url_for('rename_receipt', receipt_id=r.id) }}">✏️ Zmień nazwę</a>
|
||||
<a class="dropdown-item" href="{{ url_for('rename_receipt', receipt_id=r.id) }}">{{ ui_icon('pencil') }} Zmień nazwę</a>
|
||||
</li>
|
||||
{% if not r.file_hash %}
|
||||
<li>
|
||||
<a class="dropdown-item" href="{{ url_for('generate_receipt_hash', receipt_id=r.id) }}">🔐 Generuj
|
||||
<a class="dropdown-item" href="{{ url_for('generate_receipt_hash', receipt_id=r.id) }}">{{ ui_icon('shield-lock') }} Generuj
|
||||
hash</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li>
|
||||
<a class="dropdown-item text-danger" href="{{ url_for('delete_receipt', receipt_id=r.id) }}"
|
||||
onclick="return confirm('Na pewno usunąć plik {{ r.filename }}?');">🗑️ Usuń</a>
|
||||
onclick="return confirm('Na pewno usunąć plik {{ r.filename }}?');">{{ ui_icon('trash') }} Usuń</a>
|
||||
</li>
|
||||
<li>
|
||||
<hr class="dropdown-divider">
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item" href="{{ url_for('edit_list', list_id=r.list_id) }}">📋 Edytuj listę #{{
|
||||
<a class="dropdown-item" href="{{ url_for('edit_list', list_id=r.list_id) }}">{{ ui_icon('clipboard') }} Edytuj listę #{{
|
||||
r.list_id }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -135,7 +135,7 @@
|
||||
<hr>
|
||||
<div class="d-flex justify-content-between align-items-center mt-4">
|
||||
<form method="get" class="d-flex align-items-center">
|
||||
<label for="per_page" class="me-2">🔢 Pozycji na stronę:</label>
|
||||
<label for="per_page" class="me-2">{{ ui_icon('number') }} Pozycji na stronę:</label>
|
||||
<select id="per_page" name="per_page" class="form-select form-select-sm me-2"
|
||||
onchange="this.form.page.value = 1; this.form.submit();">
|
||||
<option value="25" {% if per_page==25 %}selected{% endif %}>25</option>
|
||||
@@ -181,7 +181,7 @@
|
||||
<div class="alert alert-warning small py-1 mb-2">Brak powiązania z listą!</div>
|
||||
<a href="{{ url_for('delete_receipt', filename=f) }}" class="btn btn-sm btn-outline-light w-100 mb-2"
|
||||
onclick="return confirm('Na pewno usunąć WYŁĄCZNIE plik {{ f }} z dysku?');">
|
||||
🗑 Usuń plik z serwera
|
||||
{{ ui_icon('trash') }} Usuń plik z serwera
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -194,7 +194,7 @@
|
||||
<div class="modal-dialog modal-xl modal-dialog-centered">
|
||||
<div class="modal-content bg-dark text-white">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">✂️ Przycinanie paragonu</h5>
|
||||
<h5 class="modal-title">{{ ui_icon('scissors') }} Przycinanie paragonu</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div style="position: relative; width: 100%; height: 75vh;">
|
||||
@@ -202,12 +202,12 @@
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-sm btn-outline-light" data-bs-dismiss="modal">❌ Anuluj</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-light" id="adminSaveCrop">💾 Zapisz</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-light" data-bs-dismiss="modal">{{ ui_icon('x') }} Anuluj</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-light" id="adminSaveCrop">{{ ui_icon('device-floppy') }} Zapisz</button>
|
||||
</div>
|
||||
<div id="adminCropLoading" class="position-absolute top-50 start-50 translate-middle text-center d-none">
|
||||
<div class="spinner-border text-light" role="status"></div>
|
||||
<div class="mt-2 text-light">⏳ Pracuję...</div>
|
||||
<div class="mt-2 text-light">{{ ui_icon('hourglass') }} Pracuję...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center flex-wrap mb-4">
|
||||
<h2 class="mb-2">⚙️ Ustawienia</h2>
|
||||
<h2 class="mb-2">{{ ui_icon('settings') }} Ustawienia</h2>
|
||||
<a href="{{ url_for('admin_panel') }}" class="btn btn-outline-secondary">← Powrót do panelu</a>
|
||||
</div>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<form method="post" id="settings-form" data-unsaved-warning="true">
|
||||
<div class="card bg-dark text-white mb-4">
|
||||
<div class="card-header border-0">
|
||||
<strong>🔎 OCR — słowa kluczowe i czułość</strong>
|
||||
<strong>{{ ui_icon('search') }} OCR — słowa kluczowe i czułość</strong>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="small text-info mb-2">
|
||||
@@ -55,8 +55,8 @@
|
||||
|
||||
<div class="card bg-dark text-white mb-4">
|
||||
<div class="card-header border-0 d-flex align-items-center justify-content-between">
|
||||
<strong>🎨 Kolory kategorii</strong>
|
||||
<button type="button" class="btn btn-outline-light btn-sm" id="reset-all">🔄 Wyczyść nadpisania</button>
|
||||
<strong>{{ ui_icon('palette') }} Kolory kategorii</strong>
|
||||
<button type="button" class="btn btn-outline-light btn-sm" id="reset-all">{{ ui_icon('refresh') }} Wyczyść nadpisania</button>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
@@ -93,12 +93,12 @@
|
||||
<button type="button"
|
||||
class="btn btn-outline-light btn-sm reset-one"
|
||||
data-target="color_{{ c.id }}">
|
||||
🔄 Wyczyść nadpisanie
|
||||
{{ ui_icon('refresh') }} Wyczyść nadpisanie
|
||||
</button>
|
||||
<button type="button"
|
||||
class="btn btn-outline-light btn-sm use-default"
|
||||
data-target="color_{{ c.id }}">
|
||||
🎯 Ustaw kolor domyślny
|
||||
{{ ui_icon('target') }} Ustaw kolor domyślny
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -124,7 +124,7 @@
|
||||
|
||||
<div class="card bg-dark text-white mb-4">
|
||||
<div class="card-header border-0">
|
||||
<strong>🔐 Bezpieczeństwo</strong>
|
||||
<strong>{{ ui_icon('shield-lock') }} Bezpieczeństwo</strong>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<label for="max_login_attempts" class="form-label">Limit błędnych logowań (hasło główne)</label>
|
||||
@@ -145,8 +145,8 @@
|
||||
|
||||
<div class="mt-4 d-flex">
|
||||
<div class="btn-group" role="group" aria-label="Akcje ustawień">
|
||||
<button type="submit" class="btn btn-outline-light">💾 Zapisz</button>
|
||||
<a href="{{ url_for('admin_panel') }}" class="btn btn-outline-light">❌ Anuluj</a>
|
||||
<button type="submit" class="btn btn-outline-light">{{ ui_icon('device-floppy') }} Zapisz</button>
|
||||
<a href="{{ url_for('admin_panel') }}" class="btn btn-outline-light">{{ ui_icon('x') }} Anuluj</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center flex-wrap mb-4 gap-2">
|
||||
<div>
|
||||
<h2 class="mb-2">🧩 Szablony list</h2>
|
||||
<h2 class="mb-2">{{ ui_icon('template') }} Szablony list</h2>
|
||||
<p class="text-secondary mb-0">Szablony są niezależne od zwykłych list i mogą służyć do szybkiego tworzenia nowych list.</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -11,7 +11,7 @@
|
||||
<div class="row g-4">
|
||||
<div class="col-lg-5">
|
||||
<div class="card bg-dark text-white mb-4"><div class="card-body">
|
||||
<h5 class="mb-3">➕ Nowy szablon ręcznie</h5>
|
||||
<h5 class="mb-3">{{ ui_icon('plus') }} Nowy szablon ręcznie</h5>
|
||||
<form method="post" class="stack-form">
|
||||
<input type="hidden" name="action" value="create_manual">
|
||||
<div class="mb-3"><label class="form-label">Nazwa</label><input type="text" name="name" class="form-control" required></div>
|
||||
@@ -21,7 +21,7 @@
|
||||
</form>
|
||||
</div></div>
|
||||
<div class="card bg-dark text-white"><div class="card-body">
|
||||
<h5 class="mb-3">📋 Utwórz z istniejącej listy</h5>
|
||||
<h5 class="mb-3">{{ ui_icon('clipboard') }} Utwórz z istniejącej listy</h5>
|
||||
<form method="post" class="stack-form">
|
||||
<input type="hidden" name="action" value="create_from_list">
|
||||
<div class="mb-3"><label class="form-label">Lista źródłowa</label><select name="source_list_id" class="form-select" required>{% for l in source_lists %}<option value="{{ l.id }}">#{{ l.id }} — {{ l.title }}</option>{% endfor %}</select></div>
|
||||
@@ -46,9 +46,9 @@
|
||||
<td>{{ template.creator.username if template.creator else '—' }}</td>
|
||||
<td>
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<form method="post"><input type="hidden" name="action" value="instantiate"><input type="hidden" name="template_id" value="{{ template.id }}"><button class="btn btn-sm btn-outline-primary" type="submit">➕ Utwórz listę</button></form>
|
||||
<form method="post"><input type="hidden" name="action" value="toggle"><input type="hidden" name="template_id" value="{{ template.id }}"><button class="btn btn-sm btn-outline-warning" type="submit">{% if template.is_active %}⏸ Wyłącz{% else %}▶ Włącz{% endif %}</button></form>
|
||||
<form method="post" onsubmit="return confirm('Usunąć szablon?')"><input type="hidden" name="action" value="delete"><input type="hidden" name="template_id" value="{{ template.id }}"><button class="btn btn-sm btn-outline-danger" type="submit">🗑 Usuń</button></form>
|
||||
<form method="post"><input type="hidden" name="action" value="instantiate"><input type="hidden" name="template_id" value="{{ template.id }}"><button class="btn btn-sm btn-outline-primary" type="submit">{{ ui_icon('plus') }} Utwórz listę</button></form>
|
||||
<form method="post"><input type="hidden" name="action" value="toggle"><input type="hidden" name="template_id" value="{{ template.id }}"><button class="btn btn-sm btn-outline-warning" type="submit">{% if template.is_active %}{{ ui_icon('player-pause') }} Wyłącz{% else %}{{ ui_icon('player-play') }} Włącz{% endif %}</button></form>
|
||||
<form method="post" onsubmit="return confirm('Usunąć szablon?')"><input type="hidden" name="action" value="delete"><input type="hidden" name="template_id" value="{{ template.id }}"><button class="btn btn-sm btn-outline-danger" type="submit">{{ ui_icon('trash') }} Usuń</button></form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
{% block content %}
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center flex-wrap mb-4">
|
||||
<h2 class="mb-2">👥 Zarządzanie użytkownikami</h2>
|
||||
<h2 class="mb-2">{{ ui_icon('users') }} Zarządzanie użytkownikami</h2>
|
||||
<a href="{{ url_for('admin_panel') }}" class="btn btn-outline-secondary">← Powrót do panelu</a>
|
||||
</div>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<!-- Formularz dodawania nowego użytkownika -->
|
||||
<div class="card bg-secondary bg-opacity-10 text-white mb-4">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title mb-3">➕ Dodaj nowego użytkownika</h5>
|
||||
<h5 class="card-title mb-3">{{ ui_icon('plus') }} Dodaj nowego użytkownika</h5>
|
||||
<form method="post" action="{{ url_for('add_user') }}">
|
||||
<div class="row g-3 align-items-end">
|
||||
<div class="col-md-4">
|
||||
@@ -28,7 +28,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 d-grid">
|
||||
<button type="submit" class="btn btn-outline-light">➕ Dodaj użytkownika</button>
|
||||
<button type="submit" class="btn btn-outline-light">{{ ui_icon('plus') }} Dodaj użytkownika</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -69,22 +69,22 @@
|
||||
<td>
|
||||
<button class="btn btn-sm btn-outline-light me-1" data-bs-toggle="modal"
|
||||
data-bs-target="#resetPasswordModal" data-user-id="{{ user.id }}" data-username="{{ user.username }}">
|
||||
🔑 Ustaw hasło
|
||||
{{ ui_icon('login') }} Ustaw hasło
|
||||
</button>
|
||||
{% if not user.is_admin %}
|
||||
<a href="/admin/promote_user/{{ user.id }}" class="btn btn-sm btn-outline-light">⬆️ Ustaw admina</a>
|
||||
<a href="/admin/promote_user/{{ user.id }}" class="btn btn-sm btn-outline-light">{{ ui_icon('arrow-up') }} Ustaw admina</a>
|
||||
{% else %}
|
||||
<a href="/admin/demote_user/{{ user.id }}" class="btn btn-sm btn-outline-light">⬇️ Usuń admina</a>
|
||||
<a href="/admin/demote_user/{{ user.id }}" class="btn btn-sm btn-outline-light">{{ ui_icon('arrow-down') }} Usuń admina</a>
|
||||
{% endif %}
|
||||
{% if user.username == 'admin' %}
|
||||
<a class="btn btn-sm btn-outline-light me-1 disabled" aria-disabled="true" tabindex="-1"
|
||||
title="Nie można usunąć konta administratora-głównego.">
|
||||
🗑️ Usuń
|
||||
{{ ui_icon('trash') }} Usuń
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="/admin/delete_user/{{ user.id }}" class="btn btn-sm btn-outline-light me-1"
|
||||
onclick="return confirm('Czy na pewno chcesz usunąć użytkownika {{ user.username }}?\\n\\nWszystkie jego listy zostaną przeniesione na administratora.')">
|
||||
🗑️ Usuń
|
||||
{{ ui_icon('trash') }} Usuń
|
||||
</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
@@ -113,7 +113,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer border-0">
|
||||
<button type="submit" class="btn btn-sm btn-outline-light w-100">💾 Zapisz nowe hasło</button>
|
||||
<button type="submit" class="btn btn-sm btn-outline-light w-100">{{ ui_icon('device-floppy') }} Zapisz nowe hasło</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="pl">
|
||||
<html lang="pl" data-ui-scale="{{ UI_SCALE }}">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||||
@@ -18,6 +18,7 @@
|
||||
<link rel="stylesheet" href="{{ static_asset_url('static_bp.serve_css', 'split/layout.css') }}">
|
||||
<link rel="stylesheet" href="{{ static_asset_url('static_bp.serve_css', 'split/responsive.css') }}">
|
||||
<link rel="stylesheet" href="{{ static_asset_url('static_bp.serve_css', 'split/pages.css') }}">
|
||||
<link rel="stylesheet" href="{{ static_asset_url('static_bp.serve_css', 'split/ui_clean.css') }}">
|
||||
|
||||
{% set hide_login_ui = request.path == '/system-auth' %}
|
||||
{% set is_live_list_view = request.endpoint in ['view_list', 'shared_list'] %}
|
||||
@@ -42,13 +43,14 @@
|
||||
</head>
|
||||
|
||||
<body class="app-body endpoint-{{ (request.endpoint or 'unknown')|replace('.', '-') }}{% if current_user.is_authenticated %} is-authenticated{% endif %}{% if request.path.startswith('/admin') %} is-admin-area{% endif %}">
|
||||
{{ TABLER_SPRITE }}
|
||||
<div class="app-backdrop"></div>
|
||||
|
||||
<header class="app-header sticky-top">
|
||||
<nav class="navbar navbar-expand-lg app-navbar">
|
||||
<div class="container-xxl px-3 px-lg-4 gap-2">
|
||||
<a class="navbar-brand app-brand" href="{{ url_for('main_page') }}">
|
||||
<span class="app-brand__icon">🛒</span>
|
||||
<span class="app-brand__icon">{{ ui_icon('shopping-cart') }}</span>
|
||||
<span>
|
||||
<span class="app-brand__title">Lista</span>
|
||||
<span class="app-brand__accent">Zakupów</span>
|
||||
@@ -107,13 +109,13 @@
|
||||
{% if not is_blocked and not hide_login_ui %}
|
||||
{% if current_user.is_authenticated %}
|
||||
{% if current_user.is_admin %}
|
||||
<a href="{{ url_for('admin_panel') }}" class="dropdown-item app-mobile-menu__item">⚙️ <span>Panel</span></a>
|
||||
<a href="{{ url_for('admin_panel') }}" class="dropdown-item app-mobile-menu__item">{{ ui_icon('settings') }} <span>Panel</span></a>
|
||||
{% endif %}
|
||||
<a href="{{ url_for('expenses') }}" class="dropdown-item app-mobile-menu__item">📊 <span>Wydatki</span></a>
|
||||
<a href="{{ url_for('my_templates') }}" class="dropdown-item app-mobile-menu__item">🧩 <span>Szablony</span></a>
|
||||
<a href="{{ url_for('logout') }}" class="dropdown-item app-mobile-menu__item">🚪 <span>Wyloguj</span></a>
|
||||
<a href="{{ url_for('expenses') }}" class="dropdown-item app-mobile-menu__item">{{ ui_icon('chart-bar') }} <span>Wydatki</span></a>
|
||||
<a href="{{ url_for('my_templates') }}" class="dropdown-item app-mobile-menu__item">{{ ui_icon('template') }} <span>Szablony</span></a>
|
||||
<a href="{{ url_for('logout') }}" class="dropdown-item app-mobile-menu__item">{{ ui_icon('logout') }} <span>Wyloguj</span></a>
|
||||
{% else %}
|
||||
<a href="{{ url_for('login') }}" class="dropdown-item app-mobile-menu__item">🔑 <span>Zaloguj</span></a>
|
||||
<a href="{{ url_for('login') }}" class="dropdown-item app-mobile-menu__item">{{ ui_icon('login') }} <span>Zaloguj</span></a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -124,13 +126,13 @@
|
||||
{% if not is_blocked and not hide_login_ui %}
|
||||
{% if current_user.is_authenticated %}
|
||||
{% if current_user.is_admin %}
|
||||
<a href="{{ url_for('admin_panel') }}" class="btn btn-outline-light btn-sm app-nav-action">⚙️ <span>Panel</span></a>
|
||||
<a href="{{ url_for('admin_panel') }}" class="btn btn-outline-light btn-sm app-nav-action">{{ ui_icon('settings') }} <span>Panel</span></a>
|
||||
{% endif %}
|
||||
<a href="{{ url_for('expenses') }}" class="btn btn-outline-light btn-sm app-nav-action">📊 <span>Wydatki</span></a>
|
||||
<a href="{{ url_for('my_templates') }}" class="btn btn-outline-light btn-sm app-nav-action">🧩 <span>Szablony</span></a>
|
||||
<a href="{{ url_for('logout') }}" class="btn btn-outline-light btn-sm app-nav-action">🚪 <span>Wyloguj</span></a>
|
||||
<a href="{{ url_for('expenses') }}" class="btn btn-outline-light btn-sm app-nav-action">{{ ui_icon('chart-bar') }} <span>Wydatki</span></a>
|
||||
<a href="{{ url_for('my_templates') }}" class="btn btn-outline-light btn-sm app-nav-action">{{ ui_icon('template') }} <span>Szablony</span></a>
|
||||
<a href="{{ url_for('logout') }}" class="btn btn-outline-light btn-sm app-nav-action">{{ ui_icon('logout') }} <span>Wyloguj</span></a>
|
||||
{% else %}
|
||||
<a href="{{ url_for('login') }}" class="btn btn-success btn-sm app-nav-action">🔑 <span>Zaloguj</span></a>
|
||||
<a href="{{ url_for('login') }}" class="btn btn-success btn-sm app-nav-action">{{ ui_icon('login') }} <span>Zaloguj</span></a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -163,6 +165,7 @@
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="{{ static_asset_url('static_bp.serve_js', 'ui_icons.js') }}"></script>
|
||||
<script src="{{ static_asset_url('static_bp.serve_js_lib', 'bootstrap.bundle.min.js') }}"></script>
|
||||
|
||||
{% if not is_blocked %}
|
||||
|
||||
@@ -15,31 +15,31 @@
|
||||
|
||||
<!-- Nazwa listy -->
|
||||
<div class="mb-3">
|
||||
<label for="title" class="form-label">📝 Nazwa listy</label>
|
||||
<label for="title" class="form-label">{{ ui_icon('notes') }} Nazwa listy</label>
|
||||
<input type="text" class="form-control bg-dark text-white border-secondary ui-consistent-input" id="title" name="title"
|
||||
value="{{ list.title }}" required>
|
||||
</div>
|
||||
|
||||
<!-- Statusy listy -->
|
||||
<div class="mb-4">
|
||||
<label class="form-label">⚙️ Statusy listy</label>
|
||||
<label class="form-label">{{ ui_icon('settings') }} Statusy listy</label>
|
||||
<div class="switch-grid">
|
||||
<div class="form-check form-switch form-check-spaced form-switch-compact app-switch">
|
||||
<input class="form-check-input" type="checkbox" id="public" name="is_public" {% if list.is_public
|
||||
%}checked{% endif %}>
|
||||
<label class="form-check-label" for="public">🌐 Publiczna (czyli mogą zobaczyć goście)</label>
|
||||
<label class="form-check-label" for="public">{{ ui_icon('world') }} Publiczna (czyli mogą zobaczyć goście)</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check form-switch form-check-spaced form-switch-compact app-switch">
|
||||
<input class="form-check-input" type="checkbox" id="temporary" name="is_temporary" {% if list.is_temporary
|
||||
%}checked{% endif %}>
|
||||
<label class="form-check-label" for="temporary">⏳ Tymczasowa (ustaw date wygasania)</label>
|
||||
<label class="form-check-label" for="temporary">{{ ui_icon('hourglass') }} Tymczasowa (ustaw date wygasania)</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check form-switch form-check-spaced form-switch-compact app-switch">
|
||||
<input class="form-check-input" type="checkbox" id="archived" name="is_archived" {% if list.is_archived
|
||||
%}checked{% endif %}>
|
||||
<label class="form-check-label" for="archived">📦 Archiwalna</label>
|
||||
<label class="form-check-label" for="archived">{{ ui_icon('box') }} Archiwalna</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -47,12 +47,12 @@
|
||||
<!-- Data/Godzina wygaśnięcia -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6">
|
||||
<label for="expires_date" class="form-label">📅 Data wygaśnięcia</label>
|
||||
<label for="expires_date" class="form-label">{{ ui_icon('calendar') }} Data wygaśnięcia</label>
|
||||
<input type="date" class="form-control bg-dark text-white border-secondary ui-consistent-input" id="expires_date"
|
||||
name="expires_date" value="{{ list.expires_at.strftime('%Y-%m-%d') if list.expires_at else '' }}">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="expires_time" class="form-label">⏰ Godzina wygaśnięcia</label>
|
||||
<label for="expires_time" class="form-label">{{ ui_icon('clock') }} Godzina wygaśnięcia</label>
|
||||
<input type="time" class="form-control bg-dark text-white border-secondary ui-consistent-input" id="expires_time"
|
||||
name="expires_time" value="{{ list.expires_at.strftime('%H:%M') if list.expires_at else '' }}">
|
||||
</div>
|
||||
@@ -61,7 +61,7 @@
|
||||
<!-- Utworzono / Zmień miesiąc -->
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">📆 Utworzono:</label>
|
||||
<label class="form-label">{{ ui_icon('calendar') }} Utworzono:</label>
|
||||
<p class="form-control-plaintext text-white">
|
||||
<span class="badge rounded-pill bg-success rounded-pill text-dark ms-1">
|
||||
{{ list.created_at.strftime('%Y-%m-%d') }}
|
||||
@@ -69,7 +69,7 @@
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">📁 Przenieś do miesiąca (format: rok-miesiąc np 2026-01)</label>
|
||||
<label class="form-label">{{ ui_icon('folder') }} Przenieś do miesiąca (format: rok-miesiąc np 2026-01)</label>
|
||||
<input type="month" id="move_to_month" name="move_to_month"
|
||||
class="form-control bg-dark text-white border-secondary ui-consistent-input">
|
||||
</div>
|
||||
@@ -77,7 +77,7 @@
|
||||
|
||||
<!-- Kategorie -->
|
||||
<div class="mb-4">
|
||||
<label for="categories" class="form-label">🏷️ Kategorie</label>
|
||||
<label for="categories" class="form-label">{{ ui_icon('tag') }} Kategorie</label>
|
||||
<select id="categories" name="categories"
|
||||
class="form-select tom-dark bg-dark text-white border-secondary ui-consistent-input">
|
||||
<option value="">– brak –</option>
|
||||
@@ -90,8 +90,8 @@
|
||||
</div>
|
||||
<!-- Przyciski -->
|
||||
<div class="btn-group mt-4" role="group">
|
||||
<button type="submit" class="btn btn-sm btn-outline-light">💾 Zapisz</button>
|
||||
<a href="{{ url_for('main_page') }}" class="btn btn-sm btn-outline-light">❌ Anuluj</a>
|
||||
<button type="submit" class="btn btn-sm btn-outline-light">{{ ui_icon('device-floppy') }} Zapisz</button>
|
||||
<a href="{{ url_for('main_page') }}" class="btn btn-sm btn-outline-light">{{ ui_icon('x') }} Anuluj</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -103,7 +103,7 @@
|
||||
</datalist>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">👥 Użytkownicy z dostępem</label>
|
||||
<label class="form-label">{{ ui_icon('users') }} Użytkownicy z dostępem</label>
|
||||
|
||||
<div class="access-editor border rounded p-2 bg-dark" data-post-url="{{ request.path }}"
|
||||
data-suggest-url="{{ url_for('edit_my_list_suggestions', list_id=list.id) }}" data-next="{{ request.path }}"
|
||||
@@ -126,7 +126,7 @@
|
||||
<div class="input-group input-group-sm">
|
||||
<input type="text" class="access-input form-control form-control-sm bg-dark text-white border-secondary"
|
||||
placeholder="Dodaj użytkownika (wiele: przecinki/enter)" list="userHintsOwner" autocomplete="off" aria-label="Dodaj użytkowników">
|
||||
<button type="button" class="access-add btn btn-sm btn-outline-light">💾 Zapisz dostęp</button>
|
||||
<button type="button" class="access-add btn btn-sm btn-outline-light">{{ ui_icon('device-floppy') }} Zapisz dostęp</button>
|
||||
</div>
|
||||
<div class="text-secondary small mt-1">Kliknij token, aby odebrać dostęp. Możesz wpisać kilka loginów oddzielonych przecinkiem.</div>
|
||||
</div>
|
||||
@@ -147,10 +147,10 @@
|
||||
</a>
|
||||
|
||||
<div class="card-body text-center p-2 small">
|
||||
<div class="text-truncate fw-semibold" title="{{ r.filename }}">📄 {{ r.filename }}</div>
|
||||
<div>📅 {{ r.uploaded_at.strftime('%Y-%m-%d %H:%M') }}</div>
|
||||
<div class="text-truncate fw-semibold" title="{{ r.filename }}">{{ ui_icon('file-text') }} {{ r.filename }}</div>
|
||||
<div>{{ ui_icon('calendar') }} {{ r.uploaded_at.strftime('%Y-%m-%d %H:%M') }}</div>
|
||||
<div>
|
||||
💾
|
||||
{{ ui_icon('device-floppy') }}
|
||||
{% if r.filesize and r.filesize >= 1024 * 1024 %}
|
||||
{{ (r.filesize / 1024 / 1024) | round(2) }} MB
|
||||
{% elif r.filesize %}
|
||||
@@ -166,19 +166,19 @@
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-dark w-100 text-start">
|
||||
<li>
|
||||
<a class="dropdown-item" href="{{ url_for('rotate_receipt_user', receipt_id=r.id) }}">🔄 Obróć o 90°</a>
|
||||
<a class="dropdown-item" href="{{ url_for('rotate_receipt_user', receipt_id=r.id) }}">{{ ui_icon('refresh') }} Obróć o 90°</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item" href="#" data-bs-toggle="modal" data-bs-target="#userCropModal"
|
||||
data-img-src="{{ url_for('uploaded_file', filename=r.filename) }}" data-receipt-id="{{ r.id }}"
|
||||
data-crop-endpoint="{{ url_for('crop_receipt_user') }}">
|
||||
✂️ Przytnij
|
||||
{{ ui_icon('scissors') }} Przytnij
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item text-danger" href="{{ url_for('delete_receipt_user', receipt_id=r.id) }}"
|
||||
onclick="return confirm('Na pewno usunąć ten paragon?')">
|
||||
🗑️ Usuń
|
||||
{{ ui_icon('trash') }} Usuń
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -194,7 +194,7 @@
|
||||
<!-- Trigger przycisk -->
|
||||
<div class="btn-group mt-4" role="group">
|
||||
<button type="button" class="btn btn-sm btn-outline-danger" data-bs-toggle="modal" data-bs-target="#deleteModal">
|
||||
🗑️ Usuń tę listę
|
||||
{{ ui_icon('trash') }} Usuń tę listę
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -215,7 +215,7 @@
|
||||
<div class="modal-footer justify-content-between">
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-sm btn-outline-light" data-bs-dismiss="modal">Anuluj</button>
|
||||
<button id="confirm-delete-btn" class="btn btn-sm btn-outline-light" disabled>🗑️ Usuń</button>
|
||||
<button id="confirm-delete-btn" class="btn btn-sm btn-outline-light" disabled>{{ ui_icon('trash') }} Usuń</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -228,7 +228,7 @@
|
||||
<div class="modal-dialog modal-xl modal-dialog-centered">
|
||||
<div class="modal-content bg-dark text-white">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">✂️ Przycinanie paragonu</h5>
|
||||
<h5 class="modal-title">{{ ui_icon('scissors') }} Przycinanie paragonu</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div style="position: relative; width: 100%; height: 75vh;">
|
||||
@@ -236,12 +236,12 @@
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-sm btn-outline-light" data-bs-dismiss="modal">❌ Anuluj</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-light" id="userSaveCrop">💾 Zapisz</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-light" data-bs-dismiss="modal">{{ ui_icon('x') }} Anuluj</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-light" id="userSaveCrop">{{ ui_icon('device-floppy') }} Zapisz</button>
|
||||
</div>
|
||||
<div id="userCropLoading" class="position-absolute top-50 start-50 translate-middle text-center d-none">
|
||||
<div class="spinner-border text-light" role="status"></div>
|
||||
<div class="mt-2 text-light">⏳ Pracuję...</div>
|
||||
<div class="mt-2 text-light">{{ ui_icon('hourglass') }} Pracuję...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||