diff --git a/backend/routes/admin.routes.js b/backend/routes/admin.routes.js index 95af67f..1b27959 100644 --- a/backend/routes/admin.routes.js +++ b/backend/routes/admin.routes.js @@ -313,7 +313,7 @@ router.get('/users/:userId', verifyToken, isAdmin, adminController.getUserById); * type: string * questionType: * type: string - * enum: [multiple_choice, true_false, short_answer] + * enum: [multiple_choice, trueFalse, short_answer] * options: * type: array * items: diff --git a/frontend/src/app/app.config.ts b/frontend/src/app/app.config.ts index bd79a4f..e060f6c 100644 --- a/frontend/src/app/app.config.ts +++ b/frontend/src/app/app.config.ts @@ -1,5 +1,5 @@ import { ApplicationConfig, ErrorHandler, provideBrowserGlobalErrorListeners, provideZonelessChangeDetection } from '@angular/core'; -import { provideRouter, withPreloading, PreloadAllModules } from '@angular/router'; +import { provideRouter, withPreloading, PreloadAllModules, withInMemoryScrolling } from '@angular/router'; import { provideHttpClient, withInterceptors } from '@angular/common/http'; import { provideAnimationsAsync } from '@angular/platform-browser/animations/async'; @@ -13,7 +13,10 @@ export const appConfig: ApplicationConfig = { provideZonelessChangeDetection(), provideRouter( routes, - withPreloading(PreloadAllModules) + withPreloading(PreloadAllModules), + withInMemoryScrolling({ + scrollPositionRestoration: 'top' + }) ), provideAnimationsAsync(), provideHttpClient( diff --git a/frontend/src/app/core/models/admin.model.ts b/frontend/src/app/core/models/admin.model.ts index 2a3d9b8..61b0881 100644 --- a/frontend/src/app/core/models/admin.model.ts +++ b/frontend/src/app/core/models/admin.model.ts @@ -8,40 +8,55 @@ */ export interface UserGrowthData { date: string; - count: number; + newUsers: number; } /** * Category popularity data for chart */ export interface CategoryPopularity { - categoryId: string; - categoryName: string; + id: string; + name: string; + slug: string; + icon: any; + color: string; quizCount: number; - percentage: number; + averageScore: number; } /** * System-wide statistics response */ export interface AdminStatistics { - totalUsers: number; - activeUsers: number; // Last 7 days - totalQuizSessions: number; - totalQuestions: number; - averageQuizScore: number; + users: AdminStatisticsUsers; + quizzes: AdminStatisticsQuizzes; + content: AdminStatisticsContent; + quizActivity: QuizActivity[]; userGrowth: UserGrowthData[]; popularCategories: CategoryPopularity[]; - stats: { - newUsersToday: number; - newUsersThisWeek: number; - newUsersThisMonth: number; - quizzesToday: number; - quizzesThisWeek: number; - quizzesThisMonth: number; +} +export interface AdminStatisticsContent { + totalCategories: number; + totalQuestions: number; + questionsByDifficulty: { + easy: number; + medium: number; + hard: number; }; } - +export interface AdminStatisticsQuizzes { + totalSessions: number; + averageScore: number; + averageScorePercentage: number; + passRate: number; + passedQuizzes: number; + failedQuizzes: number; +} +export interface AdminStatisticsUsers { + total: number; + active: number; + inactiveLast7Days: number; +} /** * API response wrapper for statistics */ @@ -51,6 +66,10 @@ export interface AdminStatisticsResponse { message?: string; } +export interface QuizActivity { + date: string; + quizzesCompleted: number; +} /** * Date range filter for statistics */ @@ -71,43 +90,58 @@ export interface AdminCacheEntry { /** * Guest session timeline data point */ -export interface GuestSessionTimelineData { - date: string; - activeSessions: number; - newSessions: number; - convertedSessions: number; -} +// export interface GuestSessionTimelineData { +// date: string; +// activeSessions: number; +// newSessions: number; +// convertedSessions: number; +// } /** * Conversion funnel stage */ -export interface ConversionFunnelStage { - stage: string; - count: number; - percentage: number; - dropoff?: number; -} +// export interface ConversionFunnelStage { +// stage: string; +// count: number; +// percentage: number; +// dropoff?: number; +// } /** * Guest analytics data */ -export interface GuestAnalytics { + +export interface GuestAnalyticsOverview { totalGuestSessions: number; activeGuestSessions: number; - conversionRate: number; // Percentage - averageQuizzesPerGuest: number; - totalConversions: number; - timeline: GuestSessionTimelineData[]; - conversionFunnel: ConversionFunnelStage[]; - stats: { - sessionsToday: number; - sessionsThisWeek: number; - sessionsThisMonth: number; - conversionsToday: number; - conversionsThisWeek: number; - conversionsThisMonth: number; + expiredGuestSessions: number; + convertedGuestSessions: number; + conversionRate: number; +} +export interface GuestAnalyticsQuizActivity { + totalGuestQuizzes: number; + completedGuestQuizzes: number; + guestQuizCompletionRate: number; + avgQuizzesPerGuest: number; + avgQuizzesBeforeConversion: number; +} + +export interface GuestAnalyticsBehavior { + bounceRate: number; + avgSessionDurationMinutes: number; +} +export interface GuestAnalyticsRecentActivity { + last30Days: { + newGuestSessions: number; + conversions: number; }; } +export interface GuestAnalytics { + overview: GuestAnalyticsOverview; + quizActivity: GuestAnalyticsQuizActivity; + behavior: GuestAnalyticsBehavior; + recentActivity: GuestAnalyticsRecentActivity; +} /** * API response wrapper for guest analytics @@ -183,11 +217,11 @@ export interface AdminUserListResponse { pagination: { currentPage: number; totalPages: number; - totalUsers: number; - limit: number; - hasNext: boolean; - hasPrev: boolean; - }; + totalItems: number; + itemsPerPage: number; + hasNextPage: boolean; + hasPreviousPage: boolean; + }; }; message?: string; } @@ -197,7 +231,13 @@ export interface AdminUserListResponse { */ export interface UserActivity { id: string; - type: 'login' | 'quiz_start' | 'quiz_complete' | 'bookmark' | 'profile_update' | 'role_change'; + type: + | 'login' + | 'quiz_start' + | 'quiz_complete' + | 'bookmark' + | 'profile_update' + | 'role_change'; description: string; timestamp: string; metadata?: { diff --git a/frontend/src/app/core/services/admin.service.ts b/frontend/src/app/core/services/admin.service.ts index e3086e2..4ecc1eb 100644 --- a/frontend/src/app/core/services/admin.service.ts +++ b/frontend/src/app/core/services/admin.service.ts @@ -73,12 +73,14 @@ export class AdminService { readonly isLoadingUsers = signal(false); readonly usersError = signal(null); readonly usersPagination = signal<{ - currentPage: number; - totalPages: number; - totalUsers: number; - limit: number; - hasNext: boolean; - hasPrev: boolean; + + currentPage: number; + totalPages: number; + totalItems: number; + itemsPerPage: number; + hasNextPage: boolean; + hasPreviousPage: boolean; + } | null>(null); readonly currentUserFilters = signal({}); @@ -95,18 +97,18 @@ export class AdminService { // Computed signals - Statistics readonly hasStats = computed(() => this.adminStatsState() !== null); - readonly totalUsers = computed(() => this.adminStatsState()?.totalUsers ?? 0); - readonly activeUsers = computed(() => this.adminStatsState()?.activeUsers ?? 0); - readonly totalQuizSessions = computed(() => this.adminStatsState()?.totalQuizSessions ?? 0); - readonly totalQuestions = computed(() => this.adminStatsState()?.totalQuestions ?? 0); - readonly averageScore = computed(() => this.adminStatsState()?.averageQuizScore ?? 0); + readonly totalUsers = computed(() => this.adminStatsState()?.users.total ?? 0); + readonly activeUsers = computed(() => this.adminStatsState()?.users.active ?? 0); + readonly totalQuizSessions = computed(() => this.adminStatsState()?.quizzes.totalSessions ?? 0); + readonly totalQuestions = computed(() => this.adminStatsState()?.content.totalQuestions ?? 0); + readonly averageScore = computed(() => this.adminStatsState()?.quizzes.averageScore ?? 0); // Computed signals - Guest Analytics readonly hasAnalytics = computed(() => this.guestAnalyticsState() !== null); - readonly totalGuestSessions = computed(() => this.guestAnalyticsState()?.totalGuestSessions ?? 0); - readonly activeGuestSessions = computed(() => this.guestAnalyticsState()?.activeGuestSessions ?? 0); - readonly conversionRate = computed(() => this.guestAnalyticsState()?.conversionRate ?? 0); - readonly avgQuizzesPerGuest = computed(() => this.guestAnalyticsState()?.averageQuizzesPerGuest ?? 0); + readonly totalGuestSessions = computed(() => this.guestAnalyticsState()?.overview.totalGuestSessions ?? 0); + readonly activeGuestSessions = computed(() => this.guestAnalyticsState()?.overview.activeGuestSessions ?? 0); + readonly conversionRate = computed(() => this.guestAnalyticsState()?.overview.conversionRate ?? 0); + readonly avgQuizzesPerGuest = computed(() => this.guestAnalyticsState()?.quizActivity.avgQuizzesPerGuest ?? 0); // Computed signals - Guest Settings readonly hasSettings = computed(() => this.guestSettingsState() !== null); @@ -116,7 +118,7 @@ export class AdminService { // Computed signals - User Management readonly hasUsers = computed(() => this.adminUsersState().length > 0); - readonly totalUsersCount = computed(() => this.usersPagination()?.totalUsers ?? 0); + readonly totalUsersCount = computed(() => this.usersPagination()?.totalItems ?? 0); readonly currentPage = computed(() => this.usersPagination()?.currentPage ?? 1); readonly totalPages = computed(() => this.usersPagination()?.totalPages ?? 1); diff --git a/frontend/src/app/core/services/pagination.service.ts b/frontend/src/app/core/services/pagination.service.ts index e4cfb71..e9cce74 100644 --- a/frontend/src/app/core/services/pagination.service.ts +++ b/frontend/src/app/core/services/pagination.service.ts @@ -14,15 +14,15 @@ export interface PaginationConfig { /** * Pagination State Interface */ -export interface PaginationState { +export interface PaginationState { currentPage: number; - pageSize: number; + itemsPerPage: number; totalItems: number; totalPages: number; startIndex: number; endIndex: number; - hasPrev: boolean; - hasNext: boolean; + hasPreviousPage: boolean; + hasNextPage: boolean; } /** @@ -61,13 +61,13 @@ export class PaginationService { return { currentPage: validCurrentPage, - pageSize, + itemsPerPage:pageSize, totalItems, totalPages, startIndex, endIndex, - hasPrev, - hasNext + hasNextPage:hasNext, + hasPreviousPage:hasPrev }; } @@ -208,13 +208,13 @@ export class PaginationService { }, nextPage: () => { - if (state().hasNext) { + if (state().hasNextPage) { config.update(c => ({ ...c, currentPage: c.currentPage + 1 })); } }, prevPage: () => { - if (state().hasPrev) { + if (state().hasPreviousPage) { config.update(c => ({ ...c, currentPage: c.currentPage - 1 })); } }, diff --git a/frontend/src/app/features/admin/admin-dashboard/admin-dashboard.component.html b/frontend/src/app/features/admin/admin-dashboard/admin-dashboard.component.html index ddee1d2..fa66be6 100644 --- a/frontend/src/app/features/admin/admin-dashboard/admin-dashboard.component.html +++ b/frontend/src/app/features/admin/admin-dashboard/admin-dashboard.component.html @@ -9,7 +9,12 @@

System-wide statistics and analytics

-
@@ -26,27 +31,47 @@
Start Date - - + + End Date - - + + - @if (hasDateFilter()) { - + }
@@ -55,221 +80,345 @@ @if (isLoading()) { -
- -

Loading statistics...

-
+
+ +

Loading statistics...

+
} @if (error() && !isLoading()) { - - -
- error_outline -

Failed to Load Statistics

-

{{ error() }}

- -
-
-
+ + +
+ error_outline +

Failed to Load Statistics

+

{{ error() }}

+ +
+
+
} @if (stats() && !isLoading()) { - -
- - -
- people -
-
-

Total Users

-

{{ formatNumber(totalUsers()) }}

- @if (stats() && stats()!.stats.newUsersThisWeek) { -

+{{ stats()!.stats.newUsersThisWeek }} this week

- } -
-
-
- - - -
- trending_up -
-
-

Active Users

-

{{ formatNumber(activeUsers()) }}

-

Last 7 days

-
-
-
- - - -
- quiz -
-
-

Total Quizzes

-

{{ formatNumber(totalQuizSessions()) }}

- @if (stats() && stats()!.stats.quizzesThisWeek) { -

+{{ stats()!.stats.quizzesThisWeek }} this week

- } -
-
-
- - - -
- help_outline -
-
-

Total Questions

-

{{ formatNumber(totalQuestions()) }}

-

In database

-
-
-
-
- - - - - - bar_chart - Average Quiz Score - - + +
+ -
-
- {{ formatPercentage(averageScore()) }} -
-

- @if (averageScore() >= 80) { - Excellent performance across all quizzes - } @else if (averageScore() >= 60) { - Good performance overall - } @else { - Room for improvement - } +

+ people +
+
+

Total Users

+

{{ formatNumber(totalUsers()) }}

+ @if (stats() && stats()!.users.inactiveLast7Days) { +

+ +{{ stats()!.users.inactiveLast7Days }} this week

+ }
- - @if (userGrowthData().length > 0) { - - - - show_chart - User Growth Over Time - - - -
- - - - - - - - - - - - - - - - @for (point of userGrowthData(); track point.date; let i = $index) { - - } - -
-
-
- } + + +
+ trending_up +
+
+

Active Users

+

{{ formatNumber(activeUsers()) }}

+

Last 7 days

+
+
+
- - @if (popularCategories().length > 0) { - - - - category - Most Popular Categories - - - -
- - - - - - - - - - - - @for (bar of getCategoryBars(); track bar.label) { - - {{ bar.value }} - {{ bar.label }} - } - -
-
-
- } + + +
+ quiz +
+
+

Total Quizzes

+

{{ formatNumber(totalQuizSessions()) }}

+ @if (stats() && stats()!.quizzes) { +

{{ stats()!.quizzes.averageScore }} Average score

+

{{ stats()!.quizzes.averageScorePercentage }} Average score percentage

+

{{ stats()!.quizzes.failedQuizzes }} Failed quizzes

+

{{ stats()!.quizzes.passRate }} Pass rate

+

{{ stats()!.quizzes.passedQuizzes }} Passed quizzes

+

{{ stats()!.quizzes.totalSessions }} Total sessions

+ } +
+
+
- -
-

Quick Actions

-
- - - - +
+
+

Total Questions

+

{{ formatNumber(totalQuestions()) }}

+

In database

+
+ + +
+ + + + + + bar_chart + Average Quiz Score + + + +
+
+ {{ + formatPercentage(averageScore()) + }} +
+

+ @if (averageScore() >= 80) { + Excellent performance across all quizzes + } @else if (averageScore() >= 60) { + Good performance overall + } @else { + Room for improvement + } +

+
+
+ + + + + + + + + + + + + + + + + @if (popularCategories().length > 0) { + + + + category + Most Popular Categories + + + +
+ + + + + + + + + + + + @for (bar of getCategoryBars(); track bar.label) { + + + {{ bar.value }} + + + {{ bar.label }} + + } + +
+
+
+ } + + +
+

Quick Actions

+
+ + + +
+
} @if (!stats() && !isLoading() && !error()) { - - - analytics -

No Statistics Available

-

Statistics will appear here once users start taking quizzes

-
-
+ + + analytics +

No Statistics Available

+

Statistics will appear here once users start taking quizzes

+
+
}
diff --git a/frontend/src/app/features/admin/admin-dashboard/admin-dashboard.component.ts b/frontend/src/app/features/admin/admin-dashboard/admin-dashboard.component.ts index 8f06c0b..7d8eda8 100644 --- a/frontend/src/app/features/admin/admin-dashboard/admin-dashboard.component.ts +++ b/frontend/src/app/features/admin/admin-dashboard/admin-dashboard.component.ts @@ -166,7 +166,7 @@ export class AdminDashboardComponent implements OnInit, OnDestroy { getMaxUserCount(): number { const data = this.userGrowthData(); if (data.length === 0) return 1; - return Math.max(...data.map(d => d.count), 1); + return Math.max(...data.map(d => d.newUsers), 1); } /** @@ -197,7 +197,7 @@ export class AdminDashboardComponent implements OnInit, OnDestroy { const data = this.userGrowthData(); if (data.length === 0) return ''; - const maxCount = Math.max(...data.map(d => d.count), 1); + const maxCount = Math.max(...data.map(d => d.newUsers), 1); const width = this.chartWidth; const height = this.chartHeight; const padding = 40; @@ -206,7 +206,7 @@ export class AdminDashboardComponent implements OnInit, OnDestroy { const points = data.map((d, i) => { const x = padding + (i / (data.length - 1)) * plotWidth; - const y = height - padding - (d.count / maxCount) * plotHeight; + const y = height - padding - (d.newUsers / maxCount) * plotHeight; return `${x},${y}`; }); @@ -235,7 +235,7 @@ export class AdminDashboardComponent implements OnInit, OnDestroy { y: height - padding - barHeight, width: barWidth, height: barHeight, - label: cat.categoryName, + label: cat.name, value: cat.quizCount }; }); diff --git a/frontend/src/app/features/admin/admin-question-form/admin-question-form.component.ts b/frontend/src/app/features/admin/admin-question-form/admin-question-form.component.ts index 5b54566..5ab3127 100644 --- a/frontend/src/app/features/admin/admin-question-form/admin-question-form.component.ts +++ b/frontend/src/app/features/admin/admin-question-form/admin-question-form.component.ts @@ -84,8 +84,8 @@ export class AdminQuestionFormComponent implements OnInit { // Available options readonly questionTypes = [ - { value: 'multiple_choice', label: 'Multiple Choice' }, - { value: 'true_false', label: 'True/False' }, + { value: 'multiple', label: 'Multiple Choice' }, + { value: 'trueFalse', label: 'True/False' }, { value: 'written', label: 'Written Answer' } ]; @@ -198,7 +198,7 @@ export class AdminQuestionFormComponent implements OnInit { private initializeForm(): void { this.questionForm = this.fb.group({ questionText: ['', [Validators.required, Validators.minLength(10)]], - questionType: ['multiple_choice', Validators.required], + questionType: ['multiple', Validators.required], categoryId: ['', Validators.required], difficulty: ['medium', Validators.required], options: this.fb.array([ @@ -333,7 +333,7 @@ export class AdminQuestionFormComponent implements OnInit { const correctAnswer = formGroup.get('correctAnswer')?.value; const options = formGroup.get('options') as FormArray; - if (questionType === 'multiple_choice' && correctAnswer && options) { + if (questionType === 'multiple' && correctAnswer && options) { const optionTexts = options.controls.map(opt => opt.get('text')?.value); const isValid = optionTexts.includes(correctAnswer); @@ -378,7 +378,7 @@ export class AdminQuestionFormComponent implements OnInit { }; // Add options for multiple choice - if (formValue.questionType === 'multiple_choice') { + if (formValue.questionType === 'multiple') { questionData.options = this.getOptionTexts(); } diff --git a/frontend/src/app/features/admin/admin-questions/admin-questions.component.html b/frontend/src/app/features/admin/admin-questions/admin-questions.component.html index e7adf04..3d5d9e2 100644 --- a/frontend/src/app/features/admin/admin-questions/admin-questions.component.html +++ b/frontend/src/app/features/admin/admin-questions/admin-questions.component.html @@ -54,8 +54,8 @@ Type All Types - Multiple Choice - True/False + Multiple Choice + True/False Written @@ -137,15 +137,15 @@ Type - @if (question.questionType === 'multiple_choice') { + @if (question.questionType === 'multiple') { radio_button_checked - MCQ - } @else if (question.questionType === 'true_false') { + MCQ + } @else if (question.questionType === 'trueFalse') { check_circle - T/F + T/F } @else { edit_note - Written + Written } diff --git a/frontend/src/app/features/admin/admin-users/admin-users.component.html b/frontend/src/app/features/admin/admin-users/admin-users.component.html index a41a3c3..f75c3dc 100644 --- a/frontend/src/app/features/admin/admin-users/admin-users.component.html +++ b/frontend/src/app/features/admin/admin-users/admin-users.component.html @@ -116,7 +116,7 @@

Users

@if (pagination()) { - Total: {{ pagination()?.totalUsers }} user{{ pagination()?.totalUsers !== 1 ? 's' : '' }} + Total: {{ pagination()?.totalItems }} user{{ pagination()?.totalItems !== 1 ? 's' : '' }} }
diff --git a/frontend/src/app/features/admin/admin-users/admin-users.component.ts b/frontend/src/app/features/admin/admin-users/admin-users.component.ts index e5fd93f..1120664 100644 --- a/frontend/src/app/features/admin/admin-users/admin-users.component.ts +++ b/frontend/src/app/features/admin/admin-users/admin-users.component.ts @@ -83,8 +83,8 @@ export class AdminUsersComponent implements OnInit { return this.paginationService.calculatePaginationState({ currentPage: pag.currentPage, - pageSize: pag.limit, - totalItems: pag.totalUsers + pageSize: pag.itemsPerPage, + totalItems: pag.totalItems }); }); diff --git a/frontend/src/app/features/admin/guest-analytics/guest-analytics.component.html b/frontend/src/app/features/admin/guest-analytics/guest-analytics.component.html index bea63e1..e4e76b0 100644 --- a/frontend/src/app/features/admin/guest-analytics/guest-analytics.component.html +++ b/frontend/src/app/features/admin/guest-analytics/guest-analytics.component.html @@ -61,8 +61,8 @@

Total Guest Sessions

{{ formatNumber(totalSessions()) }}

- @if (analytics() && analytics()!.stats.sessionsThisWeek) { -

+{{ analytics()!.stats.sessionsThisWeek }} this week

+ @if (analytics() && analytics()!.recentActivity.last30Days) { +

+{{ analytics()!.recentActivity.last30Days }} this 30 days

}
@@ -89,8 +89,8 @@

Conversion Rate

{{ formatPercentage(conversionRate()) }}

- @if (analytics() && analytics()!.totalConversions) { -

{{ analytics()!.totalConversions }} conversions

+ @if (analytics() && analytics()!.overview.conversionRate) { +

{{ analytics()!.overview.conversionRate }} conversions

}
@@ -111,7 +111,7 @@ - @if (timelineData().length > 0) { + - + - + - + - + - + - @for (point of timelineData(); track point.date; let i = $index) { + - @if (funnelData().length > 0) { + - @for (bar of getFunnelBars(); track bar.label) { - + - - + --> - {{ bar.label }} - + --> - {{ formatNumber(bar.count) }} ({{ formatPercentage(bar.percentage) }}) } @@ -216,7 +216,7 @@
- } + } -->
diff --git a/frontend/src/app/features/admin/guest-analytics/guest-analytics.component.ts b/frontend/src/app/features/admin/guest-analytics/guest-analytics.component.ts index e3fa282..dedd409 100644 --- a/frontend/src/app/features/admin/guest-analytics/guest-analytics.component.ts +++ b/frontend/src/app/features/admin/guest-analytics/guest-analytics.component.ts @@ -58,8 +58,8 @@ export class GuestAnalyticsComponent implements OnInit, OnDestroy { readonly avgQuizzes = this.adminService.avgQuizzesPerGuest; // Chart data computed signals - readonly timelineData = computed(() => this.analytics()?.timeline ?? []); - readonly funnelData = computed(() => this.analytics()?.conversionFunnel ?? []); + // readonly timelineData = computed(() => this.analytics()?.timeline ?? []); + // readonly funnelData = computed(() => this.analytics()?.conversionFunnel ?? []); // Chart dimensions readonly chartWidth = 800; @@ -100,25 +100,25 @@ export class GuestAnalyticsComponent implements OnInit, OnDestroy { /** * Calculate max value for timeline chart */ - getMaxTimelineValue(): number { - const data = this.timelineData(); - if (data.length === 0) return 1; - return Math.max( - ...data.map(d => Math.max(d.activeSessions, d.newSessions, d.convertedSessions)), - 1 - ); - } + // getMaxTimelineValue(): number { + // const data = this.timelineData(); + // if (data.length === 0) return 1; + // return Math.max( + // ...data.map(d => Math.max(d.activeSessions, d.newSessions, d.convertedSessions)), + // 1 + // ); + // } /** * Calculate Y coordinate for timeline chart */ - calculateTimelineY(value: number): number { - const maxValue = this.getMaxTimelineValue(); - const height = this.chartHeight; - const padding = 40; - const plotHeight = height - 2 * padding; - return height - padding - (value / maxValue) * plotHeight; - } + // calculateTimelineY(value: number): number { + // const maxValue = this.getMaxTimelineValue(); + // const height = this.chartHeight; + // const padding = 40; + // const plotHeight = height - 2 * padding; + // return height - padding - (value / maxValue) * plotHeight; + // } /** * Calculate X coordinate for timeline chart @@ -134,55 +134,55 @@ export class GuestAnalyticsComponent implements OnInit, OnDestroy { /** * Generate SVG path for timeline line */ - getTimelinePath(dataKey: 'activeSessions' | 'newSessions' | 'convertedSessions'): string { - const data = this.timelineData(); - if (data.length === 0) return ''; + // getTimelinePath(dataKey: 'activeSessions' | 'newSessions' | 'convertedSessions'): string { + // const data = this.timelineData(); + // if (data.length === 0) return ''; - const points = data.map((d, i) => { - const x = this.calculateTimelineX(i, data.length); - const y = this.calculateTimelineY(d[dataKey]); - return `${x},${y}`; - }); + // const points = data.map((d, i) => { + // const x = this.calculateTimelineX(i, data.length); + // const y = this.calculateTimelineY(d[dataKey]); + // return `${x},${y}`; + // }); - return `M ${points.join(' L ')}`; - } + // return `M ${points.join(' L ')}`; + // } /** * Get conversion funnel bar data */ - getFunnelBars(): Array<{ - x: number; - y: number; - width: number; - height: number; - label: string; - count: number; - percentage: number; - }> { - const stages = this.funnelData(); - if (stages.length === 0) return []; + // getFunnelBars(): Array<{ + // x: number; + // y: number; + // width: number; + // height: number; + // label: string; + // count: number; + // percentage: number; + // }> { + // const stages = this.funnelData(); + // if (stages.length === 0) return []; - const maxCount = Math.max(...stages.map(s => s.count), 1); - const width = this.chartWidth; - const height = this.funnelHeight; - const padding = 60; - const plotWidth = width - 2 * padding; - const plotHeight = height - 2 * padding; - const barHeight = plotHeight / stages.length - 20; + // const maxCount = Math.max(...stages.map(s => s.count), 1); + // const width = this.chartWidth; + // const height = this.funnelHeight; + // const padding = 60; + // const plotWidth = width - 2 * padding; + // const plotHeight = height - 2 * padding; + // const barHeight = plotHeight / stages.length - 20; - return stages.map((stage, i) => { - const barWidth = (stage.count / maxCount) * plotWidth; - return { - x: padding, - y: padding + i * (plotHeight / stages.length) + 10, - width: barWidth, - height: barHeight, - label: stage.stage, - count: stage.count, - percentage: stage.percentage - }; - }); - } + // return stages.map((stage, i) => { + // const barWidth = (stage.count / maxCount) * plotWidth; + // return { + // x: padding, + // y: padding + i * (plotHeight / stages.length) + 10, + // width: barWidth, + // height: barHeight, + // label: stage.stage, + // count: stage.count, + // percentage: stage.percentage + // }; + // }); + // } /** * Export analytics data to CSV @@ -197,26 +197,26 @@ export class GuestAnalyticsComponent implements OnInit, OnDestroy { // Summary statistics csvContent += 'Summary Statistics\n'; csvContent += 'Metric,Value\n'; - csvContent += `Total Guest Sessions,${analytics.totalGuestSessions}\n`; - csvContent += `Active Guest Sessions,${analytics.activeGuestSessions}\n`; - csvContent += `Conversion Rate,${analytics.conversionRate}%\n`; - csvContent += `Average Quizzes per Guest,${analytics.averageQuizzesPerGuest}\n`; - csvContent += `Total Conversions,${analytics.totalConversions}\n\n`; + csvContent += `Total Guest Sessions,${analytics.overview.activeGuestSessions}\n`; + csvContent += `Active Guest Sessions,${analytics.overview.activeGuestSessions}\n`; + csvContent += `Conversion Rate,${analytics.overview.conversionRate}%\n`; + csvContent += `Average Quizzes per Guest,${analytics.quizActivity.avgQuizzesPerGuest}\n`; + csvContent += `Total Conversions,${analytics.overview.conversionRate}\n\n`; // Timeline data csvContent += 'Timeline Data\n'; csvContent += 'Date,Active Sessions,New Sessions,Converted Sessions\n'; - analytics.timeline.forEach(item => { - csvContent += `${item.date},${item.activeSessions},${item.newSessions},${item.convertedSessions}\n`; - }); + // analytics.timeline.forEach(item => { + // csvContent += `${item.date},${item.activeSessions},${item.newSessions},${item.convertedSessions}\n`; + // }); csvContent += '\n'; // Funnel data csvContent += 'Conversion Funnel\n'; csvContent += 'Stage,Count,Percentage,Dropoff\n'; - analytics.conversionFunnel.forEach(stage => { - csvContent += `${stage.stage},${stage.count},${stage.percentage}%,${stage.dropoff ?? 'N/A'}\n`; - }); + // analytics.conversionFunnel.forEach(stage => { + // csvContent += `${stage.stage},${stage.count},${stage.percentage}%,${stage.dropoff ?? 'N/A'}\n`; + // }); // Create and download file const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); diff --git a/frontend/src/app/features/quiz/quiz-results/quiz-results.ts b/frontend/src/app/features/quiz/quiz-results/quiz-results.ts index 8920d27..84c1f9a 100644 --- a/frontend/src/app/features/quiz/quiz-results/quiz-results.ts +++ b/frontend/src/app/features/quiz/quiz-results/quiz-results.ts @@ -194,9 +194,9 @@ export class QuizResultsComponent implements OnInit, OnDestroy { */ getQuestionTypeText(type: string): string { switch (type) { - case 'multiple_choice': + case 'multiple': return 'Multiple Choice'; - case 'true_false': + case 'trueFalse': return 'True/False'; case 'written': return 'Written'; diff --git a/frontend/src/app/features/quiz/quiz-review/quiz-review.ts b/frontend/src/app/features/quiz/quiz-review/quiz-review.ts index 0cebf7e..a64f0bb 100644 --- a/frontend/src/app/features/quiz/quiz-review/quiz-review.ts +++ b/frontend/src/app/features/quiz/quiz-review/quiz-review.ts @@ -215,9 +215,9 @@ export class QuizReviewComponent implements OnInit, OnDestroy { */ getQuestionTypeText(type: string): string { switch (type) { - case 'multiple_choice': + case 'multiple': return 'Multiple Choice'; - case 'true_false': + case 'trueFalse': return 'True/False'; case 'written': return 'Written'; diff --git a/frontend/src/app/shared/components/confirm-dialog/confirm-dialog.html b/frontend/src/app/shared/components/confirm-dialog/confirm-dialog.html index e0a28c2..fc11ed0 100644 --- a/frontend/src/app/shared/components/confirm-dialog/confirm-dialog.html +++ b/frontend/src/app/shared/components/confirm-dialog/confirm-dialog.html @@ -1,4 +1,5 @@ -

+
+

@if (data.icon) { {{ data.icon }} } @@ -22,3 +23,5 @@ {{ data.confirmText || 'Confirm' }} + +

\ No newline at end of file diff --git a/frontend/src/app/shared/components/pagination/pagination.component.ts b/frontend/src/app/shared/components/pagination/pagination.component.ts index b59d193..e8fcc02 100644 --- a/frontend/src/app/shared/components/pagination/pagination.component.ts +++ b/frontend/src/app/shared/components/pagination/pagination.component.ts @@ -57,7 +57,7 @@ import { PaginationState } from '../../../core/services/pagination.service'; @if (showPageSizeSelector()) { @for (option of pageSizeOptions(); track option) { @@ -74,7 +74,7 @@ import { PaginationState } from '../../../core/services/pagination.service';