diff --git a/README.md b/README.md index b5de1f2..284ef41 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,12 @@ # Aplikacja List Zakupów -Prosta aplikacja webowa do zarządzania listami zakupów z obsługą użytkowników, OCR paragonów, statystykami i trybem współdzielenia. - -## Główne funkcje - -- Logowanie i zarządzanie użytkownikami (admin/user) -- Tworzenie list zakupów z pozycjami i ilością -- Wgrywanie paragonów (podstawowa obsługa OCR) -- Archiwizacja i udostępnianie list (publiczne/prywatne) -- Statystyki wydatków z podziałem na okresy, statystyki dla użytkowników -- Panel administracyjny (statystyki, produkty, paragony, zarządzanie, użytkowmicy) -- Tokeny API administratora i endpoint do pobierania ostatnich wydatków -- Ujednolicony UI formularzy, tabel i przycisków oraz drobne usprawnienia UX +Aplikacja webowa do zarządzania listami zakupów z obsługą użytkowników, OCR paragonów, statystykami i trybem współdzielenia. ## Wymagania -- Python 3.9+ -- Docker (opcjonalnie dla produkcji) +- Docker -## Instalacja lokalna (deweloperska) +## Instalacja 1. Sklonuj repozytorium: @@ -27,25 +15,7 @@ Prosta aplikacja webowa do zarządzania listami zakupów z obsługą użytkownik cd lista_zakupowa_live ``` -2. Utwórz i uzupełnij plik `.env` (zobacz `.env example`). - -3. Utwórz środowisko i zainstaluj zależności: - - ```bash - python -m venv venv - source venv/bin/activate - pip install -r requirements.txt - ``` - -4. Uruchom aplikację: - - ```bash - flask --app app.py run - ``` - -## Deploy z Docker Compose - stack (zalecana) - -1. Skonfiguruj `.env`. +1. Skonfiguruj `.env` z pliku `.env.example` 2.1 Uruchom: (pgsql) @@ -74,7 +44,7 @@ Prosta aplikacja webowa do zarządzania listami zakupów z obsługą użytkownik Aplikacja będzie dostępna pod `http://localhost:8000`. -## Domyślne dane logowania +## Domyślne dane logowania - konfigurowane z pliku `.env` - Główne hasło systemowe: `admin` - Admin: `admin` / `admin123` @@ -87,14 +57,14 @@ Ustaw `DB_ENGINE` oraz odpowiednie zmienne w `.env`: Przykład dla PostgreSQL: -```env -DB_ENGINE=pgsql -DB_HOST=db -DB_PORT=5432 -DB_NAME=myapp -DB_USER=user -DB_PASSWORD=pass -``` + ```bash + DB_ENGINE=pgsql + DB_HOST=db + DB_PORT=5432 + DB_NAME=myapp + DB_USER=user + DB_PASSWORD=pass + ``` ## CLI @@ -103,17 +73,19 @@ Opis komend administracyjnych znajduje sie w pliku `KOMENDY_CLI.txt`. Komendy CLI uruchamiamy wewnatrz kontenera aplikacji. Najwygodniej wejsc do katalogu projektu i wykonac polecenie przez `docker compose exec app`. Przykladowe: -```bash -cd /opt/lista_zakupowa_live -docker compose -f docker/compose.yml exec app sh -c 'flask lists copy-schedule --source-list-id 393 --when "2026-03-22 11:30" --owner admin' -``` + + ```bash + cd /opt/lista_zakupowa_live + docker compose -f docker/compose.yml exec app sh -c 'flask lists copy-schedule --source-list-id 393 --when "2026-03-22 11:30" --owner admin' + ``` Dodatkowe przyklady: -```bash -docker compose -f docker/compose.yml exec app sh -c 'flask lists move --list-id 393 --when "2026-03-23 08:00"' -docker compose -f docker/compose.yml exec app sh -c 'flask lists rename --list-id 393 --title "Zakupy na poniedzialek"' + ```bash + docker compose -f docker/compose.yml exec app sh -c 'flask lists move --list-id 393 --when "2026-03-23 08:00"' -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"' -``` + docker compose -f docker/compose.yml exec app sh -c 'flask lists rename --list-id 393 --title "Zakupy na poniedzialek"' + + 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"' + ``` diff --git a/deploy_docker.sh b/deploy_docker.sh index 1505184..1ba2c61 100644 --- a/deploy_docker.sh +++ b/deploy_docker.sh @@ -75,12 +75,12 @@ echo "Zapisuję hash commita do version.txt..." git rev-parse --short HEAD > version.txt if [[ "$ACTION" == "restart" ]]; then - echo "Restart kontenerów bez przebudowy obrazu..." + echo "Odtwarzam kontenery bez przebudowy obrazu..." if [[ "$PROFILE" == "sqlite" ]]; then - docker compose -f "$COMPOSE_FILE" restart + docker compose -f "$COMPOSE_FILE" up -d --force-recreate else - DB_ENGINE="$PROFILE" docker compose -f "$COMPOSE_FILE" --profile "$PROFILE" restart + DB_ENGINE="$PROFILE" docker compose -f "$COMPOSE_FILE" --profile "$PROFILE" up -d --force-recreate fi echo "Gotowe! Wersja aplikacji: $(cat version.txt)" @@ -97,7 +97,7 @@ fi echo "Pobieram najnowszy kod z repozytorium..." git pull -echo "Uruchamiam kontenery..." +echo "Uruchamiam kontenery z przebudową obrazu..." if [[ "$PROFILE" == "sqlite" ]]; then docker compose -f "$COMPOSE_FILE" up -d --build else diff --git a/shopping_app/routes_main.py b/shopping_app/routes_main.py index 859ed83..3218de1 100644 --- a/shopping_app/routes_main.py +++ b/shopping_app/routes_main.py @@ -207,11 +207,9 @@ def main_page(): @app.route("/system-auth", methods=["GET", "POST"]) def system_auth(): - if ( - current_user.is_authenticated - or request.cookies.get("authorized") == AUTHORIZED_COOKIE_VALUE - ): - flash("Jesteś już zalogowany lub autoryzowany.", "info") + + if request.cookies.get("authorized") == AUTHORIZED_COOKIE_VALUE: + flash("Jesteś już autoryzowany.", "info") return redirect(url_for("main_page")) ip = request.access_route[0] diff --git a/shopping_app/static/css/style.css b/shopping_app/static/css/style.css index ef953fe..3e5b75a 100644 --- a/shopping_app/static/css/style.css +++ b/shopping_app/static/css/style.css @@ -1571,15 +1571,15 @@ hr { min-height: 46px; } - .input-group { + .input-group:not(.ui-password-group) { flex-wrap: wrap; gap: 0.55rem; } - .input-group > .form-control, - .input-group > .form-select, - .input-group > .btn, - .input-group > .input-group-text { + .input-group:not(.ui-password-group) > .form-control, + .input-group:not(.ui-password-group) > .form-select, + .input-group:not(.ui-password-group) > .btn, + .input-group:not(.ui-password-group) > .input-group-text { width: 100% !important; flex: 1 1 100% !important; border-radius: 14px !important; @@ -2215,9 +2215,6 @@ form[data-unsaved-warning="true"].is-dirty::after { font-weight: 700; } -.ui-password-toggle { - min-width: 52px; -} .ui-password-toggle.is-active { background: rgba(255,255,255,0.1); @@ -3492,19 +3489,6 @@ input[type="checkbox"].form-check-input, white-space: nowrap; } -.ui-password-group { - flex-wrap: nowrap; -} - -.ui-password-group > .form-control { - min-width: 0; -} - -.ui-password-group > .ui-password-toggle { - flex: 0 0 auto; - width: auto; - min-width: 3rem; -} @media (max-width: 991.98px) { .app-navbar__actions { @@ -4681,263 +4665,6 @@ body.sorting-active .shopping-item-row .large-checkbox { } -/* Login/auth password field fixes */ -.endpoint-login form .form-control, -.endpoint-system_auth form .form-control { - min-height: 42px; - border-radius: 14px !important; -} - -.endpoint-login .ui-password-group, -.endpoint-system_auth .ui-password-group { - display: flex !important; - flex-wrap: nowrap !important; - align-items: stretch !important; - gap: 0 !important; -} - -.endpoint-login .ui-password-group > .form-control, -.endpoint-system_auth .ui-password-group > .form-control { - width: auto !important; - flex: 1 1 auto !important; - max-width: none !important; - border-radius: 14px 0 0 14px !important; - border-right: 0 !important; -} - -.endpoint-login .ui-password-group > .ui-password-toggle, -.endpoint-system_auth .ui-password-group > .ui-password-toggle { - appearance: none; - -webkit-appearance: none; - display: inline-flex !important; - align-items: center; - justify-content: center; - flex: 0 0 46px !important; - width: 46px !important; - min-width: 46px !important; - padding: 0 !important; - margin: 0 !important; - color: rgba(255,255,255,.78); - background: #1f2738 !important; - border: 1px solid var(--bs-border-color, #495057) !important; - border-left: 0 !important; - border-radius: 0 14px 14px 0 !important; - outline: none !important; - box-shadow: none !important; - line-height: 1; - font-size: 1rem; -} - -.endpoint-login .ui-password-group > .ui-password-toggle:hover, -.endpoint-login .ui-password-group > .ui-password-toggle:focus, -.endpoint-system_auth .ui-password-group > .ui-password-toggle:hover, -.endpoint-system_auth .ui-password-group > .ui-password-toggle:focus { - color: #fff; - background: #253047 !important; - outline: none !important; - box-shadow: none !important; -} - -.endpoint-login .ui-password-group > .ui-password-toggle.is-active, -.endpoint-system_auth .ui-password-group > .ui-password-toggle.is-active { - background: #2a3550 !important; -} - -@media (max-width: 575.98px) { - .endpoint-login .ui-password-group, - .endpoint-system_auth .ui-password-group { - width: 100%; - } - - .endpoint-login .ui-password-group > .form-control, - .endpoint-system_auth .ui-password-group > .form-control { - width: auto !important; - flex: 1 1 auto !important; - } - - .endpoint-login .ui-password-group > .ui-password-toggle, - .endpoint-system_auth .ui-password-group > .ui-password-toggle { - flex: 0 0 44px !important; - width: 44px !important; - min-width: 44px !important; - } -} - -/* final hotfix 2026-03-17: list/share parity, pending spinner, auth inputs */ -.shopping-item-row { - position: relative; -} - -.shopping-item-spinner { - position: absolute; - top: .7rem; - right: .7rem; - z-index: 2; - pointer-events: none; -} - -.shopping-item-row.is-pending .shopping-item-actions { - opacity: .72; -} - -.shopping-item-actions { - display: inline-flex; - flex: 0 0 auto; - flex-wrap: nowrap; - align-items: center; - justify-content: flex-end; - gap: .35rem; - min-height: 2.35rem; -} - -.shopping-action-btn { - display: inline-flex !important; - align-items: center; - justify-content: center; - width: 2.35rem; - height: 2.35rem; - min-width: 2.35rem; - padding: 0 !important; - line-height: 1; - border-radius: .7rem !important; - flex: 0 0 2.35rem; -} - -.shopping-action-btn--wide { - width: auto; - min-width: 5.9rem; - padding: 0 .8rem !important; - flex: 0 0 auto; -} -.shopping-action-btn--countdown { - width: auto !important; - min-width: 3.2rem !important; - padding: 0 .65rem !important; - font-variant-numeric: tabular-nums; - opacity: 1 !important; -} - - -.endpoint-list_share .shopping-item-actions, -.endpoint-shared_list .shopping-item-actions, -.endpoint-list .shopping-item-actions { - min-height: 2.35rem; -} - -.endpoint-list_share .shopping-action-btn, -.endpoint-shared_list .shopping-action-btn, -.endpoint-list .shopping-action-btn { - width: 2.35rem; - height: 2.35rem; - min-width: 2.35rem; - border-radius: .7rem !important; -} - -.endpoint-list_share .shopping-action-btn--wide, -.endpoint-shared_list .shopping-action-btn--wide, -.endpoint-list .shopping-action-btn--wide { - width: auto; - min-width: 5.9rem; -} -.endpoint-list_share .shopping-action-btn--countdown, -.endpoint-shared_list .shopping-action-btn--countdown, -.endpoint-list .shopping-action-btn--countdown { - width: auto; - min-width: 3.2rem; -} - - -@media (max-width: 575.98px) { - .shopping-item-spinner { - top: .55rem; - right: .55rem; - } - - .shopping-action-btn, - .endpoint-list_share .shopping-action-btn, - .endpoint-shared_list .shopping-action-btn, - .endpoint-list .shopping-action-btn { - width: 2.15rem; - height: 2.15rem; - min-width: 2.15rem; - border-radius: .65rem !important; - } - - .shopping-action-btn--wide, - .endpoint-list_share .shopping-action-btn--wide, - .endpoint-shared_list .shopping-action-btn--wide, - .endpoint-list .shopping-action-btn--wide { - width: auto; - min-width: 5.4rem; - padding: 0 .72rem !important; - } - - .shopping-action-btn--countdown, - .endpoint-list_share .shopping-action-btn--countdown, - .endpoint-shared_list .shopping-action-btn--countdown, - .endpoint-list .shopping-action-btn--countdown { - min-width: 3rem; - padding: 0 .55rem !important; - } -} - -.endpoint-login .card .form-control, -.endpoint-system_auth .card .form-control, -.endpoint-user_management .ui-password-group > .form-control, -.endpoint-user_management .modal .ui-password-group > .form-control { - min-height: 42px; - border-radius: 14px !important; -} - -.endpoint-user_management .ui-password-group, -.endpoint-user_management .modal .ui-password-group { - display: flex !important; - flex-wrap: nowrap !important; - align-items: stretch !important; - gap: 0 !important; -} - -.endpoint-user_management .ui-password-group > .form-control, -.endpoint-user_management .modal .ui-password-group > .form-control { - flex: 1 1 auto !important; - width: auto !important; - max-width: none !important; - border-radius: 14px 0 0 14px !important; - border-right: 0 !important; -} - -.endpoint-user_management .ui-password-group > .ui-password-toggle, -.endpoint-user_management .modal .ui-password-group > .ui-password-toggle { - appearance: none; - -webkit-appearance: none; - display: inline-flex !important; - align-items: center; - justify-content: center; - flex: 0 0 46px !important; - width: 46px !important; - min-width: 46px !important; - padding: 0 !important; - margin: 0 !important; - color: rgba(255,255,255,.78); - background: #1f2738 !important; - border: 1px solid var(--bs-border-color, #495057) !important; - border-left: 0 !important; - border-radius: 0 14px 14px 0 !important; - outline: none !important; - box-shadow: none !important; - line-height: 1; - font-size: 1rem; -} - -.endpoint-user_management .ui-password-group > .ui-password-toggle:hover, -.endpoint-user_management .ui-password-group > .ui-password-toggle:focus, -.endpoint-user_management .modal .ui-password-group > .ui-password-toggle:hover, -.endpoint-user_management .modal .ui-password-group > .ui-password-toggle:focus { - color: #fff; - background: #253047 !important; - box-shadow: none !important; -} - .endpoint-list_share .shopping-item-actions, .endpoint-shared_list .shopping-item-actions, .endpoint-view_list .shopping-item-actions, @@ -5022,23 +4749,56 @@ body:not(.sorting-active) .drag-handle { /* ========================================================= - Password toggle group: shared final version for login/auth/admin + Consistent form controls + password toggle ========================================================= */ +:root { + --ui-control-height: 42px; + --ui-control-radius: 14px; + --ui-control-focus-ring: 0 0 0 .25rem rgba(24, 64, 118, .18); +} + +.ui-consistent-input { + min-height: var(--ui-control-height) !important; + border-radius: var(--ui-control-radius) !important; +} + .ui-password-group { display: flex !important; flex-wrap: nowrap !important; align-items: stretch !important; width: 100% !important; + gap: 0 !important; + overflow: hidden !important; + border: 1px solid var(--ui-border-strong) !important; + border-radius: var(--ui-control-radius) !important; + background: rgba(255,255,255,0.04) !important; + background-image: none !important; + box-shadow: none !important; +} + +.ui-password-group:focus-within { + border-color: rgba(25, 135, 84, 0.6) !important; + box-shadow: 0 0 0 0.2rem rgba(25, 135, 84, 0.16) !important; } .ui-password-group > .form-control { flex: 1 1 auto !important; width: 1% !important; min-width: 0 !important; - min-height: 42px !important; - border-top-right-radius: 0 !important; - border-bottom-right-radius: 0 !important; - border-right: 0 !important; + min-height: var(--ui-control-height) !important; + border: 0 !important; + border-radius: 0 !important; + box-shadow: none !important; + background: transparent !important; + background-color: transparent !important; + background-image: none !important; + background-clip: padding-box !important; +} + +.ui-password-group > .form-control:focus { + box-shadow: none !important; + background: transparent !important; + background-color: transparent !important; } .ui-password-group > .ui-password-toggle { @@ -5050,19 +4810,17 @@ body:not(.sorting-active) .drag-handle { flex: 0 0 46px !important; width: 46px !important; min-width: 46px !important; - min-height: 42px !important; + min-height: var(--ui-control-height) !important; padding: 0 !important; margin: 0 !important; cursor: pointer !important; - background-color: var(--dark-700) !important; + background: transparent !important; + background-color: transparent !important; background-image: none !important; - color: var(--text-strong) !important; - border: 1px solid var(--dark-300) !important; - border-left: 0 !important; - border-top-left-radius: 0 !important; - border-bottom-left-radius: 0 !important; - border-top-right-radius: 14px !important; - border-bottom-right-radius: 14px !important; + color: rgba(255,255,255,0.72) !important; + border: 0 !important; + border-left: 1px solid rgba(255, 255, 255, 0.10) !important; + border-radius: 0 !important; box-shadow: none !important; line-height: 1 !important; } @@ -5070,15 +4828,17 @@ body:not(.sorting-active) .drag-handle { .ui-password-group > .ui-password-toggle:hover, .ui-password-group > .ui-password-toggle:focus, .ui-password-group > .ui-password-toggle:focus-visible { - background-color: var(--dark-800) !important; + background: rgba(255,255,255,0.03) !important; + background-color: rgba(255,255,255,0.03) !important; color: #fff !important; - border-color: var(--primary) !important; - box-shadow: 0 0 0 .25rem rgba(24, 64, 118, .18) !important; + border-left-color: rgba(255,255,255,0.16) !important; + box-shadow: none !important; outline: none !important; } .ui-password-group > .ui-password-toggle.is-active { - background-color: var(--dark-800) !important; + background: rgba(255,255,255,0.05) !important; + background-color: rgba(255,255,255,0.05) !important; color: #fff !important; } @@ -5086,29 +4846,40 @@ body:not(.sorting-active) .drag-handle { pointer-events: none !important; } -.endpoint-login .ui-password-group > .ui-password-toggle, -.endpoint-system_auth .ui-password-group > .ui-password-toggle, -.endpoint-user_management .ui-password-group > .ui-password-toggle, -.endpoint-user_management .modal .ui-password-group > .ui-password-toggle { - background-color: var(--dark-700) !important; - color: var(--text-strong) !important; - border-color: var(--dark-300) !important; +.endpoint-login form .form-control:not(.form-control-sm), +.endpoint-system_auth form .form-control:not(.form-control-sm), +.endpoint-edit_my_list form .form-control:not(.form-control-sm):not(.form-control-plaintext), +.endpoint-edit_list form .form-control:not(.form-control-sm):not(.form-control-plaintext), +.endpoint-user_management form .form-control:not(.form-control-sm), +.endpoint-user_management .modal .form-control:not(.form-control-sm), +.endpoint-edit_my_list form .form-select, +.endpoint-edit_list form .form-select, +.endpoint-user_management form .form-select, +.endpoint-user_management .modal .form-select { + min-height: var(--ui-control-height) !important; } -.endpoint-login .ui-password-group > .ui-password-toggle:hover, -.endpoint-login .ui-password-group > .ui-password-toggle:focus, -.endpoint-login .ui-password-group > .ui-password-toggle:focus-visible, -.endpoint-system_auth .ui-password-group > .ui-password-toggle:hover, -.endpoint-system_auth .ui-password-group > .ui-password-toggle:focus, -.endpoint-system_auth .ui-password-group > .ui-password-toggle:focus-visible, -.endpoint-user_management .ui-password-group > .ui-password-toggle:hover, -.endpoint-user_management .ui-password-group > .ui-password-toggle:focus, -.endpoint-user_management .ui-password-group > .ui-password-toggle:focus-visible, -.endpoint-user_management .modal .ui-password-group > .ui-password-toggle:hover, -.endpoint-user_management .modal .ui-password-group > .ui-password-toggle:focus, -.endpoint-user_management .modal .ui-password-group > .ui-password-toggle:focus-visible { - background-color: var(--dark-800) !important; - border-color: var(--primary) !important; +.endpoint-login form .form-control.ui-consistent-input, +.endpoint-system_auth form .form-control.ui-consistent-input, +.endpoint-edit_my_list form .ui-consistent-input, +.endpoint-edit_list form .ui-consistent-input, +.endpoint-user_management form .ui-consistent-input, +.endpoint-user_management .modal .ui-consistent-input { + border-radius: var(--ui-control-radius) !important; +} + +/* + Password fields use only the generic ui-password-group rules above. + Keep endpoint-specific overrides out of login/system_auth/admin screens, + otherwise future changes start fighting through selector specificity. +*/ +.ui-password-group > .form-control.ui-consistent-input { + border-radius: 0 !important; +} + +.endpoint-edit_my_list .access-editor .access-input, +.endpoint-edit_list .input-group.input-group-sm .form-control { + min-height: var(--ui-control-height) !important; } @media (max-width: 575.98px) { @@ -5119,7 +4890,6 @@ body:not(.sorting-active) .drag-handle { } } - /* wyróżnienie pola dodawania produktu */ .endpoint-list .shopping-entry-card, .endpoint-list_share .shopping-entry-card, @@ -5921,3 +5691,26 @@ body:not(.sorting-active) .drag-handle { font-size: 0.95rem; } } + + +/* ========================================================= + Final form-control normalization for edit/admin screens +========================================================= */ +.endpoint-edit_my_list .stack-form > .mb-3 > .ui-consistent-input, +.endpoint-edit_my_list .stack-form > .mb-4 > .ui-consistent-input, +.endpoint-edit_my_list .stack-form .row .ui-consistent-input, +.endpoint-edit_list form > .mb-3 > .ui-consistent-input, +.endpoint-edit_list form > .mb-4 > .ui-consistent-input, +.endpoint-edit_list form .row .ui-consistent-input, +.endpoint-user_management .row > [class*="col-"] > .ui-consistent-input, +.endpoint-user_management .modal .ui-consistent-input { + border-radius: var(--ui-control-radius) !important; +} + +.endpoint-edit_my_list .ts-wrapper.single .ts-control, +.endpoint-edit_list .ts-wrapper.single .ts-control, +.endpoint-edit_my_list .ts-wrapper.multi .ts-control, +.endpoint-edit_list .ts-wrapper.multi .ts-control { + min-height: var(--ui-control-height) !important; + border-radius: var(--ui-control-radius) !important; +} diff --git a/shopping_app/templates/admin/edit_list.html b/shopping_app/templates/admin/edit_list.html index ff8b1e8..0dfcca6 100644 --- a/shopping_app/templates/admin/edit_list.html +++ b/shopping_app/templates/admin/edit_list.html @@ -18,7 +18,7 @@
-
@@ -26,13 +26,13 @@
-
- {% for user in users %}
@@ -102,7 +102,7 @@
@@ -157,11 +157,11 @@
-
-
diff --git a/shopping_app/templates/admin/user_management.html b/shopping_app/templates/admin/user_management.html index 8dc1265..dfb931d 100644 --- a/shopping_app/templates/admin/user_management.html +++ b/shopping_app/templates/admin/user_management.html @@ -18,13 +18,13 @@
+ class="form-control bg-dark text-white border-secondary ui-consistent-input" placeholder="np. jan" required>
+ class="form-control bg-dark text-white border-secondary ui-consistent-input" placeholder="min. 6 znaków" required>
@@ -109,7 +109,7 @@

Dla użytkownika:

+ class="form-control bg-dark text-white border-secondary ui-consistent-input" required>