Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion otpcasestudy-angular-app/src/app/app.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { RegistrationComponent } from './components/candidate/registration/regis
import { ExamWindowComponent } from './components/exam-window/exam-window.component';
import { ExamExitGuard } from './guards/exam-exit.guard';
import { CandidateLoginComponent } from './components/candidate/candidate-login/candidate-login.component';
import { StayOnPageGuard } from './guards/stay-on-page.guard';

export const routes: Routes = [
{
Expand All @@ -35,10 +36,14 @@ export const routes: Routes = [
loadComponent: () => import('./components/admin/login/login.component').then(m => m.LoginComponent)
},
{ path: 'landing', component: LandingComponent },
{ path: 'admin-login', component: AdminLoginComponent },
{ path: 'admin-login', component: AdminLoginComponent },
{ path: 'verify-email', component: EmailVerificationComponent },
{ path: 'registration', component: RegistrationComponent },
{ path: 'exam', component: ExamWindowComponent, canDeactivate: [ExamExitGuard] },
{
path: 'exam-result',
loadComponent: () => import('./components/exam-window/result-window/result-window.component').then(m => m.ResultWindowComponent),
},
{ path: 'candidate-login', component: CandidateLoginComponent },
{ path: '', pathMatch: 'full', redirectTo: 'admin/dashboard' },
{ path: '**', redirectTo: 'landing' }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,18 @@

<span class="toolbar-spacer"></span>

<!-- Registration Toggle -->
<div class="registration-toggle">
<span class="toggle-label">Candidate Registration</span>
<mat-slide-toggle
[checked]="registrationEnabled"
(change)="toggleRegistration()"
[disabled]="loading"
class="custom-toggle"
color="primary"
>
</mat-slide-toggle>
</div>
<!-- User Menu -->
<button mat-icon-button [matMenuTriggerFor]="userMenu" class="user-menu">
<mat-icon>account_circle</mat-icon>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
}

&.active-link {
background-color: rgba(255, 255, 255, 0.7);
background-color: rgba(255, 255, 255, 0.75);
color: #3b1877;
font-weight: 500;
}
Expand Down Expand Up @@ -96,7 +96,7 @@
.toolbar-title {
font-size: 1.25rem;
font-weight: 500;
display: none;
display: none; // Hidden on small screens
}
}

Expand Down Expand Up @@ -127,6 +127,34 @@
color: white;
}

.registration-toggle {
display: flex;
align-items: center;
gap: 12px;
margin-right: 20px;
padding: 10px;

.toggle-label {
font-size: 18px;
font-weight: 500;
white-space: nowrap;
}

mat-slide-toggle {
::ng-deep .mat-slide-toggle-bar {
background-color: rgba(255, 255, 255, 0.3);
}

::ng-deep .mat-slide-toggle-thumb {
background-color: white;
}

::ng-deep .mat-slide-toggle-checked .mat-slide-toggle-bar {
background-color: #4caf50;
}
}
}

// Responsive design
@media (max-width: 768px) {
.admin-sidenav {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component } from '@angular/core';
import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Router, RouterModule } from '@angular/router';
import { MatSidenavModule } from '@angular/material/sidenav';
Expand All @@ -10,6 +10,9 @@ import { MatBadgeModule } from '@angular/material/badge';
import { MatMenuModule } from '@angular/material/menu';
import { MatDividerModule } from '@angular/material/divider';
import { AuthAdminService } from '../../../services/auth-admin.service';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatSnackBar } from '@angular/material/snack-bar';
import { ExamService } from '../../../services/exam.service';

@Component({
selector: 'app-admin-layout',
Expand All @@ -24,13 +27,20 @@ import { AuthAdminService } from '../../../services/auth-admin.service';
MatButtonModule,
MatBadgeModule,
MatMenuModule,
MatDividerModule
MatDividerModule,
MatSlideToggleModule,
],
templateUrl: './admin-layout.component.html',
styleUrls: ['./admin-layout.component.scss']
})
export class AdminLayoutComponent {
export class AdminLayoutComponent implements OnInit {
sidenavOpened = true;
registrationEnabled = false;
loading = false;

ngOnInit(): void {
this.loadRegistrationStatus();
}

menuItems = [
{
Expand Down Expand Up @@ -65,7 +75,9 @@ export class AdminLayoutComponent {
}
];

constructor(private router: Router, private authAdminService: AuthAdminService) { }
constructor(private router: Router, private authAdminService: AuthAdminService, private snackBar: MatSnackBar, private examService: ExamService,

) { }

toggleSidenav() {
this.sidenavOpened = !this.sidenavOpened;
Expand All @@ -75,4 +87,40 @@ export class AdminLayoutComponent {
this.authAdminService.logout();
this.router.navigate(['/admin-login']);
}

loadRegistrationStatus(): void {
this.examService.getRegistrationStatus().subscribe({
next: (status) => {
this.registrationEnabled = status;
},
error: (err) => {
console.error('Failed to load registration status:', err);
this.snackBar.open('Failed to load registration status', 'Close', {
duration: 3000
});
}
});
}

toggleRegistration(): void {
if (this.loading) return;

this.loading = true;
this.examService.toggleRegistration().subscribe({
next: (message) => {
this.registrationEnabled = !this.registrationEnabled;
this.loading = false;
this.snackBar.open(message, 'Close', {
duration: 3000
});
},
error: (err) => {
this.loading = false;
console.error('Failed to toggle registration:', err);
this.snackBar.open('Failed to toggle registration status', 'Close', {
duration: 3000
});
}
});
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<mat-toolbar class="admin-navbar">
<span class="navbar-title">{{ title }}</span>
<span class="spacer"></span>

<button mat-flat-button *ngIf="!isOnDashboard()" (click)="goToDashboard()">
<mat-icon>dashboard</mat-icon>
Admin Dashboard
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,34 @@
import { Component, Input } from '@angular/core';
import { Component, Input, OnInit, ViewEncapsulation } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { Router } from '@angular/router';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatSnackBar } from '@angular/material/snack-bar';
import { ExamService } from '../../../services/exam.service';

@Component({
selector: 'app-admin-navbar',
standalone: true,
imports: [CommonModule, MatToolbarModule, MatButtonModule, MatIconModule],
imports: [CommonModule, FormsModule, MatToolbarModule, MatButtonModule, MatIconModule, MatSlideToggleModule],
templateUrl: './admin-navbar.component.html',
styleUrls: ['./admin-navbar.component.scss']
styleUrls: ['./admin-navbar.component.scss'],
})
export class AdminNavbarComponent {
export class AdminNavbarComponent implements OnInit {
@Input() title = '';
constructor(private router: Router) { }
loading = false;

constructor(
private router: Router,
private examService: ExamService,
private snackBar: MatSnackBar
) { }

ngOnInit(): void {
}

goToDashboard() {
this.router.navigate(['/admin/dashboard']);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ <h3>{{ totalCandidates }}</h3>
<div class="action-buttons">
<button
mat-icon-button
color="accent"
color="primary"
(click)="downloadResults(exam)"
[disabled]="exam.status !== 'ended'"
matTooltip="Download Results"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@
color: #333;

mat-icon {
color: #2196f3;
color: #3b1877;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { MatBadgeModule } from '@angular/material/badge';
import { MatChipsModule } from '@angular/material/chips';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSnackBar } from '@angular/material/snack-bar';
import { MatDialog } from '@angular/material/dialog';
import { MatSort, MatSortModule } from '@angular/material/sort';
import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator';
import { MatFormFieldModule } from '@angular/material/form-field';
Expand Down Expand Up @@ -56,40 +55,35 @@ export class DashboardComponent implements OnInit, AfterViewInit {
constructor(
private examService: ExamService,
private snackBar: MatSnackBar,
private dialog: MatDialog
) { }

ngOnInit(): void {
this.loadExams();
}

ngAfterViewInit(): void {
console.log('Paginator: ', this.paginator);

this.dataSource.sort = this.sort;
this.dataSource.paginator = this.paginator;
}

loadExams(): void {
this.loading = true;
this.examService.getScheduledExams().subscribe((exams) => {
console.log('Parsed Exams: ', exams);
this.dataSource.data = exams;
});

// Fetch scheduled exams
this.examService.getScheduledExams().subscribe({
next: (exams) => {
const mappedExams = exams.map((exam: any) => ({
id: exam.id ?? exam.examId,
id: exam.id,
title: exam.title,
startDateTime: exam.startDateTime,
endDateTime: exam.endDateTime,
status: this.getStatusFromDates(exam.startDateTime, exam.endDateTime),
totalCandidates: 0
}));
this.dataSource = new MatTableDataSource(mappedExams);
this.calculateStats(this.dataSource.data);

// Update data source data instead of recreating it
this.dataSource.data = mappedExams;
this.calculateStats(mappedExams);
this.loading = false;
},
error: (err) => {
Expand All @@ -104,7 +98,7 @@ export class DashboardComponent implements OnInit, AfterViewInit {
next: (count) => this.activeExams = count,
error: (err) => {
console.error(err)
this.snackBar.open(err?.error?.message || 'Failed to load Active Exam Count', '', { duration: 5000 });
this.snackBar.open(err?.error?.message || 'Failed to load Active Exam Count', 'ERROR', { duration: 5000 });
}
});

Expand All @@ -113,7 +107,7 @@ export class DashboardComponent implements OnInit, AfterViewInit {
next: (count) => this.totalCandidates = count,
error: (err) => {
console.error(err);
this.snackBar.open(err?.error?.message || 'Failed to load Active Candidates', '', { duration: 5000 });
this.snackBar.open(err?.error?.message || 'Failed to load Active Candidates', 'ERROR', { duration: 5000 });
}
})
}
Expand Down Expand Up @@ -172,11 +166,12 @@ export class DashboardComponent implements OnInit, AfterViewInit {
a.download = fileName;
a.click();
window.URL.revokeObjectURL(url);
this.snackBar.open('Download complete!', '', { duration: 5000 });
a.remove();
this.snackBar.open('Download complete!', 'INFO', { duration: 5000 });
},
error: (err) => {
console.error(err);
this.snackBar.open(err?.error?.message || 'Failed to download results.', '', { duration: 5000 });
this.snackBar.open(err?.error?.message || 'Failed to download results.', 'ERROR', { duration: 5000 });
}
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
font-weight: 600;
color: #333;
mat-icon {
color: #2196f3;
color: #3b1877;
}
}
}
Expand Down Expand Up @@ -50,7 +50,7 @@
font-size: 0.95rem;
}
mat-icon {
color: #2196f3;
color: #3b1877;
}
}
.exam-actions {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export class DownloadResultsComponent implements OnInit {
this.exams = exams;
},
error: (err) => {
this.snackBar.open(err?.error?.message || 'Failed to load completed exams.', '', { duration: 5000 });
this.snackBar.open(err?.error?.message || 'Failed to load completed exams.', 'ERROR', { duration: 5000 });
}
});
}
Expand All @@ -54,11 +54,11 @@ export class DownloadResultsComponent implements OnInit {
this.examService.downloadResults(exam.id).subscribe({
next: (blob) => {
this.saveFile(blob, `${exam.title}-results.pdf`);
this.snackBar.open('Results downloaded!', '', { duration: 5000 });
this.snackBar.open('Results downloaded!', 'INFO', { duration: 5000 });
this.downloadingExamId = null;
},
error: (err) => {
this.snackBar.open(err?.error?.message || 'Failed to download results.', '', { duration: 5000 });
this.snackBar.open(err?.error?.message || 'Failed to download results.', 'ERROR', { duration: 5000 });
this.downloadingExamId = null;
}
});
Expand All @@ -71,5 +71,6 @@ export class DownloadResultsComponent implements OnInit {
a.download = filename;
a.click();
window.URL.revokeObjectURL(url);
a.remove();
}
}
Loading