Files
gridpilot.gg/apps/website/lib/view-models/AdminUserViewModel.ts
2026-01-14 16:28:39 +01:00

220 lines
5.9 KiB
TypeScript

import type { UserDto } from '@/lib/types/admin';
/**
* AdminUserViewModel
*
* View Model for admin user management.
* Transforms API DTO into UI-ready state with formatting and derived fields.
*/
export class AdminUserViewModel {
id: string;
email: string;
displayName: string;
roles: string[];
status: string;
isSystemAdmin: boolean;
createdAt: Date;
updatedAt: Date;
lastLoginAt?: Date;
primaryDriverId?: string;
// UI-specific derived fields
readonly roleBadges: string[];
readonly statusBadge: { label: string; variant: string };
readonly lastLoginFormatted: string;
readonly createdAtFormatted: string;
readonly canSuspend: boolean;
readonly canActivate: boolean;
readonly canDelete: boolean;
constructor(dto: UserDto) {
this.id = dto.id;
this.email = dto.email;
this.displayName = dto.displayName;
this.roles = dto.roles;
this.status = dto.status;
this.isSystemAdmin = dto.isSystemAdmin;
this.createdAt = new Date(dto.createdAt);
this.updatedAt = new Date(dto.updatedAt);
this.lastLoginAt = dto.lastLoginAt ? new Date(dto.lastLoginAt) : undefined;
this.primaryDriverId = dto.primaryDriverId;
// Derive role badges
this.roleBadges = this.roles.map(role => {
switch (role) {
case 'owner': return 'Owner';
case 'admin': return 'Admin';
case 'user': return 'User';
default: return role;
}
});
// Derive status badge
this.statusBadge = this.getStatusBadge();
// Format dates
this.lastLoginFormatted = this.lastLoginAt
? this.lastLoginAt.toLocaleDateString()
: 'Never';
this.createdAtFormatted = this.createdAt.toLocaleDateString();
// Derive action permissions
this.canSuspend = this.status === 'active';
this.canActivate = this.status === 'suspended';
this.canDelete = this.status !== 'deleted';
}
private getStatusBadge(): { label: string; variant: string } {
switch (this.status) {
case 'active':
return { label: 'Active', variant: 'performance-green' };
case 'suspended':
return { label: 'Suspended', variant: 'yellow-500' };
case 'deleted':
return { label: 'Deleted', variant: 'racing-red' };
default:
return { label: this.status, variant: 'gray-500' };
}
}
}
/**
* DashboardStatsViewModel
*
* View Model for admin dashboard statistics.
* Provides formatted statistics and derived metrics for UI.
*/
export class DashboardStatsViewModel {
totalUsers: number;
activeUsers: number;
suspendedUsers: number;
deletedUsers: number;
systemAdmins: number;
recentLogins: number;
newUsersToday: number;
userGrowth: {
label: string;
value: number;
color: string;
}[];
roleDistribution: {
label: string;
value: number;
color: string;
}[];
statusDistribution: {
active: number;
suspended: number;
deleted: number;
};
activityTimeline: {
date: string;
newUsers: number;
logins: number;
}[];
// UI-specific derived fields
readonly activeRate: number;
readonly activeRateFormatted: string;
readonly adminRatio: string;
readonly activityLevel: 'low' | 'medium' | 'high';
constructor(data: {
totalUsers: number;
activeUsers: number;
suspendedUsers: number;
deletedUsers: number;
systemAdmins: number;
recentLogins: number;
newUsersToday: number;
userGrowth: {
label: string;
value: number;
color: string;
}[];
roleDistribution: {
label: string;
value: number;
color: string;
}[];
statusDistribution: {
active: number;
suspended: number;
deleted: number;
};
activityTimeline: {
date: string;
newUsers: number;
logins: number;
}[];
}) {
this.totalUsers = data.totalUsers;
this.activeUsers = data.activeUsers;
this.suspendedUsers = data.suspendedUsers;
this.deletedUsers = data.deletedUsers;
this.systemAdmins = data.systemAdmins;
this.recentLogins = data.recentLogins;
this.newUsersToday = data.newUsersToday;
this.userGrowth = data.userGrowth;
this.roleDistribution = data.roleDistribution;
this.statusDistribution = data.statusDistribution;
this.activityTimeline = data.activityTimeline;
// Derive active rate
this.activeRate = this.totalUsers > 0 ? (this.activeUsers / this.totalUsers) * 100 : 0;
this.activeRateFormatted = `${Math.round(this.activeRate)}%`;
// Derive admin ratio
const nonAdmins = Math.max(1, this.totalUsers - this.systemAdmins);
this.adminRatio = `1:${Math.floor(nonAdmins / Math.max(1, this.systemAdmins))}`;
// Derive activity level
const engagementRate = this.totalUsers > 0 ? (this.recentLogins / this.totalUsers) * 100 : 0;
if (engagementRate < 20) {
this.activityLevel = 'low';
} else if (engagementRate < 50) {
this.activityLevel = 'medium';
} else {
this.activityLevel = 'high';
}
}
}
/**
* UserListViewModel
*
* View Model for user list with pagination and filtering state.
*/
export class UserListViewModel {
users: AdminUserViewModel[];
total: number;
page: number;
limit: number;
totalPages: number;
// UI-specific derived fields
readonly hasUsers: boolean;
readonly showPagination: boolean;
readonly startIndex: number;
readonly endIndex: number;
constructor(data: {
users: UserDto[];
total: number;
page: number;
limit: number;
totalPages: number;
}) {
this.users = data.users.map(dto => new AdminUserViewModel(dto));
this.total = data.total;
this.page = data.page;
this.limit = data.limit;
this.totalPages = data.totalPages;
// Derive UI state
this.hasUsers = this.users.length > 0;
this.showPagination = this.totalPages > 1;
this.startIndex = this.users.length > 0 ? (this.page - 1) * this.limit + 1 : 0;
this.endIndex = this.users.length > 0 ? (this.page - 1) * this.limit + this.users.length : 0;
}
}