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

refactor : Changes made in edit page and user-history page

parent d2d978c5
{}
\ No newline at end of file
...@@ -686,7 +686,7 @@ IMPORTANT: The "correct" value must be the EXACT TEXT of the option, not the opt ...@@ -686,7 +686,7 @@ IMPORTANT: The "correct" value must be the EXACT TEXT of the option, not the opt
type: q.correctAnswers.length > 1 ? 'mcq' : 'single' type: q.correctAnswers.length > 1 ? 'mcq' : 'single'
})); }));
await Question.insertMany(questions); await Question.insertMany(questionDocs);
updateData.totalQuestions = questions.length; updateData.totalQuestions = questions.length;
} }
......
...@@ -655,7 +655,7 @@ IMPORTANT: The "correct" value must be the EXACT TEXT of the option, not the opt ...@@ -655,7 +655,7 @@ IMPORTANT: The "correct" value must be the EXACT TEXT of the option, not the opt
type: q.correctAnswers.length > 1 ? 'mcq' : 'single' type: q.correctAnswers.length > 1 ? 'mcq' : 'single'
})); }));
await Question.insertMany(questions); await Question.insertMany(questionDocs);
updateData.totalQuestions = questions.length; updateData.totalQuestions = questions.length;
} }
......
.page-container { padding: 32px 40px; max-width: 900px; } .page-container { padding: 32px 40px; max-width: 960px; }
.page-header { margin-bottom: 28px; } .page-header { margin-bottom: 28px; }
.page-header h1 { font-size: 26px; font-weight: 700; color: var(--text-primary); margin: 8px 0 0; } .page-header h1 { font-size: 26px; font-weight: 700; color: var(--text-primary); margin: 8px 0 0; }
.back-link { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; color: var(--text-secondary); font-weight: 500; } .back-link { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; color: var(--text-secondary); font-weight: 500; text-decoration: none; }
.back-link:hover { color: var(--accent-primary); } .back-link:hover { color: var(--accent-primary); }
.loading-center { display: flex; flex-direction: column; align-items: center; padding: 80px 0; gap: 16px; } .loading-center { display: flex; flex-direction: column; align-items: center; padding: 80px 0; gap: 16px; }
.loading-center p { color: var(--text-muted); } .loading-center p { color: var(--text-muted); }
.form-card { margin-bottom: 24px; } /* Questions Header */
.quiz-form { display: flex; flex-direction: column; gap: 20px; } .questions-header { display: flex; align-items: center; justify-content: space-between; margin: 0 0 20px; }
.form-group { display: flex; flex-direction: column; flex: 1; } .questions-header h2 { font-size: 18px; font-weight: 600; color: var(--text-primary); margin: 0; }
.form-row { display: flex; gap: 16px; }
.questions-header { display: flex; align-items: center; justify-content: space-between; margin: 24px 0 16px; } /* Questions List */
.questions-header h2 { font-size: 18px; font-weight: 600; color: var(--text-primary); } .questions-list { display: flex; flex-direction: column; gap: 20px; }
.question-card { margin-bottom: 16px; display: flex; flex-direction: column; gap: 14px; } /* Question Card — matches submission-detail card style */
.question-header { display: flex; align-items: center; justify-content: space-between; } .question-card {
.q-number { font-size: 13px; font-weight: 700; color: var(--accent-primary); background: var(--accent-primary-light); padding: 4px 12px; border-radius: var(--radius-full); } background: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: 16px;
padding: 24px;
border-left: 4px solid var(--accent-primary);
transition: box-shadow 0.2s ease, border-color 0.2s ease;
}
.question-card:hover {
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.06);
}
/* Question Header */
.q-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 16px;
}
.q-number {
background: rgba(var(--accent-primary-rgb), 0.15);
color: var(--accent-primary);
padding: 4px 14px;
border-radius: 8px;
font-weight: 700;
font-size: 13px;
flex-shrink: 0;
}
.q-type-badge {
background: var(--bg-input);
color: var(--text-secondary);
padding: 4px 10px;
border-radius: 8px;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.3px;
}
.delete-q-btn {
margin-left: auto;
background: transparent;
border: 1px solid var(--danger-border);
color: var(--danger);
width: 34px;
height: 34px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s;
}
.delete-q-btn .material-symbols-rounded { font-size: 18px; }
.delete-q-btn:hover {
background: var(--danger);
color: #fff;
border-color: var(--danger);
}
/* Editable Question Text */
.q-text-wrap { margin-bottom: 16px; }
.q-text-input {
width: 100%;
padding: 12px 16px;
background: var(--bg-input);
border: 1px solid var(--border-color);
border-radius: 10px;
font-size: 15px;
line-height: 1.5;
color: var(--text-primary);
font-family: inherit;
transition: border-color 0.2s;
}
.q-text-input:focus {
outline: none;
border-color: var(--accent-primary);
box-shadow: 0 0 0 3px rgba(var(--accent-primary-rgb), 0.1);
}
.q-text-input::placeholder { color: var(--text-muted); }
/* Options Grid — same 2-col layout as submission-detail */
.options-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
margin-bottom: 16px;
}
.options-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; } .option {
.option-input-wrap { display: flex; align-items: center; gap: 8px; } padding: 0;
.option-letter { font-size: 12px; font-weight: 700; color: var(--text-muted); width: 20px; text-align: center; flex-shrink: 0; } border-radius: 10px;
background: var(--bg-input);
border: 1px solid var(--border-color);
transition: all 0.2s;
cursor: pointer;
position: relative;
}
.option:hover {
border-color: var(--accent-primary);
background: rgba(var(--accent-primary-rgb), 0.04);
}
.option.correct-answer {
background: rgba(74, 222, 128, 0.1);
border-color: rgba(74, 222, 128, 0.4);
}
.option-inner {
display: flex;
align-items: center;
gap: 8px;
padding: 2px 4px 2px 14px;
}
.option-marker {
font-weight: 700;
font-size: 16px;
color: var(--success);
width: 18px;
flex-shrink: 0;
}
.option-input {
flex: 1;
padding: 10px 12px;
background: transparent;
border: none;
font-size: 14px;
color: var(--text-primary);
font-family: inherit;
outline: none;
min-width: 0;
}
.option-input::placeholder { color: var(--text-muted); }
/* Correct Answer Row */
.correct-answer-row {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 16px;
background: rgba(74, 222, 128, 0.06);
border: 1px solid rgba(74, 222, 128, 0.2);
border-radius: 10px;
}
.correct-icon {
color: var(--success);
font-size: 20px;
}
.correct-label {
font-size: 13px;
font-weight: 600;
color: var(--text-secondary);
white-space: nowrap;
}
.correct-select {
flex: 1;
padding: 6px 12px;
background: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: 8px;
font-size: 13px;
color: var(--text-primary);
font-family: inherit;
cursor: pointer;
}
.correct-select:focus {
outline: none;
border-color: var(--success);
}
/* Save Button */
.save-btn { width: 100%; margin-top: 24px; } .save-btn { width: 100%; margin-top: 24px; }
@media (max-width: 768px) { @media (max-width: 768px) {
.page-container { padding: 20px 16px; } .page-container { padding: 20px 16px; }
.form-row, .options-grid { flex-direction: column; grid-template-columns: 1fr; } .options-grid { grid-template-columns: 1fr; }
} }
...@@ -19,32 +19,6 @@ ...@@ -19,32 +19,6 @@
@if (loading()) { @if (loading()) {
<div class="loading-center"><div class="spinner spinner-lg"></div><p>Loading quiz...</p></div> <div class="loading-center"><div class="spinner spinner-lg"></div><p>Loading quiz...</p></div>
} @else { } @else {
<div class="card card-padding form-card">
<div class="quiz-form">
<div class="form-group">
<label class="form-label">Quiz Title</label>
<input class="form-input" [(ngModel)]="title" placeholder="Quiz title">
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label">Timer (min)</label>
<input class="form-input" type="number" [(ngModel)]="timer" min="1">
</div>
<div class="form-group">
<label class="form-label">Difficulty</label>
<select class="form-select" [(ngModel)]="difficulty">
<option value="easy">Easy</option>
<option value="medium">Medium</option>
<option value="hard">Hard</option>
</select>
</div>
</div>
<div class="form-group">
<label class="form-label">Category</label>
<input class="form-input" [(ngModel)]="category" placeholder="e.g. Java, Angular, Data Structures">
</div>
</div>
</div>
@if (!locked()) { @if (!locked()) {
<div class="questions-header"> <div class="questions-header">
...@@ -54,37 +28,66 @@ ...@@ -54,37 +28,66 @@
</button> </button>
</div> </div>
<div class="questions-list">
@for (q of questions(); track $index; let i = $index) { @for (q of questions(); track $index; let i = $index) {
<div class="card card-padding question-card"> <div class="question-card">
<div class="question-header"> <!-- Question Header -->
<div class="q-header">
<span class="q-number">Q{{ i + 1 }}</span> <span class="q-number">Q{{ i + 1 }}</span>
<button class="btn btn-ghost btn-sm" (click)="removeQuestion(i)"> <span class="q-type-badge">{{ q.type === 'mcq' ? 'MCQ' : 'SINGLE' }}</span>
<button class="delete-q-btn" (click)="removeQuestion(i)" title="Delete question">
<span class="material-symbols-rounded">delete</span> <span class="material-symbols-rounded">delete</span>
</button> </button>
</div> </div>
<div class="form-group">
<input class="form-input" [ngModel]="q.question" (ngModelChange)="updateQuestion(i, 'question', $event)" placeholder="Question text"> <!-- Question Text (editable) -->
<div class="q-text-wrap">
<input class="q-text-input"
[ngModel]="q.question"
(ngModelChange)="updateQuestion(i, 'question', $event)"
placeholder="Enter question text...">
</div> </div>
<!-- Options Grid -->
<div class="options-grid"> <div class="options-grid">
@for (opt of q.options; track $index; let j = $index) { @for (opt of q.options; track $index; let j = $index) {
<div class="option-input-wrap"> <div class="option"
<span class="option-letter">{{ ['A','B','C','D'][j] }}</span> [class.correct-answer]="q.correctAnswer && opt && q.correctAnswer === opt && opt !== ''"
<input class="form-input" [ngModel]="opt" (ngModelChange)="updateOption(i, j, $event)" placeholder="Option {{ j+1 }}"> (click)="opt ? setCorrectAnswer(i, opt) : null">
</div> <div class="option-inner">
<span class="option-marker">
@if (q.correctAnswer && opt && q.correctAnswer === opt && opt !== '') {
} }
</span>
<input class="option-input"
[ngModel]="opt"
(ngModelChange)="updateOption(i, j, $event)"
placeholder="Option {{ ['A','B','C','D'][j] }}"
(click)="$event.stopPropagation()">
</div> </div>
<div class="form-row">
<div class="form-group">
<label class="form-label">Correct Answer</label>
<input class="form-input" [ngModel]="q.correctAnswer" (ngModelChange)="updateQuestion(i, 'correctAnswer', $event)" placeholder="Correct answer text">
</div> </div>
<div class="form-group" style="max-width: 120px;"> }
<label class="form-label">Marks</label>
<input class="form-input" type="number" [ngModel]="q.marks" (ngModelChange)="updateQuestion(i, 'marks', $event)" min="1">
</div> </div>
<!-- Correct Answer Selector -->
<div class="correct-answer-row">
<span class="material-symbols-rounded correct-icon">check_circle</span>
<label class="correct-label">Correct Answer:</label>
<select class="correct-select"
[ngModel]="q.correctAnswer"
(ngModelChange)="setCorrectAnswer(i, $event)">
<option value="">-- Select --</option>
@for (opt of q.options; track $index; let j = $index) {
@if (opt) {
<option [value]="opt">{{ ['A','B','C','D'][j] }}. {{ opt }}</option>
}
}
</select>
</div> </div>
</div> </div>
} }
</div>
} }
<button class="btn btn-primary btn-lg save-btn" (click)="onSave()" [disabled]="saving()"> <button class="btn btn-primary btn-lg save-btn" (click)="onSave()" [disabled]="saving()">
......
...@@ -37,15 +37,35 @@ export class EditQuizComponent implements OnInit { ...@@ -37,15 +37,35 @@ export class EditQuizComponent implements OnInit {
} }
loadQuiz(): void { loadQuiz(): void {
const overrides = history.state?.quizOverrides;
this.quizService.getAdminQuiz(this.quizId).subscribe({ this.quizService.getAdminQuiz(this.quizId).subscribe({
next: (res) => { next: (res) => {
const q = res.quiz; const q = res.quiz;
this.title = q.title; this.title = overrides ? overrides.title : q.title;
this.timer = q.timer; this.timer = overrides ? overrides.timer : q.timer;
this.category = q.category || ''; this.category = overrides ? overrides.category : (q.category || '');
this.difficulty = q.difficulty || 'medium'; this.difficulty = overrides ? overrides.difficulty : (q.difficulty || 'medium');
this.questions.set(q.questions || []);
this.locked.set(res.hasAttempts || false); // Questions come from res.questions (separate from quiz object)
const rawQuestions = res.questions || [];
const mapped = rawQuestions.map((rq: any) => {
// correctAnswers stores indices as strings like ["0"]
// Map them back to the actual option text for editing
const correctIndex = rq.correctAnswers?.[0] ? parseInt(rq.correctAnswers[0]) : -1;
const correctAnswer = (correctIndex >= 0 && rq.options[correctIndex]) ? rq.options[correctIndex] : '';
return {
_id: rq._id,
question: rq.question,
options: [...rq.options],
correctAnswer: correctAnswer,
type: rq.type || 'single'
};
});
this.questions.set(mapped);
this.locked.set((res.attemptCount || 0) > 0);
this.loading.set(false); this.loading.set(false);
}, },
error: (err) => { error: (err) => {
...@@ -54,7 +74,8 @@ export class EditQuizComponent implements OnInit { ...@@ -54,7 +74,8 @@ export class EditQuizComponent implements OnInit {
} }
}); });
} }
onSave(): void {
onSave(): void {
if (!this.title.trim()) { if (!this.title.trim()) {
this.error.set('Title is required'); this.error.set('Title is required');
return; return;
...@@ -63,30 +84,26 @@ onSave(): void { ...@@ -63,30 +84,26 @@ onSave(): void {
this.saving.set(true); this.saving.set(true);
this.error.set(''); this.error.set('');
// 🔥 Format questions correctly
const formattedQuestions = this.questions().map(q => { const formattedQuestions = this.questions().map(q => {
const options = q.options; const correctIndex = q.options.findIndex(
(opt: string) => opt === q.correctAnswer
const correctIndex = options.findIndex(
(opt: string) => opt == q.correctAnswer
); );
return { return {
question: q.question, question: q.question,
options, options: q.options,
correctAnswers: [correctIndex.toString()], correctAnswers: [correctIndex.toString()],
type: 'single' type: 'single'
}; };
}); // ✅ closes map() });
// 🔥 Final data
const data = { const data = {
title: this.title, title: this.title,
timer: this.timer, timer: this.timer,
category: this.category, category: this.category,
difficulty: this.difficulty, difficulty: this.difficulty,
questions: formattedQuestions questions: formattedQuestions
}; // ✅ closes data object };
this.quizService.updateQuiz(this.quizId, data).subscribe({ this.quizService.updateQuiz(this.quizId, data).subscribe({
next: () => { next: () => {
...@@ -98,8 +115,9 @@ onSave(): void { ...@@ -98,8 +115,9 @@ onSave(): void {
this.saving.set(false); this.saving.set(false);
this.error.set(err.error?.message || 'Failed to update quiz'); 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 };
...@@ -108,9 +126,22 @@ onSave(): void { ...@@ -108,9 +126,22 @@ onSave(): void {
updateOption(qIndex: number, optIndex: number, value: string): void { updateOption(qIndex: number, optIndex: number, value: string): void {
const q = [...this.questions()]; const q = [...this.questions()];
const oldOption = q[qIndex].options[optIndex];
const opts = [...(q[qIndex].options || [])]; const opts = [...(q[qIndex].options || [])];
opts[optIndex] = value; opts[optIndex] = value;
q[qIndex] = { ...q[qIndex], options: opts };
// If the old option was the correct answer, update correctAnswer to the new value
const updated = { ...q[qIndex], options: opts };
if (q[qIndex].correctAnswer === oldOption) {
updated.correctAnswer = value;
}
q[qIndex] = updated;
this.questions.set(q);
}
setCorrectAnswer(qIndex: number, optionText: string): void {
const q = [...this.questions()];
q[qIndex] = { ...q[qIndex], correctAnswer: optionText };
this.questions.set(q); this.questions.set(q);
} }
...@@ -121,7 +152,11 @@ onSave(): void { ...@@ -121,7 +152,11 @@ onSave(): void {
addQuestion(): void { addQuestion(): void {
this.questions.set([...this.questions(), { this.questions.set([...this.questions(), {
question: '', options: ['', '', '', ''], correctAnswer: '', marks: 1 question: '', options: ['', '', '', ''], correctAnswer: '', type: 'single'
}]); }]);
setTimeout(() => {
const cards = document.querySelectorAll('.question-card');
cards[cards.length - 1]?.scrollIntoView({ behavior: 'smooth', block: 'center' });
}, 50);
} }
} }
...@@ -47,3 +47,49 @@ ...@@ -47,3 +47,49 @@
.page-container { padding: 20px 16px; } .page-container { padding: 20px 16px; }
.quiz-grid { grid-template-columns: 1fr; } .quiz-grid { grid-template-columns: 1fr; }
} }
/* Modal Overlay Styles */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.4);
backdrop-filter: blur(4px);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
animation: fadeIn 0.2s ease-out;
}
.modal-container {
background: var(--bg-card);
border-radius: var(--radius-lg);
padding: 32px;
width: 100%;
max-width: 600px;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
border: 1px solid var(--border-color);
animation: slideUp 0.3s cubic-bezier(0.16, 1, 0.3, 1);
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
}
.modal-header h2 {
font-size: 20px;
font-weight: 700;
margin: 0;
color: var(--text-primary);
}
@keyframes slideUp {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
...@@ -61,9 +61,9 @@ ...@@ -61,9 +61,9 @@
{{ quiz.attemptCount }} attempt{{ quiz.attemptCount > 1 ? 's' : '' }} {{ quiz.attemptCount }} attempt{{ quiz.attemptCount > 1 ? 's' : '' }}
</span> </span>
} @else { } @else {
<a [routerLink]="['/admin/quiz', quiz._id, 'edit']" class="btn btn-outline btn-sm"> <button class="btn btn-outline btn-sm" (click)="openEditPopup(quiz)">
<span class="material-symbols-rounded">edit</span> Edit <span class="material-symbols-rounded">edit</span> Edit
</a> </button>
<button class="btn btn-danger btn-sm" (click)="deleteQuiz(quiz._id)"> <button class="btn btn-danger btn-sm" (click)="deleteQuiz(quiz._id)">
<span class="material-symbols-rounded">delete</span> Delete <span class="material-symbols-rounded">delete</span> Delete
</button> </button>
...@@ -74,3 +74,57 @@ ...@@ -74,3 +74,57 @@
</div> </div>
} }
</div> </div>
<!-- Edit Quiz Modal -->
@if (showEditPopup()) {
<div class="modal-overlay" (click)="closeEditPopup()">
<div class="modal-container" (click)="$event.stopPropagation()">
<div class="modal-header">
<h2>Edit Quiz Details</h2>
<button class="icon-btn" (click)="closeEditPopup()">
<span class="material-symbols-rounded">close</span>
</button>
</div>
<div class="modal-body quiz-form">
<div class="form-group">
<label class="form-label">Quiz Title</label>
<input class="form-input" [(ngModel)]="editQuizForm.title" placeholder="Quiz title">
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label">Timer (min)</label>
<input class="form-input" type="number" [(ngModel)]="editQuizForm.timer" min="1">
</div>
<div class="form-group">
<label class="form-label">Difficulty</label>
<select class="form-select" [(ngModel)]="editQuizForm.difficulty">
<option value="easy">Easy</option>
<option value="medium">Medium</option>
<option value="hard">Hard</option>
</select>
</div>
</div>
<div class="form-group">
<label class="form-label">Category</label>
<input class="form-input" [(ngModel)]="editQuizForm.category" placeholder="e.g. Java, Angular">
</div>
</div>
<div class="modal-footer" style="display: flex; gap: 12px; margin-top: 24px; justify-content: flex-end;">
<button class="btn btn-outline" (click)="editQuestions()">
<span class="material-symbols-rounded">edit_square</span> Edit Questions
</button>
<button class="btn btn-primary" (click)="saveBasicChanges()" [disabled]="savingPopup()">
@if (savingPopup()) {
<div class="spinner"></div> Saving...
} @else {
<span class="material-symbols-rounded">save</span> Save Changes
}
</button>
</div>
</div>
</div>
}
import { Component, OnInit, signal } from '@angular/core'; import { Component, OnInit, signal } from '@angular/core';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { RouterLink } from '@angular/router'; import { RouterLink, Router } from '@angular/router';
import { FormsModule } from '@angular/forms';
import { QuizService } from '../../../services/quiz.service'; import { QuizService } from '../../../services/quiz.service';
@Component({ @Component({
selector: 'app-admin-quizzes', selector: 'app-admin-quizzes',
standalone: true, standalone: true,
imports: [CommonModule, RouterLink], imports: [CommonModule, RouterLink, FormsModule],
templateUrl: './quizzes.html', templateUrl: './quizzes.html',
styleUrl: './quizzes.css' styleUrl: './quizzes.css'
}) })
...@@ -15,7 +16,17 @@ export class AdminQuizzesComponent implements OnInit { ...@@ -15,7 +16,17 @@ export class AdminQuizzesComponent implements OnInit {
loading = signal(true); loading = signal(true);
error = signal(''); error = signal('');
constructor(private quizService: QuizService) {} showEditPopup = signal(false);
editQuizForm = {
_id: '',
title: '',
timer: 10,
difficulty: 'medium',
category: ''
};
savingPopup = signal(false);
constructor(private quizService: QuizService, private router: Router) {}
ngOnInit(): void { ngOnInit(): void {
this.loadQuizzes(); this.loadQuizzes();
...@@ -45,4 +56,46 @@ export class AdminQuizzesComponent implements OnInit { ...@@ -45,4 +56,46 @@ export class AdminQuizzesComponent implements OnInit {
default: return 'badge-primary'; default: return 'badge-primary';
} }
} }
openEditPopup(quiz: any): void {
this.editQuizForm = {
_id: quiz._id,
title: quiz.title,
timer: quiz.timer,
difficulty: quiz.difficulty,
category: quiz.category
};
this.showEditPopup.set(true);
}
closeEditPopup(): void {
this.showEditPopup.set(false);
}
saveBasicChanges(): void {
this.savingPopup.set(true);
// Don't send questions, only update basic details
this.quizService.updateQuiz(this.editQuizForm._id, this.editQuizForm).subscribe({
next: () => {
this.savingPopup.set(false);
this.closeEditPopup();
this.loadQuizzes();
},
error: (err) => {
this.savingPopup.set(false);
this.error.set(err.error?.message || 'Failed to save changes');
setTimeout(() => this.error.set(''), 3000);
}
});
}
editQuestions(): void {
// Navigate to edit-quiz page with the pending changes in history.state
this.router.navigate(['/admin/quiz', this.editQuizForm._id, 'edit'], {
state: {
quizOverrides: { ...this.editQuizForm }
}
});
this.closeEditPopup();
}
} }
...@@ -326,3 +326,21 @@ ...@@ -326,3 +326,21 @@
.toast .material-symbols-rounded { .toast .material-symbols-rounded {
font-size: 20px; font-size: 20px;
} }
.evaluation-summary {
margin-top: 20px;
}
.evaluation-summary button {
background-color: #ef4444;
color: white;
padding: 10px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
}
.evaluation-summary button:hover {
background-color: #dc2626;
color: white;
}
\ No newline at end of file
...@@ -49,7 +49,9 @@ ...@@ -49,7 +49,9 @@
<table class="history-table"> <table class="history-table">
<thead> <thead>
<tr> <tr>
<th>Quiz</th> <th>Quiz Name</th>
<th>Topic</th>
<th>Candidate's comfort Level</th>
<th>Score</th> <th>Score</th>
<th>Percentage</th> <th>Percentage</th>
<th>Time Taken</th> <th>Time Taken</th>
...@@ -61,6 +63,8 @@ ...@@ -61,6 +63,8 @@
@for (sub of submissions(); track sub._id) { @for (sub of submissions(); track sub._id) {
<tr> <tr>
<td class="quiz-name">{{ sub.quizId?.title || 'Deleted Quiz' }}</td> <td class="quiz-name">{{ sub.quizId?.title || 'Deleted Quiz' }}</td>
<td>{{ sub.quizId?.category || 'N/A' }}</td>
<td>{{ getComfortLevel(sub.quizId?.category) }}</td>
<td><span class="score-badge">{{ sub.score }}/{{ sub.totalMarks }}</span></td> <td><span class="score-badge">{{ sub.score }}/{{ sub.totalMarks }}</span></td>
<td> <td>
<div class="percent-bar"> <div class="percent-bar">
...@@ -83,6 +87,11 @@ ...@@ -83,6 +87,11 @@
</div> </div>
} }
} }
<div class="evaulation-summary">
<button>
Evaluate Summary
</button>
</div>
</div> </div>
</div> </div>
......
...@@ -84,6 +84,14 @@ export class UserHistoryComponent implements OnInit { ...@@ -84,6 +84,14 @@ export class UserHistoryComponent implements OnInit {
return `${m}m ${s}s`; return `${m}m ${s}s`;
} }
getComfortLevel(topic: string): string {
const u = this.user();
if (!u || !u.topicsOfInterest || !topic) return 'N/A';
const interest = u.topicsOfInterest.find((t: any) => t.topic.toLowerCase() === topic.toLowerCase());
return interest ? `${interest.comfortLevel}%` : 'N/A';
}
logout(): void { logout(): void {
this.authService.logout(); this.authService.logout();
} }
......
.page-container { padding: 32px 40px; max-width: 900px; } .page-container { padding: 32px 40px; max-width: 960px; }
.page-header { margin-bottom: 28px; } .page-header { margin-bottom: 28px; }
.page-header h1 { font-size: 26px; font-weight: 700; color: var(--text-primary); margin: 8px 0 0; } .page-header h1 { font-size: 26px; font-weight: 700; color: var(--text-primary); margin: 8px 0 0; }
.back-link { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; color: var(--text-secondary); font-weight: 500; } .back-link { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; color: var(--text-secondary); font-weight: 500; text-decoration: none; }
.back-link:hover { color: var(--accent-primary); } .back-link:hover { color: var(--accent-primary); }
.loading-center { display: flex; flex-direction: column; align-items: center; padding: 80px 0; gap: 16px; } .loading-center { display: flex; flex-direction: column; align-items: center; padding: 80px 0; gap: 16px; }
.loading-center p { color: var(--text-muted); } .loading-center p { color: var(--text-muted); }
.form-card { margin-bottom: 24px; } /* Questions Header */
.quiz-form { display: flex; flex-direction: column; gap: 20px; } .questions-header { display: flex; align-items: center; justify-content: space-between; margin: 0 0 20px; }
.form-group { display: flex; flex-direction: column; flex: 1; } .questions-header h2 { font-size: 18px; font-weight: 600; color: var(--text-primary); margin: 0; }
.form-row { display: flex; gap: 16px; }
.questions-header { display: flex; align-items: center; justify-content: space-between; margin: 24px 0 16px; } /* Questions List */
.questions-header h2 { font-size: 18px; font-weight: 600; color: var(--text-primary); } .questions-list { display: flex; flex-direction: column; gap: 20px; }
.question-card { margin-bottom: 16px; display: flex; flex-direction: column; gap: 14px; } /* Question Card */
.question-header { display: flex; align-items: center; justify-content: space-between; } .question-card {
.q-number { font-size: 13px; font-weight: 700; color: var(--accent-primary); background: var(--accent-primary-light); padding: 4px 12px; border-radius: var(--radius-full); } background: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: 16px;
padding: 24px;
border-left: 4px solid var(--accent-primary);
transition: box-shadow 0.2s ease, border-color 0.2s ease;
}
.question-card:hover {
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.06);
}
/* Question Header */
.q-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 16px;
}
.q-number {
background: rgba(var(--accent-primary-rgb), 0.15);
color: var(--accent-primary);
padding: 4px 14px;
border-radius: 8px;
font-weight: 700;
font-size: 13px;
flex-shrink: 0;
}
.q-type-badge {
background: var(--bg-input);
color: var(--text-secondary);
padding: 4px 10px;
border-radius: 8px;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.3px;
}
.delete-q-btn {
margin-left: auto;
background: transparent;
border: 1px solid var(--danger-border);
color: var(--danger);
width: 34px;
height: 34px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s;
}
.delete-q-btn .material-symbols-rounded { font-size: 18px; }
.delete-q-btn:hover {
background: var(--danger);
color: #fff;
border-color: var(--danger);
}
/* Editable Question Text */
.q-text-wrap { margin-bottom: 16px; }
.q-text-input {
width: 100%;
padding: 12px 16px;
background: var(--bg-input);
border: 1px solid var(--border-color);
border-radius: 10px;
font-size: 15px;
line-height: 1.5;
color: var(--text-primary);
font-family: inherit;
transition: border-color 0.2s;
}
.q-text-input:focus {
outline: none;
border-color: var(--accent-primary);
box-shadow: 0 0 0 3px rgba(var(--accent-primary-rgb), 0.1);
}
.q-text-input::placeholder { color: var(--text-muted); }
/* Options Grid */
.options-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
margin-bottom: 16px;
}
.options-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; } .option {
.option-input-wrap { display: flex; align-items: center; gap: 8px; } padding: 0;
.option-letter { font-size: 12px; font-weight: 700; color: var(--text-muted); width: 20px; text-align: center; flex-shrink: 0; } border-radius: 10px;
background: var(--bg-input);
border: 1px solid var(--border-color);
transition: all 0.2s;
cursor: pointer;
position: relative;
}
.option:hover {
border-color: var(--accent-primary);
background: rgba(var(--accent-primary-rgb), 0.04);
}
.option.correct-answer {
background: rgba(74, 222, 128, 0.1);
border-color: rgba(74, 222, 128, 0.4);
}
.option-inner {
display: flex;
align-items: center;
gap: 8px;
padding: 2px 4px 2px 14px;
}
.option-marker {
font-weight: 700;
font-size: 16px;
color: var(--success);
width: 18px;
flex-shrink: 0;
}
.option-input {
flex: 1;
padding: 10px 12px;
background: transparent;
border: none;
font-size: 14px;
color: var(--text-primary);
font-family: inherit;
outline: none;
min-width: 0;
}
.option-input::placeholder { color: var(--text-muted); }
/* Correct Answer Row */
.correct-answer-row {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 16px;
background: rgba(74, 222, 128, 0.06);
border: 1px solid rgba(74, 222, 128, 0.2);
border-radius: 10px;
}
.correct-icon {
color: var(--success);
font-size: 20px;
}
.correct-label {
font-size: 13px;
font-weight: 600;
color: var(--text-secondary);
white-space: nowrap;
}
.correct-select {
flex: 1;
padding: 6px 12px;
background: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: 8px;
font-size: 13px;
color: var(--text-primary);
font-family: inherit;
cursor: pointer;
}
.correct-select:focus {
outline: none;
border-color: var(--success);
}
/* Save Button */
.save-btn { width: 100%; margin-top: 24px; } .save-btn { width: 100%; margin-top: 24px; }
@media (max-width: 768px) { @media (max-width: 768px) {
.page-container { padding: 20px 16px; } .page-container { padding: 20px 16px; }
.form-row, .options-grid { flex-direction: column; grid-template-columns: 1fr; } .options-grid { grid-template-columns: 1fr; }
} }
<div class="page-container animate-fade-in"> <div class="page-container animate-fade-in">
<div class="page-header"> <div class="page-header">
<a routerLink="/admin/quizzes" class="back-link"> <a routerLink="/hr/quizzes" class="back-link">
<span class="material-symbols-rounded">arrow_back</span> Back to Quizzes <span class="material-symbols-rounded">arrow_back</span> Back to Quizzes
</a> </a>
<h1>Edit Quiz</h1> <h1>Edit Quiz</h1>
...@@ -19,32 +19,6 @@ ...@@ -19,32 +19,6 @@
@if (loading()) { @if (loading()) {
<div class="loading-center"><div class="spinner spinner-lg"></div><p>Loading quiz...</p></div> <div class="loading-center"><div class="spinner spinner-lg"></div><p>Loading quiz...</p></div>
} @else { } @else {
<div class="card card-padding form-card">
<div class="quiz-form">
<div class="form-group">
<label class="form-label">Quiz Title</label>
<input class="form-input" [(ngModel)]="title" placeholder="Quiz title">
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label">Timer (min)</label>
<input class="form-input" type="number" [(ngModel)]="timer" min="1">
</div>
<div class="form-group">
<label class="form-label">Difficulty</label>
<select class="form-select" [(ngModel)]="difficulty">
<option value="easy">Easy</option>
<option value="medium">Medium</option>
<option value="hard">Hard</option>
</select>
</div>
</div>
<div class="form-group">
<label class="form-label">Category</label>
<input class="form-input" [(ngModel)]="category" placeholder="e.g. Java, Angular, Data Structures">
</div>
</div>
</div>
@if (!locked()) { @if (!locked()) {
<div class="questions-header"> <div class="questions-header">
...@@ -54,37 +28,62 @@ ...@@ -54,37 +28,62 @@
</button> </button>
</div> </div>
<div class="questions-list">
@for (q of questions(); track $index; let i = $index) { @for (q of questions(); track $index; let i = $index) {
<div class="card card-padding question-card"> <div class="question-card">
<div class="question-header"> <div class="q-header">
<span class="q-number">Q{{ i + 1 }}</span> <span class="q-number">Q{{ i + 1 }}</span>
<button class="btn btn-ghost btn-sm" (click)="removeQuestion(i)"> <span class="q-type-badge">{{ q.type === 'mcq' ? 'MCQ' : 'SINGLE' }}</span>
<button class="delete-q-btn" (click)="removeQuestion(i)" title="Delete question">
<span class="material-symbols-rounded">delete</span> <span class="material-symbols-rounded">delete</span>
</button> </button>
</div> </div>
<div class="form-group">
<input class="form-input" [ngModel]="q.question" (ngModelChange)="updateQuestion(i, 'question', $event)" placeholder="Question text"> <div class="q-text-wrap">
<input class="q-text-input"
[ngModel]="q.question"
(ngModelChange)="updateQuestion(i, 'question', $event)"
placeholder="Enter question text...">
</div> </div>
<div class="options-grid"> <div class="options-grid">
@for (opt of q.options; track $index; let j = $index) { @for (opt of q.options; track $index; let j = $index) {
<div class="option-input-wrap"> <div class="option"
<span class="option-letter">{{ ['A','B','C','D'][j] }}</span> [class.correct-answer]="q.correctAnswer && opt && q.correctAnswer === opt && opt !== ''"
<input class="form-input" [ngModel]="opt" (ngModelChange)="updateOption(i, j, $event)" placeholder="Option {{ j+1 }}"> (click)="opt ? setCorrectAnswer(i, opt) : null">
</div> <div class="option-inner">
<span class="option-marker">
@if (q.correctAnswer && opt && q.correctAnswer === opt && opt !== '') {
} }
</span>
<input class="option-input"
[ngModel]="opt"
(ngModelChange)="updateOption(i, j, $event)"
placeholder="Option {{ ['A','B','C','D'][j] }}"
(click)="$event.stopPropagation()">
</div> </div>
<div class="form-row">
<div class="form-group">
<label class="form-label">Correct Answer</label>
<input class="form-input" [ngModel]="q.correctAnswer" (ngModelChange)="updateQuestion(i, 'correctAnswer', $event)" placeholder="Correct answer text">
</div> </div>
<div class="form-group" style="max-width: 120px;"> }
<label class="form-label">Marks</label>
<input class="form-input" type="number" [ngModel]="q.marks" (ngModelChange)="updateQuestion(i, 'marks', $event)" min="1">
</div> </div>
<div class="correct-answer-row">
<span class="material-symbols-rounded correct-icon">check_circle</span>
<label class="correct-label">Correct Answer:</label>
<select class="correct-select"
[ngModel]="q.correctAnswer"
(ngModelChange)="setCorrectAnswer(i, $event)">
<option value="">-- Select --</option>
@for (opt of q.options; track $index; let j = $index) {
@if (opt) {
<option [value]="opt">{{ ['A','B','C','D'][j] }}. {{ opt }}</option>
}
}
</select>
</div> </div>
</div> </div>
} }
</div>
} }
<button class="btn btn-primary btn-lg save-btn" (click)="onSave()" [disabled]="saving()"> <button class="btn btn-primary btn-lg save-btn" (click)="onSave()" [disabled]="saving()">
......
...@@ -10,7 +10,7 @@ import { QuizService } from '../../../services/quiz.service'; ...@@ -10,7 +10,7 @@ import { QuizService } from '../../../services/quiz.service';
templateUrl: './edit-quiz.html', templateUrl: './edit-quiz.html',
styleUrl: './edit-quiz.css', styleUrl: './edit-quiz.css',
}) })
export class HREditQuizComponent { export class HREditQuizComponent implements OnInit {
quizId = ''; quizId = '';
title = ''; title = '';
timer = 30; timer = 30;
...@@ -36,15 +36,33 @@ export class HREditQuizComponent { ...@@ -36,15 +36,33 @@ export class HREditQuizComponent {
} }
loadQuiz(): void { loadQuiz(): void {
const overrides = history.state?.quizOverrides;
this.quizService.getHRQuiz(this.quizId).subscribe({ this.quizService.getHRQuiz(this.quizId).subscribe({
next: (res) => { next: (res) => {
const q = res.quiz; const q = res.quiz;
this.title = q.title; this.title = overrides ? overrides.title : q.title;
this.timer = q.timer; this.timer = overrides ? overrides.timer : q.timer;
this.category = q.category || ''; this.category = overrides ? overrides.category : (q.category || '');
this.difficulty = q.difficulty || 'medium'; this.difficulty = overrides ? overrides.difficulty : (q.difficulty || 'medium');
this.questions.set(q.questions || []);
this.locked.set(res.hasAttempts || false); // Questions come from res.questions (separate from quiz object)
const rawQuestions = res.questions || [];
const mapped = rawQuestions.map((rq: any) => {
const correctIndex = rq.correctAnswers?.[0] ? parseInt(rq.correctAnswers[0]) : -1;
const correctAnswer = (correctIndex >= 0 && rq.options[correctIndex]) ? rq.options[correctIndex] : '';
return {
_id: rq._id,
question: rq.question,
options: [...rq.options],
correctAnswer: correctAnswer,
type: rq.type || 'single'
};
});
this.questions.set(mapped);
this.locked.set((res.attemptCount || 0) > 0);
this.loading.set(false); this.loading.set(false);
}, },
error: (err) => { error: (err) => {
...@@ -53,7 +71,8 @@ export class HREditQuizComponent { ...@@ -53,7 +71,8 @@ export class HREditQuizComponent {
} }
}); });
} }
onSave(): void {
onSave(): void {
if (!this.title.trim()) { if (!this.title.trim()) {
this.error.set('Title is required'); this.error.set('Title is required');
return; return;
...@@ -62,30 +81,26 @@ onSave(): void { ...@@ -62,30 +81,26 @@ onSave(): void {
this.saving.set(true); this.saving.set(true);
this.error.set(''); this.error.set('');
// 🔥 Format questions correctly
const formattedQuestions = this.questions().map(q => { const formattedQuestions = this.questions().map(q => {
const options = q.options; const correctIndex = q.options.findIndex(
(opt: string) => opt === q.correctAnswer
const correctIndex = options.findIndex(
(opt: string) => opt == q.correctAnswer
); );
return { return {
question: q.question, question: q.question,
options, options: q.options,
correctAnswers: [correctIndex.toString()], correctAnswers: [correctIndex.toString()],
type: 'single' type: 'single'
}; };
}); // ✅ closes map() });
// 🔥 Final data
const data = { const data = {
title: this.title, title: this.title,
timer: this.timer, timer: this.timer,
category: this.category, category: this.category,
difficulty: this.difficulty, difficulty: this.difficulty,
questions: formattedQuestions questions: formattedQuestions
}; // ✅ closes data object };
this.quizService.updateHRQuiz(this.quizId, data).subscribe({ this.quizService.updateHRQuiz(this.quizId, data).subscribe({
next: () => { next: () => {
...@@ -97,8 +112,9 @@ onSave(): void { ...@@ -97,8 +112,9 @@ onSave(): void {
this.saving.set(false); this.saving.set(false);
this.error.set(err.error?.message || 'Failed to update quiz'); 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 };
...@@ -107,9 +123,21 @@ onSave(): void { ...@@ -107,9 +123,21 @@ onSave(): void {
updateOption(qIndex: number, optIndex: number, value: string): void { updateOption(qIndex: number, optIndex: number, value: string): void {
const q = [...this.questions()]; const q = [...this.questions()];
const oldOption = q[qIndex].options[optIndex];
const opts = [...(q[qIndex].options || [])]; const opts = [...(q[qIndex].options || [])];
opts[optIndex] = value; opts[optIndex] = value;
q[qIndex] = { ...q[qIndex], options: opts };
const updated = { ...q[qIndex], options: opts };
if (q[qIndex].correctAnswer === oldOption) {
updated.correctAnswer = value;
}
q[qIndex] = updated;
this.questions.set(q);
}
setCorrectAnswer(qIndex: number, optionText: string): void {
const q = [...this.questions()];
q[qIndex] = { ...q[qIndex], correctAnswer: optionText };
this.questions.set(q); this.questions.set(q);
} }
...@@ -120,7 +148,11 @@ onSave(): void { ...@@ -120,7 +148,11 @@ onSave(): void {
addQuestion(): void { addQuestion(): void {
this.questions.set([...this.questions(), { this.questions.set([...this.questions(), {
question: '', options: ['', '', '', ''], correctAnswer: '', marks: 1 question: '', options: ['', '', '', ''], correctAnswer: '', type: 'single'
}]); }]);
setTimeout(() => {
const cards = document.querySelectorAll('.question-card');
cards[cards.length - 1]?.scrollIntoView({ behavior: 'smooth', block: 'center' });
}, 50);
} }
} }
...@@ -47,3 +47,49 @@ ...@@ -47,3 +47,49 @@
.page-container { padding: 20px 16px; } .page-container { padding: 20px 16px; }
.quiz-grid { grid-template-columns: 1fr; } .quiz-grid { grid-template-columns: 1fr; }
} }
/* Modal Overlay Styles */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.4);
backdrop-filter: blur(4px);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
animation: fadeIn 0.2s ease-out;
}
.modal-container {
background: var(--bg-card);
border-radius: var(--radius-lg);
padding: 32px;
width: 100%;
max-width: 600px;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
border: 1px solid var(--border-color);
animation: slideUp 0.3s cubic-bezier(0.16, 1, 0.3, 1);
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
}
.modal-header h2 {
font-size: 20px;
font-weight: 700;
margin: 0;
color: var(--text-primary);
}
@keyframes slideUp {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
...@@ -52,7 +52,7 @@ ...@@ -52,7 +52,7 @@
</div> </div>
</div> </div>
<div class="quiz-card-actions"> <div class="quiz-card-actions">
<a [routerLink]="['/admin/quiz', quiz._id, 'assign']" class="btn btn-primary btn-sm"> <a [routerLink]="['/hr/quiz', quiz._id, 'assign']" class="btn btn-primary btn-sm">
<span class="material-symbols-rounded">person_add</span> Assign <span class="material-symbols-rounded">person_add</span> Assign
</a> </a>
@if (quiz.attemptCount > 0) { @if (quiz.attemptCount > 0) {
...@@ -61,9 +61,9 @@ ...@@ -61,9 +61,9 @@
{{ quiz.attemptCount }} attempt{{ quiz.attemptCount > 1 ? 's' : '' }} {{ quiz.attemptCount }} attempt{{ quiz.attemptCount > 1 ? 's' : '' }}
</span> </span>
} @else { } @else {
<a [routerLink]="['/admin/quiz', quiz._id, 'edit']" class="btn btn-outline btn-sm"> <button class="btn btn-outline btn-sm" (click)="openEditPopup(quiz)">
<span class="material-symbols-rounded">edit</span> Edit <span class="material-symbols-rounded">edit</span> Edit
</a> </button>
<button class="btn btn-danger btn-sm" (click)="deleteQuiz(quiz._id)"> <button class="btn btn-danger btn-sm" (click)="deleteQuiz(quiz._id)">
<span class="material-symbols-rounded">delete</span> Delete <span class="material-symbols-rounded">delete</span> Delete
</button> </button>
...@@ -74,3 +74,57 @@ ...@@ -74,3 +74,57 @@
</div> </div>
} }
</div> </div>
<!-- Edit Quiz Modal -->
@if (showEditPopup()) {
<div class="modal-overlay" (click)="closeEditPopup()">
<div class="modal-container" (click)="$event.stopPropagation()">
<div class="modal-header">
<h2>Edit Quiz Details</h2>
<button class="icon-btn" (click)="closeEditPopup()">
<span class="material-symbols-rounded">close</span>
</button>
</div>
<div class="modal-body quiz-form">
<div class="form-group">
<label class="form-label">Quiz Title</label>
<input class="form-input" [(ngModel)]="editQuizForm.title" placeholder="Quiz title">
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label">Timer (min)</label>
<input class="form-input" type="number" [(ngModel)]="editQuizForm.timer" min="1">
</div>
<div class="form-group">
<label class="form-label">Difficulty</label>
<select class="form-select" [(ngModel)]="editQuizForm.difficulty">
<option value="easy">Easy</option>
<option value="medium">Medium</option>
<option value="hard">Hard</option>
</select>
</div>
</div>
<div class="form-group">
<label class="form-label">Category</label>
<input class="form-input" [(ngModel)]="editQuizForm.category" placeholder="e.g. Java, Angular">
</div>
</div>
<div class="modal-footer" style="display: flex; gap: 12px; margin-top: 24px; justify-content: flex-end;">
<button class="btn btn-outline" (click)="editQuestions()">
<span class="material-symbols-rounded">edit_square</span> Edit Questions
</button>
<button class="btn btn-primary" (click)="saveBasicChanges()" [disabled]="savingPopup()">
@if (savingPopup()) {
<div class="spinner"></div> Saving...
} @else {
<span class="material-symbols-rounded">save</span> Save Changes
}
</button>
</div>
</div>
</div>
}
import { Component, OnInit, signal } from '@angular/core'; import { Component, OnInit, signal } from '@angular/core';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { RouterLink } from '@angular/router'; import { RouterLink, Router } from '@angular/router';
import { FormsModule } from '@angular/forms';
import { QuizService } from '../../../services/quiz.service'; import { QuizService } from '../../../services/quiz.service';
@Component({ @Component({
selector: 'app-hr-quizzes', selector: 'app-hr-quizzes',
standalone: true, standalone: true,
imports: [CommonModule, RouterLink], imports: [CommonModule, RouterLink, FormsModule],
templateUrl: './quizzes.html', templateUrl: './quizzes.html',
styleUrl: './quizzes.css' styleUrl: './quizzes.css'
}) })
...@@ -15,7 +16,17 @@ quizzes = signal<any[]>([]); ...@@ -15,7 +16,17 @@ quizzes = signal<any[]>([]);
loading = signal(true); loading = signal(true);
error = signal(''); error = signal('');
constructor(private quizService: QuizService) {} showEditPopup = signal(false);
editQuizForm = {
_id: '',
title: '',
timer: 10,
difficulty: 'medium',
category: ''
};
savingPopup = signal(false);
constructor(private quizService: QuizService, private router: Router) {}
ngOnInit(): void { ngOnInit(): void {
this.loadQuizzes(); this.loadQuizzes();
...@@ -45,4 +56,46 @@ quizzes = signal<any[]>([]); ...@@ -45,4 +56,46 @@ quizzes = signal<any[]>([]);
default: return 'badge-primary'; default: return 'badge-primary';
} }
} }
openEditPopup(quiz: any): void {
this.editQuizForm = {
_id: quiz._id,
title: quiz.title,
timer: quiz.timer,
difficulty: quiz.difficulty,
category: quiz.category
};
this.showEditPopup.set(true);
}
closeEditPopup(): void {
this.showEditPopup.set(false);
}
saveBasicChanges(): void {
this.savingPopup.set(true);
// Don't send questions, only update basic details
this.quizService.updateHRQuiz(this.editQuizForm._id, this.editQuizForm).subscribe({
next: () => {
this.savingPopup.set(false);
this.closeEditPopup();
this.loadQuizzes();
},
error: (err) => {
this.savingPopup.set(false);
this.error.set(err.error?.message || 'Failed to save changes');
setTimeout(() => this.error.set(''), 3000);
}
});
}
editQuestions(): void {
// Navigate to edit-quiz page with the pending changes in history.state
this.router.navigate(['/hr/quiz', this.editQuizForm._id, 'edit'], {
state: {
quizOverrides: { ...this.editQuizForm }
}
});
this.closeEditPopup();
}
} }
...@@ -83,6 +83,14 @@ export class HRUserHistoryComponent { ...@@ -83,6 +83,14 @@ export class HRUserHistoryComponent {
return `${m}m ${s}s`; return `${m}m ${s}s`;
} }
getComfortLevel(topic: string): string {
const u = this.user();
if (!u || !u.topicsOfInterest || !topic) return 'N/A';
const interest = u.topicsOfInterest.find((t: any) => t.topic.toLowerCase() === topic.toLowerCase());
return interest ? `${interest.comfortLevel}%` : 'N/A';
}
logout(): void { logout(): void {
this.authService.logout(); this.authService.logout();
} }
......
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