diff options
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.'); +} +  | 
