diff --git a/backend/src/features/user-profile/data/user-profile.repository.ts b/backend/src/features/user-profile/data/user-profile.repository.ts index 609f6ce..181d43e 100644 --- a/backend/src/features/user-profile/data/user-profile.repository.ts +++ b/backend/src/features/user-profile/data/user-profile.repository.ts @@ -176,6 +176,7 @@ export class UserProfileRepository { ...this.mapRowToUserProfile(row), isAdmin: !!row.admin_auth0_sub, adminRole: row.admin_role || null, + vehicleCount: parseInt(row.vehicle_count, 10) || 0, }; } @@ -196,6 +197,7 @@ export class UserProfileRepository { createdAt: 'up.created_at', displayName: 'up.display_name', subscriptionTier: 'up.subscription_tier', + vehicleCount: 'vehicle_count', }; const sortColumn = sortColumnMap[sortBy] || 'up.created_at'; @@ -234,14 +236,18 @@ export class UserProfileRepository { ${whereClause} `; - // Data query with admin status join + // Data query with admin status join and vehicle count const dataQuery = ` SELECT up.id, up.auth0_sub, up.email, up.display_name, up.notification_email, up.subscription_tier, up.email_verified, up.onboarding_completed_at, up.deactivated_at, up.deactivated_by, up.created_at, up.updated_at, au.auth0_sub as admin_auth0_sub, - au.role as admin_role + au.role as admin_role, + (SELECT COUNT(*) FROM vehicles v + WHERE v.user_id = up.auth0_sub + AND v.is_active = true + AND v.deleted_at IS NULL) as vehicle_count FROM user_profiles up LEFT JOIN admin_users au ON up.auth0_sub = au.auth0_sub AND au.revoked_at IS NULL ${whereClause} @@ -275,7 +281,11 @@ export class UserProfileRepository { up.subscription_tier, up.email_verified, up.onboarding_completed_at, up.deactivated_at, up.deactivated_by, up.created_at, up.updated_at, au.auth0_sub as admin_auth0_sub, - au.role as admin_role + au.role as admin_role, + (SELECT COUNT(*) FROM vehicles v + WHERE v.user_id = up.auth0_sub + AND v.is_active = true + AND v.deleted_at IS NULL) as vehicle_count FROM user_profiles up LEFT JOIN admin_users au ON up.auth0_sub = au.auth0_sub AND au.revoked_at IS NULL WHERE up.auth0_sub = $1 diff --git a/backend/src/features/user-profile/domain/user-profile.types.ts b/backend/src/features/user-profile/domain/user-profile.types.ts index 5c19c39..eb1e993 100644 --- a/backend/src/features/user-profile/domain/user-profile.types.ts +++ b/backend/src/features/user-profile/domain/user-profile.types.ts @@ -27,6 +27,7 @@ export interface UserProfile { export interface UserWithAdminStatus extends UserProfile { isAdmin: boolean; adminRole: 'admin' | 'super_admin' | null; + vehicleCount: number; } // Pagination and filter query params for listing users @@ -36,7 +37,7 @@ export interface ListUsersQuery { search?: string; tier?: SubscriptionTier; status?: 'active' | 'deactivated' | 'all'; - sortBy?: 'email' | 'createdAt' | 'displayName' | 'subscriptionTier'; + sortBy?: 'email' | 'createdAt' | 'displayName' | 'subscriptionTier' | 'vehicleCount'; sortOrder?: 'asc' | 'desc'; } diff --git a/frontend/src/features/admin/mobile/AdminUsersMobileScreen.tsx b/frontend/src/features/admin/mobile/AdminUsersMobileScreen.tsx index 4701fbf..a8dd94c 100644 --- a/frontend/src/features/admin/mobile/AdminUsersMobileScreen.tsx +++ b/frontend/src/features/admin/mobile/AdminUsersMobileScreen.tsx @@ -81,6 +81,13 @@ const StatusBadge: React.FC<{ active: boolean }> = ({ active }) => ( ); +// Vehicle count badge component +const VehicleCountBadge: React.FC<{ count: number }> = ({ count }) => ( + + {count} {count === 1 ? 'vehicle' : 'vehicles'} + +); + export const AdminUsersMobileScreen: React.FC = () => { const { isAdmin, loading: adminLoading } = useAdminAccess(); @@ -453,6 +460,7 @@ export const AdminUsersMobileScreen: React.FC = () => { )}
+ {user.isAdmin && ( diff --git a/frontend/src/features/admin/types/admin.types.ts b/frontend/src/features/admin/types/admin.types.ts index d3a183d..7a2d378 100644 --- a/frontend/src/features/admin/types/admin.types.ts +++ b/frontend/src/features/admin/types/admin.types.ts @@ -237,6 +237,7 @@ export interface ManagedUser { updatedAt: string; isAdmin: boolean; adminRole: 'admin' | 'super_admin' | null; + vehicleCount: number; } // List users response with pagination @@ -254,7 +255,7 @@ export interface ListUsersParams { search?: string; tier?: SubscriptionTier; status?: 'active' | 'deactivated' | 'all'; - sortBy?: 'email' | 'createdAt' | 'displayName' | 'subscriptionTier'; + sortBy?: 'email' | 'createdAt' | 'displayName' | 'subscriptionTier' | 'vehicleCount'; sortOrder?: 'asc' | 'desc'; } diff --git a/frontend/src/pages/admin/AdminUsersPage.tsx b/frontend/src/pages/admin/AdminUsersPage.tsx index 25e9b22..1638f0d 100644 --- a/frontend/src/pages/admin/AdminUsersPage.tsx +++ b/frontend/src/pages/admin/AdminUsersPage.tsx @@ -420,6 +420,7 @@ export const AdminUsersPage: React.FC = () => { Email Display Name Tier + Vehicles Status Admin Created @@ -450,6 +451,7 @@ export const AdminUsersPage: React.FC = () => { + {user.vehicleCount}