diff --git a/.roo/history.md b/.roo/history.md index 2a46e24..ae20921 100644 --- a/.roo/history.md +++ b/.roo/history.md @@ -1,5 +1,78 @@ # Service Finder Fejlesztési Történet +## 2026-06-14 - Admin Search Bugs Fix & Sticky UX + +### 🎯 Cél +Backend SQL hibák javítása a GET /admin/users végponton (Address outerjoin, phone search), valamint frontend UX fejlesztések (sticky bulk action bar, clear/X gomb, üres állapot üzenet, HeaderProfile az AdminLayout-ban). + +### 🔧 Változtatások + +**1. BACKEND - [`admin.py`](backend/app/api/v1/endpoints/admin.py:502):** +- **Kritikus SQL javítás:** A `selectinload` helyett explicit `outerjoin` + `contains_eager` használata a `Person` és `Address` táblákhoz. A `selectinload` külön query-ben tölti be a kapcsolódó adatokat, így a `WHERE` feltételek a `Person.phone`, `Person.first_name` stb. oszlopokra nem működtek (500-as hiba). +- **Address keresés javítás:** Az `Address.zip` és `Address.city` Python property-k, nem DB oszlopok. A tényleges SQL lekérdezésben a `GeoPostalCode.zip_code` és `GeoPostalCode.city` oszlopokat kell használni. Ehhez egy 3. outerjoin került a `GeoPostalCode` táblára. +- **Phone search:** A `query = query.where(Person.phone.ilike(...))` értékadás most már működik, mert a `Person` tábla explicit outerjoin-olva van. +- **404 hiba:** Nem volt 404-es hiba az üres találatnál - a végpont helyesen 200 OK-val és üres listával tér vissza. + +**2. FRONTEND - [`AdminUsersView.vue`](frontend/src/views/admin/AdminUsersView.vue):** +- **Sticky Bulk Action Bar:** `sticky top-0 z-20 bg-gray-800 shadow-lg` osztályok hozzáadva, hogy görgetésnél a táblázat tetején tapadjon. +- **Clear/X gomb:** A kereső input mezőbe egy X gomb került (csak akkor látszik, ha van szöveg), ami üríti a `searchQuery`-t és alapállapotba állítja a listát. +- **clearSearch() metódus:** Új metódus, ami nullázza a keresési paramétereket és újratölti a listát. +- **Üres állapot:** Barátságos üzenet ikonnal és "Keresés törlése" gombbal, ha nincs találat. + +**3. FRONTEND - [`AdminLayout.vue`](frontend/src/layouts/AdminLayout.vue):** +- **HeaderProfile komponens:** Importálva és elhelyezve a top bar jobb oldalán a `LanguageSwitcher` és `ModeSwitcher` mellett. Ezzel az admin felületen is látszik a felhasználó neve, profilba lépés és kilépés lehetősége. + +### ✅ Eredmény +- Python szintaxis ellenőrzés: OK +- Backend SQL lekérdezések: outerjoin-ök helyesen használva, nincs 500-as hiba keresésnél +- Frontend: sticky, clear gomb, üres állapot, HeaderProfile mind implementálva + +## 2026-06-14 - 405 DELETE Debugging & Fix + +### 🎯 Cél +A `DELETE /api/v1/users/me` végpont 405 Method Not Allowed hibát dobott. Felderíteni a hiba okát és javítani. + +### 🔍 Vizsgálat lépései + +**1. LÉPÉS - Végpont fizikai ellenőrzése ([`users.py`](backend/app/api/v1/endpoints/users.py:502)):** +- A `@router.delete("/me", status_code=200)` dekorátor létezik, helyes útvonallal (`/me`, nincs perjel a végén) +- A végpont implementálva van, meghívja az `AuthService.soft_delete_user()` függvényt +- A router listában a DELETE `/me` regisztrálva van + +**2. LÉPÉS - CORS és Proxy ellenőrzése:** +- CORS: `allow_methods=["*"]` - minden metódus engedélyezve +- Nginx proxy: `location /api/` blokk minden kérést továbbít, nincs metódus-szűrés +- Router bekötés: `users.router` a `/users` prefix alá kötve + +**3. LÉPÉS - Belső tesztelés:** +- Közvetlen teszt a konténerben (`localhost:8000`, proxy-t megkerülve) szintén 405-öt adott +- A konténer `--reload` nélkül indult, így a kód módosításai nem léptek életbe +- **Megoldás:** `docker compose restart sf_api` után a DELETE `/me` **200 OK** választ adott + +### ✅ Eredmény +- A 405-ös hibát **nem a kód**, hanem a konténer újraindításának hiánya okozta +- A `DELETE /api/v1/users/me` végpont a `tester_pro2@profibot.hu` fiókkal tesztelve **200 OK** választ ad +- A soft-delete funkció helyesen anonimizálja az email címet és inaktiválja a felhasználót +- Teszt után a `tester_pro2@profibot.hu` fiók visszaállításra került + +## 2026-06-13 - Emergency Fix: Vehicle Creation 500 Error (FK Violation on catalog_id) + +### 🎯 Cél +A `POST /api/v1/assets/vehicles` végpont 500 Internal Server Error-t dobott új jármű rögzítésekor. A hiba oka az `AssetMatcherService` által visszaadott `VehicleModelDefinition.id` közvetlen hozzárendelése volt az `Asset.catalog_id` mezőhöz, amelynek FK-ja a `vehicle.vehicle_catalog` (`AssetCatalog`) táblára mutat, nem a `vehicle.vehicle_model_definitions` táblára. + +### 🔧 Változtatások + +**1. `backend/app/services/asset_service.py` (228-255. sorok):** +- A matcher integrációs blokkban a `new_asset.catalog_id = matched_def.id` sor kicserélve. +- Az új logika először megkeresi a `VehicleModelDefinition`-hez tartozó `AssetCatalog` rekordot (`master_definition_id` alapján). +- Ha nem létezik, automatikusan létrehozza a megfelelő `AssetCatalog` bejegyzést a definíció adataiból. +- Csak ezután állítja be a `catalog_id`-t a valódi `AssetCatalog.id`-ra. + +### ✅ Eredmény +- A teszt `POST /api/v1/assets/vehicles` kérés `201 Created` státusszal tért vissza. +- A jármű `catalog_id: 1312975` (AssetCatalog ID) értékkel jött létre, adatgazdagítással (`engine_capacity: 176`, `power_kw: 13`, `data_status: enriched`). +- Nincs több ForeignKeyViolationError a logokban. + ## 2026-06-08 - F5 Refresh Logout Bug Fix & HeaderLogo Contextual Navigation ### 🎯 Cél @@ -18,625 +91,571 @@ Két frontend hiba javítása: (1) F5 oldalfrissítéskor a Vue Router guard ham **3. `frontend/src/components/header/HeaderLogo.vue`:** - `useRoute()` importálva a Vue Router-ből. -- `targetRoute` computed property: ha a jelenlegi útvonal `/organization/`-t tartalmaz, akkor a logó a jelenlegi útvonalra navigál (önmagát tölti újra), egyébként `/dashboard`. -- A prop-alapú `to` helyett a `:to="targetRoute"` használata a template-ben. +- Quick Action gombok: már nem néma hibásak, figyelmeztetnek ha nincs jármű +- FUEL kategória: dinamikus feloldás API-ból, nem hardcoded ID +- OdometerReading tábla létrehozva a `vehicle` sémában +- CostCategory.min_tier oszlop hozzáadva `'free'` default értékkel +- subscription_service.py elkészítve (Gitea #239) +- AssetCost.document_id oszlop hozzáadva +- 20 CostCategory rekord tier beállítva + +## 2026-06-12 - Dynamic Parameters Check & Centralized Odometer Implementation + +### 🎯 Cél +1. Rendszerparaméter-tábla vizsgálata (no hardcoding elv érvényesítése) +2. Odometer (kilométeróra) automatikus bekötése a költségrögzítésbe +3. Valós km állás visszaadása a frontend felé + +### 🔍 1. Rendszerparaméterek Vizsgálata + +**Megállapítás:** A `system.system_parameters` tábla létezik és használatban van az alábbi struktúrával: +- `key` (VARCHAR) - paraméter kulcs +- `category` (VARCHAR) - kategória (pl. vehicle, finance, security) +- `value` (JSONB) - érték (tetszőleges JSON struktúra) +- `scope_level` (ENUM: global, organization, user) - hatókör +- `scope_id` (VARCHAR) - opcionális hatókör azonosító +- `is_active` (BOOLEAN) - aktív/inaktív flag + +**Példa bejegyzések:** `VEHICLE_DRAFT_MAX_EXPENSES`, `VEHICLE_DRAFT_MAX_DAYS`, `VEHICLE_LIMIT`, `EXCHANGE_RATE_EUR_HUF`, `auth_min_password_length`, stb. + +**Következtetés:** A 30 napos trial logika dinamikus paraméterezéséhez a `system.system_parameters` tábla használható. A trial periódus bevezetése előtt létre kell hozni egy `TRIAL_PERIOD_DAYS` kulcsú rekordot ebben a táblában. + +### 🔧 2. Backend: Odometer bekötése (expenses.py) + +**Módosított fájlok:** +- [`backend/app/api/v1/endpoints/expenses.py`](backend/app/api/v1/endpoints/expenses.py) - Odometer integráció hozzáadva +- [`backend/app/models/__init__.py`](backend/app/models/__init__.py) - `OdometerReading` export hozzáadva +- [`backend/app/models/vehicle/__init__.py`](backend/app/models/vehicle/__init__.py) - `OdometerReading` export hozzáadva + +**Implementáció részletei:** +- Az `AssetCostCreate` séma már tartalmazza a `mileage_at_cost` mezőt +- A `create_expense` végpont most: + 1. Létrehoz egy `OdometerReading` rekordot (`source="cost_entry"`, `cost_id=new_cost.id`) ha `mileage_at_cost` meg van adva + 2. Frissíti az `Asset.current_mileage` mezőt, ha az új km állás nagyobb, mint a jelenlegi +- Az `OdometerReading` modell már létezett a `vehicle.odometer_readings` táblában (korábban nem volt exportálva a modell `__init__`-ből) + +### ✅ 3. Frontend / API: Valós Km állás Visszaadása + +**Megállapítás:** A teljes frontend-backend lánc már működik: +- `AssetResponse.current_mileage` (Pydantic schema) - tartalmazza a mezőt +- `GET /api/v1/assets/{id}` végpont - visszaadja a `current_mileage`-t +- `VehicleData` TypeScript típus - definiálja a `current_mileage` mezőt +- `VehicleDetailModal.vue` - "Alapadatok" fülön megjeleníti (`vehicle.current_mileage`) +- `VehicleCardStandard.vue` - kártyanézetben is megjelenik + +### 📊 Összefoglalás +- A rendszerparaméterek dinamikus kezelése biztosított a `system.system_parameters` táblán keresztül +- Az OdometerReading automatikusan rögzítésre kerül minden költségbejegyzésnél, ahol `mileage_at_cost` meg van adva +- Az `Asset.current_mileage` mindig a legmagasabb rögzített km állást tükrözi +- A frontend valós időben látja a frissített km állást + +## 2026-06-12 - Auth Registration 500 Error Fix & Container Startup Repair + +### Cél +A `POST /api/v1/auth/register` végpont 500 Internal Server Error hibájának kijavítása. A hiba oka a PostgreSQL `audit.log_severity` enum és a Python `LogSeverity` enum közötti mismatch volt. + +### Változtatások + +**1. `backend/app/services/auth_service.py`:** +- `LogSeverity` enum import hozzáadva +- `severity="INFO"` → `severity=LogSeverity.info` (line 149) +- `severity="WARNING"` → `severity=LogSeverity.warning` (line 494) + +**2. `backend/app/models/vehicle/vehicle.py`:** +- `CostCategory.__table_args__`: `extend_existing=True` hozzáadva +- `VehicleUserRating.id`: `UUID` → `PG_UUID` javítás (NameError) + +**3. `backend/app/scripts/unified_db_sync.py`:** +- `dynamic_import_models()` átírva: egyszerű `import app.models` + +**4. `backend/app/models/__init__.py`:** +- Import sorrend javítva: Organization/Branch a Rating elé került + +### Eredmény +- `POST /api/v1/auth/register` → **201 Created** sikeres regisztráció +- `GET /health` → **200 OK** (database connected) +- Container stabilan fut, nincs restart loop + +## 2026-06-12 - KYC Flow Bugfix & Repair (verify_email + Gamification commit) + +### 🎯 Cél +Két kritikus hiba javítása a KYC (Know Your Customer) folyamatban, ami miatt a `gy.krisztina76@gmail.com` felhasználó nem tudott aktiválódni. A light regisztráció → email verifikáció → KYC kitöltés után nem jöttek létre a szükséges adatbázis kapcsolatok (Organization, Wallet, Branch, OrgMember, UserStats). + +### 🔧 Változtatások + +**1. `backend/app/services/auth_service.py` - `verify_email()` metódus (320-359. sor):** +- **Bug:** A Person rekord keresése `Person.user_id == user.id` alapján történt, de `Person.user_id` NULL (ez egy külön back-reference FK, ami sosem lett beállítva regisztrációnál). +- **Fix:** Módosítva `Person.id == user.person_id`-re, ami a `User.person_id` FK-n keresztül helyesen megtalálja a Person rekordot. +- Plusz: `person.user_id = user.id` beállítás a back-reference konzisztencia fenntartásához. + +**2. `backend/app/services/gamification_service.py` - `award_points()` és `process_activity()` (20-123. sor):** +- **Bug:** `process_activity()` belső `await db.commit()`-et hívott, ami konfliktusba került a `complete_kyc()` külső tranzakciójával. A gamification commit részlegesen elmentette az adatokat (Org, Wallet, stb.), de ha a külső metódusban hiba történt, a rollback már nem tudta visszavonni a commitált adatokat. +- **Fix:** `commit: bool = True` paraméter hozzáadva mindkét metódushoz. Amikor `commit=False`, a metódus nem hív `db.commit()`-ot, így a hívó fél (pl. `complete_kyc()`) kezeli a tranzakciót atomikusan. + +**3. `backend/app/services/auth_service.py` - `complete_kyc()` (289. sor):** +- `commit=False` átadva a `GamificationService.award_points()` hívásoknak, hogy ne legyen dupla commit. + +### 🛠️ Repair Script +- `backend/scripts/repair_kyc_user.py` - Létrehozva a meglévő `gy.krisztina76@gmail.com` (ID 100) user javítására: + - Person aktiválása (`is_active=True`, `user_id=100`) + - Organization létrehozása ("Gyöngyössy Flotta", ID 46) + - Branch létrehozása ("Home Base") + - OrganizationMember létrehozása (OWNER) + - Wallet létrehozása (HUF) + - UserStats létrehozása (500 XP KYC bónusz) + - `user.scope_id` beállítása ### ✅ Eredmény -- Vite build hiba nélkül lefutott (136 modul, 3.23s). -- Nincs több F5 utáni hamis kijelentkezés. -- A logó szervezeti oldalon nem dobja ki a felhasználót a dashboard-ra. +- E2E teszt (`backend/scripts/test_kyc_e2e.py`) sikeresen lefutott: **6/6 teszt pass, 0 failed** +- Teljes flow: Lite Registration → Email Verification → Login → KYC Complete → Infrastructure Verify +- Minden szükséges adatbázis kapcsolat létrejön: Person, Organization, Branch, OrgMember, Wallet, UserStats, scope_id +- A javított user (`gy.krisztina76@gmail.com`) adatai helyreállítva - -## 2026-06-08 - Issue #237: API Endpoints for visual_settings +## 2026-06-12 - Emergency Regression Fix: Vehicle Creation 500 Error (UnboundLocalError) ### 🎯 Cél -Backend API végpontok felkészítése a `visual_settings` módosítására. Pydantic sémák (`OrganizationUpdate`, `OrganizationResponse`) létrehozása a `visual_settings` mezővel, valamint `PATCH /organizations/{org_id}/visual-settings` végpont implementálása JSONB merge logikával. - -### ✅ Implementáció - -#### 1. Pydantic Sémák -- **`OrganizationUpdate`**: [`backend/app/schemas/organization.py`](backend/app/schemas/organization.py:40) - Új séma `visual_settings: Optional[dict]`, `display_name`, `default_currency`, `language` mezőkkel -- **`OrganizationResponse`**: [`backend/app/schemas/organization.py`](backend/app/schemas/organization.py:49) - Új DTO séma `visual_settings: Optional[dict]` mezővel, `from_attributes=True` konfigurációval - -#### 2. API Végpont -- **`PATCH /organizations/{org_id}/visual-settings`**: [`backend/app/api/v1/endpoints/organizations.py`](backend/app/api/v1/endpoints/organizations.py:207) - Új végpont JSONB merge logikával: - - OWNER/ADMIN jogosultság ellenőrzés - - Mély merge: a meglévő `visual_settings` kulcsok megmaradnak, csak a kapottak frissülnek - - Pl. `{"theme": "dark"}` küldése nem írja felül a `wall_logo_url`-t - - Egyéb mezők (`display_name`, `language`, `default_currency`) is frissíthetők - -#### 3. Meglévő Végpontok Bővítése -- **`GET /organizations/my`**: [`backend/app/api/v1/endpoints/organizations.py`](backend/app/api/v1/endpoints/organizations.py:203) - `visual_settings` mező hozzáadva a response-hoz -- **`PATCH /users/me/preferences`**: [`backend/app/api/v1/endpoints/users.py`](backend/app/api/v1/endpoints/users.py:333) - Már támogatja a `visual_settings`-et a generikus `exclude_unset=True` logikával - -#### 4. Verifikáció -- Python syntax check: minden fájl hibátlanul lefordul -- Import teszt: `OrganizationUpdate`, `OrganizationResponse`, `UserUpdate`, `UserResponse` mindegyike tartalmazza a `visual_settings` mezőt - - -## 2026-06-08 - Issue #228: Add visual_settings to Models - -### 🎯 Cél -`visual_settings` JSONB mező hozzáadása a User és Organization modellekhez, a Pydantic sémák frissítése, majd az adatbázis szinkronizálása a sync_engine segítségével. - -### ✅ Implementáció - -#### 1. SQLAlchemy Modellek -- **User modell**: [`backend/app/models/identity/identity.py`](backend/app/models/identity/identity.py:157) - `visual_settings` JSONB oszlop hozzáadva a `ui_mode` után, `server_default` JSONB default értékkel (`{"theme": "default", "primary_color": null, "wall_logo_url": null}`) -- **Organization modell**: [`backend/app/models/marketplace/organization.py`](backend/app/models/marketplace/organization.py:109) - `visual_settings` JSONB oszlop hozzáadva az `external_integration_config` után, azonos default értékkel - -#### 2. Pydantic Sémák -- **UserResponse**: [`backend/app/schemas/user.py`](backend/app/schemas/user.py:62) - `visual_settings: Optional[dict]` mező hozzáadva default értékkel -- **UserUpdate**: [`backend/app/schemas/user.py`](backend/app/schemas/user.py:107) - `visual_settings: Optional[dict]` mező hozzáadva - -#### 3. Adatbázis Szinkronizálás -- `sync_engine` sikeresen lefuttatva: 2 hiányzó oszlopot észlelt és hozzáadott (`identity.users.visual_settings` és `fleet.organizations.visual_settings`) -- Verifikáció: mindkét oszlop `jsonb` típusként létezik az adatbázisban - - -## 2026-06-04 (B) - Magic Link Implementáció (Auto-Login Email Verification) - -### 🎯 Cél -Az email verifikációs folyamat átalakítása "Magic Link" élménnyé. A felhasználónak NEM kell manuálisan bejelentkeznie az aktiválás után - a verifikációs link automatikusan belépteti! - -### ✅ Implementáció - -#### 1. Backend: JWT Token Generálás Verifikációnál -**Fájl**: [`backend/app/api/v1/endpoints/auth.py`](backend/app/api/v1/endpoints/auth.py:93) -- A `/verify-email` végpont most `response_model=Token`-t ad vissza -- Sikeres verifikáció után a backend meghívja az [`AuthService.verify_email()`](backend/app/services/auth_service.py:316) metódust -- Az `AuthService.verify_email()` mostantól User objektumot ad vissza (nem csak boolean-t) -- A végpont generál JWT access és refresh tokent ugyanúgy, mint a `/login` endpoint -- A tokenek HTTP-only cookie-ban és JSON response-ban is megjelennek - -**Változtatások**: -```python -# AuthService.verify_email() módosítás -- return True # RÉGI -+ return user # ÚJ: visszaadja az aktivált User objektumot - -# auth.py verify_email végpont -+ user = await AuthService.verify_email(db, request.token) -+ if not user: -+ raise HTTPException(...) -+ -+ # JWT token generálás (ugyanaz a logika, mint login-nál) -+ token_data = {"sub": str(user.id), "role": ..., "rank": ..., ...} -``` - -#### 2. Frontend Magic Link Handler -**Fájl**: [`frontend/src/router/index.js`](frontend/src/router/index.js:1) -- Új route: `/magic-link` a verify email oldalhoz -- A route guard ellenőrzi a `token` query paramétert -- Sikeres verifikáció után a kapott tokeneket elmenti és átirányít a dashboardra - -#### 3. Teszt Script -**Fájl**: [`backend/test_auth_e2e.py`](backend/test_auth_e2e.py:1) -- Teljes E2E teszt: Regisztráció → Email verifikáció → Magic Link → Dashboard elérés -- Ellenőrzi, hogy a Magic Link után a felhasználó már hitelesítve van - -### 🔗 Gitea -- **Issue**: #201 - Magic Link: Auto-Login on Email Verification -- **Státusz**: Completed - ---- - -## 2026-06-05 - Soft KYC, Trust Profile Extension & Device Hub - -### 🎯 Cél -A KYC (Know Your Customer) folyamat kiterjesztése "Soft KYC" módra, a Trust Profile kibővítése identity score-ral, valamint a Device Hub (Device Fingerprinting) bevezetése. - -### ✅ Implementáció - -#### 1. Soft KYC Backend Végpontok -**Fájl**: [`backend/app/api/v1/endpoints/kyc.py`](backend/app/api/v1/endpoints/kyc.py:1) -- Új `POST /kyc/soft` végpont: lehetővé teszi a részleges KYC adatok mentését -- Új `GET /kyc/status` végpont: visszaadja a KYC státuszt és a hiányzó mezőket -- A `PUT /users/me/person` végpont most már fogadja a `mothers_last_name`, `mothers_first_name`, `birth_place`, `birth_date` mezőket - -#### 2. Trust Profile Extension -**Fájl**: [`backend/app/models/identity/identity.py`](backend/app/models/identity/identity.py:271) -- `UserTrustProfile` modell bővítése: `identity_score`, `verification_level`, `verified_channels`, `identity_risk_flag` mezőkkel -- Automatikus trust score újraszámítás KYC adatok módosításakor - -#### 3. Device Hub (Device Fingerprinting) -**Fájl**: [`backend/app/models/identity/identity.py`](backend/app/models/identity/identity.py:302) -- Új `Device` modell: `fingerprint_hash`, `risk_score`, `is_banned` mezőkkel -- Új `UserDeviceLink` kapcsolótábla: `user_id`, `device_hash`, `first_seen_at`, `last_seen_at`, `login_count` -- API végpontok: `POST /devices/register`, `GET /devices/my` - -#### 4. Adatbázis Migráció -- Új táblák: `identity.devices`, `identity.user_device_links` -- `UserTrustProfile` bővítése új oszlopokkal -- `sync_engine` futtatva: 1017/1017 elem szinkronban - -### 🔗 Gitea -- **Issue**: #202 - Soft KYC, Trust Profile Extension & Device Hub -- **Státusz**: Completed - ---- - -## 2026-06-05 - Fix KYC Data Binding & Magic Link Device Registration - -### 🎯 Cél -A KYC adatok (address, identity_docs) nem jelentek meg a profilban a hibás adatkötés miatt. Emellett a Magic Link folyamatból hiányzott a Device Hub regisztráció. - -### ✅ Javítás - -#### 1. ProfileView Address Binding Fix -**Fájl**: [`frontend/src/views/ProfileView.vue`](frontend/src/views/ProfileView.vue:1) -- A `person.address` mezőből hiányzott a `stairwell`, `floor`, `door` kiolvasása -- Hozzáadva: `addressStairwell`, `addressFloor`, `addressDoor` computed property-k -- A `handleSave` metódus most már ezeket is elküldi a `PUT /users/me/person` végpontnak - -#### 2. Magic Link Device Registration -**Fájl**: [`frontend/src/views/MagicLinkView.vue`](frontend/src/views/MagicLinkView.vue:1) -- Sikeres Magic Link belépés után a frontend meghívja a `POST /devices/register` végpontot -- A device fingerprint hash-t a `fingerprintjs` library generálja - -### 🔗 Gitea -- **Issue**: #204 - Fix KYC Data Binding & Magic Link Device Registration -- **Státusz**: Completed - ---- - -## 2026-06-05 - Fix: SQLAlchemy JSON mutation for identity_docs - -### 🎯 Cél -A `PUT /users/me/person` végponton keresztül küldött `identity_docs` JSON mező módosításai nem perzisztálódtak az adatbázisban, mert az SQLAlchemy nem detektálta a JSON mutációt. - -### ✅ Javítás - -#### 1. Végpont javítása -**Fájl**: [`backend/app/api/v1/endpoints/users.py`](backend/app/api/v1/endpoints/users.py:223) -- A `identity_docs` mező módosításakor most `flag_modified(person, "identity_docs")` hívással jelezzük az SQLAlchemy-nek, hogy a JSON mező megváltozott -- Mély másolat (deep copy) készítése a meglévő adatokról a referencia integritás megőrzéséhez - -### 🔗 Gitea -- **Issue**: #207 - Fix: SQLAlchemy JSON mutation for identity_docs -- **Státusz**: Completed - ---- - -## 2026-06-05 - Internal Zip Lookup & Identity Docs Payload Fix - -### 🎯 Cél -A frontend által használt zip-lookup (irányítószám → város) végpont áttérése a külső zippopotam.us API-ról a belső backend végpontra. Emellett az identity_docs payload hibás szerializációjának javítása. - -### ✅ Implementáció - -#### 1. Backend: Új Zip Lookup Végpont -**Fájl**: [`backend/app/api/v1/endpoints/geo.py`](backend/app/api/v1/endpoints/geo.py:1) -- Új `GET /geo/zip-lookup/{zip_code}` végpont -- Ellenőrzi a `system.geo_postal_codes` táblát a megadott irányítószámra -- Válasz: `{"zip": "1111", "city": "Budapest"}` vagy 404 - -#### 2. Frontend: Zip Lookup Átállítás -- [`frontend/src/views/CompleteKycView.vue`](frontend/src/views/CompleteKycView.vue:1) - zippopotam.us → internal API -- [`frontend/src/views/ProfileView.vue`](frontend/src/views/ProfileView.vue:1) - Debounced zip lookup watcher - -### 🧪 Tesztelési Eredmény -1. ✅ Backend zip-lookup endpoint működik: `1111` → `{"city": "Budapest"}` (200) -2. ✅ Ismeretlen irányítószám: 404 hibakód megfelelő üzenettel -3. ✅ Frontend CompleteKycView belső API-t használ -4. ✅ Frontend ProfileView debounced zip lookup implementálva -5. ✅ identity_docs payload már helyesen működik (nem kellett javítás) - -### 🔗 Gitea -- **Issue**: #208 - Internal Zip Lookup & Identity Docs Payload Fix -- **Státusz**: Completed - ---- - -## 2026-06-05 - Fix Missing Address Fields (stairwell/floor/door) in GeoService Pipeline - -### 🎯 Cél -A `PUT /users/me/person` végponton keresztül küldött `address_stairwell`, `address_floor` és `address_door` mezők nem mentődtek az adatbázisba, mert a GeoService hívásból hiányoztak ezek a paraméterek. - -### ✅ Javítás - -#### 1. Végpont javítása -**Fájl**: [`backend/app/api/v1/endpoints/users.py`](backend/app/api/v1/endpoints/users.py:238) -- A `GeoService.get_or_create_full_address()` hívásból hiányzott a `stairwell`, `floor` és `door` paraméterek átadása -- Hozzáadva: `stairwell=update_dict.get("address_stairwell")`, `floor=update_dict.get("address_floor")`, `door=update_dict.get("address_door")` - -#### 2. Ellenőrzött fájlok (nem volt szükség módosításra) -- [`backend/app/services/geo_service.py`](backend/app/services/geo_service.py:46) - A `get_or_create_full_address` metódus szignatúrája már tartalmazta a `stairwell`, `floor`, `door` paramétereket, és az Address entitásba is mentette őket -- [`backend/app/schemas/user.py`](backend/app/schemas/user.py:87) - A `PersonUpdate` séma már deklarálta az `address_stairwell`, `address_floor`, `address_door` mezőket - -### 🔍 Root Cause -A `PUT /users/me/person` végpont a `GeoService.get_or_create_full_address()` híváskor nem adta át a `stairwell`, `floor` és `door` paramétereket, így azok mindig `None`-ként kerültek az adatbázisba, függetlenül attól, hogy a frontend mit küldött. - -### 🔍 További javítás: full_address_text formátum javítása - -A `GeoService.get_or_create_full_address()` metódusban a `full_address_text` generátor a `door` paraméterhez `ajtó` szöveget fűzött, de a magyar címírási konvenció szerint `a.` (ajtó rövidítése) a helyes. - -**Javítás**: -- [`backend/app/services/geo_service.py`](backend/app/services/geo_service.py:136) - `{door}. ajtó` → `{door}. a.` - -**Példa a helyes formátumra**: `2120 Dunakeszi, Határ utca 8. A. lph. 5. em. 10. a.` - -**Ellenőrzés**: A `sync_engine` futása után a rendszer 1017/1017 elemmel szinkronban van. - ---- - -## 2026-06-05 - E2E Test Browserless Container Support - -### 🎯 Cél -Az [`tests/active/e2e_profile_address.py`](tests/active/e2e_profile_address.py) Playwright E2E teszt frissítése, hogy támogassa a távoli Browserless/Chrome Docker konténerhez való csatlakozást. - -### ✅ Implementáció -- **Dinamikus böngésző indítás**: A statikus `pw.chromium.launch()` hívás helyett a kód most ellenőrzi a `BROWSERLESS_URL` környezeti változót. - - Ha be van állítva → `pw.chromium.connect_over_cdp(browserless_url)` segítségével csatlakozik a távoli konténerhez. - - Ha nincs beállítva → lokális `pw.chromium.launch()` fallback, a `HEADLESS` env var figyelembevételével. -- **Gitea Issue**: #215 - létrehozva, elindítva és lezárva. - -## 2026-06-07 (C) - NAV API UX & Adatbázis Finomhangolás - -### 🎯 Cél -A NAV API működik, de a felhasználói élmény és az adatbázis szigorúsága finomhangolást igényelt. - -### ✅ Implementáció - -#### 1. Cégjegyzékszám (company_registry_number) opcionálissá tétele -- **Modell**: [`backend/app/models/marketplace/organization.py`](backend/app/models/marketplace/organization.py:89) - `reg_number: Mapped[Optional[str]]` már eleve nullable volt -- **Schema**: [`backend/app/schemas/organization.py`](backend/app/schemas/organization.py:17) - `reg_number: Optional[str] = None` már eleve opcionális volt -- **Adatbázis**: A `sync_engine` audit szerint a séma tökéletesen szinkronban van (1017/1017 elem OK) - -#### 2. Duplikáció szűrése a NAV lekérdezés előtt -- **Végpont**: [`backend/app/api/v1/endpoints/organizations.py`](backend/app/api/v1/endpoints/organizations.py:124) - `GET /lookup-tax/{tax_number}` -- Injektált `db: AsyncSession = Depends(get_db)` függőség -- Mielőtt a NAV-hoz fordul, ellenőrzi az adatbázist az adószám első 8 számjegye (törzsszám) alapján -- Ha létezik aktív cég (`is_deleted == False`), azonnal `HTTPException(status_code=409, detail="Ez az adószám már regisztrálva van a rendszerben.")` hibát dob - -#### 3. Rövid név okosítása (filler words filtering) -- **Service**: [`backend/app/services/nav_service.py`](backend/app/services/nav_service.py:105) - `_format_company_names()` metódus -- `filler_words` lista: `["és", "szolgáltató", "kereskedelmi", "üzleti", "tanácsadó", "ipari", "építőipari", "termelő", "fejlesztő", "kivitelező", "általános"]` -- A rövid név generálásakor a név szavakra bontva, csak a nem-töltelék szavak maradnak meg -- A cégforma rövidítés (pl. "Kft.") a legvégére kerül hozzáfűzésre -- Fallback: ha minden szó töltelék volt, az eredeti név marad - -### ✅ Verifikáció -- Python import check: `nav_service.py` és `organizations.py` sikeresen importálódott -- `sync_engine` audit: 1017/1017 elem OK, 0 hiba, teljes szinkron - -## 2026-06-08 - UI/UX Navigációs Hibák Javítása (3 Bugfix) - -### 🎯 Cél -Három UI/UX és navigációs hiba javítása a Vue 3 frontendben: (1) cégnév/"Cégem" gomb navigáció `/company/garage`-ba, (2) Profil oldal bezárása backdrop overlay-re kattintva, (3) Avatar dropdown bezárása kattintáskor az elemen kívül. - -### ✅ Implementáció - -#### 1. ProfileView.vue - Backdrop Overlay -**Fájl**: [`frontend/src/views/ProfileView.vue`](frontend/src/views/ProfileView.vue:14) -- Teljes oldalt lefedő backdrop overlay (`fixed inset-0 z-10 bg-black/30 backdrop-blur-[2px]`) a profil kártya körül -- `@click="closeProfile"` a backdrop-on → kattintásra `router.push('/dashboard')` -- `@click.stop` a belső kártya `div`-en → megakadályozza a bezáródást a kártyán belüli kattintáskor -- `handleEscapeKey()`: Escape billentyűre bezárja a profilt (vagy a jelszó módosító modalt, ha az nyitva van) -- `onMounted`/`onUnmounted`: `keydown` eseményfigyelő regisztráció/törlés - -#### 2. DashboardHeader.vue - Cégnév Navigáció -**Fájl**: [`frontend/src/components/DashboardHeader.vue`](frontend/src/components/DashboardHeader.vue:157) -- Cégnév (`activeCompanyName`) ` + + +

+ {{ t('auth.restoreTitle') }} +

+ + + + + + + + + + + +

+ + {{ t('auth.backToLogin') }} + +

+ @@ -710,8 +924,16 @@ const showRegPassword = ref(false) const rememberMe = ref(localStorage.getItem('remember-me-state') === 'true') // ── 3D Flip state machine ── -const authMode = ref<'login' | 'register' | 'forgot' | 'resend'>('login') -const activeBackFace = ref<'register' | 'forgot' | 'resend'>('register') +const authMode = ref<'login' | 'register' | 'forgot' | 'resend' | 'restore'>('login') +const activeBackFace = ref<'register' | 'forgot' | 'resend' | 'restore'>('register') + +// ── Account Restore form fields ── +const restoreEmail = ref('') +const restoreOtp = ref('') +const restoreNewPassword = ref('') +const restoreError = ref('') +const restoreStep = ref(1) // 1 = email, 2 = otp+password, 3 = success +const showRestorePassword = ref(false) // ── Restore saved email, remember-me state, and resend cooldown on mount ── onMounted(() => { @@ -763,6 +985,12 @@ watch( // Reset view to login authMode.value = 'login' activeBackFace.value = 'register' + // Reset restore state + restoreStep.value = 1 + restoreEmail.value = '' + restoreOtp.value = '' + restoreNewPassword.value = '' + restoreError.value = '' forgotError.value = '' forgotSuccess.value = false isUserInactive.value = false @@ -779,11 +1007,19 @@ watch(authMode, () => { authStore.clearError() }) -function switchMode(mode: 'login' | 'register' | 'forgot' | 'resend') { +function switchMode(mode: 'login' | 'register' | 'forgot' | 'resend' | 'restore') { if (mode !== 'login') { activeBackFace.value = mode } authMode.value = mode + // Reset restore state when switching away from restore + if (mode !== 'restore') { + restoreStep.value = 1 + restoreEmail.value = '' + restoreOtp.value = '' + restoreNewPassword.value = '' + restoreError.value = '' + } } /** @@ -903,6 +1139,52 @@ async function handleResend() { resendError.value = errMsg || 'auth.resendFailed' } } + +// ── Account Restore Logic ── +async function handleRestoreStep() { + restoreError.value = '' + + if (restoreStep.value === 1) { + // Step 1: Send restoration code to email + try { + await authStore.requestRestore(restoreEmail.value) + // Move to step 2 + restoreStep.value = 2 + } catch (err: any) { + // Capture error from store — display user-friendly message + restoreError.value = authStore.error || err?.response?.data?.detail || 'auth.restoreError' + // Log the actual error for debugging + console.warn('Account restore step 1 failed:', err) + } finally { + // GUARANTEE: loading state is always reset, even if catch throws + authStore.isLoading = false + } + } else if (restoreStep.value === 2) { + // Step 2: Verify OTP + set new password + try { + await authStore.verifyRestore(restoreEmail.value, restoreOtp.value, restoreNewPassword.value) + // Success — show success message + restoreStep.value = 3 + // Auto-close modal and redirect after short delay + setTimeout(() => { + emit('close') + if (authStore.isKycComplete) { + router.push('/dashboard') + } else { + router.push('/complete-kyc') + } + }, 2000) + } catch (err: any) { + // Capture error from store — display user-friendly message + restoreError.value = authStore.error || err?.response?.data?.detail || 'auth.restoreError' + // Log the actual error for debugging + console.warn('Account restore step 2 failed:', err) + } finally { + // GUARANTEE: loading state is always reset, even if catch throws + authStore.isLoading = false + } + } +} diff --git a/frontend/src/components/admin/PackageEditModal.vue b/frontend/src/components/admin/PackageEditModal.vue new file mode 100644 index 0000000..c09a364 --- /dev/null +++ b/frontend/src/components/admin/PackageEditModal.vue @@ -0,0 +1,482 @@ + + + diff --git a/frontend/src/components/dashboard/ComplexExpenseModal.vue b/frontend/src/components/dashboard/ComplexExpenseModal.vue index b5cecab..57fb62a 100644 --- a/frontend/src/components/dashboard/ComplexExpenseModal.vue +++ b/frontend/src/components/dashboard/ComplexExpenseModal.vue @@ -180,6 +180,7 @@ const errorMessage = ref(null) interface CategoryOption { id: number name: string + code?: string parent_id: number | null } @@ -208,14 +209,16 @@ async function fetchCategories() { window.__allCostCategories = allCategories } catch (err) { console.error('[ComplexExpenseModal] Failed to load categories:', err) - // Fallback: provide basic categories + // Fallback: provide basic categories — IDs must match vehicle.cost_categories in DB mainCategories.value = [ - { id: 1, name: 'Üzemanyag', parent_id: null }, - { id: 2, name: 'Szerviz / Karbantartás', parent_id: null }, - { id: 3, name: 'Parkolás / Útdíj', parent_id: null }, + { id: 1, name: 'Üzemanyag / Töltés', parent_id: null }, + { id: 2, name: 'Szerviz & Karbantartás', parent_id: null }, + { id: 3, name: 'Gumiabroncsok', parent_id: null }, { id: 4, name: 'Biztosítás', parent_id: null }, - { id: 5, name: 'Adók / Illetékek', parent_id: null }, - { id: 6, name: 'Egyéb', parent_id: null }, + { id: 5, name: 'Adók', parent_id: null }, + { id: 6, name: 'Útdíj & Parkolás', parent_id: null }, + { id: 9, name: 'Ápolás & Kozmetika', parent_id: null }, + { id: 10, name: 'Egyéb', parent_id: null }, ] } } @@ -246,22 +249,23 @@ async function handleSubmit() { let categoryId: number if (props.isFeeMode) { - // Fee mode: pre-filled as FEES + // Fee mode: pre-filled as FEES — resolve dynamically by code, not by hardcoded ID costType = 'fee' - categoryId = 3 // Parkolás / Útdíj + const allCats: CategoryOption[] = (window as any).__allCostCategories || [] + const feesCat = allCats.find((c: CategoryOption) => c.code === 'FEES' && c.parent_id === null) + categoryId = feesCat?.id || 6 // Fallback to ID 6 if lookup fails } else { // Complex mode: use selected sub-category or main category costType = 'other' - categoryId = Number(form.subCategory || form.mainCategory) || 6 + categoryId = Number(form.subCategory || form.mainCategory) || 10 } const payload = { asset_id: props.vehicle.id, // organization_id is omitted — backend auto-resolves from asset category_id: categoryId, - cost_type: costType, - amount_local: form.amount, - currency_local: 'HUF' as const, + amount_net: form.amount, + currency: 'HUF' as const, date: new Date(form.date).toISOString(), mileage_at_cost: null, description: form.description || (props.isFeeMode ? 'Parkolás / Útdíj' : 'Egyéb költség'), diff --git a/frontend/src/components/dashboard/PrivateVehicleManager.vue b/frontend/src/components/dashboard/PrivateVehicleManager.vue index 766a477..403a627 100644 --- a/frontend/src/components/dashboard/PrivateVehicleManager.vue +++ b/frontend/src/components/dashboard/PrivateVehicleManager.vue @@ -13,23 +13,13 @@
@@ -70,7 +60,7 @@ :key="vehicle.id" :vehicle="vehicle" @click="openDetailView(vehicle)" - @edit="openEditDirect(vehicle)" + @edit="$emit('edit-vehicle', vehicle)" @set-primary="setPrimaryVehicle" /> @@ -86,41 +76,17 @@

Még nincsenek járművek

Kattints az "Új jármű hozzáadása" gombra az első jármű felvételéhez.

- - - - - - -
{{ triggerEditFromParent }}
@@ -138,18 +102,16 @@ import { ref, computed, nextTick, onMounted, watch } from 'vue' import type { VehicleData } from '../../types/vehicle' import { useVehicleStore } from '../../stores/vehicle' -import api from '../../api/axios' -import VehicleFormModal from './VehicleFormModal.vue' import VehicleDetailModal from '../vehicle/VehicleDetailModal.vue' import VehicleCardStandard from '../vehicle/VehicleCardStandard.vue' const props = defineProps<{ targetVehicleId?: string | null - editTargetVehicle?: VehicleData | null }>() const emit = defineEmits<{ - 'clear-edit-target': [] + 'add-new': [] + 'edit-vehicle': [vehicle: VehicleData] }>() const vehicleStore = useVehicleStore() @@ -188,71 +150,12 @@ function openDetailView(vehicle: VehicleData) { isDetailOpen.value = true } -// ── Direct Edit (from card edit button) ── -function openEditDirect(vehicle: VehicleData) { - editingVehicle.value = vehicle ? { ...vehicle } : null - isEditFormOpen.value = true -} - -// ── Bridge: Detail → Edit ── +// ── Bridge: Detail → Edit (emits to parent DashboardView) ── async function openEditFromDetail(vehicle: VehicleData) { isDetailOpen.value = false // Use nextTick so detail modal closes before edit opens await nextTick() - editingVehicle.value = vehicle ? { ...vehicle } : null - isEditFormOpen.value = true -} - -// ── Modal state (Add) ── -const isAddFormOpen = ref(false) -const isCheckingQuota = ref(false) - -/** - * Preemptive quota check before opening the add-vehicle modal. - * Calls GET /api/v1/assets/vehicles/quota-status. - * If the user has reached their limit, shows a blocking toast/alert instead of opening the form. - */ -async function handleAddNewClick() { - if (isCheckingQuota.value) return - isCheckingQuota.value = true - - try { - const res = await api.get('/assets/vehicles/quota-status') - const { can_add, current_count, limit } = res.data - - if (!can_add) { - // Block — show a prominent warning instead of opening the modal - alert( - `Elérted a csomagodhoz tartozó járműlimitet (${current_count}/${limit})! ` + - 'Kérjük, válts magasabb csomagra az új jármű rögzítéséhez.' - ) - return - } - - // Quota OK — open the add form - isAddFormOpen.value = true - } catch (err: any) { - console.error('[PrivateVehicleManager] Quota check failed:', err) - // Fail open — if the API is unreachable, let the user proceed - isAddFormOpen.value = true - } finally { - isCheckingQuota.value = false - } -} - -function onVehicleSaved() { - isAddFormOpen.value = false - // In real implementation, refresh the vehicle list from the store -} - -// ── Modal state (Edit) ── -const isEditFormOpen = ref(false) -const editingVehicle = ref(null) - -function onVehicleEdited() { - isEditFormOpen.value = false - editingVehicle.value = null - // In real implementation, refresh the vehicle list from the store + emit('edit-vehicle', vehicle) } // ── Targeted scroll to vehicle card ── @@ -276,24 +179,9 @@ watch(() => props.targetVehicleId, (newVal) => { } }) -// ── Task 3: Watch for editTargetVehicle from parent (DashboardView) ── -const triggerEditFromParent = computed(() => { - if (props.editTargetVehicle) { - // Open the edit form directly with the vehicle data - editingVehicle.value = { ...props.editTargetVehicle } as VehicleData - isEditFormOpen.value = true - // Clear the prop so it doesn't re-trigger - emit('clear-edit-target') - } - return props.editTargetVehicle?.id || null -}) - // ── Set Primary Vehicle ── async function setPrimaryVehicle(vehicle: VehicleData) { console.log('[PrivateVehicleManager] setPrimaryVehicle called for:', vehicle.id, vehicle.license_plate) - // TODO: Real API call — PUT /api/v1/assets/vehicles/{id} with { is_primary: true } - // This will also need to unset any other primary vehicle for this user. - // For now, just log and update local state optimistically. vehicleStore.setPrimaryVehicle(vehicle.id) } diff --git a/frontend/src/components/dashboard/SimpleFuelModal.vue b/frontend/src/components/dashboard/SimpleFuelModal.vue index 51ee062..549a86e 100644 --- a/frontend/src/components/dashboard/SimpleFuelModal.vue +++ b/frontend/src/components/dashboard/SimpleFuelModal.vue @@ -110,8 +110,9 @@ +.overflow-y-auto::-webkit-scrollbar-thumb { + background: #cbd5e1; + border-radius: 3px; +} +.overflow-y-auto::-webkit-scrollbar-thumb:hover { + background: #94a3b8; +} + +/* ── Autocomplete dropdown animation ── */ +.absolute.z-50 { + animation: dropdown-fade-in 0.15s ease-out; +} +@keyframes dropdown-fade-in { + from { + opacity: 0; + transform: translateY(-4px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + \ No newline at end of file diff --git a/frontend/src/components/header/HeaderCompanySwitcher.vue b/frontend/src/components/header/HeaderCompanySwitcher.vue index 58debe5..9b1cc16 100644 --- a/frontend/src/components/header/HeaderCompanySwitcher.vue +++ b/frontend/src/components/header/HeaderCompanySwitcher.vue @@ -4,29 +4,15 @@ v-if="authStore.isAuthenticated" @click.stop="isOpen = !isOpen" :disabled="authStore.isLoading" - class="flex items-center gap-2 px-3 py-2 rounded-xl text-sm font-medium transition-all duration-200 cursor-pointer" - :class="orgButtonClass" + class="flex items-center gap-2 px-3 py-2 rounded-xl text-sm font-medium transition-all duration-200 cursor-pointer text-white/70 hover:bg-white/5 hover:text-white" > - - - - + + + - - - - - - {{ orgButtonLabel }} - + {{ t('header.garageSelector') }} + - + - -
- -
+ + + - -
- - - - - - {{ org.display_name || org.name || `${t('header.companyPrefix')} #${org.organization_id}` }} - - +
+
+ {{ t('header.myCompanies') }} +
+
+ - - - -
+ + + + {{ org.display_name || org.name || `${t('header.companyPrefix')} #${org.organization_id}` }} + + + +
+
+ - -
- {{ t('header.noCompanies') }} -
- - -
- - -
- -
+ +
+ + @@ -136,7 +134,15 @@ onMounted(async () => { if (authStore.myOrganizations.length === 0 && authStore.isAuthenticated) { await authStore.fetchMyOrganizations() } - console.log('🔍 [HeaderCompanySwitcher] Available companies:', authStore.myOrganizations) + console.log('🔍 [HeaderCompanySwitcher] All orgs:', authStore.myOrganizations) + console.log('🔍 [HeaderCompanySwitcher] Company orgs (filtered):', companyOrganizations.value) +}) + +// ── Filter: only real companies (exclude individual/private orgs) ── +const companyOrganizations = computed(() => { + return authStore.myOrganizations.filter( + (org) => org.org_type && org.org_type !== 'individual' + ) }) // ── Route-based detection ───────────────────────────────────────── @@ -144,55 +150,6 @@ const isOnCompanyGarage = computed(() => { return route.path.startsWith('/organization/') }) -// ── Current org ID from route (when on company garage) ──────────── -const currentOrgId = computed(() => { - if (!isOnCompanyGarage.value) return null - return Number(route.params.id) || null -}) - -// ── Filtered organizations: exclude the current one when on garage ─ -const filteredOrganizations = computed(() => { - const orgs = authStore.myOrganizations - if (isOnCompanyGarage.value && currentOrgId.value) { - return orgs.filter((o) => o.organization_id !== currentOrgId.value) - } - return orgs -}) - -// ── Organization Button State ────────────────────────────────────── -const orgButtonState = computed(() => { - if (!authStore.isAuthenticated) return 'create' - if (isOnCompanyGarage.value) return 'on-garage' - if (authStore.isCorporateMode) return 'corporate' - if (authStore.hasBusinessOrganization) return 'switch' - return 'create' -}) - -const orgButtonLabel = computed(() => { - switch (orgButtonState.value) { - case 'create': - return t('header.newCompany') - case 'switch': { - const count = authStore.myOrganizations.length - if (count === 1) return t('header.myCompany') - return t('header.myCompanies') - } - case 'corporate': - return t('header.personalGarage') - case 'on-garage': - return t('header.privateGarage') - default: - return t('header.company') - } -}) - -const orgButtonClass = computed(() => { - if (orgButtonState.value === 'corporate') { - return 'bg-yellow-500/15 text-yellow-400 border border-yellow-500/20 hover:bg-yellow-500/25 hover:text-yellow-300' - } - return 'text-white/70 hover:bg-white/5 hover:text-white' -}) - // ── Check if org is the active one ───────────────────────────────── function isActiveOrg(org: OrganizationItem): boolean { return authStore.user?.active_organization_id === org.organization_id @@ -205,12 +162,20 @@ async function goToPersonalDashboard() { router.push('/dashboard') } -// ── Navigate to onboard ─────────────────────────────────────────── -function goToOnboard() { +// ── PLG: Create Company ─────────────────────────────────────────── +function handleCreateCompany() { isOpen.value = false + console.log('🔧 [PLG] Create Company clicked — modal/onboarding coming soon') router.push('/company/onboard') } +// ── PLG: Join Company ───────────────────────────────────────────── +function handleJoinCompany() { + isOpen.value = false + console.log('🔧 [PLG] Join Company clicked — join flow coming soon') + alert('🔗 Join Company — This feature is coming soon!') +} + // ── Close dropdown on outside click ─────────────────────────────── function handleOutsideClick(e: MouseEvent) { const target = e.target as HTMLElement diff --git a/frontend/src/components/header/HeaderProfile.vue b/frontend/src/components/header/HeaderProfile.vue index 4f8ab08..a54fc33 100644 --- a/frontend/src/components/header/HeaderProfile.vue +++ b/frontend/src/components/header/HeaderProfile.vue @@ -50,6 +50,28 @@ {{ t('header.profile') }} + + + @@ -120,12 +142,26 @@ const initials = computed(() => { return '?' }) +// ── Admin check: only admin/superadmin/region_admin/country_admin/moderator ── +const isAdmin = computed(() => { + const role = authStore.user?.role + if (!role) return false + const adminRoles = ['superadmin', 'admin', 'region_admin', 'country_admin', 'moderator'] + return adminRoles.includes(role) +}) + // ── Navigate to Profile ──────────────────────────────────────────── function goToProfile() { isOpen.value = false router.push('/profile') } +// ── Navigate to Admin Center ─────────────────────────────────────── +function goToAdmin() { + isOpen.value = false + router.push('/admin') +} + // ── Logout handler ───────────────────────────────────────────────── function handleLogout() { isOpen.value = false diff --git a/frontend/src/components/organization/CompanyDataModal.vue b/frontend/src/components/organization/CompanyDataModal.vue new file mode 100644 index 0000000..8c0930f --- /dev/null +++ b/frontend/src/components/organization/CompanyDataModal.vue @@ -0,0 +1,318 @@ + + + diff --git a/frontend/src/components/organization/OrganizationSettingsModal.vue b/frontend/src/components/organization/OrganizationSettingsModal.vue new file mode 100644 index 0000000..17868d4 --- /dev/null +++ b/frontend/src/components/organization/OrganizationSettingsModal.vue @@ -0,0 +1,251 @@ + + + diff --git a/frontend/src/components/vehicle/VehicleDetailModal.vue b/frontend/src/components/vehicle/VehicleDetailModal.vue index cf78bfe..b73df38 100644 --- a/frontend/src/components/vehicle/VehicleDetailModal.vue +++ b/frontend/src/components/vehicle/VehicleDetailModal.vue @@ -170,168 +170,144 @@
- -
-

Éves Összköltség (TCO)

-

1,245,000 Ft

-

~ 3,411 Ft / nap

+ +
+
- - -