diff options
Diffstat (limited to 'ufund-ui/src/app/components/cupboard')
4 files changed, 249 insertions, 118 deletions
diff --git a/ufund-ui/src/app/components/cupboard/cupboard.component.css b/ufund-ui/src/app/components/cupboard/cupboard.component.css index e45d929..9c37582 100644 --- a/ufund-ui/src/app/components/cupboard/cupboard.component.css +++ b/ufund-ui/src/app/components/cupboard/cupboard.component.css @@ -52,3 +52,36 @@ margin-top: 3px; } } + +#header2 { + display: flex; + flex-direction: column; + gap: 10px +} + +#searchArea { + display: flex; + + form { + display: flex; + width: 100%; + gap: 10px; + } + + input[type=text] { + display: flex; + width: 100%; + } +} + +#sortArea { + display: flex; + flex-direction: row; + gap: 10px; + align-items: center; +} + +select { + font-size: 14pt; + padding: 5px; +} diff --git a/ufund-ui/src/app/components/cupboard/cupboard.component.html b/ufund-ui/src/app/components/cupboard/cupboard.component.html index 37954bb..cd8fec2 100644 --- a/ufund-ui/src/app/components/cupboard/cupboard.component.html +++ b/ufund-ui/src/app/components/cupboard/cupboard.component.html @@ -1,38 +1,50 @@ <div id="box"> <div id="header"> <h1> Cupboard </h1> - <button *ngIf="isManager()" class="button2" (click)="this.selectForm('create')"><span class="icon">add</span>Create Need</button> + <ng-template #create> + <app-need-edit [mode]="'Create'" (refreshNeedList)="refresh()"></app-need-edit> + </ng-template> + <button *ngIf="usersService.isManager()" class="button2" (click)="modalService.showModal(create)"><span class="icon">add</span>Create Need</button> </div> - <app-need-list (currentNeed) = populateForm($event) #needList></app-need-list> -</div> -<ng-template [ngIf]="isManager()" > -<div> - <app-need-edit *ngIf="selectedForm === 'update'" [selectedNeed]="selectedNeed" (refreshNeedList)="needList.refresh()"></app-need-edit> - <div> - <div id="create-form" *ngIf="selectedForm === 'create'"> - <h1> Create Need </h1> - <form #cupboardForm="ngForm" (ngSubmit)="submit(cupboardForm.value)"> - <label>Name:</label><br> - <input type="text" name="name" ngModel><br> - <label>Image:</label><br> - <input type="text" name="image" ngModel><br> - <label>Location:</label><br> - <input type="text" name="location" ngModel><br> - <label>Max Goal:</label><br> - <input type="number" name="maxGoal" ngModel><br> - <label>Type</label><br> - <input type="radio" name="type" value="MONETARY" ngModel> - <label>Monetary</label><br> - <input type="radio" name="type" value="PHYSICAL" ngModel> - <label>Physical</label><br> - <input type="checkbox" name="urgent" value="false" ngModel> - <label>Urgent</label><br> - <label>Description</label><br> - <textarea name="description"></textarea><br> - <input type="submit" value="Submit"> - </form> - </div> + <div id="header2"> + <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)] = "currentSortAlgo" class="wide-input" (change)="search(searchForm.value)" [value]="currentSortAlgo"> + <option *ngFor="let algorithm of Object.keys(SortingAlgorithms)" [value]="algorithm"> + {{SortingAlgorithms[algorithm].display[sortMode === 'Ascending' ? 0 : 1]}} + </option> + </select> + <button (click)="toggleSortMode(searchForm.value)" [title]="sortMode"> + <span class="icon">{{sortMode === 'Ascending' ? 'arrow_upward': 'arrow_downward'}}</span> + </button> + <label>Needs per page: </label> + <input type ="number" [(ngModel)]="itemsPerPage" min="1" max="{{searchResults.length}}"> + </div> </div> + <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> + + <ng-template let-need #NLActions> + <button *ngIf="usersService.isHelper()" (click)="addToBasket(need)" [disabled]="usersService.inBasket(usersService.getBasket() | async, need)"> + <span class="icon">{{usersService.inBasket(usersService.getBasket() | async, need)? "check": "add" }}</span>Add To Basket + </button> + <ng-template #edit> + <app-need-edit *ngIf="need" [mode]="'Edit'" [need]="need" (refreshNeedList)="refresh()"></app-need-edit> + </ng-template> + <button *ngIf="usersService.isManager()" (click)="modalService.showModal(edit)"> + <span class="icon">edit</span>Edit Need + </button> + <button *ngIf="usersService.isManager()" (click)="deleteNeed(need.id)" > + <span class="icon">delete</span>Delete Need + </button> + </ng-template> + <app-need-list [actionArea]="NLActions" *ngIf="searchResults.length > 0" [needs]="searchResults" [itemsPerPage]="itemsPerPage" #needList/> </div> -</ng-template> diff --git a/ufund-ui/src/app/components/cupboard/cupboard.component.ts b/ufund-ui/src/app/components/cupboard/cupboard.component.ts index a4706b3..b03b77e 100644 --- a/ufund-ui/src/app/components/cupboard/cupboard.component.ts +++ b/ufund-ui/src/app/components/cupboard/cupboard.component.ts @@ -1,11 +1,14 @@ import {Component, OnInit, ViewChild} from '@angular/core'; -import { CupboardService } from '../../services/cupboard.service'; -import { Need } from '../../models/Need'; -import { userType } from '../../models/User'; -import { catchError, of } from 'rxjs'; -import { NeedListComponent } from '../need-list/need-list.component'; +import {CupboardService} from '../../services/cupboard.service'; +import {Need} from '../../models/Need'; +import {catchError, of} from 'rxjs'; +import {NeedListComponent} from '../need-list/need-list.component'; import {AuthService} from '../../services/auth.service'; import {ToastsService, ToastType} from '../../services/toasts.service'; +import {UsersService} from '../../services/users.service'; +import {SortingAlgoArrays} from './sorting'; +import {Router} from '@angular/router'; +import {ModalService} from '../../services/modal.service'; @Component({ selector: 'app-cupboard', @@ -13,111 +16,125 @@ import {ToastsService, ToastType} from '../../services/toasts.service'; templateUrl: './cupboard.component.html', styleUrl: './cupboard.component.css' }) - export class CupboardComponent implements OnInit { - selectedForm?: string = undefined; - needs: any; + // selectedForm?: string = undefined; + // needs: any; @ViewChild("needList") needList?: NeedListComponent + private searchDelay: any; + needs: Need[] = []; + searchResults: Need[] = []; + sortMode: 'Ascending' | 'Descending' = 'Ascending' + itemsPerPage = 5; + currentSortAlgo = 'sortByPriority'; + constructor( private cupboardService: CupboardService, private authService: AuthService, - private toastService: ToastsService + private toastService: ToastsService, + protected usersService: UsersService, + private router: Router, + protected modalService: ModalService ) {} ngOnInit(): void { - this.cupboardService.getNeeds().subscribe(n => this.needs = n); - if (this.isManager()) { - console.log("Admin view of Cupboard"); - } else { - console.log("Limited helper view of Cupboard"); - } + this.cupboardService.getNeeds().subscribe(n => { + this.needs = n; + // this.refresh() + this.search(null) + }); + this.authService.getCurrentUserSubject().subscribe( + () => this.usersService.refreshBasket()) + } + + refresh() { + this.cupboardService.getNeeds().subscribe(n => { + if (this.sortMode == 'Ascending') { + this.needs = n.sort(SortingAlgoArrays[this.currentSortAlgo].func); + } else { + this.needs = n.sort(SortingAlgoArrays[this.currentSortAlgo].func).reverse(); + } + this.searchResults = this.needs; + // this.updateVisibleNeeds(); + }); + + const form = document.getElementById('search-form') as HTMLFormElement; + form.reset(); + this.search(null); } - selectedNeed: any = { - name: '', - location:'', - id: null, - maxGoal: null, - type: '', - urgent: false - }; - selectedNeedId: number | null = null; - searchResults: any[] = []; + async search(form: any) { + console.log(this.currentSortAlgo) + //wait .25 seconds before searching but cancel if another search is made during the wait to prevent too many api calls - selectForm(name: string) { - //get search results from the need list - if (this.needList) { - this.searchResults = this.needList.searchResults; + //remove previous search if it exists + if (this.searchDelay) { + clearTimeout(this.searchDelay); } - console.log(this.searchResults) - this.selectedForm = name; - if (name == 'update') { - if (this.searchResults) { - this.searchResults.forEach((element: any) => { - console.log(element) - }); - } + if (form) { + this.searchDelay = setTimeout(() => { + + if (form) { + const currentSearchValue = form.search; //latest value of the search + this.cupboardService.searchNeeds(currentSearchValue).subscribe((n) => { + if (this.sortMode == 'Ascending') { + this.searchResults = n.sort(SortingAlgoArrays[this.currentSortAlgo].func); + } else { + this.searchResults = n.sort(SortingAlgoArrays[this.currentSortAlgo].func).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; } } - async updateSearchResults() { - if (this.needList) { - while (this.selectedForm == 'update') { - this.searchResults = this.needList.searchResults - await new Promise(resolve => setTimeout(resolve, 100)); - } + toggleSortMode(form : any) { + if (this.sortMode == 'Ascending'){ + this.sortMode = 'Descending' + } else { + this.sortMode = 'Ascending' } + this.search(form) } - populateForm(need: any): void { - this.selectForm('update'); - this.selectedNeed = { ...need }; + deleteNeed(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.authService.getCurrentUser()?.type; - return type === ("MANAGER" as unknown as userType); + addToBasket(need: Need) { + 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) + .pipe(catchError((err, _) => { + console.error(err); + return of(); + })) + .subscribe(() => { + let action = {label: "View Basket", onAction: () => this.router.navigate(['/basket'])} + this.toastService.sendToast(ToastType.INFO, `"${need.name}" Added to basket`, action) + this.usersService.refreshBasket(); + }); + } else { + this.toastService.sendToast(ToastType.ERROR, "This need is already in your basket!") + } + } } - submit(form: any) { - const need: Need = { - name: form.name, - image: form.image, - location: form.location, - id: 0, - maxGoal: form.maxGoal, - type: form.type, - urgent: !!form.urgent, - filterAttributes: [], - current: 0, - description: form.description - }; - console.log("need:", need); - console.log("form submitted. creating need: ", need); - this.cupboardService.createNeed(need) - .pipe(catchError((ex, _) => { - if (ex.status == 500) { - this.toastService.sendToast(ToastType.ERROR, "Fields cannot be blank"); - } else if (ex.status == 400) { - this.toastService.sendToast(ToastType.ERROR, ex.error); - } else { - this.toastService.sendToast(ToastType.ERROR, "Error on creating need"); - } - return of() - })) - .subscribe( - (result) => { - if (result) { - console.log("need created successfully"); - this.needList?.refresh() - } else { - console.log("need creation failed"); - } - } - - ); - } + protected readonly SortingAlgorithms = SortingAlgoArrays; + protected readonly Object = Object; } diff --git a/ufund-ui/src/app/components/cupboard/sorting.ts b/ufund-ui/src/app/components/cupboard/sorting.ts new file mode 100644 index 0000000..5c37019 --- /dev/null +++ b/ufund-ui/src/app/components/cupboard/sorting.ts @@ -0,0 +1,69 @@ +import {Need} from '../../models/Need'; + +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; +} + +const sortByType: sortAlgo = (a:Need, b:Need): number => { + if(a.type == b.type) { + return sortByName(a,b); + } + else if(a.type > b.type) { + return -1; + } + return 1; +} + +export const SortingAlgoArrays: {[key: string]: { func: sortAlgo, display: [string, string]}} = { + sortByPriority: { func: sortByPriority, display: ["Highest Priority", "Lowest Priority" ] }, + sortByName: { func: sortByName, display: ["Name (A to Z)", "Name (Z to A)" ] }, + sortByLocation: { func: sortByLocation, display: ["Location (A to Z)", "Location (Z to A)" ] }, + sortByCompletion: { func: sortByCompletion, display: ["Most Completed", "Least Completed" ] }, + sortByGoal: { func: sortByGoal, display: ["Largest Maximum Goal", "Smallest Maximum Goal" ] }, + sortByType: { func: sortByType, display: ["Type (Physical first)", "Type (Monetary first)" ] }, +}; |