view data fixes

This commit is contained in:
2026-01-23 15:30:23 +01:00
parent e22033be38
commit f8099f04bc
213 changed files with 3466 additions and 3003 deletions

View File

@@ -1,10 +1,8 @@
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";
import { UserStatusDisplay } from "@/lib/display-objects/UserStatusDisplay";
import { UserRoleDisplay } from "@/lib/display-objects/UserRoleDisplay";
import { DateDisplay } from "@/lib/display-objects/DateDisplay";
/**
* AdminUserViewModel
@@ -13,159 +11,48 @@ import { ActivityLevelDisplay } from "../display-objects/ActivityLevelDisplay";
* Transforms API DTO into UI-ready state with formatting and derived fields.
*/
export class AdminUserViewModel extends ViewModel {
id: string;
email: string;
displayName: string;
roles: string[];
status: string;
isSystemAdmin: boolean;
createdAt: Date;
updatedAt: Date;
lastLoginAt?: Date;
primaryDriverId?: string;
private readonly data: AdminUserViewData;
// UI-specific derived fields (primitive outputs only)
readonly roleBadges: string[];
readonly statusBadgeLabel: string;
readonly statusBadgeVariant: string;
readonly lastLoginFormatted: string;
readonly createdAtFormatted: string;
constructor(viewData: AdminUserViewData) {
constructor(data: 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;
this.data = data;
}
// Derive role badges using Display Object
this.roleBadges = this.roles.map(role => UserRoleDisplay.roleLabel(role));
get id(): string { return this.data.id; }
get email(): string { return this.data.email; }
get displayName(): string { return this.data.displayName; }
get roles(): string[] { return this.data.roles; }
get status(): string { return this.data.status; }
get isSystemAdmin(): boolean { return this.data.isSystemAdmin; }
get createdAt(): string { return this.data.createdAt; }
get updatedAt(): string { return this.data.updatedAt; }
get lastLoginAt(): string | undefined { return this.data.lastLoginAt; }
get primaryDriverId(): string | undefined { return this.data.primaryDriverId; }
// Derive status badge using Display Object
this.statusBadgeLabel = UserStatusDisplay.statusLabel(this.status);
this.statusBadgeVariant = UserStatusDisplay.statusVariant(this.status);
/** UI-specific: Role badges using Display Object */
get roleBadges(): string[] {
return this.roles.map(role => UserRoleDisplay.roleLabel(role));
}
// Format dates using Display Object
this.lastLoginFormatted = this.lastLoginAt
/** UI-specific: Status badge label using Display Object */
get statusBadgeLabel(): string {
return UserStatusDisplay.statusLabel(this.status);
}
/** UI-specific: Status badge variant using Display Object */
get statusBadgeVariant(): string {
return UserStatusDisplay.statusVariant(this.status);
}
/** UI-specific: Formatted last login date */
get lastLoginFormatted(): string {
return this.lastLoginAt
? DateDisplay.formatShort(this.lastLoginAt)
: 'Never';
this.createdAtFormatted = DateDisplay.formatShort(this.createdAt);
}
/** UI-specific: Formatted creation date */
get createdAtFormatted(): string {
return DateDisplay.formatShort(this.createdAt);
}
}
/**
* DashboardStatsViewModel
*
* View Model for admin dashboard statistics.
* Provides formatted statistics and derived metrics for UI.
*/
export class DashboardStatsViewModel extends ViewModel {
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 (primitive outputs only)
readonly activeRate: number;
readonly activeRateFormatted: string;
readonly adminRatio: string;
readonly activityLevelLabel: string;
readonly activityLevelValue: 'low' | 'medium' | 'high';
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;
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 using Display Object
const engagementRate = this.totalUsers > 0 ? (this.recentLogins / this.totalUsers) * 100 : 0;
this.activityLevelLabel = ActivityLevelDisplay.levelLabel(engagementRate);
this.activityLevelValue = ActivityLevelDisplay.levelValue(engagementRate);
}
}
/**
* UserListViewModel
*
* View Model for user list with pagination and filtering state.
*/
export class UserListViewModel extends ViewModel {
users: AdminUserViewModel[];
total: number;
page: number;
limit: number;
totalPages: number;
// UI-specific derived fields (primitive outputs only)
readonly hasUsers: boolean;
readonly showPagination: boolean;
readonly startIndex: number;
readonly endIndex: number;
constructor(data: {
users: AdminUserViewData[];
total: number;
page: number;
limit: number;
totalPages: number;
}) {
super();
this.users = data.users.map(viewData => new AdminUserViewModel(viewData));
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;
}
}