feat: Admin User Management - Display vehicles per user with system-wide vehicle count widget #11

Closed
opened 2026-01-04 02:24:44 +00:00 by egullickson · 1 comment
Owner

Summary

Enhance the admin user management interface to display vehicle information associated with each user, and add a system-wide total vehicles widget to the admin dashboard.

Requirements

1. Total Vehicles Widget (Admin Dashboard)

  • Add a new widget displaying the total number of vehicles across all users
  • Match the existing "Total Users" widget style exactly (same card design, different icon/number)
  • Position: To the right of the Total Users widget

2. User Vehicle List (Admin User Management)

  • Display vehicles associated with each user in an expandable accordion row
  • Display format: Year / Make / Model only (privacy consideration - no additional details)
  • Interaction: Click on user row to expand/collapse vehicle list inline
  • Accordion style keeps context, no navigation away from user list

3. User Profile Page

  • Users should be able to see their own vehicles on their profile page
  • Same Year / Make / Model format

Security Requirements

  • Admin Only: Vehicle-per-user viewing is restricted to admin accounts
  • Strict User Separation: Normal users cannot see any other user's information
  • Users can only view their own vehicle information on their profile

UI/UX Specifications

Component Style
Total Vehicles Widget Match Total Users widget exactly (card, typography, spacing)
Vehicle Count Icon Vehicle/car icon (differentiate from user icon)
Accordion Row Inline expansion below user row
Vehicle List Simple table: Year

Acceptance Criteria

  • Total Vehicles widget displays correct system-wide count
  • Widget positioned to right of Total Users widget
  • Widget styling matches Total Users exactly
  • Admin can expand user row to see their vehicles
  • Vehicle list shows only Year/Make/Model
  • Non-admin users cannot access other users' vehicle data
  • Users can see their own vehicles on profile page
  • Mobile responsive (both widgets and accordion)
  • Desktop responsive
## Summary Enhance the admin user management interface to display vehicle information associated with each user, and add a system-wide total vehicles widget to the admin dashboard. ## Requirements ### 1. Total Vehicles Widget (Admin Dashboard) - Add a new widget displaying the total number of vehicles across all users - Match the existing "Total Users" widget style exactly (same card design, different icon/number) - Position: To the right of the Total Users widget ### 2. User Vehicle List (Admin User Management) - Display vehicles associated with each user in an expandable accordion row - **Display format**: Year / Make / Model only (privacy consideration - no additional details) - **Interaction**: Click on user row to expand/collapse vehicle list inline - Accordion style keeps context, no navigation away from user list ### 3. User Profile Page - Users should be able to see their own vehicles on their profile page - Same Year / Make / Model format ## Security Requirements - **Admin Only**: Vehicle-per-user viewing is restricted to admin accounts - **Strict User Separation**: Normal users cannot see any other user's information - Users can only view their own vehicle information on their profile ## UI/UX Specifications | Component | Style | |-----------|-------| | Total Vehicles Widget | Match Total Users widget exactly (card, typography, spacing) | | Vehicle Count Icon | Vehicle/car icon (differentiate from user icon) | | Accordion Row | Inline expansion below user row | | Vehicle List | Simple table: Year | Make | Model | ## Acceptance Criteria - [ ] Total Vehicles widget displays correct system-wide count - [ ] Widget positioned to right of Total Users widget - [ ] Widget styling matches Total Users exactly - [ ] Admin can expand user row to see their vehicles - [ ] Vehicle list shows only Year/Make/Model - [ ] Non-admin users cannot access other users' vehicle data - [ ] Users can see their own vehicles on profile page - [ ] Mobile responsive (both widgets and accordion) - [ ] Desktop responsive
egullickson added the
status
backlog
type
feature
labels 2026-01-04 02:24:54 +00:00
egullickson added
status
in-progress
and removed
status
backlog
labels 2026-01-04 18:19:38 +00:00
Author
Owner

Plan: Admin User Management - Vehicle Display Features

Phase: Planning | Agent: Planner | Status: AWAITING_REVIEW

Overview

This plan implements three features for issue #11: (1) Total Vehicles widget on admin dashboard, (2) expandable user rows showing vehicle details in admin user management, and (3) My Vehicles section on user profile page. The approach uses separate API endpoints for stats and vehicle details, enabling cacheable stats queries and lazy-loading of vehicle data on row expansion.

Planning Context

Decision Log

Decision Reasoning Chain
Separate stats + vehicles endpoints Dashboard needs total count for widget -> embedding in user list would require client-side aggregation -> separate /api/admin/stats endpoint is cacheable and single-purpose
Lazy-load vehicles on row expansion Users may not expand most rows -> fetching all vehicles upfront wastes bandwidth -> on-demand fetch via /api/admin/users/:id/vehicles reduces payload
Year/Make/Model only for admin view Issue specifies privacy consideration -> VIN/license plate are sensitive -> minimal data exposure follows principle of least privilege
MUI Collapse for row expansion AdminUsersPage uses MUI Table -> Collapse integrates natively with TableRow -> maintains consistent UI patterns
Reuse existing vehicles API for profile User profile shows own vehicles -> /api/vehicles is already user-scoped via JWT -> no new endpoint needed for profile feature
React Query hooks for data fetching Admin feature uses useUsers pattern (React Query) -> consistent data fetching patterns -> automatic cache invalidation
requireAdmin guard on all new endpoints Existing admin routes use this guard -> protects admin-only data -> consistent authorization pattern

Rejected Alternatives

Alternative Why Rejected
Include VIN/license plate in admin view Issue #11 explicitly requires Year/Make/Model only for privacy; additional data exposure unnecessary
Embed vehicles array in user list response Performance impact with many users; would require loading all vehicles for all users on page load
Calculate total from sum of vehicleCounts Client-side calculation inaccurate during pagination; server-side count is authoritative
Single combined stats+vehicles endpoint Violates single responsibility; stats are cacheable while vehicle lists are user-specific

Constraints & Assumptions

  • Technical: TypeScript 5.x, React 18.x, MUI 5.x, PostgreSQL 16.x, Jest for testing
  • Pattern: Feature capsule structure in backend/src/features/
  • Convention: snake_case DB columns -> camelCase API via mapRow()
  • Requirement: Mobile + Desktop responsive (320px, 768px, 1920px viewports)
  • Testing: Integration tests preferred per default-conventions domain='testing'

Known Risks

Risk Mitigation Anchor
Stats query performance with large vehicle count Single COUNT(*) query with index on is_active; no joins needed N/A (new code)
Admin accessing deactivated user's vehicles Use existing is_active filter in vehicle query vehicles.repository.ts:47 WHERE user_id = $1 AND is_active = true

Invisible Knowledge

Architecture

Admin Dashboard Flow:
  Browser
    |
    +--> GET /api/admin/stats --> {totalVehicles, totalUsers}
    |
    +--> GET /api/admin/users --> [{user, vehicleCount}]
    |
    +--> [click row] --> GET /api/admin/users/:id/vehicles
                              --> [{year, make, model}]

User Profile Flow:
  Browser --> GET /api/vehicles --> [{user's own vehicles}]

Data Flow

Stats Endpoint:
  Request --> requireAdmin guard --> Query vehicles table (COUNT) 
          --> Query user_profiles table (COUNT) --> Response

User Vehicles Endpoint:
  Request --> requireAdmin guard --> Query vehicles table (WHERE user_id)
          --> Map to {year, make, model} --> Response

Invariants

  • Admin endpoints always require valid admin JWT (requireAdmin guard)
  • Vehicle data for other users is admin-only; users can only see own vehicles
  • Vehicle display format is Year/Make/Model for all contexts (privacy)

Milestones

Milestone 1: Backend - Admin Stats Endpoint

Files:

  • backend/src/features/user-profile/data/user-profile.repository.ts
  • backend/src/features/admin/api/users.controller.ts
  • backend/src/features/admin/api/admin.routes.ts

Requirements:

  • Add getTotalVehicleCount() method to user-profile.repository.ts
  • Add getAdminStats() handler to users.controller.ts
  • Register GET /api/admin/stats route with requireAdmin guard
  • Return {totalVehicles: number, totalUsers: number}

Acceptance Criteria:

  • GET /api/admin/stats returns correct totalVehicles count
  • GET /api/admin/stats returns correct totalUsers count
  • Endpoint requires admin authentication (401 without valid admin JWT)
  • Response time < 100ms for typical dataset

Tests:

  • Test files: backend/src/features/admin/tests/integration/admin-stats.integration.test.ts
  • Test type: integration
  • Backing: default-derived
  • Scenarios:
    • Normal: Returns correct counts with populated data
    • Edge: Returns zero counts with empty database
    • Error: Returns 401 for non-admin user

Milestone 2: Backend - User Vehicles Endpoint

Files:

  • backend/src/features/user-profile/data/user-profile.repository.ts
  • backend/src/features/admin/api/users.controller.ts
  • backend/src/features/admin/api/admin.routes.ts
  • backend/src/features/admin/types/admin.types.ts

Requirements:

  • Add getUserVehiclesForAdmin(auth0Sub) method returning [{year, make, model}]
  • Add getUserVehicles() handler to users.controller.ts
  • Register GET /api/admin/users/:auth0Sub/vehicles route
  • Filter to active vehicles only (is_active = true)

Acceptance Criteria:

  • GET /api/admin/users/:auth0Sub/vehicles returns vehicle list
  • Response contains only year, make, model fields (no VIN, license plate)
  • Returns empty array for users with no vehicles
  • Endpoint requires admin authentication

Tests:

  • Test files: backend/src/features/admin/tests/integration/admin-user-vehicles.integration.test.ts
  • Test type: integration
  • Backing: default-derived
  • Scenarios:
    • Normal: Returns vehicles for user with vehicles
    • Edge: Returns empty array for user with no vehicles
    • Edge: Returns empty array for non-existent user
    • Error: Returns 401 for non-admin user

Milestone 3: Frontend Desktop - Admin Dashboard Updates

Files:

  • frontend/src/features/admin/api/admin.api.ts
  • frontend/src/features/admin/hooks/useUsers.ts
  • frontend/src/features/admin/types/admin.types.ts
  • frontend/src/pages/admin/AdminUsersPage.tsx

Requirements:

  • Add getAdminStats() and getUserVehicles(auth0Sub) to admin.api.ts
  • Add useAdminStats() and useUserVehicles(auth0Sub) hooks
  • Update AdminSectionHeader stats array to include Total Vehicles
  • Add MUI Collapse component for expandable vehicle rows
  • Display vehicles as Year / Make / Model table within collapsed section

Acceptance Criteria:

  • Total Vehicles widget displays correct count next to Total Users
  • Widget styling matches Total Users widget exactly
  • Clicking user row expands to show vehicle list
  • Vehicle list shows Year | Make | Model columns
  • Collapse animation is smooth
  • Works on desktop viewport (1920px)

Tests:

  • Test files: frontend/src/features/admin/__tests__/AdminUsersPage.test.tsx
  • Test type: integration (React Testing Library)
  • Backing: default-derived
  • Scenarios:
    • Normal: Stats display correctly
    • Normal: Row expansion shows vehicles
    • Edge: Empty vehicle list displays appropriate message

Milestone 4: Frontend Desktop - Profile Vehicles Section

Files:

  • frontend/src/pages/SettingsPage.tsx

Requirements:

  • Add My Vehicles Card section after Profile section
  • Use existing useVehicles() hook from vehicles feature
  • Display vehicles as Year / Make / Model list
  • Link to garage for vehicle management

Acceptance Criteria:

  • My Vehicles section appears on Settings page
  • Displays user's vehicles in Year / Make / Model format
  • Shows count of vehicles in section header
  • Handles empty state (no vehicles message)
  • Works on desktop viewport

Tests:

  • Test files: frontend/src/pages/__tests__/SettingsPage.test.tsx
  • Test type: integration (React Testing Library)
  • Backing: default-derived
  • Scenarios:
    • Normal: Vehicles displayed correctly
    • Edge: Empty state with no vehicles

Milestone 5: Frontend Mobile - Admin + Profile Updates

Files:

  • frontend/src/features/admin/mobile/AdminUsersMobileScreen.tsx
  • frontend/src/features/settings/mobile/MobileSettingsScreen.tsx

Requirements:

  • Add Total Vehicles stat to AdminUsersMobileScreen header
  • Add accordion expansion for user vehicles in mobile list
  • Add My Vehicles section to MobileSettingsScreen
  • Ensure touch targets >= 44px
  • Test on 320px and 768px viewports

Acceptance Criteria:

  • Total Vehicles widget visible in mobile admin view
  • User cards expand to show vehicles on tap
  • My Vehicles section works on mobile Settings
  • All touch targets meet 44px minimum
  • No horizontal scrolling required

Tests:

  • Test files: frontend/src/features/admin/mobile/__tests__/AdminUsersMobileScreen.test.tsx
  • Test type: integration (React Testing Library)
  • Backing: default-derived
  • Scenarios:
    • Normal: Stats and accordion work on mobile
    • Normal: Profile vehicles display on mobile

Milestone 6: Documentation

Files:

  • backend/src/features/admin/README.md

Requirements:

  • Document new admin stats and user vehicles endpoints
  • Update API endpoint list with new routes
  • Note privacy consideration for Year/Make/Model only

Acceptance Criteria:

  • README documents both new endpoints
  • Request/response formats are specified
  • Admin-only access requirement is noted

Milestone Dependencies

M1 ----+
       |
M2 ----+--> M3 --> M5
       |
       +--> M4 --> M5
                   |
                   +--> M6

M1 and M2 can execute in parallel (both backend).
M3 requires M1 and M2 (frontend needs backend APIs).
M4 can start immediately (uses existing API).
M5 requires M3 and M4 patterns established.
M6 follows all implementation.


Verdict: AWAITING_REVIEW | Next: Quality review of plan

## Plan: Admin User Management - Vehicle Display Features **Phase**: Planning | **Agent**: Planner | **Status**: AWAITING_REVIEW ## Overview This plan implements three features for issue #11: (1) Total Vehicles widget on admin dashboard, (2) expandable user rows showing vehicle details in admin user management, and (3) My Vehicles section on user profile page. The approach uses separate API endpoints for stats and vehicle details, enabling cacheable stats queries and lazy-loading of vehicle data on row expansion. ## Planning Context ### Decision Log | Decision | Reasoning Chain | |----------|-----------------| | Separate stats + vehicles endpoints | Dashboard needs total count for widget -> embedding in user list would require client-side aggregation -> separate /api/admin/stats endpoint is cacheable and single-purpose | | Lazy-load vehicles on row expansion | Users may not expand most rows -> fetching all vehicles upfront wastes bandwidth -> on-demand fetch via /api/admin/users/:id/vehicles reduces payload | | Year/Make/Model only for admin view | Issue specifies privacy consideration -> VIN/license plate are sensitive -> minimal data exposure follows principle of least privilege | | MUI Collapse for row expansion | AdminUsersPage uses MUI Table -> Collapse integrates natively with TableRow -> maintains consistent UI patterns | | Reuse existing vehicles API for profile | User profile shows own vehicles -> /api/vehicles is already user-scoped via JWT -> no new endpoint needed for profile feature | | React Query hooks for data fetching | Admin feature uses useUsers pattern (React Query) -> consistent data fetching patterns -> automatic cache invalidation | | requireAdmin guard on all new endpoints | Existing admin routes use this guard -> protects admin-only data -> consistent authorization pattern | ### Rejected Alternatives | Alternative | Why Rejected | |-------------|--------------| | Include VIN/license plate in admin view | Issue #11 explicitly requires Year/Make/Model only for privacy; additional data exposure unnecessary | | Embed vehicles array in user list response | Performance impact with many users; would require loading all vehicles for all users on page load | | Calculate total from sum of vehicleCounts | Client-side calculation inaccurate during pagination; server-side count is authoritative | | Single combined stats+vehicles endpoint | Violates single responsibility; stats are cacheable while vehicle lists are user-specific | ### Constraints & Assumptions - **Technical**: TypeScript 5.x, React 18.x, MUI 5.x, PostgreSQL 16.x, Jest for testing - **Pattern**: Feature capsule structure in backend/src/features/ - **Convention**: snake_case DB columns -> camelCase API via mapRow() - **Requirement**: Mobile + Desktop responsive (320px, 768px, 1920px viewports) - **Testing**: Integration tests preferred per default-conventions domain='testing' ### Known Risks | Risk | Mitigation | Anchor | |------|------------|--------| | Stats query performance with large vehicle count | Single COUNT(*) query with index on is_active; no joins needed | N/A (new code) | | Admin accessing deactivated user's vehicles | Use existing is_active filter in vehicle query | vehicles.repository.ts:47 `WHERE user_id = $1 AND is_active = true` | ## Invisible Knowledge ### Architecture ``` Admin Dashboard Flow: Browser | +--> GET /api/admin/stats --> {totalVehicles, totalUsers} | +--> GET /api/admin/users --> [{user, vehicleCount}] | +--> [click row] --> GET /api/admin/users/:id/vehicles --> [{year, make, model}] User Profile Flow: Browser --> GET /api/vehicles --> [{user's own vehicles}] ``` ### Data Flow ``` Stats Endpoint: Request --> requireAdmin guard --> Query vehicles table (COUNT) --> Query user_profiles table (COUNT) --> Response User Vehicles Endpoint: Request --> requireAdmin guard --> Query vehicles table (WHERE user_id) --> Map to {year, make, model} --> Response ``` ### Invariants - Admin endpoints always require valid admin JWT (requireAdmin guard) - Vehicle data for other users is admin-only; users can only see own vehicles - Vehicle display format is Year/Make/Model for all contexts (privacy) ## Milestones ### Milestone 1: Backend - Admin Stats Endpoint **Files**: - `backend/src/features/user-profile/data/user-profile.repository.ts` - `backend/src/features/admin/api/users.controller.ts` - `backend/src/features/admin/api/admin.routes.ts` **Requirements**: - Add `getTotalVehicleCount()` method to user-profile.repository.ts - Add `getAdminStats()` handler to users.controller.ts - Register GET /api/admin/stats route with requireAdmin guard - Return `{totalVehicles: number, totalUsers: number}` **Acceptance Criteria**: - GET /api/admin/stats returns correct totalVehicles count - GET /api/admin/stats returns correct totalUsers count - Endpoint requires admin authentication (401 without valid admin JWT) - Response time < 100ms for typical dataset **Tests**: - **Test files**: `backend/src/features/admin/tests/integration/admin-stats.integration.test.ts` - **Test type**: integration - **Backing**: default-derived - **Scenarios**: - Normal: Returns correct counts with populated data - Edge: Returns zero counts with empty database - Error: Returns 401 for non-admin user --- ### Milestone 2: Backend - User Vehicles Endpoint **Files**: - `backend/src/features/user-profile/data/user-profile.repository.ts` - `backend/src/features/admin/api/users.controller.ts` - `backend/src/features/admin/api/admin.routes.ts` - `backend/src/features/admin/types/admin.types.ts` **Requirements**: - Add `getUserVehiclesForAdmin(auth0Sub)` method returning `[{year, make, model}]` - Add `getUserVehicles()` handler to users.controller.ts - Register GET /api/admin/users/:auth0Sub/vehicles route - Filter to active vehicles only (is_active = true) **Acceptance Criteria**: - GET /api/admin/users/:auth0Sub/vehicles returns vehicle list - Response contains only year, make, model fields (no VIN, license plate) - Returns empty array for users with no vehicles - Endpoint requires admin authentication **Tests**: - **Test files**: `backend/src/features/admin/tests/integration/admin-user-vehicles.integration.test.ts` - **Test type**: integration - **Backing**: default-derived - **Scenarios**: - Normal: Returns vehicles for user with vehicles - Edge: Returns empty array for user with no vehicles - Edge: Returns empty array for non-existent user - Error: Returns 401 for non-admin user --- ### Milestone 3: Frontend Desktop - Admin Dashboard Updates **Files**: - `frontend/src/features/admin/api/admin.api.ts` - `frontend/src/features/admin/hooks/useUsers.ts` - `frontend/src/features/admin/types/admin.types.ts` - `frontend/src/pages/admin/AdminUsersPage.tsx` **Requirements**: - Add `getAdminStats()` and `getUserVehicles(auth0Sub)` to admin.api.ts - Add `useAdminStats()` and `useUserVehicles(auth0Sub)` hooks - Update AdminSectionHeader stats array to include Total Vehicles - Add MUI Collapse component for expandable vehicle rows - Display vehicles as Year / Make / Model table within collapsed section **Acceptance Criteria**: - Total Vehicles widget displays correct count next to Total Users - Widget styling matches Total Users widget exactly - Clicking user row expands to show vehicle list - Vehicle list shows Year | Make | Model columns - Collapse animation is smooth - Works on desktop viewport (1920px) **Tests**: - **Test files**: `frontend/src/features/admin/__tests__/AdminUsersPage.test.tsx` - **Test type**: integration (React Testing Library) - **Backing**: default-derived - **Scenarios**: - Normal: Stats display correctly - Normal: Row expansion shows vehicles - Edge: Empty vehicle list displays appropriate message --- ### Milestone 4: Frontend Desktop - Profile Vehicles Section **Files**: - `frontend/src/pages/SettingsPage.tsx` **Requirements**: - Add My Vehicles Card section after Profile section - Use existing `useVehicles()` hook from vehicles feature - Display vehicles as Year / Make / Model list - Link to garage for vehicle management **Acceptance Criteria**: - My Vehicles section appears on Settings page - Displays user's vehicles in Year / Make / Model format - Shows count of vehicles in section header - Handles empty state (no vehicles message) - Works on desktop viewport **Tests**: - **Test files**: `frontend/src/pages/__tests__/SettingsPage.test.tsx` - **Test type**: integration (React Testing Library) - **Backing**: default-derived - **Scenarios**: - Normal: Vehicles displayed correctly - Edge: Empty state with no vehicles --- ### Milestone 5: Frontend Mobile - Admin + Profile Updates **Files**: - `frontend/src/features/admin/mobile/AdminUsersMobileScreen.tsx` - `frontend/src/features/settings/mobile/MobileSettingsScreen.tsx` **Requirements**: - Add Total Vehicles stat to AdminUsersMobileScreen header - Add accordion expansion for user vehicles in mobile list - Add My Vehicles section to MobileSettingsScreen - Ensure touch targets >= 44px - Test on 320px and 768px viewports **Acceptance Criteria**: - Total Vehicles widget visible in mobile admin view - User cards expand to show vehicles on tap - My Vehicles section works on mobile Settings - All touch targets meet 44px minimum - No horizontal scrolling required **Tests**: - **Test files**: `frontend/src/features/admin/mobile/__tests__/AdminUsersMobileScreen.test.tsx` - **Test type**: integration (React Testing Library) - **Backing**: default-derived - **Scenarios**: - Normal: Stats and accordion work on mobile - Normal: Profile vehicles display on mobile --- ### Milestone 6: Documentation **Files**: - `backend/src/features/admin/README.md` **Requirements**: - Document new admin stats and user vehicles endpoints - Update API endpoint list with new routes - Note privacy consideration for Year/Make/Model only **Acceptance Criteria**: - README documents both new endpoints - Request/response formats are specified - Admin-only access requirement is noted ## Milestone Dependencies ``` M1 ----+ | M2 ----+--> M3 --> M5 | +--> M4 --> M5 | +--> M6 ``` M1 and M2 can execute in parallel (both backend). M3 requires M1 and M2 (frontend needs backend APIs). M4 can start immediately (uses existing API). M5 requires M3 and M4 patterns established. M6 follows all implementation. --- *Verdict*: AWAITING_REVIEW | *Next*: Quality review of plan
egullickson added
status
review
and removed
status
in-progress
labels 2026-01-04 19:19:02 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: egullickson/motovaultpro#11