diff options
author | benal01 <bja4245@rit.edu> | 2025-04-01 09:34:36 -0400 |
---|---|---|
committer | benal01 <bja4245@rit.edu> | 2025-04-01 09:34:36 -0400 |
commit | 7ed26c5ee7171a502f6f8527fc55de2bb77eab3b (patch) | |
tree | 2046e58c146097aac21c9e352771420c31df6589 /ufund-ui/src/app/components/need-list | |
parent | ef46ddd082bb91d0262363536d46fe3eb4da47be (diff) | |
parent | d8330f1ac85b26d08ca4df5ce3875078d7b4f47f (diff) | |
download | JellySolutions-7ed26c5ee7171a502f6f8527fc55de2bb77eab3b.tar.gz JellySolutions-7ed26c5ee7171a502f6f8527fc55de2bb77eab3b.tar.bz2 JellySolutions-7ed26c5ee7171a502f6f8527fc55de2bb77eab3b.zip |
Merge branch 'main' of https://github.com/RIT-SWEN-261-02/team-project-2245-swen-261-02-2b-jellysolutions
Diffstat (limited to 'ufund-ui/src/app/components/need-list')
3 files changed, 379 insertions, 107 deletions
diff --git a/ufund-ui/src/app/components/need-list/need-list.component.css b/ufund-ui/src/app/components/need-list/need-list.component.css index bbc3f2c..5f2e5e1 100644 --- a/ufund-ui/src/app/components/need-list/need-list.component.css +++ b/ufund-ui/src/app/components/need-list/need-list.component.css @@ -1,24 +1,110 @@ -:host { - list-style-type:circle; - border: 2px solid #000; - display: block; - width: 30%; - border-radius: 5px; - +#header { + display: flex; + flex-direction: column; + gap: 10px } -li, div { - border: 2px solid #000; +.needEntry { + background-color: #2e2e2e; + display: flex; + flex-direction: column; border-radius: 5px; +} + +#needList { + display: flex; + flex-direction: column; + gap: 15px +} + +select { + font-size: 14pt; padding: 5px; - margin: 5px; +} + +#searchArea { + display: flex; + form { + display: flex; + width: 100%; + gap: 10px; + } + + input[type=text] { + display: flex; + width: 100%; + } } -#search-form { - background-color: #d9d9d9; - padding: 10px 20px 20px 20px; - border: 2px solid #000; - border-radius: 5px; - visibility: visible; - }
\ No newline at end of file +#sortArea { + display: flex; + flex-direction: row; + gap: 10px; + align-items: center; +} + +.needName { + font-weight: bold; +} + +.needType { + text-transform: uppercase; + font-size: 10pt; +} + +.split { + display: flex; + flex-direction: row; + justify-content: space-between; + + + .left { + display: flex; + flex-direction: column; + } + + .right { + display: flex; + flex-direction: column; + align-items: end; + } +} + +.urgent { + font-size: 11pt; + background-color: rgba(255, 165, 0, 0.27); + color: rgba(255, 165, 0, 1); + padding: 2px; + border-radius: 5px; +} + +.prog { + display: flex; + flex-direction: column; +} + +.clickable { + padding: 10px; + background-color: #3a3a3a; + border-radius: 5px; + cursor: pointer; +} + +.clickable:hover { + background-color: #444444; +} + +.actionArea { + display: flex; + padding: 5px; + gap: 5px; +} + +#page-selector { + display: flex; + align-items: center; + justify-content: center; + padding: 10px; + gap: 10px +} diff --git a/ufund-ui/src/app/components/need-list/need-list.component.html b/ufund-ui/src/app/components/need-list/need-list.component.html index 36c12d0..c0501ba 100644 --- a/ufund-ui/src/app/components/need-list/need-list.component.html +++ b/ufund-ui/src/app/components/need-list/need-list.component.html @@ -1,28 +1,71 @@ -<h1>Needs List</h1> -<input id="search-button" type="button" value="Search" (click)="open()"> -<div id="search-form"> - <form #searchForm="ngForm"> - <label>Search:</label><br> - <input type="text" name="search" (input)="search(searchForm.value)" ngModel> - <input type="button" value="Clear" (click)="searchForm.reset()"> <br> - </form> - <button (click)="close()">Close</button> - <div> - <h2 id="search-status">Search Results:</h2> - <div *ngFor="let need of searchResults"> - <a routerLink="/need/{{need.id}}"> - {{need.name}} - </a> - <button (click)="delete(need.id)" *ngIf="isManager()">Delete</button> - <!-- <button (click)="add(need)" *ngIf="isHelper()">Add To Basket</button> --> +<div id="header"> + <div id="searchArea"> + <form id="search-form" #searchForm="ngForm"> + <input type="text" name="search" class="wide-input" placeholder="Search in {{needs.length}} needs..." (input)="search(searchForm.value)" ngModel> + <input type="reset" value="Clear" (click)="search(null)"> <br> + </form> + </div> + <div id="sortArea"> + <label for="sort">Sort by: </label> + <select id='sort' [(ngModel)] = "sortSelection" class="wide-input" (change)="search(searchForm.value)" [value]="sortSelection"> + <option *ngFor="let algorithm of SortingAlgoArrays" value="{{algorithm.name}}"> + {{algorithm.display[sortMode === 'Ascending' ? 0 : 1]}} + </option> + </select> + <button (click)="changeSortMode(searchForm.value)"> + <span class="icon">{{sortMode === 'Ascending' ? 'arrow_upward': 'arrow_downward'}}</span> + </button> + <label>Needs per page: </label> + <input type ="number" [(ngModel)]="itemsPerPage" (change)="resetVisibleNeeds()" min="1" max="{{searchResults.length}}"> + </div> + <!--<button (click)="close()">Close</button>--> +</div> + +<!-- display for when results are present and filtered--> +<h2 *ngIf="searchResults.length < needs.length && searchResults.length != 0"> Search Results({{needs.length - searchResults.length}} needs filtered): </h2> +<h2 *ngIf="searchResults.length == needs.length"> All Needs </h2> +<h2 *ngIf="searchResults.length == 0"> No Results Found </h2> +<div id="needList"> + <div *ngFor="let need of visibleNeeds" class="needEntry"> + <div [routerLink]="'/need/' + need.id" class="clickable"> + <div class="split"> + <div class="left"> + <span class="needName">{{need.name}}</span> + <span class="needType">{{need.type}}</span> + </div> + + <div class="right"> + <span *ngIf="need.urgent" class="urgent">URGENT</span> + <span *ngIf="need.location"><span class="icon">location_on</span>{{need.location}}</span> + </div> + </div> + + <br> + + <div class="prog"> + <span id="hover-status-label-{{need.id}}"> </span> + <span>{{need.current}}/{{need.maxGoal}} ({{((need.current / need.maxGoal) * 100).toFixed(0)}}%)</span> + <progress [value]="need.current" [max]="need.maxGoal"></progress> + </div> + + </div> + + <div class="actionArea"> + <button *ngIf="isHelper()" (click)="add(need)"> + <span class="icon">add</span>Add To Basket + </button> + <button *ngIf="isManager()" (click)="select(need)"> + <span class="icon">edit</span>Edit Need + </button> + <button *ngIf="isManager()" (click)="delete(need.id)" > + <span class="icon">delete</span>Delete Need + </button> </div> </div> </div> -<li *ngFor="let need of needs"> - <a routerLink="/need/{{need.id}}"> - {{need.name}} - </a> - <button (click)="delete(need.id)" *ngIf="isManager()">Delete</button> - <button (click)="add(need)" *ngIf="isHelper()">Add To Basket</button> -</li> +<div id="page-selector"> + <button *ngIf="currentPage > 0" (click)="decrementPage()"><span class="icon">arrow_back_ios</span></button> + <span>Page {{currentPage + 1}} of {{totalPages}}</span> + <button *ngIf="currentPage < totalPages - 1" (click)="incrementPage()"><span class="icon">arrow_forward_ios</span></button> +</div> diff --git a/ufund-ui/src/app/components/need-list/need-list.component.ts b/ufund-ui/src/app/components/need-list/need-list.component.ts index 25f05d6..cd3d9bd 100644 --- a/ufund-ui/src/app/components/need-list/need-list.component.ts +++ b/ufund-ui/src/app/components/need-list/need-list.component.ts @@ -1,8 +1,61 @@ -import { Component } from '@angular/core'; +import {Component, EventEmitter, Output} from '@angular/core'; import {Need} from '../../models/Need'; import {CupboardService} from '../../services/cupboard.service'; -import { UsersService } from '../../services/users.service'; -import { userType } from '../../models/User'; +import {UsersService} from '../../services/users.service'; +import {userType} from '../../models/User'; +import {AuthService} from '../../services/auth.service'; +import {catchError, of} from 'rxjs'; +import {ToastsService, ToastType} from '../../services/toasts.service'; + +interface sortAlgo { + (a: Need,b: Need): number; +} + +// sort functions +const sortByName: sortAlgo = (a: Need, b: Need): number => { + if(a.name.toLocaleLowerCase() < b.name.toLocaleLowerCase()) { + return -1; + } + return 1; +} + +const sortByGoal: sortAlgo = (a: Need, b: Need): number => { + if(a.maxGoal == b.maxGoal) { + return sortByName(a,b); + } + else if(a.maxGoal > b.maxGoal) { + return -1; + } + return 1; +} + +const sortByCompletion: sortAlgo = (a: Need, b: Need): number => { + if(a.current == b.current) { + return sortByGoal(a,b); + } + else if(a.current > b.current) { + return -1; + } + return 1; +} + +const sortByPriority: sortAlgo = (a: Need, b: Need): number => { + if(a.urgent == b.urgent) { + return sortByGoal(a,b); + } + else if(a.urgent && !b.urgent) { + return -1; + } + return 1; +} + +const sortByLocation: sortAlgo = (a: Need, b: Need): number => { + if(a.location.toLocaleLowerCase() < b.location.toLocaleLowerCase()) { + return -1; + } + return 1; +} + @Component({ selector: 'app-need-list', standalone: false, @@ -10,54 +63,88 @@ import { userType } from '../../models/User'; styleUrl: './need-list.component.css' }) export class NeedListComponent { + selectedNeed: Need | null = null; needs: Need[] = []; searchResults: Need[] = []; + visibleNeeds: Need[] = []; + sortMode: 'Ascending' | 'Descending' = 'Ascending' + currentPage: number = 0; + itemsPerPage: number = 5; + totalPages: number = Math.ceil(this.needs.length / this.itemsPerPage); + + decrementPage() { + this.currentPage--; + this.updateVisibleNeeds(); + } + + incrementPage() { + this.currentPage++; + this.updateVisibleNeeds(); + } + + editNeedsPerPage(amount: number) { + this.itemsPerPage = amount; + this.updateVisibleNeeds(); + } + + updateVisibleNeeds() { + this.totalPages = Math.ceil(this.searchResults.length / this.itemsPerPage); + this.visibleNeeds = this.searchResults.slice(this.currentPage * this.itemsPerPage, (this.currentPage + 1) * this.itemsPerPage); + } + + resetVisibleNeeds() { + this.currentPage = 0; + this.updateVisibleNeeds(); + } + + currentSortAlgo: sortAlgo = sortByPriority; + sortSelection: string = 'sortByPriority'; + + SortingAlgoArrays: {func:sortAlgo,name:string, display:string[]}[] = [ + {func:sortByPriority,name:"sortByPriority", display:["Highest Priority", "Lowest Priority"]}, + {func:sortByName,name:"sortByName", display:["Name (A to Z)", "Name (Z to A)"]}, + {func:sortByLocation,name:"sortByLocation", display:["Location (A to Z)", "Location (Z to A)"]}, + {func:sortByCompletion,name:"sortByCompletion", display:["Most Completed", "Least Completed"]}, + {func:sortByGoal,name:"sortByGoal", display:["Largest Maximum Goal", "Smallest Maximum Goal"]}, + ]; + + @Output() currentNeed = new EventEmitter<Need>(); constructor( private cupboardService: CupboardService, - private usersService: UsersService + private usersService: UsersService, + private authService: AuthService, + private toastService: ToastsService ) {} refresh() { - this.cupboardService.getNeeds().subscribe(n => this.needs = n) + this.cupboardService.getNeeds().subscribe(n => { + if (this.sortMode == 'Ascending') { + this.needs = n.sort(this.currentSortAlgo); + } else { + this.needs = n.sort(this.currentSortAlgo).reverse(); + } + this.searchResults = this.needs; + this.updateVisibleNeeds(); + }); + + const form = document.getElementById('search-form') as HTMLFormElement; + form.reset(); + this.search(null); } ngOnInit(): void { this.refresh() - this.close(); - } - - private showElement(element: any) { - if (element){ - element.style.visibility = 'visible'; - element.style.position = 'relative'; - } - } - - private hideElement(element: any) { - if (element){ - element.style.visibility = 'hidden'; - element.style.position = 'absolute'; - } } - private updateSearchStatus(text: string) { - let element = document.getElementById('search-status'); - if (element) { - element.innerHTML = text; + changeSortMode(form : any) { + if (this.sortMode == 'Ascending'){ + this.sortMode = 'Descending' + } else { + this.sortMode = 'Ascending' } - } - - open() { - this.hideElement(document.getElementById('search-button')); - this.showElement(document.getElementById('search-form')); - } - - close() { - this.hideElement(document.getElementById('search-form')); - this.showElement(document.getElementById('search-button')); - this.hideElement(document.getElementById('search-status')); + this.search(form) } private searchDelay: any; @@ -69,64 +156,120 @@ export class NeedListComponent { if (this.searchDelay) { clearTimeout(this.searchDelay); } + if (form) { + this.searchDelay = setTimeout(() => { - this.searchDelay = setTimeout(() => { - const currentSearchValue = form.search; //latest value of the search - this.cupboardService.searchNeeds(currentSearchValue).subscribe((n) => { - this.searchResults = n; - console.log(currentSearchValue, this.searchResults); - this.showElement(document.getElementById('search-results')); - this.showElement(document.getElementById('search-status')); - if (this.searchResults.length === this.needs.length) { - this.updateSearchStatus("Please refine your search"); - this.searchResults = []; - } else if (this.searchResults.length === 0) { - this.updateSearchStatus("No results found"); - } else { - this.updateSearchStatus("Search results:"); - } - }); - }, 250); + if (form) { + //sorting based on algo selected + this.SortingAlgoArrays.forEach(algo => { + if(algo.name === this.sortSelection) { + this.currentSortAlgo = algo.func; + console.log("changed sorting algorithm to: ", algo.name + this.sortMode) + return + } + }); + + const currentSearchValue = form.search; //latest value of the search + this.cupboardService.searchNeeds(currentSearchValue).subscribe((n) => { + if (this.sortMode == 'Ascending') { + this.searchResults = n.sort(this.currentSortAlgo); + } else { + this.searchResults = n.sort(this.currentSortAlgo).reverse(); + } + this.updateVisibleNeeds(); + }); + } + }, 250); + } else { + //user has cleared the search bar, we can skip the timeout for a 1/4 second faster response + //clear timeout to stop pending search + clearTimeout(this.searchDelay); + this.searchResults = this.needs; + } } delete(id : number) { this.cupboardService.deleteNeed(id).subscribe(() => { + this.toastService.sendToast(ToastType.INFO, "Need deleted.") this.needs = this.needs.filter(n => n.id !== id) }) + this.refresh(); } isManager() { - const type = this.usersService.getCurrentUser()?.type; + const type = this.authService.getCurrentUser()?.type; return type === ("MANAGER" as unknown as userType); } isHelper() { - const type = this.usersService.getCurrentUser()?.type; + const type = this.authService.getCurrentUser()?.type; return type === ("HELPER" as unknown as userType); } + changeText(id : number, text : string) { + const span = document.getElementById('hover-status-label-' + id); + if (span) { + span.innerHTML = ' ' + text; + } + } + add(need: Need) { - const currentUser = this.usersService.getCurrentUser(); + const currentUser = this.authService.getCurrentUser(); //console.log("get current user in angular:", currentUser) if (currentUser) { if (!currentUser.basket.includes(need.id)) { currentUser.basket.push(need.id); - this.usersService.updateUser(currentUser).subscribe(() => { - this.usersService.refreshBasket(); - error: (err: any) => { - console.error(err); - } - }); + this.usersService.updateUser(currentUser) + .pipe(catchError((err, _) => { + console.error(err); + return of(); + })) + .subscribe(() => { + this.usersService.refreshBasket(); + }); } else { - window.alert("This need is already in your basket!") + this.toastService.sendToast(ToastType.ERROR, "This need is already in your basket!") } - - } - } back() { - this.searchResults = []; + this.searchResults = this.needs; + } + + select(need : Need) { + //emit value + this.currentNeed.emit(need); + if (this.selectedNeed) { + //revert already selected need to previous style + console.log(need.id); + let button = document.getElementById('need-button-' + this.selectedNeed.id); + if (button) { + console.log(button) + button.style.background = 'lightgray'; + button.style.marginLeft = '0%'; + button.style.width = '98%'; + } + button = document.getElementById('need-edit-button-' + this.selectedNeed.id); + if (button) { + button.style.visibility = 'visible'; + } + } + //change selected need to selected style + this.selectedNeed = need; + let button = document.getElementById('need-button-' + need.id); + if (button) { + button.style.background = 'white'; + button.style.marginLeft = '4%'; + button.style.width = '100%'; + } + button = document.getElementById('need-edit-button-' + need.id); + if (button) { + button.style.visibility = 'hidden'; + } } } +function not(location: string) { + throw new Error('Function not implemented.'); +} + |