199 lines
7.3 KiB
HTML
199 lines
7.3 KiB
HTML
<!-- Loading State -->
|
|
<div *ngIf="isLoading()" class="loading-container">
|
|
<mat-spinner diameter="50"></mat-spinner>
|
|
<p>Loading quiz history...</p>
|
|
</div>
|
|
|
|
<!-- Error State -->
|
|
<div *ngIf="error() && !isLoading()" class="error-container">
|
|
<mat-icon class="error-icon">error_outline</mat-icon>
|
|
<h2>Failed to Load History</h2>
|
|
<p>{{ error() }}</p>
|
|
<button mat-raised-button color="primary" (click)="loadHistory()">
|
|
<mat-icon>refresh</mat-icon>
|
|
Try Again
|
|
</button>
|
|
</div>
|
|
|
|
<!-- History Content -->
|
|
<div *ngIf="!isLoading() && !error()" class="history-container">
|
|
|
|
<!-- Header -->
|
|
<div class="history-header">
|
|
<div class="header-title">
|
|
<h1>
|
|
<mat-icon>history</mat-icon>
|
|
Quiz History
|
|
</h1>
|
|
<p class="subtitle">View all your completed quizzes</p>
|
|
</div>
|
|
<button mat-raised-button color="primary" (click)="exportToCSV()" [disabled]="isEmpty()">
|
|
<mat-icon>download</mat-icon>
|
|
Export CSV
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Filters and Sort -->
|
|
<mat-card class="filters-card">
|
|
<mat-card-content>
|
|
<div class="filters-row">
|
|
<mat-form-field appearance="outline" class="filter-field">
|
|
<mat-label>Filter by Category</mat-label>
|
|
<mat-select [value]="selectedCategory()" (selectionChange)="onCategoryChange($event.value)">
|
|
<mat-option [value]="null">All Categories</mat-option>
|
|
<mat-option *ngFor="let category of categories()" [value]="category.id">
|
|
{{ category.name }}
|
|
</mat-option>
|
|
</mat-select>
|
|
</mat-form-field>
|
|
|
|
<mat-form-field appearance="outline" class="filter-field">
|
|
<mat-label>Sort By</mat-label>
|
|
<mat-select [value]="sortBy()" (selectionChange)="onSortChange($event.value)">
|
|
<mat-option value="date">Date (Newest First)</mat-option>
|
|
<mat-option value="score">Score (Highest First)</mat-option>
|
|
</mat-select>
|
|
</mat-form-field>
|
|
|
|
<button mat-icon-button (click)="refresh()" matTooltip="Refresh">
|
|
<mat-icon>refresh</mat-icon>
|
|
</button>
|
|
</div>
|
|
</mat-card-content>
|
|
</mat-card>
|
|
|
|
<!-- Empty State -->
|
|
<div *ngIf="isEmpty()" class="empty-state">
|
|
<mat-icon class="empty-icon">quiz</mat-icon>
|
|
<h2>No Quiz History</h2>
|
|
<p>You haven't completed any quizzes yet. Start your first quiz to see it here!</p>
|
|
<button mat-raised-button color="primary" [routerLink]="['/quiz/setup']">
|
|
<mat-icon>play_arrow</mat-icon>
|
|
Start a Quiz
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Desktop Table View -->
|
|
<mat-card class="table-card desktop-only" *ngIf="!isEmpty()">
|
|
<table mat-table [dataSource]="history()" class="history-table">
|
|
|
|
<!-- Date Column -->
|
|
<ng-container matColumnDef="date">
|
|
<th mat-header-cell *matHeaderCellDef>Date</th>
|
|
<td mat-cell *matCellDef="let session">
|
|
{{ formatDate(session.completedAt || session.startedAt) }}
|
|
</td>
|
|
</ng-container>
|
|
|
|
<!-- Category Column -->
|
|
<ng-container matColumnDef="category">
|
|
<th mat-header-cell *matHeaderCellDef>Category</th>
|
|
<td mat-cell *matCellDef="let session">
|
|
{{ session.category.name || 'Unknown' }}
|
|
</td>
|
|
</ng-container>
|
|
|
|
<!-- Score Column -->
|
|
<ng-container matColumnDef="score">
|
|
<th mat-header-cell *matHeaderCellDef>Score</th>
|
|
<td mat-cell *matCellDef="let session">
|
|
<span class="score-badge" [ngClass]="getScoreColor(session.score, session.totalQuestions)">
|
|
{{ session.score.earned }}/{{ session.questions.total }}
|
|
<span class="percentage">({{ ((session.score.earned / session.questions.total) * 100).toFixed(0) }}%)</span>
|
|
</span>
|
|
</td>
|
|
</ng-container>
|
|
|
|
<!-- Time Column -->
|
|
<ng-container matColumnDef="time">
|
|
<th mat-header-cell *matHeaderCellDef>Time Spent</th>
|
|
<td mat-cell *matCellDef="let session">
|
|
{{ formatDuration(session.time.spent) }}
|
|
</td>
|
|
</ng-container>
|
|
|
|
<!-- Status Column -->
|
|
<ng-container matColumnDef="status">
|
|
<th mat-header-cell *matHeaderCellDef>Status</th>
|
|
<td mat-cell *matCellDef="let session">
|
|
<mat-chip [ngClass]="getStatusClass(session.status)">
|
|
{{ session.status === 'in_progress' ? 'In Progress' :
|
|
session.status === 'completed' ? 'Completed' :
|
|
'Abandoned' }}
|
|
</mat-chip>
|
|
</td>
|
|
</ng-container>
|
|
|
|
<!-- Actions Column -->
|
|
<ng-container matColumnDef="actions">
|
|
<th mat-header-cell *matHeaderCellDef>Actions</th>
|
|
<td mat-cell *matCellDef="let session">
|
|
<button mat-icon-button (click)="viewResults(session.id)" matTooltip="View Results"
|
|
*ngIf="session.status === 'completed'">
|
|
<mat-icon>visibility</mat-icon>
|
|
</button>
|
|
<button mat-icon-button (click)="reviewQuiz(session.id)" matTooltip="Review Quiz"
|
|
*ngIf="session.status === 'completed'">
|
|
<mat-icon>rate_review</mat-icon>
|
|
</button>
|
|
</td>
|
|
</ng-container>
|
|
|
|
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
|
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
|
</table>
|
|
</mat-card>
|
|
|
|
<!-- Mobile Card View -->
|
|
<div class="mobile-cards mobile-only" *ngIf="!isEmpty()">
|
|
<mat-card *ngFor="let session of history()" class="history-card">
|
|
<mat-card-content>
|
|
<div class="card-header">
|
|
<div class="card-title">
|
|
<mat-icon>quiz</mat-icon>
|
|
<span>{{ session.category?.name || 'Unknown' }}</span>
|
|
</div>
|
|
<mat-chip [ngClass]="getStatusClass(session.status)">
|
|
{{ session.status === 'in_progress' ? 'In Progress' :
|
|
session.status === 'completed' ? 'Completed' :
|
|
'Abandoned' }}
|
|
</mat-chip>
|
|
</div>
|
|
|
|
<div class="card-details">
|
|
<div class="detail-row">
|
|
<mat-icon>calendar_today</mat-icon>
|
|
<span>{{ formatDate(session.completedAt || session.startedAt) }}</span>
|
|
</div>
|
|
<div class="detail-row">
|
|
<mat-icon>timer</mat-icon>
|
|
<span>{{ formatDuration(session.time.spent) }}</span>
|
|
</div>
|
|
<div class="detail-row score-row">
|
|
<span class="score-label">Score:</span>
|
|
<span class="score-value" [ngClass]="getScoreColor(session.score.earned, session.questions.total)">
|
|
{{ session.score.earned }}/{{ session.questions.total }}
|
|
({{ ((session.score.earned / session.questions.total) * 100).toFixed(0) }}%)
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card-actions" *ngIf="session.status === 'completed'">
|
|
<button mat-button color="primary" (click)="viewResults(session.id)">
|
|
<mat-icon>visibility</mat-icon>
|
|
View Results
|
|
</button>
|
|
<button mat-button color="accent" (click)="reviewQuiz(session.id)">
|
|
<mat-icon>rate_review</mat-icon>
|
|
Review
|
|
</button>
|
|
</div>
|
|
</mat-card-content>
|
|
</mat-card>
|
|
</div>
|
|
|
|
<!-- Pagination -->
|
|
<mat-paginator *ngIf="!isEmpty()" [length]="totalItems()" [pageSize]="pageSize()" [pageIndex]="currentPage() - 1"
|
|
[pageSizeOptions]="[5, 10, 20, 50]" (page)="onPageChange($event)" showFirstLastButtons>
|
|
</mat-paginator>
|
|
</div> |