330 lines
13 KiB
HTML
330 lines
13 KiB
HTML
<div class="admin-user-detail-container">
|
|
<!-- Header -->
|
|
<div class="page-header">
|
|
<div class="header-left">
|
|
<button mat-icon-button (click)="goBack()" class="back-button" aria-label="Go back to users list">
|
|
<mat-icon>arrow_back</mat-icon>
|
|
</button>
|
|
<h1 class="page-title">User Details</h1>
|
|
</div>
|
|
<div class="header-actions">
|
|
<button mat-icon-button (click)="refreshUser()" [disabled]="isLoading()"
|
|
matTooltip="Refresh user details" aria-label="Refresh">
|
|
<mat-icon>refresh</mat-icon>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Breadcrumb -->
|
|
<nav class="breadcrumb" aria-label="Breadcrumb navigation">
|
|
<a routerLink="/admin" class="breadcrumb-link">Admin</a>
|
|
<mat-icon class="breadcrumb-separator">chevron_right</mat-icon>
|
|
<a routerLink="/admin/users" class="breadcrumb-link">Users</a>
|
|
<mat-icon class="breadcrumb-separator">chevron_right</mat-icon>
|
|
<span class="breadcrumb-current">{{ user()?.username || 'User Detail' }}</span>
|
|
</nav>
|
|
|
|
<!-- Loading State -->
|
|
@if (isLoading()) {
|
|
<div class="loading-container">
|
|
<mat-spinner diameter="48"></mat-spinner>
|
|
<p class="loading-text">Loading user details...</p>
|
|
</div>
|
|
}
|
|
|
|
<!-- Error State -->
|
|
@if (error() && !isLoading()) {
|
|
<mat-card class="error-card">
|
|
<mat-card-content>
|
|
<div class="error-content">
|
|
<mat-icon class="error-icon">error</mat-icon>
|
|
<h2>Error Loading User</h2>
|
|
<p>{{ error() }}</p>
|
|
<button mat-raised-button color="primary" (click)="goBack()">
|
|
<mat-icon>arrow_back</mat-icon>
|
|
Back to Users
|
|
</button>
|
|
</div>
|
|
</mat-card-content>
|
|
</mat-card>
|
|
}
|
|
|
|
<!-- User Detail Content -->
|
|
@if (user() && !isLoading()) {
|
|
<div class="detail-content">
|
|
<!-- User Profile Card -->
|
|
<mat-card class="profile-card">
|
|
<mat-card-header>
|
|
<div class="profile-header">
|
|
<div class="user-avatar">
|
|
<mat-icon>account_circle</mat-icon>
|
|
</div>
|
|
<div class="user-info">
|
|
<h2 class="user-name">{{ user()!.username }}</h2>
|
|
<p class="user-email">{{ user()!.email }}</p>
|
|
<div class="user-badges">
|
|
<mat-chip [class]="'chip-' + getRoleColor(user()!.role)">
|
|
<mat-icon>{{ user()!.role === 'admin' ? 'admin_panel_settings' : 'person' }}</mat-icon>
|
|
{{ user()!.role | titlecase }}
|
|
</mat-chip>
|
|
<mat-chip [class]="'chip-' + getStatusColor(user()!.isActive)">
|
|
<mat-icon>{{ user()!.isActive ? 'check_circle' : 'cancel' }}</mat-icon>
|
|
{{ user()!.isActive ? 'Active' : 'Inactive' }}
|
|
</mat-chip>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</mat-card-header>
|
|
<mat-card-content>
|
|
<div class="profile-details">
|
|
<div class="detail-row">
|
|
<mat-icon>event</mat-icon>
|
|
<div class="detail-info">
|
|
<span class="detail-label">Member Since</span>
|
|
<span class="detail-value">{{ memberSince() }}</span>
|
|
</div>
|
|
</div>
|
|
<div class="detail-row">
|
|
<mat-icon>schedule</mat-icon>
|
|
<div class="detail-info">
|
|
<span class="detail-label">Last Active</span>
|
|
<span class="detail-value">{{ lastActive() }}</span>
|
|
</div>
|
|
</div>
|
|
@if (user()!.metadata?.registrationMethod) {
|
|
<div class="detail-row">
|
|
<mat-icon>how_to_reg</mat-icon>
|
|
<div class="detail-info">
|
|
<span class="detail-label">Registration Method</span>
|
|
<span class="detail-value">{{ user()!.metadata!.registrationMethod === 'guest_conversion' ? 'Guest Conversion' : 'Direct' }}</span>
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
</mat-card-content>
|
|
<mat-card-actions class="profile-actions">
|
|
<button mat-raised-button color="primary" (click)="editUserRole()">
|
|
<mat-icon>edit</mat-icon>
|
|
Edit Role
|
|
</button>
|
|
<button mat-raised-button [color]="user()!.isActive ? 'warn' : 'accent'" (click)="toggleUserStatus()">
|
|
<mat-icon>{{ user()!.isActive ? 'block' : 'check_circle' }}</mat-icon>
|
|
{{ user()!.isActive ? 'Deactivate' : 'Activate' }}
|
|
</button>
|
|
</mat-card-actions>
|
|
</mat-card>
|
|
|
|
<!-- Statistics Cards -->
|
|
<div class="stats-grid">
|
|
<mat-card class="stat-card">
|
|
<mat-card-content>
|
|
<div class="stat-icon primary">
|
|
<mat-icon>quiz</mat-icon>
|
|
</div>
|
|
<div class="stat-info">
|
|
<h3 class="stat-value">{{ formatNumber(user()!.statistics.totalQuizzes) }}</h3>
|
|
<p class="stat-label">Total Quizzes</p>
|
|
</div>
|
|
</mat-card-content>
|
|
</mat-card>
|
|
|
|
<mat-card class="stat-card">
|
|
<mat-card-content>
|
|
<div class="stat-icon success">
|
|
<mat-icon>grade</mat-icon>
|
|
</div>
|
|
<div class="stat-info">
|
|
<h3 class="stat-value">{{ user()!.statistics.averageScore.toFixed(1) }}%</h3>
|
|
<p class="stat-label">Average Score</p>
|
|
</div>
|
|
</mat-card-content>
|
|
</mat-card>
|
|
|
|
<mat-card class="stat-card">
|
|
<mat-card-content>
|
|
<div class="stat-icon accent">
|
|
<mat-icon>check_circle</mat-icon>
|
|
</div>
|
|
<div class="stat-info">
|
|
<h3 class="stat-value">{{ user()!.statistics.accuracy.toFixed(1) }}%</h3>
|
|
<p class="stat-label">Accuracy</p>
|
|
</div>
|
|
</mat-card-content>
|
|
</mat-card>
|
|
|
|
<mat-card class="stat-card">
|
|
<mat-card-content>
|
|
<div class="stat-icon warn">
|
|
<mat-icon>local_fire_department</mat-icon>
|
|
</div>
|
|
<div class="stat-info">
|
|
<h3 class="stat-value">{{ user()!.statistics.currentStreak }}</h3>
|
|
<p class="stat-label">Current Streak</p>
|
|
</div>
|
|
</mat-card-content>
|
|
</mat-card>
|
|
|
|
<mat-card class="stat-card">
|
|
<mat-card-content>
|
|
<div class="stat-icon primary">
|
|
<mat-icon>help_outline</mat-icon>
|
|
</div>
|
|
<div class="stat-info">
|
|
<h3 class="stat-value">{{ formatNumber(user()!.statistics.totalQuestionsAnswered) }}</h3>
|
|
<p class="stat-label">Questions Answered</p>
|
|
</div>
|
|
</mat-card-content>
|
|
</mat-card>
|
|
|
|
<mat-card class="stat-card">
|
|
<mat-card-content>
|
|
<div class="stat-icon success">
|
|
<mat-icon>timer</mat-icon>
|
|
</div>
|
|
<div class="stat-info">
|
|
<h3 class="stat-value">{{ formatDuration(user()!.statistics.totalTimeSpent) }}</h3>
|
|
<p class="stat-label">Time Spent</p>
|
|
</div>
|
|
</mat-card-content>
|
|
</mat-card>
|
|
</div>
|
|
|
|
<!-- Additional Stats Card -->
|
|
<mat-card class="additional-stats-card">
|
|
<mat-card-header>
|
|
<mat-card-title>
|
|
<mat-icon>analytics</mat-icon>
|
|
Additional Statistics
|
|
</mat-card-title>
|
|
</mat-card-header>
|
|
<mat-card-content>
|
|
<div class="stats-details">
|
|
<div class="stat-detail-row">
|
|
<span class="stat-detail-label">Correct Answers:</span>
|
|
<span class="stat-detail-value">{{ formatNumber(user()!.statistics.correctAnswers) }}</span>
|
|
</div>
|
|
<div class="stat-detail-row">
|
|
<span class="stat-detail-label">Longest Streak:</span>
|
|
<span class="stat-detail-value">{{ user()!.statistics.longestStreak }} days</span>
|
|
</div>
|
|
@if (user()!.statistics.favoriteCategory) {
|
|
<div class="stat-detail-row">
|
|
<span class="stat-detail-label">Favorite Category:</span>
|
|
<span class="stat-detail-value">
|
|
{{ user()!.statistics.favoriteCategory!.name }}
|
|
({{ user()!.statistics.favoriteCategory!.quizCount }} quizzes)
|
|
</span>
|
|
</div>
|
|
}
|
|
<div class="stat-detail-row">
|
|
<span class="stat-detail-label">Quizzes This Week:</span>
|
|
<span class="stat-detail-value">{{ user()!.statistics.recentActivity.quizzesThisWeek }}</span>
|
|
</div>
|
|
<div class="stat-detail-row">
|
|
<span class="stat-detail-label">Quizzes This Month:</span>
|
|
<span class="stat-detail-value">{{ user()!.statistics.recentActivity.quizzesThisMonth }}</span>
|
|
</div>
|
|
</div>
|
|
</mat-card-content>
|
|
</mat-card>
|
|
|
|
<!-- Quiz History -->
|
|
<mat-card class="quiz-history-card">
|
|
<mat-card-header>
|
|
<mat-card-title>
|
|
<mat-icon>history</mat-icon>
|
|
Quiz History
|
|
</mat-card-title>
|
|
</mat-card-header>
|
|
<mat-card-content>
|
|
@if (hasQuizHistory()) {
|
|
<div class="quiz-history-list">
|
|
@for (quiz of user()!.quizHistory; track quiz.id) {
|
|
<div class="quiz-history-item">
|
|
<div class="quiz-history-header">
|
|
<div class="quiz-category">
|
|
<mat-icon>category</mat-icon>
|
|
<span>{{ quiz.categoryName }}</span>
|
|
</div>
|
|
<div class="quiz-date">{{ formatDateTime(quiz.completedAt) }}</div>
|
|
</div>
|
|
<div class="quiz-history-stats">
|
|
<div class="quiz-stat">
|
|
<mat-icon [class]="'score-icon-' + getScoreColor(quiz.percentage)">grade</mat-icon>
|
|
<span class="quiz-stat-label">Score:</span>
|
|
<span [class]="'quiz-stat-value-' + getScoreColor(quiz.percentage)">
|
|
{{ quiz.score }}/{{ quiz.totalQuestions }} ({{ quiz.percentage.toFixed(1) }}%)
|
|
</span>
|
|
</div>
|
|
<div class="quiz-stat">
|
|
<mat-icon>timer</mat-icon>
|
|
<span class="quiz-stat-label">Time:</span>
|
|
<span class="quiz-stat-value">{{ formatDuration(quiz.timeTaken) }}</span>
|
|
</div>
|
|
<button mat-icon-button (click)="viewQuizDetails(quiz.id)"
|
|
matTooltip="View quiz details" class="quiz-action-btn">
|
|
<mat-icon>visibility</mat-icon>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
} @else {
|
|
<div class="empty-state">
|
|
<mat-icon>quiz</mat-icon>
|
|
<p>No quiz history available</p>
|
|
</div>
|
|
}
|
|
</mat-card-content>
|
|
</mat-card>
|
|
|
|
<!-- Activity Timeline -->
|
|
<mat-card class="activity-timeline-card">
|
|
<mat-card-header>
|
|
<mat-card-title>
|
|
<mat-icon>timeline</mat-icon>
|
|
Activity Timeline
|
|
</mat-card-title>
|
|
</mat-card-header>
|
|
<mat-card-content>
|
|
@if (hasActivity()) {
|
|
<mat-list class="activity-list">
|
|
@for (activity of user()!.activityTimeline; track activity.id) {
|
|
<mat-list-item class="activity-item">
|
|
<mat-icon [class]="'activity-icon-' + getActivityColor(activity.type)" matListItemIcon>
|
|
{{ getActivityIcon(activity.type) }}
|
|
</mat-icon>
|
|
<div matListItemTitle class="activity-description">{{ activity.description }}</div>
|
|
<div matListItemLine class="activity-time">{{ formatRelativeTime(activity.timestamp) }}</div>
|
|
@if (activity.metadata) {
|
|
<div matListItemLine class="activity-metadata">
|
|
@if (activity.metadata.categoryName) {
|
|
<span class="metadata-item">
|
|
<mat-icon>category</mat-icon>
|
|
{{ activity.metadata.categoryName }}
|
|
</span>
|
|
}
|
|
@if (activity.metadata.score !== undefined) {
|
|
<span class="metadata-item">
|
|
<mat-icon>grade</mat-icon>
|
|
{{ activity.metadata.score }}%
|
|
</span>
|
|
}
|
|
</div>
|
|
}
|
|
</mat-list-item>
|
|
<mat-divider></mat-divider>
|
|
}
|
|
</mat-list>
|
|
} @else {
|
|
<div class="empty-state">
|
|
<mat-icon>timeline</mat-icon>
|
|
<p>No activity recorded</p>
|
|
</div>
|
|
}
|
|
</mat-card-content>
|
|
</mat-card>
|
|
</div>
|
|
}
|
|
</div>
|