171 lines
5.5 KiB
TypeScript
171 lines
5.5 KiB
TypeScript
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 extends ViewModel {
|
|
id: string;
|
|
email: string;
|
|
displayName: string;
|
|
roles: string[];
|
|
status: string;
|
|
isSystemAdmin: boolean;
|
|
createdAt: Date;
|
|
updatedAt: Date;
|
|
lastLoginAt?: Date;
|
|
primaryDriverId?: string;
|
|
|
|
// 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) {
|
|
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 using Display Object
|
|
this.roleBadges = this.roles.map(role => UserRoleDisplay.roleLabel(role));
|
|
|
|
// Derive status badge using Display Object
|
|
this.statusBadgeLabel = UserStatusDisplay.statusLabel(this.status);
|
|
this.statusBadgeVariant = UserStatusDisplay.statusVariant(this.status);
|
|
|
|
// Format dates using Display Object
|
|
this.lastLoginFormatted = this.lastLoginAt
|
|
? DateDisplay.formatShort(this.lastLoginAt)
|
|
: 'Never';
|
|
this.createdAtFormatted = 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;
|
|
}
|
|
} |