frontend költség beállítások

This commit is contained in:
Roo
2026-06-23 21:11:21 +00:00
parent 5b437b220d
commit 71ef33bb85
90 changed files with 11850 additions and 1053 deletions

271
docs/dashboard_ui_audit.md Normal file
View File

@@ -0,0 +1,271 @@
# P0 Audit: Private Dashboard (`/dashboard`) UI & State Architecture
**Date:** 2026-06-21
**Scope:** Read-only audit of routing, components, store, and navigation paths
**Auditor:** Fast Coder (Core Developer)
---
## 1. Routing Architecture
**Router file:** [`frontend/src/router/index.ts`](frontend/src/router/index.ts)
The `/dashboard` route is a **child route** under the [`PrivateLayout.vue`](frontend/src/layouts/PrivateLayout.vue) wrapper:
| Path | Name | Component | Auth |
|------|------|-----------|------|
| `/dashboard` | `dashboard` | [`DashboardView.vue`](frontend/src/views/DashboardView.vue) | `requiresAuth` |
| `/dashboard/vehicles` | `FleetView` | [`FleetView.vue`](frontend/src/views/vehicles/FleetView.vue) | `requiresAuth` |
| `/dashboard/vehicles/:id` | `VehicleDetails` | [`VehicleDetailsView.vue`](frontend/src/views/vehicles/VehicleDetailsView.vue) | `requiresAuth` |
| `/dashboard/service-finder` | `service-finder` | [`ServiceFinderView.vue`](frontend/src/views/ServiceFinderView.vue) | `requiresAuth` |
| `/dashboard/subscription` | `subscription` | [`SubscriptionPlansView.vue`](frontend/src/views/SubscriptionPlansView.vue) | `requiresAuth` |
**Corporate mirror routes** exist under `/organization/:id/` with identical child structure.
---
## 2. DashboardView.vue — Main Entry Point
**File:** [`frontend/src/views/DashboardView.vue`](frontend/src/views/DashboardView.vue)
### Data Fetching (Lifecycle)
```typescript
onMounted(() => {
vehicleStore.fetchVehicles() // GET /assets/vehicles
authStore.fetchMyOrganizations() // GET /organizations/my
})
```
- **`fetchVehicles()`** is called unconditionally on mount.
- **`fetchMyOrganizations()`** loads the user's org list for corporate mode detection.
- No cost/financial data is fetched at the dashboard level — costs are lazy-loaded per-vehicle in the detail modal.
### 5-Card Grid Layout
The dashboard renders 5 cards in a `grid-cols-1 md:grid-cols-3 lg:grid-cols-5` layout:
| # | Card Component | File | Events |
|---|---------------|------|--------|
| 1 | `MyVehiclesCard` | [`frontend/src/components/dashboard/MyVehiclesCard.vue`](frontend/src/components/dashboard/MyVehiclesCard.vue) | `@open-card` |
| 2 | `CostsActionsCard` | [`frontend/src/components/dashboard/CostsActionsCard.vue`](frontend/src/components/dashboard/CostsActionsCard.vue) | `@cost-saved` |
| 3 | `ServiceFinderCard` | [`frontend/src/components/dashboard/ServiceFinderCard.vue`](frontend/src/components/dashboard/ServiceFinderCard.vue) | — |
| 4 | `GamificationCard` | [`frontend/src/components/dashboard/GamificationCard.vue`](frontend/src/components/dashboard/GamificationCard.vue) | `@open-card` |
| 5 | `ProfileTrustCard` | [`frontend/src/components/dashboard/ProfileTrustCard.vue`](frontend/src/components/dashboard/ProfileTrustCard.vue) | `@open-card` |
### Modal System
The DashboardView manages **3 modal layers**:
1. **Flip Modal** (`activeCard`): A 3D-flip overlay that shows `PrivateVehicleManager` when `activeCard === 'vehicles'`.
2. **VehicleDetailModal**: Deep-link modal with 4 tabs (Basics, Finance, Alerts, ServiceBook).
3. **VehicleFormModal**: Add/Edit vehicle form (standalone, decoupled from the manager).
---
## 3. MyVehiclesCard — The Garage Tile
**File:** [`frontend/src/components/dashboard/MyVehiclesCard.vue`](frontend/src/components/dashboard/MyVehiclesCard.vue)
### Data Source
```typescript
const displayVehicles = computed(() => vehicleStore.sortedVehicles)
```
Uses the Pinia store's `sortedVehicles` getter directly. Shows max **3 vehicles** (`.slice(0, 3)`).
### Vehicle Rendering
Each vehicle is rendered via [`VehicleCardCompact`](frontend/src/components/vehicle/VehicleCardCompact.vue) — a minimal row component showing:
- `license_plate` (via `VehiclePlateBadge`)
- `brand` / `model` / `nickname`
### Click Actions
- **Card click** → `navigateToFleet()``/dashboard/vehicles` (or `/organization/:id/vehicles`)
- **Plate click** → `openVehicleDetail(vehicle)``/dashboard/vehicles/:id` (or `/organization/:id/vehicles/:id`)
---
## 4. VehicleCardStandard — Full Tile Component
**File:** [`frontend/src/components/vehicle/VehicleCardStandard.vue`](frontend/src/components/vehicle/VehicleCardStandard.vue)
### Props Used (template bindings)
| Template Expression | Backend `AssetResponse` Field | Status |
|---|---|---|
| `vehicle.image` | ❌ **NOT in AssetResponse** | ⚠️ **BROKEN**`AssetResponse` has no `image` field |
| `vehicle.brand` | ✅ `brand` | OK |
| `vehicle.nickname` | ❌ **NOT in AssetResponse** | ⚠️ **BROKEN** — No `nickname` field in schema |
| `vehicle.license_plate` | ✅ `license_plate` | OK |
| `vehicle.countryCode` / `vehicle.country_code` | ❌ **NOT in AssetResponse** | ⚠️ **BROKEN** — No country code field |
| `vehicle.model` | ✅ `model` | OK |
| `vehicle.year_of_manufacture` / `vehicle.year` | ✅ `year_of_manufacture` | OK |
| `vehicle.current_mileage` / `vehicle.mileage` | ✅ `current_mileage` | OK |
| `vehicle.engine` / `vehicle.fuel_type` | ❌ `engine` **NOT in AssetResponse** | ⚠️ **BROKEN**`engine` is a display-only field; `fuel_type` is OK |
| `vehicle.individual_equipment?.color` / `vehicle.color` | ❌ `color` **NOT in AssetResponse** | ⚠️ **BROKEN**`color` is stored in `individual_equipment` JSONB, but the root `color` field doesn't exist |
| `vehicle.nextService` | ❌ **NOT in AssetResponse** | ⚠️ **BROKEN** — No `nextService` field |
### Critical Broken Bindings Summary
| Field | Used In | Backend Reality |
|-------|---------|-----------------|
| `vehicle.image` | CardStandard (image area) | Not in `AssetResponse` — always shows placeholder |
| `vehicle.nickname` | CardStandard, CardCompact | Not in `AssetResponse` — always null |
| `vehicle.countryCode` / `country_code` | CardStandard, CardCompact, DetailModal | Not in `AssetResponse` — plate badge shows no country |
| `vehicle.engine` | CardStandard (engine row) | Not in `AssetResponse` — falls back to `fuel_type` |
| `vehicle.color` | CardStandard (color row) | Not in `AssetResponse` — stored in `individual_equipment.color` |
| `vehicle.nextService` | CardStandard (service row) | Not in `AssetResponse` — always shows "—" |
| `vehicle.mileage` (deprecated) | CardStandard, DetailModal | Falls back to `current_mileage` — OK but deprecated |
---
## 5. Pinia Store Audit
**File:** [`frontend/src/stores/vehicle.ts`](frontend/src/stores/vehicle.ts)
### API Endpoint
```typescript
const res = await api.get('/assets/vehicles')
```
**Correct endpoint** — matches backend `GET /api/v1/assets/vehicles`.
### Vehicle Interface vs Backend Schema
The frontend `Vehicle` interface (line 10-109) is **mostly aligned** with the backend [`AssetResponse`](backend/app/schemas/asset.py:32). However:
| Frontend `Vehicle` Field | Backend `AssetResponse` | Match |
|---|---|---|
| `id: string` | `id: UUID` | ✅ |
| `vin` | `vin` | ✅ |
| `license_plate` | `license_plate` | ✅ |
| `name` | `name` | ✅ |
| `catalog_id` | `catalog_id` | ✅ |
| `vehicle_class` | `vehicle_class` | ✅ |
| `brand` | `brand` | ✅ |
| `model` | `model` | ✅ |
| `trim_level` | `trim_level` | ✅ |
| `fuel_type` | `fuel_type` | ✅ |
| `engine_capacity` | `engine_capacity` | ✅ |
| `power_kw` | `power_kw` | ✅ |
| `torque_nm` | `torque_nm` | ✅ |
| `cylinder_layout` | `cylinder_layout` | ✅ |
| `transmission_type` | `transmission_type` | ✅ |
| `drive_type` | `drive_type` | ✅ |
| `euro_classification` | `euro_classification` | ✅ |
| `curb_weight` | `curb_weight` | ✅ |
| `max_weight` | `max_weight` | ✅ |
| `door_count` | `door_count` | ✅ |
| `seat_count` | `seat_count` | ✅ |
| `current_mileage` | `current_mileage` | ✅ |
| `condition_score` | `condition_score` | ✅ |
| `status` | `status` | ✅ |
| `is_verified` | `is_verified` | ✅ |
| `year_of_manufacture` | `year_of_manufacture` | ✅ |
| `created_at` | `created_at` | ✅ |
| `updated_at` | `updated_at` | ✅ |
| `is_under_warranty` | `is_under_warranty` | ✅ |
| `warranty_expiry_date` | `warranty_expiry_date` | ✅ |
| `registration_certificate_number` | `registration_certificate_number` | ✅ |
| `registration_certificate_validity` | `registration_certificate_validity` | ✅ |
| `vehicle_registration_document_number` | `vehicle_registration_document_number` | ✅ |
| `registration_country` | `registration_country` | ✅ |
| `first_domestic_registration_date` | `first_domestic_registration_date` | ✅ |
| `import_country` | `import_country` | ✅ |
| `title_document_number` | `title_document_number` | ✅ |
| `engine_number` | `engine_number` | ✅ |
| `number_of_previous_owners` | `number_of_previous_owners` | ✅ |
| `is_for_sale` | `is_for_sale` | ✅ |
| `price` | `price` | ✅ |
| `currency` | `currency` | ✅ |
| `is_primary` | `is_primary` | ✅ |
| `current_organization_id` | `current_organization_id` | ✅ |
| `owner_organization_id` | `owner_organization_id` | ✅ |
| `individual_equipment` | `individual_equipment` | ✅ |
**The store interface is well-aligned with the backend.** The broken bindings are in the **display components** (`VehicleCardStandard`, `VehicleCardCompact`) which reference fields that exist in the `VehicleData` type (a superset with mock-data fallbacks) but not in the actual backend response.
---
## 6. Navigation / Drill-Down Paths
### Path 1: Dashboard → Fleet View
```
/dashboard → click card → /dashboard/vehicles (FleetView.vue)
```
FleetView renders all vehicles in a `BaseCard` grid. Clicking a card navigates to:
```
/dashboard/vehicles/:id (VehicleDetailsView.vue)
```
### Path 2: Dashboard → Vehicle Detail Modal (in-page)
```
/dashboard → plate-click → VehicleDetailModal (Teleport)
```
This is an **in-page modal**, not a route change. The modal has 4 tabs:
- **Alapadatok** (Basics) — reads from `AssetResponse` directly
- **Pénzügyek (TCO)** — lazy-fetches `GET /assets/{id}/costs`
- **Figyelmeztetések** — reads from `individual_equipment` JSONB
- **Szervizkönyv** — lazy-fetches `GET /assets/{id}/events`
### Path 3: Dashboard → Flip Modal → PrivateVehicleManager
```
/dashboard → "My Vehicles" card click → Flip Modal → PrivateVehicleManager
```
The PrivateVehicleManager shows `VehicleCardStandard` tiles in a carousel. Clicking a tile opens the same `VehicleDetailModal`.
### Path 4: Corporate Mode
```
/organization/:id → /organization/:id/vehicles/:vehicleId
```
All navigation functions in `MyVehiclesCard`, `FleetView`, and `PrivateVehicleManager` check `authStore.isCorporateMode` and route accordingly.
---
## 7. Broken Bindings & Immediate Fixes Required
### 🔴 CRITICAL (Data never renders)
| # | Component | Field | Impact |
|---|-----------|-------|--------|
| 1 | `VehicleCardStandard` | `vehicle.image` | Image area always shows placeholder SVG |
| 2 | `VehicleCardStandard` | `vehicle.nickname` | Nickname section always empty |
| 3 | `VehicleCardStandard` | `vehicle.countryCode` / `country_code` | Plate badge has no country flag |
| 4 | `VehicleCardStandard` | `vehicle.engine` | Engine row falls back to `fuel_type` (acceptable but not ideal) |
| 5 | `VehicleCardStandard` | `vehicle.color` | Color row always shows "—" |
| 6 | `VehicleCardStandard` | `vehicle.nextService` | Service row always shows "—" |
### 🟡 MODERATE (Fallback works but data is incomplete)
| # | Component | Field | Impact |
|---|-----------|-------|--------|
| 7 | `VehicleCardStandard` | `vehicle.mileage` (deprecated) | Falls back to `current_mileage` — OK |
| 8 | `VehicleDetailModal` | `vehicle.body_type` | Falls back to `individual_equipment.car_specs.body_type` — OK |
### 🟢 GREEN (Fully functional)
- All fields in the Pinia store `Vehicle` interface match the backend `AssetResponse`
- The `fetchVehicles()` endpoint (`GET /assets/vehicles`) is correct
- The `fetchAssetFinancials()` endpoint (`GET /assets/vehicles/{id}/financials`) is correct
- Cost fetching (`GET /assets/{id}/costs`) is correct
- Event fetching (`GET /assets/{id}/events`) is correct
- Navigation paths correctly distinguish private vs corporate mode
---
## 8. Recommendations
1. **Add missing fields to `AssetResponse`** (or use `individual_equipment` JSONB):
- `nickname` → add to `AssetResponse` as an optional string
- `country_code` → already in `individual_equipment`, expose at root level
- `next_service_mileage` → computed field or stored in `individual_equipment`
2. **Fix `VehicleCardStandard` to use `individual_equipment.color`** instead of `vehicle.color`:
```typescript
vehicle.individual_equipment?.color || vehicle.color || '—'
```
This already works for the color field (line 103) but `vehicle.color` is never set by the API.
3. **Remove `vehicle.image` from `VehicleCardStandard`** or add an image URL field to the backend schema if vehicle images are planned.
4. **Add `country_code` to `AssetResponse`** at the root level so the plate badge can display country flags without digging into JSONB.
5. **Consider adding a `next_service` computed field** to the backend `AssetResponse` or the store getter, derived from `current_mileage` + service interval.