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/static/css/style.css b/shopping_app/static/css/style.css index ef953fe..3bef0ee 100644 --- a/shopping_app/static/css/style.css +++ b/shopping_app/static/css/style.css @@ -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,8 +4749,19 @@ 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; @@ -5035,7 +4773,9 @@ body:not(.sorting-active) .drag-handle { flex: 1 1 auto !important; width: 1% !important; min-width: 0 !important; - min-height: 42px !important; + min-height: var(--ui-control-height) !important; + border-top-left-radius: var(--ui-control-radius) !important; + border-bottom-left-radius: var(--ui-control-radius) !important; border-top-right-radius: 0 !important; border-bottom-right-radius: 0 !important; border-right: 0 !important; @@ -5050,7 +4790,7 @@ 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; @@ -5061,8 +4801,8 @@ body:not(.sorting-active) .drag-handle { 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; + border-top-right-radius: var(--ui-control-radius) !important; + border-bottom-right-radius: var(--ui-control-radius) !important; box-shadow: none !important; line-height: 1 !important; } @@ -5073,7 +4813,7 @@ body:not(.sorting-active) .drag-handle { background-color: var(--dark-800) !important; color: #fff !important; border-color: var(--primary) !important; - box-shadow: 0 0 0 .25rem rgba(24, 64, 118, .18) !important; + box-shadow: var(--ui-control-focus-ring) !important; outline: none !important; } @@ -5086,6 +4826,35 @@ body:not(.sorting-active) .drag-handle { pointer-events: none !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 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; +} + +.endpoint-edit_my_list .access-editor .access-input, +.endpoint-edit_list .input-group.input-group-sm .form-control, +.endpoint-user_management .row .ui-password-group > .form-control, +.endpoint-user_management .modal .ui-password-group > .form-control { + min-height: var(--ui-control-height) !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, @@ -5119,7 +4888,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 +5689,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 @@