Skip to content

Commit e63f15c

Browse files
committed
Extract dialog box into a new component
1 parent 8ce20c2 commit e63f15c

File tree

6 files changed

+318
-222
lines changed

6 files changed

+318
-222
lines changed

frontend/src/app/questions/question-dialog.component.css

Whitespace-only changes.
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<p-dialog
2+
header="Header"
3+
(onHide)="cancel()"
4+
(onShow)="show()"
5+
[(visible)]="isDialogVisible"
6+
[modal]="true"
7+
[style]="{ width: '25rem' }">
8+
<ng-template pTemplate="header">
9+
<div class="inline-flex align-items-center justify-content-center gap-2">
10+
<span class="font-bold white-space-nowrap"> {{ headerMessage }}</span>
11+
</div>
12+
</ng-template>
13+
14+
<form [formGroup]="questionFormGroup">
15+
<div class="field grid col-12">
16+
<label for="title">Title</label>
17+
<input
18+
formControlName="title"
19+
type="text"
20+
pInputText
21+
id="title"
22+
required
23+
class="text-base text-color surface-overlay p-2 border-1 border-solid surface-border border-round appearance-none outline-none focus:border-primary w-full" />
24+
@if (isTitleInvalid) {
25+
<small class="text-red-300">Title is required.</small>
26+
}
27+
</div>
28+
<div class="formgrid grid field">
29+
<div class="field col-12 md:col-6">
30+
<label for="questionTopics">Topics</label>
31+
<p-multiSelect
32+
required="true"
33+
[style]="{ width: '100%' }"
34+
[options]="topics"
35+
formControlName="topics"
36+
optionLabel="label"
37+
optionValue="value"
38+
placeholder="Select Topics" />
39+
@if (isTopicsInvalid) {
40+
<small class="text-red-300">Topic(s) is required.</small>
41+
}
42+
</div>
43+
<div class="field col-12 md:col-6">
44+
<label for="questionTopics">Difficulty</label>
45+
<p-dropdown
46+
[required]="true"
47+
[style]="{ width: '100%' }"
48+
[options]="difficulties"
49+
formControlName="difficulty"
50+
optionLabel="label"
51+
optionValue="value"
52+
placeholder="Select Difficulty" />
53+
@if (isDifficultyInvalid) {
54+
<small class="text-red-300">Difficulty is required.</small>
55+
}
56+
</div>
57+
</div>
58+
<div class="field grid col-12" field>
59+
<label for="questionDescription">Description</label>
60+
<textarea
61+
formControlName="description"
62+
[required]="true"
63+
id="questionDescription"
64+
type="text"
65+
rows="6"
66+
class="text-base text-color surface-overlay p-2 border-1 border-solid surface-border border-round appearance-none outline-none focus:border-primary w-full">
67+
</textarea>
68+
@if (isDescriptionInvalid) {
69+
<small class="text-red-300">Description is required.</small>
70+
}
71+
</div>
72+
</form>
73+
74+
<ng-template pTemplate="footer">
75+
<p-button label="Cancel" [text]="true" severity="secondary" (onClick)="isDialogVisible = false" />
76+
<p-button
77+
label="Save"
78+
class="p-button-success"
79+
(onClick)="saveQuestion()"
80+
[disabled]="!questionFormGroup.valid" />
81+
</ng-template>
82+
</p-dialog>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { ComponentFixture, TestBed } from '@angular/core/testing';
2+
3+
import { QuestionDialogComponent } from './question-dialog.component';
4+
5+
describe('QuestionDialogComponent', () => {
6+
let component: QuestionDialogComponent;
7+
let fixture: ComponentFixture<QuestionDialogComponent>;
8+
9+
beforeEach(async () => {
10+
await TestBed.configureTestingModule({
11+
imports: [QuestionDialogComponent],
12+
}).compileComponents();
13+
14+
fixture = TestBed.createComponent(QuestionDialogComponent);
15+
component = fixture.componentInstance;
16+
fixture.detectChanges();
17+
});
18+
19+
it('should create', () => {
20+
expect(component).toBeTruthy();
21+
});
22+
});
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
import { Component, EventEmitter, Input, Output, OnInit } from '@angular/core';
2+
import { Question, QuestionBody, SingleQuestionResponse } from './question.model';
3+
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
4+
import { DialogModule } from 'primeng/dialog';
5+
import { MultiSelectModule } from 'primeng/multiselect';
6+
import { DropdownModule } from 'primeng/dropdown';
7+
import { ButtonModule } from 'primeng/button';
8+
import { ConfirmationService, MessageService } from 'primeng/api';
9+
import { QuestionService } from '../../_services/question.service';
10+
import { Difficulty } from './difficulty.model';
11+
import { Topic } from './topic.model';
12+
import { HttpErrorResponse } from '@angular/common/http';
13+
14+
@Component({
15+
selector: 'app-question-dialog',
16+
standalone: true,
17+
imports: [
18+
ButtonModule,
19+
DialogModule,
20+
ButtonModule,
21+
ReactiveFormsModule,
22+
MultiSelectModule,
23+
DropdownModule,
24+
QuestionDialogComponent,
25+
],
26+
providers: [QuestionService, ConfirmationService, MessageService],
27+
templateUrl: './question-dialog.component.html',
28+
styleUrl: './question-dialog.component.css',
29+
})
30+
export class QuestionDialogComponent implements OnInit {
31+
@Input() question!: Question;
32+
@Input() headerMessage!: string;
33+
@Input() isDialogVisible = false;
34+
@Input() topics!: Topic[];
35+
@Input() difficulties!: Difficulty[];
36+
@Output() dialogClose = new EventEmitter<void>();
37+
@Output() questionUpdate = new EventEmitter<Question>();
38+
@Output() questionAdd = new EventEmitter<Question>();
39+
@Output() errorReceive = new EventEmitter<string>();
40+
@Output() successfulRequest = new EventEmitter<string>();
41+
42+
questionFormGroup!: FormGroup;
43+
44+
submitted = false;
45+
46+
questions: Question[] = [];
47+
48+
constructor(private questionService: QuestionService) {}
49+
50+
ngOnInit(): void {
51+
this.initFormGroup();
52+
}
53+
54+
get isTitleInvalid(): boolean {
55+
const titleControl = this.questionFormGroup.controls['title'];
56+
return titleControl.dirty && titleControl.invalid;
57+
}
58+
59+
get isDescriptionInvalid(): boolean {
60+
const descriptionControl = this.questionFormGroup.controls['description'];
61+
return descriptionControl.dirty && descriptionControl.invalid;
62+
}
63+
64+
get isDifficultyInvalid(): boolean {
65+
const difficultyControl = this.questionFormGroup.controls['difficulty'];
66+
return difficultyControl.dirty && difficultyControl.invalid;
67+
}
68+
69+
get isTopicsInvalid(): boolean {
70+
const topicsControl = this.questionFormGroup.controls['topics'];
71+
return topicsControl.dirty && topicsControl.invalid;
72+
}
73+
74+
saveQuestion() {
75+
this.submitted = true;
76+
77+
if (!this.questionFormGroup.valid) {
78+
return;
79+
}
80+
81+
if (this.question.id) {
82+
// update
83+
this.handleEditQuestionResponse(this.question.id, this.questionFormGroup.value);
84+
} else {
85+
// add
86+
this.handleAddQuestionResponse();
87+
}
88+
89+
this.dialogClose.emit();
90+
this.question = {} as Question;
91+
}
92+
93+
cancel() {
94+
this.resetFormGroup();
95+
this.dialogClose.emit();
96+
}
97+
98+
show() {
99+
this.setFormValue();
100+
}
101+
102+
handleEditQuestionResponse(id: number, question: QuestionBody) {
103+
this.questionService.updateQuestion(id, question).subscribe({
104+
next: (response: SingleQuestionResponse) => {
105+
this.questionUpdate.emit(response.data);
106+
},
107+
error: (error: HttpErrorResponse) => {
108+
this.errorReceive.emit(error.error.message);
109+
},
110+
complete: () => {
111+
this.successfulRequest.emit('Question has been updated successfully');
112+
},
113+
});
114+
}
115+
116+
handleAddQuestionResponse() {
117+
this.questionService.addQuestion(this.questionFormGroup.value).subscribe({
118+
next: (response: SingleQuestionResponse) => {
119+
this.questionAdd.emit(response.data);
120+
},
121+
error: (error: HttpErrorResponse) => {
122+
this.errorReceive.emit('Failed to add new question. ' + error.error.message);
123+
},
124+
complete: () => {
125+
this.successfulRequest.emit('New Question Added');
126+
},
127+
});
128+
}
129+
130+
initFormGroup() {
131+
this.questionFormGroup = new FormGroup({
132+
topics: new FormControl<string[] | null>([], [Validators.required]),
133+
difficulty: new FormControl<Difficulty[] | null>([], [Validators.required]),
134+
title: new FormControl<string | null>('', [Validators.required]),
135+
description: new FormControl<string | null>('', [Validators.required]),
136+
});
137+
}
138+
139+
setFormValue() {
140+
this.questionFormGroup.patchValue({
141+
title: this.question.title,
142+
description: this.question.description,
143+
topics: this.question.topics,
144+
difficulty: this.question.difficulty,
145+
});
146+
}
147+
148+
resetFormGroup() {
149+
this.questionFormGroup.reset({
150+
topics: [],
151+
difficulty: '',
152+
title: '',
153+
description: '',
154+
});
155+
}
156+
}

frontend/src/app/questions/questions.component.html

Lines changed: 12 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -75,82 +75,17 @@ <h3 class="m-0">Manage Questions</h3>
7575
</ng-template>
7676
</p-table>
7777
</ng-container>
78-
<p-dialog header="Header" [(visible)]="isDialogVisible" [modal]="true" [style]="{ width: '25rem' }">
79-
<ng-template pTemplate="header">
80-
<div class="inline-flex align-items-center justify-content-center gap-2">
81-
<span class="font-bold white-space-nowrap"> {{ dialogHeader }}</span>
82-
</div>
83-
</ng-template>
84-
85-
<form [formGroup]="questionFormGroup">
86-
<div class="field grid col-12">
87-
<label for="title">Title</label>
88-
<input
89-
formControlName="title"
90-
type="text"
91-
pInputText
92-
id="title"
93-
required
94-
class="text-base text-color surface-overlay p-2 border-1 border-solid surface-border border-round appearance-none outline-none focus:border-primary w-full" />
95-
@if (isTitleInvalid) {
96-
<small class="text-red-300">Title is required.</small>
97-
}
98-
</div>
99-
<div class="formgrid grid field">
100-
<div class="field col-12 md:col-6">
101-
<label for="questionTopics">Topics</label>
102-
<p-multiSelect
103-
required="true"
104-
[style]="{ width: '100%' }"
105-
[options]="topics"
106-
formControlName="topics"
107-
optionLabel="label"
108-
optionValue="value"
109-
placeholder="Select Topics" />
110-
@if (isTopicsInvalid) {
111-
<small class="text-red-300">Topic(s) is required.</small>
112-
}
113-
</div>
114-
<div class="field col-12 md:col-6">
115-
<label for="questionTopics">Difficulty</label>
116-
<p-dropdown
117-
[required]="true"
118-
[style]="{ width: '100%' }"
119-
[options]="difficulties"
120-
formControlName="difficulty"
121-
optionLabel="label"
122-
optionValue="value"
123-
placeholder="Select Difficulty" />
124-
@if (isDifficultyInvalid) {
125-
<small class="text-red-300">Difficulty is required.</small>
126-
}
127-
</div>
128-
</div>
129-
<div class="field grid col-12" field>
130-
<label for="questionDescription">Description</label>
131-
<textarea
132-
formControlName="description"
133-
[required]="true"
134-
id="questionDescription"
135-
type="text"
136-
rows="6"
137-
class="text-base text-color surface-overlay p-2 border-1 border-solid surface-border border-round appearance-none outline-none focus:border-primary w-full">
138-
</textarea>
139-
@if (isDescriptionInvalid) {
140-
<small class="text-red-300">Description is required.</small>
141-
}
142-
</div>
143-
</form>
144-
145-
<ng-template pTemplate="footer">
146-
<p-button label="Cancel" [text]="true" severity="secondary" (onClick)="isDialogVisible = false" />
147-
<p-button
148-
label="Save"
149-
class="p-button-success"
150-
(onClick)="saveQuestion()"
151-
[disabled]="!questionFormGroup.valid" />
152-
</ng-template>
153-
</p-dialog>
154-
78+
<app-question-dialog
79+
[isDialogVisible]="isDialogVisible"
80+
[headerMessage]="dialogHeader"
81+
[topics]="topics"
82+
[difficulties]="difficulties"
83+
[question]="question"
84+
(dialogClose)="onDialogClose()"
85+
(questionUpdate)="onQuestionUpdate($event)"
86+
(questionAdd)="onQuestionAdd($event)"
87+
(errorReceive)="onErrorReceive($event)"
88+
(successfulRequest)="onSuccessfulRequest($event)">
89+
</app-question-dialog>
15590
<p-confirmDialog [style]="{ width: '450px' }" />
15691
</div>

0 commit comments

Comments
 (0)