Files
service-finder/docs/dashboard_ui_audit.md
2026-06-23 21:11:21 +00:00

13 KiB

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

The /dashboard route is a child route under the PrivateLayout.vue wrapper:

Path Name Component Auth
/dashboard dashboard DashboardView.vue requiresAuth
/dashboard/vehicles FleetView FleetView.vue requiresAuth
/dashboard/vehicles/:id VehicleDetails VehicleDetailsView.vue requiresAuth
/dashboard/service-finder service-finder ServiceFinderView.vue requiresAuth
/dashboard/subscription subscription 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

Data Fetching (Lifecycle)

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 @open-card
2 CostsActionsCard frontend/src/components/dashboard/CostsActionsCard.vue @cost-saved
3 ServiceFinderCard frontend/src/components/dashboard/ServiceFinderCard.vue
4 GamificationCard frontend/src/components/dashboard/GamificationCard.vue @open-card
5 ProfileTrustCard 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

Data Source

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 — a minimal row component showing:

  • license_plate (via VehiclePlateBadge)
  • brand / model / nickname

Click Actions

  • Card clicknavigateToFleet()/dashboard/vehicles (or /organization/:id/vehicles)
  • Plate clickopenVehicleDetail(vehicle)/dashboard/vehicles/:id (or /organization/:id/vehicles/:id)

4. VehicleCardStandard — Full Tile Component

File: frontend/src/components/vehicle/VehicleCardStandard.vue

Props Used (template bindings)

Template Expression Backend AssetResponse Field Status
vehicle.image NOT in AssetResponse ⚠️ BROKENAssetResponse 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 ⚠️ BROKENengine is a display-only field; fuel_type is OK
vehicle.individual_equipment?.color / vehicle.color color NOT in AssetResponse ⚠️ BROKENcolor 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

API Endpoint

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. 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:

    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.