Commit d2648061 authored by AravindR-K's avatar AravindR-K

Fixed quiz genreation bugs + editing bugs

parent 1d54e11a
...@@ -2,4 +2,4 @@ PORT=5000 ...@@ -2,4 +2,4 @@ PORT=5000
#MONGODB_URI=mongodb://localhost:27017/quiz_app #MONGODB_URI=mongodb://localhost:27017/quiz_app
MONGODB_URI=mongodb://127.0.0.1:27017/quiz_app MONGODB_URI=mongodb://127.0.0.1:27017/quiz_app
JWT_SECRET=quiz_app_super_secret_key_2026 JWT_SECRET=quiz_app_super_secret_key_2026
JWT_EXPIRES_IN=7d JWT_EXPIRES_IN=1d
...@@ -43,10 +43,10 @@ const quizSchema = new mongoose.Schema({ ...@@ -43,10 +43,10 @@ const quizSchema = new mongoose.Schema({
}], }],
difficulty: { difficulty: {
type: String, type: String,
enum: ['Beginner', 'Intermediate', 'Advanced'], enum: ['easy', 'medium', 'hard'],
default: 'Intermediate' default: 'medium'
}, },
topic: { topic: {
type: String, type: String,
trim: true trim: true
}, },
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
"main": "server.js", "main": "server.js",
"scripts": { "scripts": {
"start": "node server.js", "start": "node server.js",
"dev": "nodemon server.js", "dev": "nodemon --ignore uploads/ server.js",
"seed": "node seedAdmin.js", "seed": "node seedAdmin.js",
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1"
}, },
......
This diff is collapsed.
...@@ -7,7 +7,7 @@ const router = express.Router(); ...@@ -7,7 +7,7 @@ const router = express.Router();
// Generate JWT Token // Generate JWT Token
const generateToken = (id) => { const generateToken = (id) => {
return jwt.sign({ id }, process.env.JWT_SECRET, { return jwt.sign({ id }, process.env.JWT_SECRET, {
expiresIn: process.env.JWT_EXPIRES_IN || '7d' expiresIn: process.env.JWT_EXPIRES_IN || '1d'
}); });
}; };
......
const express = require('express'); const express = require('express');
const cors = require('cors'); const cors = require('cors');
const cookieParser = require('cookie-parser'); const cookieParser = require('cookie-parser');
const dotenv = require('dotenv'); const dotenv = require('dotenv');
const connectDB = require('./config/db'); const connectDB = require('./config/db');
// Load env vars // Load env vars
dotenv.config(); dotenv.config();
// Connect to database // Connect to database
connectDB(); connectDB();
const app = express(); const app = express();
// Middleware // Middleware
const allowedOrigins = [ const allowedOrigins = [
'http://localhost:4200', 'http://localhost:4200',
'http://localhost:3000', 'http://localhost:3000',
process.env.FRONTEND_URL process.env.FRONTEND_URL
].filter(Boolean); ].filter(Boolean);
app.use(cors({ app.use(cors({
origin: function (origin, callback) { origin: function (origin, callback) {
// Allow requests with no origin (mobile apps, curl, Postman, server-to-server) // Allow requests with no origin (mobile apps, curl, Postman, server-to-server)
if (!origin) return callback(null, true); if (!origin) return callback(null, true);
// Allow any vercel.app previews and production deployments // Allow any vercel.app previews and production deployments
if (origin.endsWith('.vercel.app') || origin.includes('vercel.app')) { if (origin.endsWith('.vercel.app') || origin.includes('vercel.app')) {
return callback(null, true); return callback(null, true);
}
// Allow explicitly listed origins
if (allowedOrigins.includes(origin)) {
return callback(null, true);
}
// Block everything else
console.warn(`CORS blocked origin: ${origin}`);
return callback(new Error(`CORS policy: origin ${origin} not allowed`), false);
},
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization']
}));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cookieParser());
// Routes
app.use('/api/auth', require('./routes/auth'));
app.use('/api/admin', require('./routes/admin'));
app.use('/api/hr', require('./routes/hr'));
app.use('/api/candidate', require('./routes/candidate'));
// Keep backward compatibility for old student endpoints
app.use('/api/student', require('./routes/candidate'));
// Health check
app.get('/api/health', (req, res) => {
res.json({ status: 'OK', message: 'QuizMaster Pro API is running' });
});
// Error handling middleware
app.use((err, req, res, next) => {
console.error(err.stack);
if (err.name === 'MulterError') {
if (err.code === 'LIMIT_FILE_SIZE') {
return res.status(400).json({ message: 'File size exceeds 5MB limit' });
}
return res.status(400).json({ message: err.message });
} }
// Allow explicitly listed origins res.status(500).json({ message: 'Something went wrong!', error: err.message });
if (allowedOrigins.includes(origin)) { });
return callback(null, true);
}
// Block everything else
console.warn(`CORS blocked origin: ${origin}`);
return callback(new Error(`CORS policy: origin ${origin} not allowed`), false);
},
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization']
}));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cookieParser());
// Routes
app.use('/api/auth', require('./routes/auth'));
app.use('/api/admin', require('./routes/admin'));
app.use('/api/hr', require('./routes/hr'));
app.use('/api/candidate', require('./routes/candidate'));
// Keep backward compatibility for old student endpoints
app.use('/api/student', require('./routes/candidate'));
// Health check
app.get('/api/health', (req, res) => {
res.json({ status: 'OK', message: 'QuizMaster Pro API is running' });
});
// Error handling middleware
app.use((err, req, res, next) => {
console.error(err.stack);
if (err.name === 'MulterError') {
if (err.code === 'LIMIT_FILE_SIZE') {
return res.status(400).json({ message: 'File size exceeds 5MB limit' });
}
return res.status(400).json({ message: err.message });
}
res.status(500).json({ message: 'Something went wrong!', error: err.message });
});
const PORT = process.env.PORT || 5000; const PORT = process.env.PORT || 5000;
app.listen(PORT, () => { app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`); console.log(`Server running on port ${PORT}`);
}); });
import { HttpInterceptorFn } from '@angular/common/http'; import { HttpInterceptorFn } from '@angular/common/http';
export const authInterceptor: HttpInterceptorFn = (req, next) => { export const authInterceptor: HttpInterceptorFn = (req, next) => {
const token = localStorage.getItem('token'); const token = sessionStorage.getItem('token');
if (token) { if (token) {
const cloned = req.clone({ const cloned = req.clone({
......
...@@ -56,34 +56,53 @@ export class EditQuizComponent implements OnInit { ...@@ -56,34 +56,53 @@ export class EditQuizComponent implements OnInit {
} }
}); });
} }
onSave(): void {
if (!this.title.trim()) {
this.error.set('Title is required');
return;
}
this.saving.set(true);
this.error.set('');
onSave(): void { // 🔥 Format questions correctly
if (!this.title.trim()) { this.error.set('Title is required'); return; } const formattedQuestions = this.questions().map(q => {
this.saving.set(true); const options = q.options;
this.error.set('');
const data = { const correctIndex = options.findIndex(
title: this.title, (opt: string) => opt == q.correctAnswer
timer: this.timer, );
category: this.category,
difficulty: this.difficulty, return {
topic: this.topic, question: q.question,
questions: this.questions() options,
correctAnswers: [correctIndex.toString()],
type: 'single'
}; };
}); // ✅ closes map()
this.quizService.updateQuiz(this.quizId, data).subscribe({ // 🔥 Final data
next: () => { const data = {
this.saving.set(false); title: this.title,
this.success.set('Quiz updated successfully!'); timer: this.timer,
setTimeout(() => this.router.navigate(['/admin/quizzes']), 1200); category: this.category,
}, difficulty: this.difficulty,
error: (err) => { topic: this.topic,
this.saving.set(false); questions: formattedQuestions
this.error.set(err.error?.message || 'Failed to update quiz'); }; // ✅ closes data object
}
});
}
this.quizService.updateQuiz(this.quizId, data).subscribe({
next: () => {
this.saving.set(false);
this.success.set('Quiz updated successfully!');
setTimeout(() => this.router.navigate(['/admin/quizzes']), 1200);
},
error: (err) => {
this.saving.set(false);
this.error.set(err.error?.message || 'Failed to update quiz');
}
}); // ✅ closes subscribe
} // ✅ closes function
updateQuestion(index: number, field: string, value: any): void { updateQuestion(index: number, field: string, value: any): void {
const q = [...this.questions()]; const q = [...this.questions()];
q[index] = { ...q[index], [field]: value }; q[index] = { ...q[index], [field]: value };
......
<div class="dashboard-layout"> <div class="dashboard-layout">
<aside class="sidebar"> <aside class="sidebar">
<div class="sidebar-header"> <div class="sidebar-header">
<span class="logo-icon">📝</span><h2>QuizMaster</h2><span class="role-badge">Admin</span> <span class="logo-icon">📝</span>
<h2>QuizMaster</h2><span class="role-badge">Admin</span>
</div> </div>
<nav class="sidebar-nav"> <nav class="sidebar-nav">
<a routerLink="/admin/dashboard" class="nav-item"><span class="nav-icon">🏠</span><span>Dashboard</span></a> <a routerLink="/admin/dashboard" class="nav-item"><span class="nav-icon">🏠</span><span>Dashboard</span></a>
<a routerLink="/admin/users" class="nav-item"><span class="nav-icon">👥</span><span>Users</span></a> <a routerLink="/admin/users" class="nav-item"><span class="nav-icon">👥</span><span>Users</span></a>
<a routerLink="/admin/generate-quiz" class="nav-item active"><span class="nav-icon"></span><span>Generate Quiz</span></a> <a routerLink="/admin/generate-quiz" class="nav-item active"><span class="nav-icon"></span><span>Generate
Quiz</span></a>
</nav> </nav>
<div class="sidebar-footer"> <div class="sidebar-footer">
<div class="user-info"> <div class="user-info">
<div class="user-avatar">{{ authService.currentUser()?.name?.charAt(0) || 'A' }}</div> <div class="user-avatar">{{ authService.currentUser()?.name?.charAt(0) || 'A' }}</div>
<div class="user-details"><span class="user-name">{{ authService.currentUser()?.name }}</span><span class="user-email">{{ authService.currentUser()?.email }}</span></div> <div class="user-details"><span class="user-name">{{ authService.currentUser()?.name }}</span><span
class="user-email">{{ authService.currentUser()?.email }}</span></div>
</div> </div>
<button class="logout-btn" (click)="logout()"><span>🚪</span> Logout</button> <button class="logout-btn" (click)="logout()"><span>🚪</span> Logout</button>
</div> </div>
...@@ -24,10 +27,10 @@ ...@@ -24,10 +27,10 @@
</div> </div>
@if (success()) { @if (success()) {
<div class="alert alert-success">✅ {{ success() }}</div> <div class="alert alert-success">✅ {{ success() }}</div>
} }
@if (error()) { @if (error()) {
<div class="alert alert-error">⚠️ {{ error() }}</div> <div class="alert alert-error">⚠️ {{ error() }}</div>
} }
<div class="create-section"> <div class="create-section">
...@@ -35,7 +38,8 @@ ...@@ -35,7 +38,8 @@
<div class="form-row"> <div class="form-row">
<div class="form-group"> <div class="form-group">
<label for="title">Quiz Name</label> <label for="title">Quiz Name</label>
<input type="text" id="title" [(ngModel)]="title" name="title" placeholder="e.g., JavaScript Fundamentals" /> <input type="text" id="title" [(ngModel)]="title" name="title"
placeholder="e.g., JavaScript Fundamentals" />
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="timer">Timer (minutes)</label> <label for="timer">Timer (minutes)</label>
...@@ -48,53 +52,77 @@ ...@@ -48,53 +52,77 @@
<div class="file-upload" (click)="fileInput.click()"> <div class="file-upload" (click)="fileInput.click()">
<input #fileInput type="file" accept=".xlsx,.xls" (change)="onFileSelected($event)" hidden /> <input #fileInput type="file" accept=".xlsx,.xls" (change)="onFileSelected($event)" hidden />
@if (fileName()) { @if (fileName()) {
<span class="file-icon">📄</span> <span class="file-icon">📄</span>
<span class="file-name">{{ fileName() }}</span> <span class="file-name">{{ fileName() }}</span>
<span class="file-change">Change</span> <span class="file-change">Change</span>
} @else { } @else {
<span class="upload-icon">📤</span> <span class="upload-icon">📤</span>
<span class="upload-text">Click to upload Excel file</span> <span class="upload-text">Click to upload Excel file</span>
<span class="upload-hint">Format: Question | Option1 | Option2 | Option3 | Option4 | Correct</span> <span class="upload-hint">Format: Question | Option1 | Option2 | Option3 | Option4 | Correct</span>
} }
</div> </div>
</div> </div>
<button type="submit" class="btn btn-primary" [disabled]="loading()"> <div class="form-group">
<label>Assign Quiz</label>
<div style="margin-bottom: 10px;">
<label>
<input type="checkbox" [checked]="assignToAll()" (change)="assignToAll.set(!assignToAll())" />
Assign to All Users
</label>
</div>
@if (!assignToAll()) {
<div class="group-list">
@for (group of groups(); track group._id) {
<label style="display:block; margin-bottom:6px;">
<input type="checkbox" [value]="group.name" (change)="onGroupToggle(group.name, $event)" />
{{ group.name }}
</label>
}
</div>
}
</div>
<button mat-raised-button color="primary" type="submit" class="btn btn-primary" [disabled]="loading()">
@if (loading()) { @if (loading()) {
<span class="spinner"></span> Creating Quiz... <span class="spinner"></span> Creating Quiz...
} @else { } @else {
🚀 Create Quiz 🚀 Create Quiz
} }
</button> </button>
</form> </form>
</div> </div>
<!-- Existing Quizzes --> <!-- Existing Quizzes -->
<h2 class="section-title">Existing Quizzes</h2> <h2 class="section-title">Existing Quizzes</h2>
@if (loadingQuizzes()) { @if (loadingQuizzes()) {
<div class="loading-state"><div class="loader"></div></div> <div class="loading-state">
<div class="loader"></div>
</div>
} @else if (quizzes().length === 0) { } @else if (quizzes().length === 0) {
<div class="empty-state"> <div class="empty-state">
<span class="empty-icon">📋</span> <span class="empty-icon">📋</span>
<h3>No quizzes yet</h3> <h3>No quizzes yet</h3>
<p>Create your first quiz above</p> <p>Create your first quiz above</p>
</div> </div>
} @else { } @else {
<div class="quiz-grid"> <div class="quiz-grid">
@for (quiz of quizzes(); track quiz._id) { @for (quiz of quizzes(); track quiz._id) {
<div class="quiz-card"> <div class="quiz-card">
<div class="quiz-card-header"> <div class="quiz-card-header">
<h4>{{ quiz.title }}</h4> <h4>{{ quiz.title }}</h4>
<button class="delete-btn" (click)="deleteQuiz(quiz._id)">🗑️</button> <button class="delete-btn" (click)="deleteQuiz(quiz._id)">🗑️</button>
</div> </div>
<div class="quiz-meta"> <div class="quiz-meta">
<span>⏱️ {{ quiz.timer }} min</span> <span>⏱️ {{ quiz.timer }} min</span>
<span>📝 {{ quiz.totalQuestions }} questions</span> <span>📝 {{ quiz.totalQuestions }} questions</span>
</div> </div>
<span class="quiz-date">Created {{ quiz.createdAt | date:'mediumDate' }}</span> <span class="quiz-date">Created {{ quiz.createdAt | date:'mediumDate' }}</span>
</div>
}
</div> </div>
}
</div>
} }
</main> </main>
</div> </div>
\ No newline at end of file
...@@ -4,11 +4,12 @@ import { FormsModule } from '@angular/forms'; ...@@ -4,11 +4,12 @@ import { FormsModule } from '@angular/forms';
import { RouterLink } from '@angular/router'; import { RouterLink } from '@angular/router';
import { AuthService } from '../../../services/auth.service'; import { AuthService } from '../../../services/auth.service';
import { QuizService } from '../../../services/quiz.service'; import { QuizService } from '../../../services/quiz.service';
import { MatButtonModule } from '@angular/material/button';
@Component({ @Component({
selector: 'app-generate-quiz', selector: 'app-generate-quiz',
standalone: true, standalone: true,
imports: [CommonModule, FormsModule, RouterLink], imports: [CommonModule, FormsModule, RouterLink, MatButtonModule],
templateUrl: './generate-quiz.html', templateUrl: './generate-quiz.html',
styleUrl: './generate-quiz.css' styleUrl: './generate-quiz.css'
}) })
...@@ -17,7 +18,9 @@ export class GenerateQuizComponent implements OnInit { ...@@ -17,7 +18,9 @@ export class GenerateQuizComponent implements OnInit {
timer = 30; timer = 30;
selectedFile: File | null = null; selectedFile: File | null = null;
fileName = signal<string>(''); fileName = signal<string>('');
groups = signal<any[]>([]);
selectedGroups = signal<string[]>([]);
assignToAll = signal<boolean>(true);
loading = signal<boolean>(false); loading = signal<boolean>(false);
success = signal<string>(''); success = signal<string>('');
error = signal<string>(''); error = signal<string>('');
...@@ -25,11 +28,18 @@ export class GenerateQuizComponent implements OnInit { ...@@ -25,11 +28,18 @@ export class GenerateQuizComponent implements OnInit {
quizzes = signal<any[]>([]); quizzes = signal<any[]>([]);
loadingQuizzes = signal<boolean>(true); loadingQuizzes = signal<boolean>(true);
constructor(public authService: AuthService, private quizService: QuizService) {} constructor(public authService: AuthService, private quizService: QuizService) { }
ngOnInit(): void { ngOnInit(): void {
console.log("Component loaded");
this.loadQuizzes(); this.loadQuizzes();
this.quizService.getAdminGroups().subscribe({
next: (res) => {
this.groups.set(res.groups || []);
},
error: () => {
console.log("Failed to load groups");
}
});
} }
loadQuizzes(): void { loadQuizzes(): void {
...@@ -72,7 +82,8 @@ export class GenerateQuizComponent implements OnInit { ...@@ -72,7 +82,8 @@ export class GenerateQuizComponent implements OnInit {
formData.append('title', this.title); formData.append('title', this.title);
formData.append('timer', this.timer.toString()); formData.append('timer', this.timer.toString());
formData.append('questionsFile', this.selectedFile); formData.append('questionsFile', this.selectedFile);
formData.append('assignToAll', this.assignToAll().toString());
formData.append('assignedGroups', JSON.stringify(this.selectedGroups()));
this.quizService.createQuiz(formData).subscribe({ this.quizService.createQuiz(formData).subscribe({
next: (res) => { next: (res) => {
this.loading.set(false); this.loading.set(false);
...@@ -98,6 +109,15 @@ export class GenerateQuizComponent implements OnInit { ...@@ -98,6 +109,15 @@ export class GenerateQuizComponent implements OnInit {
}); });
} }
} }
onGroupToggle(groupName: string, event: any): void {
const selected = this.selectedGroups();
if (event.target.checked) {
this.selectedGroups.set([...selected, groupName]);
} else {
this.selectedGroups.set(selected.filter(g => g !== groupName));
}
}
logout(): void { logout(): void {
this.authService.logout(); this.authService.logout();
......
...@@ -148,7 +148,7 @@ ...@@ -148,7 +148,7 @@
.main-content { .main-content {
flex: 1; flex: 1;
margin-left: 260px; margin-left: 0px;
padding: 24px 32px; padding: 24px 32px;
} }
......
...@@ -31,8 +31,8 @@ export class AuthService { ...@@ -31,8 +31,8 @@ export class AuthService {
} }
private loadUserFromStorage(): void { private loadUserFromStorage(): void {
const token = localStorage.getItem('token'); const token = sessionStorage.getItem('token');
const user = localStorage.getItem('user'); const user = sessionStorage.getItem('user');
if (token && user) { if (token && user) {
this.currentUser.set(JSON.parse(user)); this.currentUser.set(JSON.parse(user));
this.isLoggedIn.set(true); this.isLoggedIn.set(true);
...@@ -49,7 +49,7 @@ export class AuthService { ...@@ -49,7 +49,7 @@ export class AuthService {
} }
logout(): void { logout(): void {
const token = localStorage.getItem('token'); const token = sessionStorage.getItem('token');
if (token) { if (token) {
this.http.post(`${this.apiUrl}/logout`, {}).subscribe({ this.http.post(`${this.apiUrl}/logout`, {}).subscribe({
complete: () => this.clearAuth(), complete: () => this.clearAuth(),
...@@ -62,23 +62,23 @@ export class AuthService { ...@@ -62,23 +62,23 @@ export class AuthService {
private handleAuth(res: AuthResponse): void { private handleAuth(res: AuthResponse): void {
if (res.token) { if (res.token) {
localStorage.setItem('token', res.token); sessionStorage.setItem('token', res.token);
} }
localStorage.setItem('user', JSON.stringify(res.user)); sessionStorage.setItem('user', JSON.stringify(res.user));
this.currentUser.set(res.user); this.currentUser.set(res.user);
this.isLoggedIn.set(true); this.isLoggedIn.set(true);
} }
private clearAuth(): void { private clearAuth(): void {
localStorage.removeItem('token'); sessionStorage.removeItem('token');
localStorage.removeItem('user'); sessionStorage.removeItem('user');
this.currentUser.set(null); this.currentUser.set(null);
this.isLoggedIn.set(false); this.isLoggedIn.set(false);
this.router.navigate(['/login']); this.router.navigate(['/login']);
} }
getToken(): string | null { getToken(): string | null {
return localStorage.getItem('token'); return sessionStorage.getItem('token');
} }
getUserRole(): string | null { getUserRole(): string | null {
......
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class QuizService { export class QuizService {
private adminUrl = 'http://localhost:5000/api/admin'; private adminUrl = 'http://localhost:5000/api/admin';
private hrUrl = 'http://localhost:5000/api/hr'; private hrUrl = 'http://localhost:5000/api/hr';
private candidateUrl = 'http://localhost:5000/api/candidate'; private candidateUrl = 'http://localhost:5000/api/candidate';
constructor(private http: HttpClient) {} constructor(private http: HttpClient) {}
// ========== ADMIN ENDPOINTS ========== // ========== ADMIN ENDPOINTS ==========
getAdminStats(): Observable<any> { getAdminStats(): Observable<any> {
return this.http.get(`${this.adminUrl}/stats`); return this.http.get(`${this.adminUrl}/stats`);
} }
getUsers(role?: string): Observable<any> { getUsers(role?: string): Observable<any> {
const url = role ? `${this.adminUrl}/users?role=${role}` : `${this.adminUrl}/users`; const url = role ? `${this.adminUrl}/users?role=${role}` : `${this.adminUrl}/users`;
return this.http.get(url); return this.http.get(url);
} }
getLoggedInUsers(): Observable<any> { getLoggedInUsers(): Observable<any> {
return this.http.get(`${this.adminUrl}/users/logged-in`); return this.http.get(`${this.adminUrl}/users/logged-in`);
} }
createHRUser(data: { name: string; email: string; password: string }): Observable<any> { createHRUser(data: { name: string; email: string; password: string }): Observable<any> {
return this.http.post(`${this.adminUrl}/users/create-hr`, data); return this.http.post(`${this.adminUrl}/users/create-hr`, data);
} }
deleteUser(userId: string): Observable<any> { deleteUser(userId: string): Observable<any> {
return this.http.delete(`${this.adminUrl}/users/${userId}`); return this.http.delete(`${this.adminUrl}/users/${userId}`);
} }
getUserHistory(userId: string): Observable<any> { getUserHistory(userId: string): Observable<any> {
return this.http.get(`${this.adminUrl}/users/${userId}/history`); return this.http.get(`${this.adminUrl}/users/${userId}/history`);
} }
getSubmissionDetails(submissionId: string): Observable<any> { getSubmissionDetails(submissionId: string): Observable<any> {
return this.http.get(`${this.adminUrl}/submissions/${submissionId}`); return this.http.get(`${this.adminUrl}/submissions/${submissionId}`);
} }
createQuiz(formData: FormData): Observable<any> { createQuiz(formData: FormData): Observable<any> {
return this.http.post(`${this.adminUrl}/quiz/create`, formData); return this.http.post(`${this.adminUrl}/quiz/create`, formData);
} }
createQuizManual(data: any): Observable<any> { createQuizManual(data: any): Observable<any> {
return this.http.post(`${this.adminUrl}/quiz/create-manual`, data); return this.http.post(`${this.adminUrl}/quiz/create-manual`, data);
} }
getAdminQuizzes(): Observable<any> { getAdminQuizzes(): Observable<any> {
return this.http.get(`${this.adminUrl}/quizzes`); return this.http.get(`${this.adminUrl}/quizzes`);
} }
getAdminQuiz(quizId: string): Observable<any> { getAdminQuiz(quizId: string): Observable<any> {
return this.http.get(`${this.adminUrl}/quiz/${quizId}`); return this.http.get(`${this.adminUrl}/quiz/${quizId}`);
} }
updateQuiz(quizId: string, data: any): Observable<any> { updateQuiz(quizId: string, data: any): Observable<any> {
return this.http.put(`${this.adminUrl}/quiz/${quizId}`, data); return this.http.put(`${this.adminUrl}/quiz/${quizId}`, data);
} }
assignQuiz(quizId: string, data: any): Observable<any> { assignQuiz(quizId: string, data: any): Observable<any> {
return this.http.put(`${this.adminUrl}/quiz/${quizId}/assign`, data); return this.http.put(`${this.adminUrl}/quiz/${quizId}/assign`, data);
} }
deleteQuiz(quizId: string): Observable<any> { deleteQuiz(quizId: string): Observable<any> {
return this.http.delete(`${this.adminUrl}/quiz/${quizId}`); return this.http.delete(`${this.adminUrl}/quiz/${quizId}`);
} }
getAdminCategories(): Observable<any> { getAdminCategories(): Observable<any> {
return this.http.get(`${this.adminUrl}/categories`); return this.http.get(`${this.adminUrl}/categories`);
} }
getAdminGroups(): Observable<any> { getAdminGroups(): Observable<any> {
return this.http.get(`${this.adminUrl}/groups`); return this.http.get(`${this.adminUrl}/groups`);
} }
// ========== HR ENDPOINTS ========== // ========== HR ENDPOINTS ==========
getHRStats(): Observable<any> { getHRStats(): Observable<any> {
return this.http.get(`${this.hrUrl}/stats`); return this.http.get(`${this.hrUrl}/stats`);
} }
getHRQuizzes(): Observable<any> { getHRQuizzes(): Observable<any> {
return this.http.get(`${this.hrUrl}/quizzes`); return this.http.get(`${this.hrUrl}/quizzes`);
} }
getHRQuiz(quizId: string): Observable<any> { getHRQuiz(quizId: string): Observable<any> {
return this.http.get(`${this.hrUrl}/quiz/${quizId}`); return this.http.get(`${this.hrUrl}/quiz/${quizId}`);
} }
createHRQuiz(formData: FormData): Observable<any> { createHRQuiz(formData: FormData): Observable<any> {
return this.http.post(`${this.hrUrl}/quiz/create`, formData); return this.http.post(`${this.hrUrl}/quiz/create`, formData);
} }
createHRQuizManual(data: any): Observable<any> { createHRQuizManual(data: any): Observable<any> {
return this.http.post(`${this.hrUrl}/quiz/create-manual`, data); return this.http.post(`${this.hrUrl}/quiz/create-manual`, data);
} }
updateHRQuiz(quizId: string, data: any): Observable<any> { updateHRQuiz(quizId: string, data: any): Observable<any> {
return this.http.put(`${this.hrUrl}/quiz/${quizId}`, data); return this.http.put(`${this.hrUrl}/quiz/${quizId}`, data);
} }
deleteHRQuiz(quizId: string): Observable<any> { deleteHRQuiz(quizId: string): Observable<any> {
return this.http.delete(`${this.hrUrl}/quiz/${quizId}`); return this.http.delete(`${this.hrUrl}/quiz/${quizId}`);
} }
getHRCandidates(): Observable<any> { getHRCandidates(): Observable<any> {
return this.http.get(`${this.hrUrl}/candidates`); return this.http.get(`${this.hrUrl}/candidates`);
} }
getHRCandidateHistory(userId: string): Observable<any> { getHRCandidateHistory(userId: string): Observable<any> {
return this.http.get(`${this.hrUrl}/candidates/${userId}/history`); return this.http.get(`${this.hrUrl}/candidates/${userId}/history`);
} }
getHRSubmissionDetails(submissionId: string): Observable<any> { getHRSubmissionDetails(submissionId: string): Observable<any> {
return this.http.get(`${this.hrUrl}/submissions/${submissionId}`); return this.http.get(`${this.hrUrl}/submissions/${submissionId}`);
} }
getHRCategories(): Observable<any> { getHRCategories(): Observable<any> {
return this.http.get(`${this.hrUrl}/categories`); return this.http.get(`${this.hrUrl}/categories`);
} }
getHRGroups(): Observable<any> { getHRGroups(): Observable<any> {
return this.http.get(`${this.hrUrl}/groups`); return this.http.get(`${this.hrUrl}/groups`);
} }
// ========== CANDIDATE ENDPOINTS ========== // ========== CANDIDATE ENDPOINTS ==========
getAvailableQuizzes(): Observable<any> { getAvailableQuizzes(): Observable<any> {
return this.http.get(`${this.candidateUrl}/quizzes`); return this.http.get(`${this.candidateUrl}/quizzes`);
} }
getQuizForTaking(quizId: string): Observable<any> { getQuizForTaking(quizId: string): Observable<any> {
return this.http.get(`${this.candidateUrl}/quiz/${quizId}`); return this.http.get(`${this.candidateUrl}/quiz/${quizId}`);
} }
submitQuiz(quizId: string, answers: any[], timeTaken: number): Observable<any> { submitQuiz(quizId: string, answers: any[], timeTaken: number): Observable<any> {
return this.http.post(`${this.candidateUrl}/quiz/${quizId}/submit`, { answers, timeTaken }); return this.http.post(`${this.candidateUrl}/quiz/${quizId}/submit`, { answers, timeTaken });
} }
getCandidateProfile(): Observable<any> { getCandidateProfile(): Observable<any> {
return this.http.get(`${this.candidateUrl}/profile`); return this.http.get(`${this.candidateUrl}/profile`);
} }
getResultDetails(submissionId: string): Observable<any> { getResultDetails(submissionId: string): Observable<any> {
return this.http.get(`${this.candidateUrl}/results/${submissionId}`); return this.http.get(`${this.candidateUrl}/results/${submissionId}`);
}
} }
}
...@@ -33,6 +33,21 @@ export class ThemeService { ...@@ -33,6 +33,21 @@ export class ThemeService {
} }
private applyTheme(theme: ThemeMode): void { private applyTheme(theme: ThemeMode): void {
// Existing (your custom theme)
document.documentElement.setAttribute('data-theme', theme); document.documentElement.setAttribute('data-theme', theme);
// NEW (Material theme)
const body = document.body;
body.classList.remove('azure-theme', 'magenta-theme', 'cyan-theme', 'rose-theme');
// Map your themes → material themes
const themeMap: Record<ThemeMode, string> = {
light: 'azure-theme',
dark: 'magenta-theme',
blue: 'cyan-theme'
};
body.classList.add(themeMap[theme] || 'azure-theme');
} }
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment