view data fixes
This commit is contained in:
@@ -1,12 +1,18 @@
|
||||
import type { UserDto } from '@/lib/types/admin';
|
||||
import type { AdminUserViewData } from '@/lib/view-data/AdminUserViewData';
|
||||
import type { DashboardStatsViewData } from '@/lib/view-data/DashboardStatsViewData';
|
||||
import { ViewModel } from "../contracts/view-models/ViewModel";
|
||||
import { UserStatusDisplay } from "../display-objects/UserStatusDisplay";
|
||||
import { UserRoleDisplay } from "../display-objects/UserRoleDisplay";
|
||||
import { DateDisplay } from "../display-objects/DateDisplay";
|
||||
import { ActivityLevelDisplay } from "../display-objects/ActivityLevelDisplay";
|
||||
|
||||
/**
|
||||
* AdminUserViewModel
|
||||
*
|
||||
*
|
||||
* View Model for admin user management.
|
||||
* Transforms API DTO into UI-ready state with formatting and derived fields.
|
||||
*/
|
||||
export class AdminUserViewModel {
|
||||
export class AdminUserViewModel extends ViewModel {
|
||||
id: string;
|
||||
email: string;
|
||||
displayName: string;
|
||||
@@ -18,73 +24,48 @@ export class AdminUserViewModel {
|
||||
lastLoginAt?: Date;
|
||||
primaryDriverId?: string;
|
||||
|
||||
// UI-specific derived fields
|
||||
// UI-specific derived fields (primitive outputs only)
|
||||
readonly roleBadges: string[];
|
||||
readonly statusBadge: { label: string; variant: string };
|
||||
readonly statusBadgeLabel: string;
|
||||
readonly statusBadgeVariant: 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;
|
||||
constructor(viewData: AdminUserViewData) {
|
||||
super();
|
||||
this.id = viewData.id;
|
||||
this.email = viewData.email;
|
||||
this.displayName = viewData.displayName;
|
||||
this.roles = viewData.roles;
|
||||
this.status = viewData.status;
|
||||
this.isSystemAdmin = viewData.isSystemAdmin;
|
||||
this.createdAt = new Date(viewData.createdAt);
|
||||
this.updatedAt = new Date(viewData.updatedAt);
|
||||
this.lastLoginAt = viewData.lastLoginAt ? new Date(viewData.lastLoginAt) : undefined;
|
||||
this.primaryDriverId = viewData.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 role badges using Display Object
|
||||
this.roleBadges = this.roles.map(role => UserRoleDisplay.roleLabel(role));
|
||||
|
||||
// Derive status badge
|
||||
this.statusBadge = this.getStatusBadge();
|
||||
// Derive status badge using Display Object
|
||||
this.statusBadgeLabel = UserStatusDisplay.statusLabel(this.status);
|
||||
this.statusBadgeVariant = UserStatusDisplay.statusVariant(this.status);
|
||||
|
||||
// Format dates
|
||||
this.lastLoginFormatted = this.lastLoginAt
|
||||
? this.lastLoginAt.toLocaleDateString()
|
||||
// Format dates using Display Object
|
||||
this.lastLoginFormatted = this.lastLoginAt
|
||||
? DateDisplay.formatShort(this.lastLoginAt)
|
||||
: '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' };
|
||||
}
|
||||
this.createdAtFormatted = DateDisplay.formatShort(this.createdAt);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DashboardStatsViewModel
|
||||
*
|
||||
*
|
||||
* View Model for admin dashboard statistics.
|
||||
* Provides formatted statistics and derived metrics for UI.
|
||||
*/
|
||||
export class DashboardStatsViewModel {
|
||||
export class DashboardStatsViewModel extends ViewModel {
|
||||
totalUsers: number;
|
||||
activeUsers: number;
|
||||
suspendedUsers: number;
|
||||
@@ -113,52 +94,26 @@ export class DashboardStatsViewModel {
|
||||
logins: number;
|
||||
}[];
|
||||
|
||||
// UI-specific derived fields
|
||||
// UI-specific derived fields (primitive outputs only)
|
||||
readonly activeRate: number;
|
||||
readonly activeRateFormatted: string;
|
||||
readonly adminRatio: string;
|
||||
readonly activityLevel: 'low' | 'medium' | 'high';
|
||||
readonly activityLevelLabel: string;
|
||||
readonly activityLevelValue: '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;
|
||||
constructor(viewData: DashboardStatsViewData) {
|
||||
super();
|
||||
this.totalUsers = viewData.totalUsers;
|
||||
this.activeUsers = viewData.activeUsers;
|
||||
this.suspendedUsers = viewData.suspendedUsers;
|
||||
this.deletedUsers = viewData.deletedUsers;
|
||||
this.systemAdmins = viewData.systemAdmins;
|
||||
this.recentLogins = viewData.recentLogins;
|
||||
this.newUsersToday = viewData.newUsersToday;
|
||||
this.userGrowth = viewData.userGrowth;
|
||||
this.roleDistribution = viewData.roleDistribution;
|
||||
this.statusDistribution = viewData.statusDistribution;
|
||||
this.activityTimeline = viewData.activityTimeline;
|
||||
|
||||
// Derive active rate
|
||||
this.activeRate = this.totalUsers > 0 ? (this.activeUsers / this.totalUsers) * 100 : 0;
|
||||
@@ -168,44 +123,40 @@ export class DashboardStatsViewModel {
|
||||
const nonAdmins = Math.max(1, this.totalUsers - this.systemAdmins);
|
||||
this.adminRatio = `1:${Math.floor(nonAdmins / Math.max(1, this.systemAdmins))}`;
|
||||
|
||||
// Derive activity level
|
||||
// Derive activity level using Display Object
|
||||
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';
|
||||
}
|
||||
this.activityLevelLabel = ActivityLevelDisplay.levelLabel(engagementRate);
|
||||
this.activityLevelValue = ActivityLevelDisplay.levelValue(engagementRate);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* UserListViewModel
|
||||
*
|
||||
*
|
||||
* View Model for user list with pagination and filtering state.
|
||||
*/
|
||||
export class UserListViewModel {
|
||||
export class UserListViewModel extends ViewModel {
|
||||
users: AdminUserViewModel[];
|
||||
total: number;
|
||||
page: number;
|
||||
limit: number;
|
||||
totalPages: number;
|
||||
|
||||
// UI-specific derived fields
|
||||
// UI-specific derived fields (primitive outputs only)
|
||||
readonly hasUsers: boolean;
|
||||
readonly showPagination: boolean;
|
||||
readonly startIndex: number;
|
||||
readonly endIndex: number;
|
||||
|
||||
constructor(data: {
|
||||
users: UserDto[];
|
||||
users: AdminUserViewData[];
|
||||
total: number;
|
||||
page: number;
|
||||
limit: number;
|
||||
totalPages: number;
|
||||
}) {
|
||||
this.users = data.users.map(dto => new AdminUserViewModel(dto));
|
||||
super();
|
||||
this.users = data.users.map(viewData => new AdminUserViewModel(viewData));
|
||||
this.total = data.total;
|
||||
this.page = data.page;
|
||||
this.limit = data.limit;
|
||||
|
||||
Reference in New Issue
Block a user