From 9b2a6c1c0ade9a49d99de5cb291e2c32d5c22f51 Mon Sep 17 00:00:00 2001 From: lynnetteeee Date: Sun, 29 Sep 2024 02:31:20 +0800 Subject: [PATCH 1/7] Add filter feature for Difficulty and Category (UI for another day) --- .../question-list.component.html | 2 +- .../question-list/question-list.component.ts | 52 +++++++++----- .../search-and-filter.component.css | 71 ++++++++++++++----- .../search-and-filter.component.html | 48 +++++++++++-- .../search-and-filter.component.ts | 54 +++++++++++++- peer-prep-fe/src/services/question.service.ts | 10 +++ 6 files changed, 192 insertions(+), 45 deletions(-) diff --git a/peer-prep-fe/src/components/question-list/question-list.component.html b/peer-prep-fe/src/components/question-list/question-list.component.html index 7726717a20..2b8e7b7464 100644 --- a/peer-prep-fe/src/components/question-list/question-list.component.html +++ b/peer-prep-fe/src/components/question-list/question-list.component.html @@ -1,6 +1,6 @@

List of Questions

- +
  • diff --git a/peer-prep-fe/src/components/question-list/question-list.component.ts b/peer-prep-fe/src/components/question-list/question-list.component.ts index 0a7a095f90..6cc45363ce 100644 --- a/peer-prep-fe/src/components/question-list/question-list.component.ts +++ b/peer-prep-fe/src/components/question-list/question-list.component.ts @@ -16,6 +16,7 @@ import {QuestionService} from "../../services/question.service"; export class QuestionListComponent implements OnInit { questions: Question[] = []; + // filteredQuestions: Question[] = []; constructor(private questionService : QuestionService) { } @@ -23,25 +24,42 @@ export class QuestionListComponent implements OnInit { this.loadQuestions(); } - loadQuestions(hasSort?: boolean) { - if (hasSort) { - // default alphabetical sorting for now - this.questionService.getAllQuestionSorted("question_title", "asc").subscribe((data: any) => { - this.questions = data.data.data; - }, (error) => { - console.error('Error fetching: ', error); - }) - } else { - // gets default ordering of questions - this.questionService.getAllQuestion().subscribe((data: any) => { - this.questions = data.data.data; - }, (error) => { - console.error('Error fetching: ', error); - }) - } + loadQuestions() { + // gets default ordering of questions + this.questionService.getAllQuestion().subscribe((data: any) => { + this.questions = data.data.data; + }, (error) => { + console.error('Error fetching: ', error); + }) + } + + loadSortedQuestions() { + this.questionService.getAllQuestionSorted("question_title", "asc").subscribe((data: any) => { + this.questions = data.data.data; + }, (error) => { + console.error('Error fetching: ', error); + }) + } + + loadFilteredQuestions(filterBy?: string, filterValues?: string) { + this.questionService.getFilteredQuestions(filterBy, filterValues).subscribe((data: any) => { + this.questions = data.data.data; + // this.filteredQuestions = data.data.data; + }, (error) => { + console.error('Error fetching: ', error); + }) + } + + applyFilter(event: { filterBy: string, filterValues: string} ) { + this.loadFilteredQuestions(event.filterBy, event.filterValues); + console.log('Filtering by:', event.filterBy, event.filterValues); } refreshQuestions(hasSort?: boolean) { - this.loadQuestions(hasSort); + if (hasSort) { + this.loadSortedQuestions(); + } else { + this.loadQuestions(); + } } } diff --git a/peer-prep-fe/src/components/search-and-filter/search-and-filter.component.css b/peer-prep-fe/src/components/search-and-filter/search-and-filter.component.css index 1f5fa25b56..838030f50e 100644 --- a/peer-prep-fe/src/components/search-and-filter/search-and-filter.component.css +++ b/peer-prep-fe/src/components/search-and-filter/search-and-filter.component.css @@ -1,4 +1,10 @@ - .container { +/* Remove flex from all elements */ +* { + box-sizing: border-box; /* Instead of display: flex */ +} + +/* Fix flex layout only for specific elements */ +.container { display: flex; align-items: center; } @@ -13,7 +19,7 @@ font-size: 22px; background-color: transparent; border: 1px solid white; - padding: 3px 40px 3px 40px; + padding: 3px 40px 3px 40px; /* Adjust padding */ margin-left: 10px; border-radius: 10px; width: 400px; @@ -21,39 +27,66 @@ } .search-icon { - position: absolute; - right: 10px; + position: absolute; + right: 10px; top: 50%; transform: translateY(-50%); - color: white; + color: white; pointer-events: none; } .button-container { - display: flex; - margin-left: 20px; + display: flex; + margin-left: 20px; + position: relative; +} + +.dropdown { + position: relative; +} + +.dropdown-content { + display: none; + position: absolute; + background-color: white; + min-width: 160px; + box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2); + z-index: 1; + top: 100%; /* Position it below the filter button */ + left: 0; +} + +.filter-container:hover .dropdown-content { + display: block; +} + +/* Styling for buttons inside dropdowns */ +.dropdown-content button { + color: black; + padding: 12px 16px; + text-decoration: none; + display: block; + background-color: #f9f9f9; + width: 100%; } +.dropdown-content button:hover { + background-color: #ddd; +} + +/* Button styling */ #button { background-color: #003D84; - color: white; + color: white; font-family: "Signika Negative", sans-serif; font-size: 20px; border-radius: 10px; - padding: 6px 15px; + padding: 6px 15px; margin-right: 8px; border: none; cursor: pointer; } - +/* #button:last-child { margin-right: 0; -} - -* { - display: flex; - flex-direction: row; - align-items: center; - justify-content: center; -} - +} */ diff --git a/peer-prep-fe/src/components/search-and-filter/search-and-filter.component.html b/peer-prep-fe/src/components/search-and-filter/search-and-filter.component.html index e1e5e007e5..5c588f968b 100644 --- a/peer-prep-fe/src/components/search-and-filter/search-and-filter.component.html +++ b/peer-prep-fe/src/components/search-and-filter/search-and-filter.component.html @@ -1,11 +1,47 @@
    - - + +
    +
    - - - + + + +
    + + + + +
    + +
    -
    \ No newline at end of file +
+ \ No newline at end of file diff --git a/peer-prep-fe/src/components/search-and-filter/search-and-filter.component.ts b/peer-prep-fe/src/components/search-and-filter/search-and-filter.component.ts index b3711bbc18..8c0be326ec 100644 --- a/peer-prep-fe/src/components/search-and-filter/search-and-filter.component.ts +++ b/peer-prep-fe/src/components/search-and-filter/search-and-filter.component.ts @@ -1,20 +1,70 @@ import {Component, EventEmitter, Output} from '@angular/core'; import {EditPageComponent} from "../../edit-page/edit-page.component"; +import { CommonModule } from '@angular/common'; +import { QuestionService } from '../../services/question.service'; import {MatDialog} from "@angular/material/dialog"; import {AddPageComponent} from "../../add-page/add-page.component"; +import { Category } from '../../app/models/category.model'; @Component({ selector: 'app-search-and-filter', standalone: true, - imports: [], + imports: [CommonModule], templateUrl: './search-and-filter.component.html', styleUrl: './search-and-filter.component.css' }) export class SearchAndFilterComponent { @Output() refresh = new EventEmitter(); @Output() sort = new EventEmitter(); // event for when "SORT" button is clicked + @Output() filter = new EventEmitter<{ filterBy: string, filterValues: string }>(); - constructor(private dialog: MatDialog) {} + categories: string[] = []; + showFilterOptions = false; // Controls showing filter options + isDifficultyClicked = false; + isCategoryClicked = false; + + constructor(private dialog: MatDialog, private questionService: QuestionService) {} + + ngOnInit(): void { + this.questionService.getQuestionCategories().subscribe( + (response: any) => { + console.log('Category API response:', response); + // Map the response to Category array + this.categories = response.data.data; + }, + (error) => { + console.error('Error fetching categories:', error); + } + ); + } + + // Show difficulty options + showDifficultyOptions() { + this.isDifficultyClicked = true; + this.isCategoryClicked = false; + } + + // Show category options + showCategoryOptions() { + this.isCategoryClicked = true; + this.isDifficultyClicked = false; + } + + // Emit an event to filter by difficulty + filterByDifficulty(difficulty: string) { + console.log('Filtering by difficulty:', difficulty); + this.filter.emit({ filterBy: 'question_complexity', filterValues: difficulty }); + this.isDifficultyClicked = false; + this.showFilterOptions = false; + } + + // Emit an event to filter by category + filterByCategory(category: string) { + console.log('Filtering by category:', category); + this.filter.emit({ filterBy: 'question_categories', filterValues: category }); + this.isCategoryClicked = false; + this.showFilterOptions = false; + } sortQuestions() { console.log('Sort button clicked'); diff --git a/peer-prep-fe/src/services/question.service.ts b/peer-prep-fe/src/services/question.service.ts index 164b8f511a..37dd15c28d 100644 --- a/peer-prep-fe/src/services/question.service.ts +++ b/peer-prep-fe/src/services/question.service.ts @@ -20,6 +20,16 @@ export class QuestionService { return this.http.get(url); } + getFilteredQuestions(filterBy?: string, filterValues?: string): Observable { + const url = filterBy ? `${this.baseUrl}?filterBy=${filterBy}&filterValues=${filterValues}` : this.baseUrl; + // const url = `${this.baseUrl}?filterBy=question_categories&filterValues=Arrays`; + return this.http.get(url); + } + + getQuestionCategories(): Observable { + return this.http.get(`${this.baseUrl}/categories`); + } + getQuestion(id: string): Observable { return this.http.get(`${this.baseUrl}/${id}`); } From b4501adeb17930dc89748f4be1dbfbe12dd91db8 Mon Sep 17 00:00:00 2001 From: lynnetteeee Date: Sun, 29 Sep 2024 02:31:58 +0800 Subject: [PATCH 2/7] Add Category service, model (spec tbc) for future use --- peer-prep-fe/src/app/models/category.model.ts | 4 ++++ .../src/services/category.service.spec.ts | 20 +++++++++++++++++++ peer-prep-fe/src/services/category.service.ts | 18 +++++++++++++++++ 3 files changed, 42 insertions(+) create mode 100644 peer-prep-fe/src/app/models/category.model.ts create mode 100644 peer-prep-fe/src/services/category.service.spec.ts create mode 100644 peer-prep-fe/src/services/category.service.ts diff --git a/peer-prep-fe/src/app/models/category.model.ts b/peer-prep-fe/src/app/models/category.model.ts new file mode 100644 index 0000000000..cd277d1c9e --- /dev/null +++ b/peer-prep-fe/src/app/models/category.model.ts @@ -0,0 +1,4 @@ +export interface Category { + category_id: string, + category_name: string +} \ No newline at end of file diff --git a/peer-prep-fe/src/services/category.service.spec.ts b/peer-prep-fe/src/services/category.service.spec.ts new file mode 100644 index 0000000000..3e408dbb63 --- /dev/null +++ b/peer-prep-fe/src/services/category.service.spec.ts @@ -0,0 +1,20 @@ +import { TestBed } from '@angular/core/testing'; + +import { CategoryService } from './category.service'; +import {HttpClientModule} from "@angular/common/http"; + +describe('CategoryService', () => { + let service: CategoryService; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientModule], + providers: [CategoryService] + }); + service = TestBed.inject(CategoryService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/peer-prep-fe/src/services/category.service.ts b/peer-prep-fe/src/services/category.service.ts new file mode 100644 index 0000000000..5f2ccc5d40 --- /dev/null +++ b/peer-prep-fe/src/services/category.service.ts @@ -0,0 +1,18 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { Category } from '../app/models/category.model'; + +@Injectable({ + providedIn: 'root' +}) +export class CategoryService { + private baseUrl = 'http://localhost:8080/categories' + + constructor(private http: HttpClient) {} + + // Fetch categories from the API + getCategories(): Observable { + return this.http.get(this.baseUrl); + } +} From 1308ca35d9917cd3240ffe054f8535ab26aadfcd Mon Sep 17 00:00:00 2001 From: lynnetteeee Date: Sun, 29 Sep 2024 15:57:52 +0800 Subject: [PATCH 3/7] Fix search button styling --- .../search-and-filter.component.css | 17 +++++++++++++---- .../search-and-filter.component.html | 2 +- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/peer-prep-fe/src/components/search-and-filter/search-and-filter.component.css b/peer-prep-fe/src/components/search-and-filter/search-and-filter.component.css index 9377c357b6..285d9026bf 100644 --- a/peer-prep-fe/src/components/search-and-filter/search-and-filter.component.css +++ b/peer-prep-fe/src/components/search-and-filter/search-and-filter.component.css @@ -19,31 +19,40 @@ font-size: 22px; background-color: transparent; border: 1px solid white; - padding: 3px 40px 3px 40px; + padding: 2px 40px 6px 40px; + margin-right: 10px; /* margin-left: 10px; */ border-radius: 10px; width: 400px; text-transform: uppercase; } +.search-button { + background-color: transparent; + border-radius: 10px; +} + .search-icon { /* position: absolute; */ /* top: 50%; */ /* transform: translateY(-50%); */ - color: white; + /* color: white; */ + background-color: transparent; pointer-events: none; } button[type="submit"] { /* text-indent: -999px; */ overflow: hidden; + color: white; width: 40px; + height: 40px; padding: 0; margin: 0; /* border: 1px solid transparent; */ - border-radius: inherit; + border-radius: 10px; cursor: pointer; - opacity: 0.7; + opacity: 1; } button[type="submit"]:hover { diff --git a/peer-prep-fe/src/components/search-and-filter/search-and-filter.component.html b/peer-prep-fe/src/components/search-and-filter/search-and-filter.component.html index 69aba10053..a475d64a74 100644 --- a/peer-prep-fe/src/components/search-and-filter/search-and-filter.component.html +++ b/peer-prep-fe/src/components/search-and-filter/search-and-filter.component.html @@ -1,7 +1,7 @@
-
From c1ef4580b2a4194392f1c46455308fcdb443a579 Mon Sep 17 00:00:00 2001 From: lynnetteeee Date: Sun, 29 Sep 2024 16:12:24 +0800 Subject: [PATCH 4/7] Fix spec file --- peer-prep-fe/package-lock.json | 8 ++++++++ peer-prep-fe/package.json | 1 + .../search-and-filter/search-and-filter.component.spec.ts | 6 ++++-- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/peer-prep-fe/package-lock.json b/peer-prep-fe/package-lock.json index e9458c0e84..713005e7be 100644 --- a/peer-prep-fe/package-lock.json +++ b/peer-prep-fe/package-lock.json @@ -31,6 +31,7 @@ "@angular/cli": "^18.2.4", "@angular/compiler-cli": "^18.2.0", "@types/jasmine": "~5.1.0", + "@types/mocha": "^10.0.8", "jasmine-core": "~5.2.0", "karma": "~6.4.0", "karma-chrome-launcher": "~3.2.0", @@ -4473,6 +4474,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/mocha": { + "version": "10.0.8", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.8.tgz", + "integrity": "sha512-HfMcUmy9hTMJh66VNcmeC9iVErIZJli2bszuXc6julh5YGuRb/W5OnkHjwLNYdFlMis0sY3If5SEAp+PktdJjw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/mute-stream": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/@types/mute-stream/-/mute-stream-0.0.4.tgz", diff --git a/peer-prep-fe/package.json b/peer-prep-fe/package.json index 908ad487d7..a73172e8b4 100644 --- a/peer-prep-fe/package.json +++ b/peer-prep-fe/package.json @@ -33,6 +33,7 @@ "@angular/cli": "^18.2.4", "@angular/compiler-cli": "^18.2.0", "@types/jasmine": "~5.1.0", + "@types/mocha": "^10.0.8", "jasmine-core": "~5.2.0", "karma": "~6.4.0", "karma-chrome-launcher": "~3.2.0", diff --git a/peer-prep-fe/src/components/search-and-filter/search-and-filter.component.spec.ts b/peer-prep-fe/src/components/search-and-filter/search-and-filter.component.spec.ts index 22d61c24cb..864b6898c6 100644 --- a/peer-prep-fe/src/components/search-and-filter/search-and-filter.component.spec.ts +++ b/peer-prep-fe/src/components/search-and-filter/search-and-filter.component.spec.ts @@ -1,6 +1,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; - import { SearchAndFilterComponent } from './search-and-filter.component'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; // Import this to handle HttpClient +import { ReactiveFormsModule } from '@angular/forms'; // Import if your component uses reactive forms describe('SearchAndFilterComponent', () => { let component: SearchAndFilterComponent; @@ -8,7 +9,8 @@ describe('SearchAndFilterComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [SearchAndFilterComponent] + declarations: [SearchAndFilterComponent], // Components go here, not in imports + imports: [HttpClientTestingModule, ReactiveFormsModule] // Add any necessary modules, such as HttpClientTestingModule }) .compileComponents(); From e7a9efcfaedde3bbb00dae488c53bc09ccde222c Mon Sep 17 00:00:00 2001 From: lynnetteeee Date: Sun, 29 Sep 2024 16:13:29 +0800 Subject: [PATCH 5/7] Clean up comments in spec file --- .../search-and-filter/search-and-filter.component.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/peer-prep-fe/src/components/search-and-filter/search-and-filter.component.spec.ts b/peer-prep-fe/src/components/search-and-filter/search-and-filter.component.spec.ts index 864b6898c6..0391d58b24 100644 --- a/peer-prep-fe/src/components/search-and-filter/search-and-filter.component.spec.ts +++ b/peer-prep-fe/src/components/search-and-filter/search-and-filter.component.spec.ts @@ -1,7 +1,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { SearchAndFilterComponent } from './search-and-filter.component'; import { HttpClientTestingModule } from '@angular/common/http/testing'; // Import this to handle HttpClient -import { ReactiveFormsModule } from '@angular/forms'; // Import if your component uses reactive forms +import { ReactiveFormsModule } from '@angular/forms'; describe('SearchAndFilterComponent', () => { let component: SearchAndFilterComponent; @@ -9,8 +9,8 @@ describe('SearchAndFilterComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [SearchAndFilterComponent], // Components go here, not in imports - imports: [HttpClientTestingModule, ReactiveFormsModule] // Add any necessary modules, such as HttpClientTestingModule + declarations: [SearchAndFilterComponent], // For components + imports: [HttpClientTestingModule, ReactiveFormsModule] // For modules }) .compileComponents(); From 950b73c6efc8a3c3d733fe990b4b2793f1e3e885 Mon Sep 17 00:00:00 2001 From: lynnetteeee Date: Sun, 29 Sep 2024 16:15:00 +0800 Subject: [PATCH 6/7] Update categories spec file --- .../question-categories/question-categories.component.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/peer-prep-fe/src/components/question-categories/question-categories.component.spec.ts b/peer-prep-fe/src/components/question-categories/question-categories.component.spec.ts index c88589dc21..e008bac563 100644 --- a/peer-prep-fe/src/components/question-categories/question-categories.component.spec.ts +++ b/peer-prep-fe/src/components/question-categories/question-categories.component.spec.ts @@ -8,7 +8,7 @@ describe('QuestionCategoriesComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [QuestionCategoriesComponent] + declarations: [QuestionCategoriesComponent] }) .compileComponents(); From 3f610ff2d4a6694d02905488ad9d88e74a4eedd5 Mon Sep 17 00:00:00 2001 From: lynnetteeee Date: Sun, 29 Sep 2024 16:17:36 +0800 Subject: [PATCH 7/7] Update spec files --- .../question-categories/question-categories.component.spec.ts | 2 +- .../search-and-filter/search-and-filter.component.spec.ts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/peer-prep-fe/src/components/question-categories/question-categories.component.spec.ts b/peer-prep-fe/src/components/question-categories/question-categories.component.spec.ts index e008bac563..c88589dc21 100644 --- a/peer-prep-fe/src/components/question-categories/question-categories.component.spec.ts +++ b/peer-prep-fe/src/components/question-categories/question-categories.component.spec.ts @@ -8,7 +8,7 @@ describe('QuestionCategoriesComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [QuestionCategoriesComponent] + imports: [QuestionCategoriesComponent] }) .compileComponents(); diff --git a/peer-prep-fe/src/components/search-and-filter/search-and-filter.component.spec.ts b/peer-prep-fe/src/components/search-and-filter/search-and-filter.component.spec.ts index 0391d58b24..35e7c38faa 100644 --- a/peer-prep-fe/src/components/search-and-filter/search-and-filter.component.spec.ts +++ b/peer-prep-fe/src/components/search-and-filter/search-and-filter.component.spec.ts @@ -9,8 +9,7 @@ describe('SearchAndFilterComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [SearchAndFilterComponent], // For components - imports: [HttpClientTestingModule, ReactiveFormsModule] // For modules + imports: [HttpClientTestingModule, ReactiveFormsModule, SearchAndFilterComponent] }) .compileComponents();