diff options
Diffstat (limited to 'ufund-ui/src/app/components/need-list')
3 files changed, 410 insertions, 144 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 3a89a20..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,138 +1,275 @@ -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 {catchError, of} from 'rxjs';  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, -    templateUrl: './need-list.component.html', -    styleUrl: './need-list.component.css' +  selector: 'app-need-list', +  standalone: false, +  templateUrl: './need-list.component.html', +  styleUrl: './need-list.component.css'  })  export class NeedListComponent { -    needs: Need[] = []; -    searchResults: Need[] = []; +  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); -    constructor( -        private cupboardService: CupboardService, -        private usersService: UsersService, -        private authService: AuthService -    ) {} +  decrementPage() { +    this.currentPage--; +    this.updateVisibleNeeds();     +  } -    refresh() { -        this.cupboardService.getNeeds().subscribe(n => this.needs = n) -    } +  incrementPage() { +    this.currentPage++; +    this.updateVisibleNeeds();     +  } -    ngOnInit(): void { -        this.refresh() +  editNeedsPerPage(amount: number) { +    this.itemsPerPage = amount; +    this.updateVisibleNeeds(); +  } -        this.close(); -    } +  updateVisibleNeeds() { +    this.totalPages = Math.ceil(this.searchResults.length / this.itemsPerPage); +    this.visibleNeeds = this.searchResults.slice(this.currentPage * this.itemsPerPage, (this.currentPage + 1) * this.itemsPerPage); +  } -    private showElement(element: any) { -        if (element) { -            element.style.visibility = 'visible'; -            element.style.position = 'relative'; -        } -    } +  resetVisibleNeeds() { +    this.currentPage = 0; +    this.updateVisibleNeeds(); +  } -    private hideElement(element: any) { -        if (element) { -            element.style.visibility = 'hidden'; -            element.style.position = 'absolute'; -        } -    } +  currentSortAlgo: sortAlgo = sortByPriority; +  sortSelection: string = 'sortByPriority'; -    private updateSearchStatus(text: string) { -        let element = document.getElementById('search-status'); -        if (element) { -            element.innerHTML = text; -        } -    } +  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"]}, +  ]; -    open() { -        this.hideElement(document.getElementById('search-button')); -        this.showElement(document.getElementById('search-form')); -    } +  @Output() currentNeed = new EventEmitter<Need>(); -    close() { -        this.hideElement(document.getElementById('search-form')); -        this.showElement(document.getElementById('search-button')); -        this.hideElement(document.getElementById('search-status')); -    } +  constructor( +    private cupboardService: CupboardService, +    private usersService: UsersService, +    private authService: AuthService, +    private toastService: ToastsService +  ) {} -    private searchDelay: any; - -    async search(form: any) { -        //wait .25 seconds before searching but cancel if another search is made during the wait to prevent too many api calls - -        //remove previous search if it exists -        if (this.searchDelay) { -            clearTimeout(this.searchDelay); -        } - -        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); -    } +    refresh() { +        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(); +        }); -    delete(id: number) { -        this.cupboardService.deleteNeed(id).subscribe(() => { -            this.needs = this.needs.filter(n => n.id !== id) -        }) +        const form = document.getElementById('search-form') as HTMLFormElement; +        form.reset(); +        this.search(null);      } -    isManager() { -        const type = this.authService.getCurrentUser()?.type; -        return type === ("MANAGER" as unknown as userType); +  ngOnInit(): void { +    this.refresh() +    +  } + +  changeSortMode(form : any) { +    if (this.sortMode == 'Ascending'){ +      this.sortMode = 'Descending' +    } else { +      this.sortMode = 'Ascending'      } +    this.search(form) +  } + +  private searchDelay: any; -    isHelper() { -        const type = this.authService.getCurrentUser()?.type; -        return type === ("HELPER" as unknown as userType); +  async search(form: any) { +    //wait .25 seconds before searching but cancel if another search is made during the wait to prevent too many api calls + +    //remove previous search if it exists +    if (this.searchDelay) { +      clearTimeout(this.searchDelay);      } +    if (form) { +      this.searchDelay = setTimeout(() => { + +        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 +            } +          }); -    add(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: any, _) => { -                        console.error(err); -                        return of(); -                    })) -                    .subscribe(() => { -                        this.usersService.refreshBasket(); -                    }); +          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 { -                window.alert("This need is already in your basket!") +              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.authService.getCurrentUser()?.type; +    return type === ("MANAGER" as unknown as userType); +  } +  isHelper() { +    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.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(() => { +                this.usersService.refreshBasket(); +            }); +      } else { +        this.toastService.sendToast(ToastType.ERROR, "This need is already in your basket!") +      }      } +  } + +  back() { +    this.searchResults = this.needs; +  } -    back() { -        this.searchResults = []; +  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.'); +} +  | 
