-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #30 from CS3219-AY2425S1/question-spa-frontend
Implement Question SPA
- Loading branch information
Showing
23 changed files
with
851 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { TestBed } from '@angular/core/testing'; | ||
|
||
import { QuestionService } from './question.service'; | ||
|
||
describe('QuestionService', () => { | ||
let service: QuestionService; | ||
|
||
beforeEach(() => { | ||
TestBed.configureTestingModule({}); | ||
service = TestBed.inject(QuestionService); | ||
}); | ||
|
||
it('should be created', () => { | ||
expect(service).toBeTruthy(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
import { HttpClient, HttpErrorResponse, HttpHeaders, HttpParams } from '@angular/common/http'; | ||
import { Injectable } from '@angular/core'; | ||
import { API_CONFIG } from '../app/api.config'; | ||
import { catchError, Observable, throwError } from 'rxjs'; | ||
import { SingleQuestionResponse, QuestionResponse, QuestionBody } from '../app/questions/question.model'; | ||
import { TopicResponse } from '../app/questions/topic.model'; | ||
|
||
@Injectable({ | ||
providedIn: 'root', | ||
}) | ||
export class QuestionService { | ||
private baseUrl = API_CONFIG.baseUrl; | ||
|
||
private httpOptions = { | ||
headers: new HttpHeaders({ | ||
'Content-Type': 'application/json', | ||
}), | ||
}; | ||
|
||
constructor(private http: HttpClient) {} | ||
|
||
getQuestions( | ||
title?: string, | ||
description?: string, | ||
topics?: string[], | ||
difficulty?: string, | ||
): Observable<QuestionResponse> { | ||
let params = new HttpParams(); | ||
|
||
if (title) { | ||
params = params.append('title', title); | ||
} | ||
if (description) { | ||
params = params.append('description', description); | ||
} | ||
if (topics && topics.length > 0) { | ||
params = params.append('topics', topics.join(',')); | ||
} | ||
if (difficulty) { | ||
params = params.append('difficulty', difficulty); | ||
} | ||
|
||
// send request | ||
return this.http.get<QuestionResponse>(this.baseUrl + '/questions', { params }); | ||
} | ||
|
||
getQuestionByID(id: number): Observable<QuestionResponse> { | ||
return this.http.get<QuestionResponse>(this.baseUrl + '/questions/' + id); | ||
} | ||
|
||
getQuestionByParam(topics: string[], difficulty: string, limit?: number): Observable<QuestionResponse> { | ||
let params = new HttpParams(); | ||
|
||
if (limit) { | ||
params = params.append('limit', limit); | ||
} | ||
params = params.append('topics', topics.join(',')).append('difficulty', difficulty); | ||
|
||
return this.http.get<QuestionResponse>(this.baseUrl + '/questions/search', { params }); | ||
} | ||
|
||
getTopics(): Observable<TopicResponse> { | ||
return this.http.get<TopicResponse>(this.baseUrl + '/questions/topics'); | ||
} | ||
|
||
addQuestion(question: QuestionBody): Observable<SingleQuestionResponse> { | ||
return this.http | ||
.post<SingleQuestionResponse>(this.baseUrl + '/questions', question, this.httpOptions) | ||
.pipe(catchError(this.handleError)); | ||
} | ||
|
||
updateQuestion(id: number, question: QuestionBody): Observable<SingleQuestionResponse> { | ||
return this.http | ||
.put<SingleQuestionResponse>(this.baseUrl + '/questions/' + id, question, this.httpOptions) | ||
.pipe(catchError(this.handleError)); | ||
} | ||
|
||
deleteQuestion(id: number): Observable<SingleQuestionResponse> { | ||
return this.http | ||
.delete<SingleQuestionResponse>(this.baseUrl + '/questions/' + id) | ||
.pipe(catchError(this.handleError)); | ||
} | ||
|
||
handleError(error: HttpErrorResponse) { | ||
return throwError(error); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export const API_CONFIG = { | ||
baseUrl: 'http://localhost:8081', | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,14 @@ | ||
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core'; | ||
import { provideRouter } from '@angular/router'; | ||
|
||
import { routes } from './app.routes'; | ||
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async'; | ||
import { provideHttpClient } from '@angular/common/http'; | ||
|
||
export const appConfig: ApplicationConfig = { | ||
providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes), provideAnimationsAsync()], | ||
providers: [ | ||
provideZoneChangeDetection({ eventCoalescing: true }), | ||
provideRouter(routes), | ||
provideAnimationsAsync(), | ||
provideHttpClient(), | ||
], | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export interface Column { | ||
field: string; | ||
header: string; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
export enum DifficultyLevels { | ||
EASY = 'Easy', | ||
MEDIUM = 'Medium', | ||
HARD = 'Hard', | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export interface Difficulty { | ||
label: string; | ||
value: string; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
.container { | ||
padding: 2rem; | ||
background-color: var(--surface-section); | ||
border-radius: 0.75rem; | ||
display: flex; | ||
flex-direction: column; | ||
align-items: center; | ||
} | ||
|
||
.form-container { | ||
display: flex; | ||
flex-direction: column; | ||
gap: 1rem; | ||
width: 100%; | ||
} | ||
|
||
.form-field { | ||
display: flex; | ||
flex-direction: column; | ||
gap: 0.5rem; | ||
width: 100%; | ||
} |
102 changes: 102 additions & 0 deletions
102
frontend/src/app/questions/question-dialog.component.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
<p-dialog | ||
header="Header" | ||
(onHide)="cancel()" | ||
(onShow)="show()" | ||
[(visible)]="isDialogVisible" | ||
[modal]="true" | ||
[style]="{ width: '25rem' }"> | ||
<ng-template pTemplate="header"> | ||
<div class="inline-flex align-items-center justify-content-center gap-2"> | ||
<span class="font-bold white-space-nowrap"> {{ headerMessage }}</span> | ||
</div> | ||
</ng-template> | ||
|
||
<form [formGroup]="questionFormGroup"> | ||
<div class="form-field mb-4"> | ||
<label for="title">Title</label> | ||
<input | ||
formControlName="title" | ||
type="text" | ||
pInputText | ||
id="title" | ||
required | ||
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" /> | ||
@if (isTitleInvalid) { | ||
<small class="text-red-300">Title is required.</small> | ||
} | ||
</div> | ||
<div class="form-field mb-4"> | ||
<label for="questionTopics">Topics</label> | ||
|
||
<p-multiSelect | ||
#topicSelector | ||
required="true" | ||
class="w-12" | ||
[style]="{ width: '100%' }" | ||
[options]="topics" | ||
(onFilter)="onFilterTopics($event)" | ||
formControlName="topics" | ||
optionLabel="label" | ||
optionValue="value" | ||
placeholder="Select Topics"> | ||
<ng-template class="w-12 p-fluid" let-option pTemplate="item"> | ||
<span>{{ option.label }}</span> | ||
</ng-template> | ||
|
||
<ng-template pTemplate="footer" class="w-12 p-fluid"> | ||
@if (hasNoResultsFound) { | ||
<p-button | ||
class="w-12 p-fluid" | ||
type="button" | ||
label="Add as New Topic" | ||
icon="pi pi-plus" | ||
(click)="addNewTopic()" /> | ||
} | ||
</ng-template> | ||
</p-multiSelect> | ||
@if (isTopicsInvalid) { | ||
<small class="text-red-300">Topic(s) is required.</small> | ||
} | ||
</div> | ||
<div class="form-field mb-4"> | ||
<label for="questionTopics">Difficulty</label> | ||
|
||
<p-dropdown | ||
class="w-12" | ||
autoWidth="false" | ||
[required]="true" | ||
[style]="{ width: '100%' }" | ||
[options]="difficulties" | ||
formControlName="difficulty" | ||
optionLabel="label" | ||
optionValue="value" | ||
placeholder="Select Difficulty" /> | ||
@if (isDifficultyInvalid) { | ||
<small class="text-red-300">Difficulty is required.</small> | ||
} | ||
</div> | ||
<div class="form-field"> | ||
<label for="questionDescription">Description</label> | ||
<textarea | ||
formControlName="description" | ||
[required]="true" | ||
id="questionDescription" | ||
type="text" | ||
rows="6" | ||
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"> | ||
</textarea> | ||
@if (isDescriptionInvalid) { | ||
<small class="text-red-300">Description is required.</small> | ||
} | ||
</div> | ||
</form> | ||
|
||
<ng-template pTemplate="footer"> | ||
<p-button label="Cancel" [text]="true" severity="secondary" (onClick)="dialogClose.emit()" /> | ||
<p-button | ||
label="Save" | ||
class="p-button-success" | ||
(onClick)="saveQuestion()" | ||
[disabled]="!questionFormGroup.valid" /> | ||
</ng-template> | ||
</p-dialog> |
22 changes: 22 additions & 0 deletions
22
frontend/src/app/questions/question-dialog.component.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||
|
||
import { QuestionDialogComponent } from './question-dialog.component'; | ||
|
||
describe('QuestionDialogComponent', () => { | ||
let component: QuestionDialogComponent; | ||
let fixture: ComponentFixture<QuestionDialogComponent>; | ||
|
||
beforeEach(async () => { | ||
await TestBed.configureTestingModule({ | ||
imports: [QuestionDialogComponent], | ||
}).compileComponents(); | ||
|
||
fixture = TestBed.createComponent(QuestionDialogComponent); | ||
component = fixture.componentInstance; | ||
fixture.detectChanges(); | ||
}); | ||
|
||
it('should create', () => { | ||
expect(component).toBeTruthy(); | ||
}); | ||
}); |
Oops, something went wrong.