aboutsummaryrefslogtreecommitdiff
path: root/ufund-ui
diff options
context:
space:
mode:
authorGunther6070 <haydenhartman10@yahoo.com>2025-04-05 13:44:33 -0400
committerGunther6070 <haydenhartman10@yahoo.com>2025-04-05 13:44:33 -0400
commitef52495d781a3adcec79bfbc9067f70f5ec3c8ab (patch)
treee16e8cf36f1cafcf198d21d57a0b3a908116b3df /ufund-ui
parentd7c974a98de42f41f3ca16f961d2dee96a3c4313 (diff)
parent0103ffc6f84d04433943c644ab759c1d04b5e681 (diff)
downloadJellySolutions-ef52495d781a3adcec79bfbc9067f70f5ec3c8ab.tar.gz
JellySolutions-ef52495d781a3adcec79bfbc9067f70f5ec3c8ab.tar.bz2
JellySolutions-ef52495d781a3adcec79bfbc9067f70f5ec3c8ab.zip
Merge branch 'main' into checkout-improvement
# Conflicts: # ufund-ui/src/app/components/funding-basket/funding-basket.component.html # ufund-ui/src/app/components/funding-basket/funding-basket.component.ts
Diffstat (limited to 'ufund-ui')
-rw-r--r--ufund-ui/src/app/app.component.css20
-rw-r--r--ufund-ui/src/app/app.component.html35
-rw-r--r--ufund-ui/src/app/app.component.ts2
-rw-r--r--ufund-ui/src/app/components/cupboard/cupboard.component.css33
-rw-r--r--ufund-ui/src/app/components/cupboard/cupboard.component.html74
-rw-r--r--ufund-ui/src/app/components/cupboard/cupboard.component.ts191
-rw-r--r--ufund-ui/src/app/components/cupboard/sorting.ts69
-rw-r--r--ufund-ui/src/app/components/dashboard/dashboard.component.css10
-rw-r--r--ufund-ui/src/app/components/dashboard/dashboard.component.html31
-rw-r--r--ufund-ui/src/app/components/dashboard/dashboard.component.ts8
-rw-r--r--ufund-ui/src/app/components/funding-basket/funding-basket.component.html62
-rw-r--r--ufund-ui/src/app/components/funding-basket/funding-basket.component.ts13
-rw-r--r--ufund-ui/src/app/components/home-page/home-page.component.css2
-rw-r--r--ufund-ui/src/app/components/mini-need-list/mini-need-list.component.css4
-rw-r--r--ufund-ui/src/app/components/mini-need-list/mini-need-list.component.html2
-rw-r--r--ufund-ui/src/app/components/mini-need-list/mini-need-list.component.ts2
-rw-r--r--ufund-ui/src/app/components/need-edit/need-edit.component.css42
-rw-r--r--ufund-ui/src/app/components/need-edit/need-edit.component.html62
-rw-r--r--ufund-ui/src/app/components/need-edit/need-edit.component.ts158
-rw-r--r--ufund-ui/src/app/components/need-list/need-list.component.css37
-rw-r--r--ufund-ui/src/app/components/need-list/need-list.component.html40
-rw-r--r--ufund-ui/src/app/components/need-list/need-list.component.ts301
-rw-r--r--ufund-ui/src/app/components/need-page/need-page.component.css4
-rw-r--r--ufund-ui/src/app/components/need-page/need-page.component.html99
-rw-r--r--ufund-ui/src/app/components/need-page/need-page.component.ts31
-rw-r--r--ufund-ui/src/app/components/toast/toast.component.css4
-rw-r--r--ufund-ui/src/app/components/toast/toast.component.html2
-rw-r--r--ufund-ui/src/app/services/cupboard.service.ts4
-rw-r--r--ufund-ui/src/app/services/modal.service.ts25
-rw-r--r--ufund-ui/src/app/services/users.service.ts18
-rw-r--r--ufund-ui/src/styles.css5
31 files changed, 685 insertions, 705 deletions
diff --git a/ufund-ui/src/app/app.component.css b/ufund-ui/src/app/app.component.css
index f4ed668..8b6b28a 100644
--- a/ufund-ui/src/app/app.component.css
+++ b/ufund-ui/src/app/app.component.css
@@ -4,6 +4,26 @@
height: 100%;
}
+#scroll {
+ overflow: auto;
+ min-height: 100%;
+ display: flex;
+ flex-direction: column;
+}
+
+.modal {
+ position: absolute;
+ background-color: #000000a8;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ z-index: 3;
+}
+
#header {
display: flex;
flex-direction: row;
diff --git a/ufund-ui/src/app/app.component.html b/ufund-ui/src/app/app.component.html
index f697695..30a5347 100644
--- a/ufund-ui/src/app/app.component.html
+++ b/ufund-ui/src/app/app.component.html
@@ -1,17 +1,22 @@
-<div id="header">
- <div>
- <a routerLink="/">
- <h1>Jelly Solutions</h1>
- </a>
+<div id="scroll">
+ <div id="header">
+ <div>
+ <a routerLink="/">
+ <h1>Jelly Solutions</h1>
+ </a>
+ </div>
+ <div>
+ <a *ngIf="(currentUser | async)?.type === userType.MANAGER" routerLink="/dashboard">Dashboard</a>
+ <a routerLink="/cupboard">Cupboard</a>
+ <a *ngIf="(currentUser | async)?.type === userType.HELPER" routerLink="/basket">Basket</a>
+ <!-- <span>{{currentUser$ | async}}</span>-->
+ <button *ngIf="currentUser | async" (click)="logout()">Log Out</button>
+ <button *ngIf="!(currentUser | async)" (click)="login()">Log In</button>
+ </div>
</div>
- <div>
- <a *ngIf="(currentUser | async)?.type === userType.MANAGER" routerLink="/dashboard">Dashboard</a>
- <a routerLink="/cupboard">Cupboard</a>
- <a *ngIf="(currentUser | async)?.type === userType.HELPER" routerLink="/basket">Basket</a>
-<!-- <span>{{currentUser$ | async}}</span>-->
- <button *ngIf="currentUser | async" (click)="logout()"> Log Out</button>
- <button *ngIf="!(currentUser | async)" (click)="login()"> Log In</button>
- </div>
-</div>
-<router-outlet />
+ <router-outlet />
+</div>
+<div *ngIf="modalService.getModalBehaviorSubject() | async" class="modal">
+ <ng-container [ngTemplateOutlet]="modalService.getModalBehaviorSubject() | async" />
+</div>
diff --git a/ufund-ui/src/app/app.component.ts b/ufund-ui/src/app/app.component.ts
index bc0e71a..615397f 100644
--- a/ufund-ui/src/app/app.component.ts
+++ b/ufund-ui/src/app/app.component.ts
@@ -5,6 +5,7 @@ import {AuthService} from './services/auth.service';
import {ToastsService} from './services/toasts.service';
import {User, userType} from './models/User';
import {ActivatedRoute, Router} from '@angular/router';
+import {ModalService} from './services/modal.service';
@Component({
selector: 'app-root',
@@ -22,6 +23,7 @@ export class AppComponent implements OnInit {
private route: ActivatedRoute,
protected toastService: ToastsService,
private viewContainerRef: ViewContainerRef,
+ protected modalService: ModalService,
@Inject(DOCUMENT) private document: Document
) {}
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)" ] },
+};
diff --git a/ufund-ui/src/app/components/dashboard/dashboard.component.css b/ufund-ui/src/app/components/dashboard/dashboard.component.css
index 185fdc2..742a151 100644
--- a/ufund-ui/src/app/components/dashboard/dashboard.component.css
+++ b/ufund-ui/src/app/components/dashboard/dashboard.component.css
@@ -1,7 +1,11 @@
:host {
display: flex;
- flex-direction: column;
+ justify-content: center;
+}
+
+#box {
+ display: flex;
width: 800px;
- align-self: center;
- gap: 20px
+ flex-direction: column;
+ gap: 10px;
}
diff --git a/ufund-ui/src/app/components/dashboard/dashboard.component.html b/ufund-ui/src/app/components/dashboard/dashboard.component.html
index 2af467c..074ca76 100644
--- a/ufund-ui/src/app/components/dashboard/dashboard.component.html
+++ b/ufund-ui/src/app/components/dashboard/dashboard.component.html
@@ -1,14 +1,17 @@
-
-<h1>Admin Dashboard</h1>
-<!--<app-mini-need-list [needList]="topNeeds" jtitle="Top needs" url="/cupboard"/>-->
-<!--<app-mini-need-list [needList]="almostThere" jtitle="Almost there" url="/cupboard"/>-->
-<!--<app-mini-need-list [needList]="inBasket" jtitle="In your basket" url="/basket"/>-->
-<span>_ Registered users</span>
-<span *ngIf="count"> {{count | async}} </span>
-<span>_ Fulfilled needs</span>
-<app-mini-need-list [needList]="fulfilledNeeds.getValue()" jtitle="Fulfilled needs"> </app-mini-need-list>
-<span>_ Most fulfilled needs</span>
-<app-mini-need-list [needList]="mostFulfilledNeeds.getValue()" jtitle="Most fulfilled"> </app-mini-need-list>
-<span>_ Total monetary contributions</span>
-<span *ngIf="totalDonations">${{totalDonations | async}} </span>
-<span>_ </span>
+<div id="box">
+ @if ((authService.getCurrentUserSubject() | async)?.type === userType.MANAGER) {
+ <h1>Admin Dashboard</h1>
+ <span>_ Registered users</span>
+ <span *ngIf="count"> {{count | async}} </span>
+ <span>_ Fulfilled needs</span>
+ <app-mini-need-list [needList]="fulfilledNeeds.getValue()" label="Fulfilled needs"> </app-mini-need-list>
+ <span>_ Most fulfilled needs</span>
+ <app-mini-need-list [needList]="mostFulfilledNeeds.getValue()" label="Most fulfilled"> </app-mini-need-list>
+ <span>_ Total monetary contributions</span>
+ <span *ngIf="totalDonations">${{totalDonations | async}} </span>
+ <span>_ </span>
+ } @else {
+ <h1>Unauthorized</h1>
+ <span>This page requires you to be logged in as an admin! <a routerLink="/login">Log In</a></span>
+ }
+</div>
diff --git a/ufund-ui/src/app/components/dashboard/dashboard.component.ts b/ufund-ui/src/app/components/dashboard/dashboard.component.ts
index 9bf7627..2ab4db2 100644
--- a/ufund-ui/src/app/components/dashboard/dashboard.component.ts
+++ b/ufund-ui/src/app/components/dashboard/dashboard.component.ts
@@ -5,6 +5,7 @@ import {CupboardService} from '../../services/cupboard.service';
import {UsersService} from '../../services/users.service';
import {BehaviorSubject} from 'rxjs';
import {GoalType, Need} from '../../models/Need';
+import {userType} from '../../models/User';
@Component({
selector: 'app-dashboard',
@@ -28,12 +29,6 @@ export class DashboardComponent implements OnInit{
) {}
ngOnInit() {
- let user = this.authService.getCurrentUser()
- if(!localStorage.getItem("credential") && !user) {
- this.router.navigate(['/login'])
- return
- }
-
this.userService.getCount().subscribe(count => this.count.next(count))
this.cupboardService.getNeeds().subscribe(needs => {
let totalValue = 0
@@ -55,4 +50,5 @@ export class DashboardComponent implements OnInit{
}
+ protected readonly userType = userType;
}
diff --git a/ufund-ui/src/app/components/funding-basket/funding-basket.component.html b/ufund-ui/src/app/components/funding-basket/funding-basket.component.html
index 67e1083..7b1a933 100644
--- a/ufund-ui/src/app/components/funding-basket/funding-basket.component.html
+++ b/ufund-ui/src/app/components/funding-basket/funding-basket.component.html
@@ -1,45 +1,25 @@
<div id="box">
- <h1>Funding Basket</h1>
- <ng-template [ngIf]="usersService.getBasket().getValue().length">
- <div id="needList">
- <div *ngFor="let need of usersService.getBasket().getValue()" 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">
- <input type="number" placeholder="Quantity" min="1" [id]="need.id" class="contribution" (input)="resetColor($event)">
- <button class="removeNeed" title="delete need" (click)="this.usersService.removeNeed(need.id)">
- <span class="icon">delete</span> Remove from Basket
- </button>
- </div>
+ @if ((authService.getCurrentUserSubject() | async)?.type === userType.HELPER) {
+ <h1>Funding Basket</h1>
+ <ng-template [ngIf]="(usersService.getBasket() | async)?.length">
+ <ng-template let-need #NLActions>
+ <input type="number" placeholder="Quantity" min="1" [id]="need?.id" class="contribution" (input)="resetColor($event)">
+ <button class="removeNeed" (click)="this.usersService.removeNeed(need.id)">
+ <span class="icon">delete</span>Remove from Basket
+ </button>
+ </ng-template>
+ <app-need-list [actionArea]="NLActions" [needs]="(usersService.getBasket() | async)!"/>
+ <br>
+ <div id="footer">
+ <button class="button2" title="checkout" (click)="checkout()">Checkout</button>
+ <span id="running-total">Your current running total is: ${{runningTotal | async}}</span>
</div>
+ </ng-template>
+ <div *ngIf="!usersService.getBasket().getValue().length">
+ <span>There are no needs in your basket! </span><a routerLink="/cupboard">Browse the cupboard</a>
</div>
- <br>
- <div id="footer">
- <button class="button2" title="checkout" (click)="checkout()">Checkout</button>
- <span id="running-total">Your current running total is: ${{runningTotal | async}}</span>
- </div>
- </ng-template>
- <div *ngIf="!usersService.getBasket().getValue().length">
- <span>There are no needs in your basket! </span><a routerLink="/cupboard">Browse the cupboard</a>
- </div>
+ } @else {
+ <h1>Unauthorized</h1>
+ <span>This page requires you to be logged in as a user! <a routerLink="/login">Log In</a></span>
+ }
</div>
diff --git a/ufund-ui/src/app/components/funding-basket/funding-basket.component.ts b/ufund-ui/src/app/components/funding-basket/funding-basket.component.ts
index a0ba609..78ce958 100644
--- a/ufund-ui/src/app/components/funding-basket/funding-basket.component.ts
+++ b/ufund-ui/src/app/components/funding-basket/funding-basket.component.ts
@@ -2,9 +2,10 @@ import {Component, Input, OnInit, ViewChild} from '@angular/core';
import {UsersService} from '../../services/users.service';
import {Router} from '@angular/router';
import {CupboardService} from '../../services/cupboard.service';
-import {BehaviorSubject, firstValueFrom} from 'rxjs';
+import {BehaviorSubject, firstValueFrom, of} from 'rxjs';
import {AuthService} from '../../services/auth.service';
import {ToastsService, ToastType} from '../../services/toasts.service';
+import {userType} from '../../models/User';
import {GoalType} from '../../models/Need';
@Component({
@@ -19,19 +20,14 @@ export class FundingBasketComponent implements OnInit {
private router: Router,
protected cupboardService: CupboardService,
protected usersService: UsersService,
- private authService: AuthService,
+ protected authService: AuthService,
private toastService: ToastsService
) {}
public runningTotal = new BehaviorSubject(0)
@ViewChild("contribution") contribution?: Input;
- // this is for login rerouting
ngOnInit(): void {
- if (!this.authService.getCurrentUser()) {
- this.router.navigate(['/login'], {queryParams: {redir: this.router.url}});
- return;
- }
this.usersService.refreshBasket();
}
@@ -78,4 +74,7 @@ export class FundingBasketComponent implements OnInit {
(ev.target as HTMLInputElement).setAttribute("style", "border-color: unset")
}
+
+ protected readonly of = of;
+ protected readonly userType = userType;
}
diff --git a/ufund-ui/src/app/components/home-page/home-page.component.css b/ufund-ui/src/app/components/home-page/home-page.component.css
index a10377f..b64be18 100644
--- a/ufund-ui/src/app/components/home-page/home-page.component.css
+++ b/ufund-ui/src/app/components/home-page/home-page.component.css
@@ -1,5 +1,5 @@
:host {
- height: 100%;
+ flex-grow: 1;
display: flex;
flex-direction: column;
align-items: center;
diff --git a/ufund-ui/src/app/components/mini-need-list/mini-need-list.component.css b/ufund-ui/src/app/components/mini-need-list/mini-need-list.component.css
index 090bea9..d01ac54 100644
--- a/ufund-ui/src/app/components/mini-need-list/mini-need-list.component.css
+++ b/ufund-ui/src/app/components/mini-need-list/mini-need-list.component.css
@@ -45,6 +45,10 @@
cursor: pointer;
}
+.needEntry:hover {
+ background-color: #444444;
+}
+
.needName {
font-weight: bold;
}
diff --git a/ufund-ui/src/app/components/mini-need-list/mini-need-list.component.html b/ufund-ui/src/app/components/mini-need-list/mini-need-list.component.html
index 9febfa5..91f51cf 100644
--- a/ufund-ui/src/app/components/mini-need-list/mini-need-list.component.html
+++ b/ufund-ui/src/app/components/mini-need-list/mini-need-list.component.html
@@ -1,5 +1,5 @@
<div id="header">
- <span>{{jtitle}}</span>
+ <span>{{label}}</span>
<a *ngIf="url" [routerLink]="url">Show All<span class="icon">arrow_forward_ios</span></a>
</div>
diff --git a/ufund-ui/src/app/components/mini-need-list/mini-need-list.component.ts b/ufund-ui/src/app/components/mini-need-list/mini-need-list.component.ts
index c909ae6..18b176b 100644
--- a/ufund-ui/src/app/components/mini-need-list/mini-need-list.component.ts
+++ b/ufund-ui/src/app/components/mini-need-list/mini-need-list.component.ts
@@ -10,7 +10,7 @@ import {Need} from '../../models/Need';
export class MiniNeedListComponent {
@Input() needList?: Need[]
- @Input() jtitle?: string
+ @Input() label?: string
@Input() url?: string
constructor(
diff --git a/ufund-ui/src/app/components/need-edit/need-edit.component.css b/ufund-ui/src/app/components/need-edit/need-edit.component.css
index 17605c2..b06e061 100644
--- a/ufund-ui/src/app/components/need-edit/need-edit.component.css
+++ b/ufund-ui/src/app/components/need-edit/need-edit.component.css
@@ -1,4 +1,4 @@
-:host {
+#box {
/*position: absolute;*/
/*background-color: rgba(0, 0, 0, 0.5);*/
/*display: flex;*/
@@ -8,14 +8,44 @@
/*right: 0;*/
/*z-index: 5;*/
/*justify-content: center;*/
+ padding: 10px;
+ border-style: solid;
+ border-width: 1px;
+ border-color: #6c6c6c;
+ background-color: #2e2e2e;
+ border-radius: 5px;
+ position: relative;
+ width: 500px;
}
#create-form, #delete-form, #update-form {
- margin-top: 50px;
- background-color: #3a3a3a;
- padding: 10px 20px 20px 20px;
- border: 2px solid #000;
- border-radius: 5px;
+ /*margin-top: 50px;*/
+
+ /*padding: 10px 20px 20px 20px;*/
+ /*border: 2px solid #000;*/
+ /*border-radius: 5px;*/
/*visibility: visible;*/
/*position: absolute;*/
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+
+ div {
+ display: flex;
+ flex-direction: column;
+ }
+}
+
+#closeButton {
+ position: absolute;
+ width: 35px;
+ right: 10px;
+}
+
+textarea {
+ height: 200px;
+}
+
+label {
+ padding: 3px;
}
diff --git a/ufund-ui/src/app/components/need-edit/need-edit.component.html b/ufund-ui/src/app/components/need-edit/need-edit.component.html
index e776415..ed4bfb3 100644
--- a/ufund-ui/src/app/components/need-edit/need-edit.component.html
+++ b/ufund-ui/src/app/components/need-edit/need-edit.component.html
@@ -1,23 +1,45 @@
-<div id="update-form">
- <h1> Update Need </h1>
- <form #updateForm="ngForm" (ngSubmit)="update(updateForm.value)">
- <label>Name:</label><br>
- <input type="text" name="name" [(ngModel)]="selectedNeed.name"><br>
- <label>Image:</label><br>
- <input type="text" name="image" [(ngModel)]="selectedNeed.image"><br>
- <label>Location:</label><br>
- <input type="text" name="location" [(ngModel)]="selectedNeed.location"><br>
- <label>Max Goal:</label><br>
- <input type="number" name="maxGoal" [(ngModel)]="selectedNeed.maxGoal"><br>
- <label>Type</label><br>
- <input type="radio" name="type" value="MONETARY" [(ngModel)]="selectedNeed.type">
- <label>Monetary</label><br>
- <input type="radio" name="type" value="PHYSICAL" [(ngModel)]="selectedNeed.type">
- <label>Physical</label><br>
- <input type="checkbox" name="urgent" [(ngModel)]="selectedNeed.urgent">
- <label>Urgent</label> <br>
- <label>Description</label> <br>
- <textarea name="description" [(ngModel)]="selectedNeed.description"></textarea><br>
+<div id="box">
+ <button id="closeButton" (click)="modalService.hideModal()"><span class="icon">close</span></button>
+ <h2>{{this.mode}} Need</h2>
+ <form #updateForm="ngForm" id="update-form" (ngSubmit)="submit(updateForm.value)">
+ <div>
+ <span>Name:</span>
+ <input type="text" name="name" [(ngModel)]="needCopy.name">
+ </div>
+ <div>
+ <span>Image:</span>
+ <input type="text" name="image" [(ngModel)]="needCopy.image">
+ </div>
+ <div>
+ <span>Location:</span>
+ <input type="text" name="location" [(ngModel)]="needCopy.location">
+ </div>
+ <div>
+ <span>Max Goal:</span>
+ <input type="number" name="maxGoal" [(ngModel)]="needCopy.maxGoal">
+ </div>
+ <div>
+ <span>Type:</span>
+ <label>
+ <input type="radio" name="type" value="MONETARY" [(ngModel)]="needCopy.type">
+ Monetary
+ </label>
+ <label>
+ <input type="radio" name="type" value="PHYSICAL" [(ngModel)]="needCopy.type">
+ Physical
+ </label>
+ </div>
+ <div>
+ <span>Urgency:</span>
+ <label>
+ <input type="checkbox" name="urgent" [(ngModel)]="needCopy.urgent">
+ Urgent
+ </label>
+ </div>
+ <div>
+ <span>Description:</span>
+ <textarea name="description" [(ngModel)]="needCopy.description"></textarea>
+ </div>
<input type="submit" value="Submit">
</form>
diff --git a/ufund-ui/src/app/components/need-edit/need-edit.component.ts b/ufund-ui/src/app/components/need-edit/need-edit.component.ts
index 2462534..abfa543 100644
--- a/ufund-ui/src/app/components/need-edit/need-edit.component.ts
+++ b/ufund-ui/src/app/components/need-edit/need-edit.component.ts
@@ -1,61 +1,109 @@
-import { Component, Input, Output, EventEmitter } from '@angular/core';
-import { Need, GoalType } from '../../models/Need';
-import { CupboardService } from '../../services/cupboard.service';
-import { catchError, of } from 'rxjs';
-import { ToastsService, ToastType } from '../../services/toasts.service';
+import {Component, EventEmitter, Input, OnChanges, Output} from '@angular/core';
+import {GoalType, Need} from '../../models/Need';
+import {CupboardService} from '../../services/cupboard.service';
+import {catchError, of} from 'rxjs';
+import {ToastsService, ToastType} from '../../services/toasts.service';
+import {ModalService} from '../../services/modal.service';
+import {Router} from '@angular/router';
@Component({
- selector: 'app-need-edit',
- standalone: false,
- templateUrl: './need-edit.component.html',
- styleUrl: './need-edit.component.css'
+ selector: 'app-need-edit',
+ standalone: false,
+ templateUrl: './need-edit.component.html',
+ styleUrl: './need-edit.component.css'
})
-export class NeedEditComponent {
- constructor(
- private cupboardService: CupboardService,
- private toastService: ToastsService
-
- ) {}
-
- @Input() selectedNeed!: Need;
- @Output() refreshNeedList = new EventEmitter<void>();
-
- update(form: any) {
- console.log(form);
- const need: Need = {
- name: form.name,
- image: form.image,
- location: form.location,
- id: this.selectedNeed.id, //system will control this
- maxGoal: form.maxGoal,
- type: GoalType[form.type as keyof typeof GoalType],
- urgent: form.urgent,
- filterAttributes: [],
- current: 0,
- description: form.description
- };
-
- this.cupboardService.updateNeed(need.id, 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 updated successfully");
- this.refreshNeedList.emit();
+export class NeedEditComponent implements OnChanges {
+
+ @Input() mode!: "Create" | "Edit"
+ @Input() need?: Need;
+ @Output() refreshNeedList = new EventEmitter<void>();
+
+ needCopy: any = {}
+
+ constructor(
+ private cupboardService: CupboardService,
+ private toastService: ToastsService,
+ protected modalService: ModalService,
+ private router: Router
+ ) {}
+
+ ngOnChanges() {
+ this.needCopy = {...this.need}
+ }
+
+ submit(form: any) {
+ const need: Need = {
+ name: form.name,
+ image: form.image,
+ location: form.location,
+ id: this.needCopy.id, //system will control this
+ maxGoal: form.maxGoal,
+ type: GoalType[form.type as keyof typeof GoalType],
+ urgent: form.urgent,
+ filterAttributes: [],
+ current: 0,
+ description: form.description
+ };
+
+ if (this.mode == "Edit") {
+ this.updateNeed(need)
+ } else if (this.mode === 'Create') {
+ this.createNeed(need)
+ }
+ }
+
+ updateNeed(need: Need) {
+ this.cupboardService.updateNeed(need.id, 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) {
+ let action = {label: 'View Need', onAction: () => this.router.navigate([`/need/${need.id}`])}
+ this.toastService.sendToast(ToastType.INFO, "Need updated successfully", action)
+ this.modalService.hideModal()
+ console.log("need updated successfully");
+ this.refreshNeedList.emit();
+ } else {
+ console.log("need update failed");
+ }
+ }
+ );
+ }
+
+ createNeed(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 {
- console.log("need update failed");
+ this.toastService.sendToast(ToastType.ERROR, "Error on creating need");
+ }
+ return of()
+ }))
+ .subscribe(
+ (result) => {
+ if (result) {
+ let action = {label: 'View Need', onAction: () => this.router.navigate([`/need/${result.id}`])}
+ this.toastService.sendToast(ToastType.INFO, "Need created successfully", action)
+ this.modalService.hideModal()
+ console.log("need created successfully");
+ this.refreshNeedList.emit();
+ } else {
+ console.log("need creation failed");
+ }
}
- }
- );
- }
-} \ No newline at end of file
+ );
+ }
+}
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 e17609b..622b64a 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,9 +1,3 @@
-#header {
- display: flex;
- flex-direction: column;
- gap: 10px
-}
-
.needEntry {
background-color: #2e2e2e;
display: flex;
@@ -17,33 +11,6 @@
gap: 15px
}
-select {
- font-size: 14pt;
- padding: 5px;
-}
-
-#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;
-}
-
.needName {
font-weight: bold;
}
@@ -105,6 +72,10 @@ select {
gap: 5px;
}
+.actionArea:empty {
+ padding: 0;
+}
+
#page-selector {
display: flex;
align-items: center;
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 84f80dc..406bfa0 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,30 +1,4 @@
-<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)="editNeedsPerPage()" 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">
@@ -46,19 +20,9 @@
<span>{{need.type.toString() == 'MONETARY' ? '$' : ''}}{{need.current}}/{{need.type.toString() == 'MONETARY' ? '$' : ''}}{{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 *ngIf="actionArea" class="actionArea">
+ <ng-container [ngTemplateOutlet]="actionArea" [ngTemplateOutletContext]="{$implicit: need}"/>
</div>
</div>
</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 ae6bc99..40af9f5 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,293 +1,58 @@
-import {Component, EventEmitter, Output} from '@angular/core';
+import {Component, Input, OnChanges, TemplateRef} from '@angular/core';
import {GoalType, Need} from '../../models/Need';
-import {CupboardService} from '../../services/cupboard.service';
-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,
- 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 {
- 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);
+export class NeedListComponent implements OnChanges {
- getPrefix(need: Need) {
- return (need.type === GoalType.MONETARY) ? "$" : "";
- }
+ @Input({required: true}) needs!: Need[]
+ @Input() itemsPerPage: number = 5;
+ @Input() actionArea: TemplateRef<any> | null = null
- decrementPage() {
- this.currentPage--;
- this.updateVisibleNeeds();
- }
+ visibleNeeds: Need[] = [];
+ currentPage: number = 0;
+ totalPages: number = 0;
- incrementPage() {
- this.currentPage++;
- this.updateVisibleNeeds();
- }
+ constructor(
- lastPage() {
- this.currentPage = this.totalPages - 1
- this.updateVisibleNeeds()
- }
+ ) {}
- firstPage() {
- this.currentPage = 0
- this.updateVisibleNeeds()
- }
-
- editNeedsPerPage() {
- if (this.itemsPerPage > this.searchResults.length) {
- this.itemsPerPage = this.searchResults.length;
- }
- if (this.itemsPerPage < 1) {
- this.itemsPerPage = 1;
+ ngOnChanges() {
+ this.updateVisibleNeeds()
}
- this.resetVisibleNeeds();
- }
-
- 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 authService: AuthService,
- private toastService: ToastsService
- ) {}
-
- 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();
- });
-
- const form = document.getElementById('search-form') as HTMLFormElement;
- form.reset();
- this.search(null);
+ getPrefix(need: Need) {
+ return (need.type === GoalType.MONETARY) ? "$" : "";
}
- ngOnInit(): void {
- this.refresh()
-
- }
-
- changeSortMode(form : any) {
- if (this.sortMode == 'Ascending'){
- this.sortMode = 'Descending'
- } else {
- this.sortMode = 'Ascending'
+ decrementPage() {
+ this.currentPage--;
+ this.updateVisibleNeeds();
}
- this.search(form)
- }
-
- 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);
+ incrementPage() {
+ this.currentPage++;
+ this.updateVisibleNeeds();
}
- 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
- }
- });
-
- 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.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;
+ lastPage() {
+ this.currentPage = this.totalPages - 1
+ this.updateVisibleNeeds()
}
- }
- 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.toastService.sendToast(ToastType.INFO, "Need added to your basket!")
- 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!")
- }
+ firstPage() {
+ this.currentPage = 0
+ this.updateVisibleNeeds()
}
- }
-
- back() {
- 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';
- }
+ updateVisibleNeeds() {
+ this.totalPages = Math.ceil(this.needs.length / this.itemsPerPage);
+ this.visibleNeeds = this.needs.slice(this.currentPage * this.itemsPerPage, (this.currentPage + 1) * this.itemsPerPage);
}
- //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';
- }
- }
protected readonly GoalType = GoalType;
}
diff --git a/ufund-ui/src/app/components/need-page/need-page.component.css b/ufund-ui/src/app/components/need-page/need-page.component.css
index 44db4b4..f1b7f1f 100644
--- a/ufund-ui/src/app/components/need-page/need-page.component.css
+++ b/ufund-ui/src/app/components/need-page/need-page.component.css
@@ -66,8 +66,8 @@
.actionArea {
display: flex;
- padding: 5px;
- gap: 5px;
+ padding: 5px 0;
+ gap: 10px;
margin-top: 10px;
}
diff --git a/ufund-ui/src/app/components/need-page/need-page.component.html b/ufund-ui/src/app/components/need-page/need-page.component.html
index 6921eac..ff5990f 100644
--- a/ufund-ui/src/app/components/need-page/need-page.component.html
+++ b/ufund-ui/src/app/components/need-page/need-page.component.html
@@ -1,51 +1,58 @@
<div id="box">
- <h1>{{need.name}}</h1>
- <span class="needType">{{need.type}} GOAL</span>
- <p>{{need.description}}</p>
- <div class="prog">
-<!-- <span>{{need?.current}} / {{need?.maxGoal}}</span>-->
- <progress [value]="need.current" [max]="need.maxGoal"></progress>
- <span>This goal is <strong>{{(((need.current)*100) / (need.maxGoal)).toFixed(0)}}%</strong> complete!</span>
- </div>
-
-
- <div class="split">
- <div class="left">
- <span><strong>Target Goal:</strong> {{(need.type === GoalType.MONETARY) ? "$" : ""}}{{need.maxGoal}}</span>
-
- <span><strong>Amount Currently Collected:</strong> {{need.type.toString() == 'MONETARY' ? '$' : ''}}{{need.current}}</span>
-
- <span><strong>Location:</strong> {{need.location}}</span>
-
- <span><strong>Urgency: </strong>
- <span *ngIf="!need.urgent">Not urgent</span>
- <span *ngIf="need.urgent" class="urgent">URGENT</span>
- </span>
-
- <div *ngIf="need.filterAttributes.length > 0">
- <strong>Tags:</strong>
- <ul style="display: flex; column-gap: 24px;">
- <li *ngFor="let tag of need?.filterAttributes">
- <p>{{tag}}</p>
- </li>
- </ul>
+ @if (need) {
+ <h1>{{need.name}}</h1>
+ <span class="needType">{{need.type}} GOAL</span>
+ <p>{{need.description}}</p>
+ <div class="prog">
+ <progress [value]="need.current" [max]="need.maxGoal"></progress>
+ <span>This goal is <strong>{{(((need.current)*100) / (need.maxGoal)).toFixed(0)}}%</strong> complete!</span>
+ </div>
+
+
+ <div class="split">
+ <div class="left">
+ <span><strong>Target Goal:</strong> {{(need.type === GoalType.MONETARY) ? "$" : ""}}{{need.maxGoal}}</span>
+
+ <span><strong>Amount Currently Collected:</strong> {{need.type.toString() == 'MONETARY' ? '$' : ''}}{{need.current}}</span>
+
+ <span><strong>Location:</strong> {{need.location}}</span>
+
+ <span><strong>Urgency: </strong>
+ <span *ngIf="!need.urgent">Not urgent</span>
+ <span *ngIf="need.urgent" class="urgent">URGENT</span>
+ </span>
+
+ <div *ngIf="need.filterAttributes?.length">
+ <strong>Tags:</strong>
+ <ul style="display: flex; column-gap: 24px;">
+ <li *ngFor="let tag of need?.filterAttributes">
+ <p>{{tag}}</p>
+ </li>
+ </ul>
+ </div>
+ </div>
+ <div class="right">
+ <img *ngIf="need.image" alt="Need image" class="need-image" [src]="need.image"/>
</div>
</div>
- <div class="right">
- <img *ngIf="need.image" alt="Need image" class="need-image" [src]="need.image"/>
+
+
+ <div class="actionArea">
+ <button *ngIf="usersService.isHelper()" (click)="add(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 [mode]="'Edit'" *ngIf="need" [need]="need" (refreshNeedList)="ngOnInit()"></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)="delete(need!.id)" >
+ <span class="icon">delete</span>Delete Need
+ </button>
</div>
- </div>
-
-
- <div class="actionArea">
- <button *ngIf="isHelper()" (click)="add(need!)">
- <span class="icon">add</span>Add To Basket
- </button>
-<!-- <button *ngIf="isManager()" (click)="edit(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>
+ } @else {
+ <h1>Need not found</h1>
+ <span>The requested need does not exist. <a routerLink="/cupboard">Browse the cupboard</a></span>
+ }
</div>
diff --git a/ufund-ui/src/app/components/need-page/need-page.component.ts b/ufund-ui/src/app/components/need-page/need-page.component.ts
index ad4cacf..0967266 100644
--- a/ufund-ui/src/app/components/need-page/need-page.component.ts
+++ b/ufund-ui/src/app/components/need-page/need-page.component.ts
@@ -1,12 +1,12 @@
-import {Component, Input} from '@angular/core';
+import {Component, Input, OnInit} from '@angular/core';
import {GoalType, Need} from '../../models/Need';
import {ActivatedRoute, Router} from "@angular/router";
import {CupboardService} from "../../services/cupboard.service";
-import {userType} from '../../models/User';
import {AuthService} from '../../services/auth.service';
import {catchError, of} from 'rxjs';
import {ToastsService, ToastType} from '../../services/toasts.service';
import {UsersService} from '../../services/users.service';
+import {ModalService} from '../../services/modal.service';
@Component({
selector: 'app-need-page',
@@ -14,18 +14,17 @@ import {UsersService} from '../../services/users.service';
templateUrl: './need-page.component.html',
styleUrl: './need-page.component.css'
})
-export class NeedPageComponent {
+export class NeedPageComponent implements OnInit {
constructor(
private route: ActivatedRoute,
private cupboardService: CupboardService,
private authService: AuthService,
- private usersService: UsersService,
+ protected usersService: UsersService,
private toastService: ToastsService,
- private router: Router
+ private router: Router,
+ protected modalService: ModalService
) {}
- public GoalType = GoalType;
-
@Input() need!: Need;
ngOnInit(): void {
@@ -37,16 +36,6 @@ export class NeedPageComponent {
window.history.back();
}
- 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);
- }
-
add(need: Need) {
const currentUser = this.authService.getCurrentUser();
//console.log("get current user in angular:", currentUser)
@@ -69,19 +58,17 @@ export class NeedPageComponent {
delete(id : number) {
this.cupboardService.deleteNeed(id)
- .pipe(catchError((ex, r) => {
+ .pipe(catchError((ex, _) => {
this.toastService.sendToast(ToastType.ERROR, ex.error)
return of()
}))
.subscribe(() => {
// this.needs = this.needs.filter(n => n.id !== id)
this.toastService.sendToast(ToastType.INFO, "Need deleted")
- this.router.navigate(['/'])
+ this.router.navigate(['/cupboard'])
})
// this.refresh();
}
- edit(need: Need) {
-
- }
+ readonly GoalType = GoalType
}
diff --git a/ufund-ui/src/app/components/toast/toast.component.css b/ufund-ui/src/app/components/toast/toast.component.css
index 4cd81fe..82e2ff3 100644
--- a/ufund-ui/src/app/components/toast/toast.component.css
+++ b/ufund-ui/src/app/components/toast/toast.component.css
@@ -14,7 +14,7 @@
animation: slideDown .5s ease-in-out;
transition: transform .5s;
align-self: center;
- z-index: 3;
+ z-index: 4;
position: absolute;
top: 15px;
display: flex;
@@ -39,7 +39,7 @@
}
}
-.toast.hide {
+.toast.hide:not(:has(:hover)) {
transform: translateY(-90px);
}
diff --git a/ufund-ui/src/app/components/toast/toast.component.html b/ufund-ui/src/app/components/toast/toast.component.html
index dccf869..dc33ecd 100644
--- a/ufund-ui/src/app/components/toast/toast.component.html
+++ b/ufund-ui/src/app/components/toast/toast.component.html
@@ -1,6 +1,6 @@
<div class="toast" [ngClass]="ToastType[type].toLowerCase()" #toastDiv>
<span>{{this.message}}</span>
- <a *ngIf="this.action" (click)="this.action.onAction()">{{this.action.label}}</a>
+ <a *ngIf="this.action" href="#" (click)="this.action.onAction(); $event.preventDefault(); hide()">{{this.action.label}}</a>
<button (click)="hide()">
<span class="icon">close</span>
</button>
diff --git a/ufund-ui/src/app/services/cupboard.service.ts b/ufund-ui/src/app/services/cupboard.service.ts
index 1060476..a87dee2 100644
--- a/ufund-ui/src/app/services/cupboard.service.ts
+++ b/ufund-ui/src/app/services/cupboard.service.ts
@@ -23,8 +23,8 @@ export class CupboardService {
private authService: AuthService
) {}
- createNeed(need: Need): Observable<boolean> {
- return this.http.post<boolean>(this.url, need, this.httpOptions())
+ createNeed(need: Need): Observable<Need> {
+ return this.http.post<Need>(this.url, need, this.httpOptions())
}
getNeeds(): Observable<Need[]> {
diff --git a/ufund-ui/src/app/services/modal.service.ts b/ufund-ui/src/app/services/modal.service.ts
new file mode 100644
index 0000000..04f2f3a
--- /dev/null
+++ b/ufund-ui/src/app/services/modal.service.ts
@@ -0,0 +1,25 @@
+import {Injectable, TemplateRef} from '@angular/core';
+import {BehaviorSubject} from 'rxjs';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class ModalService {
+
+ private modal = new BehaviorSubject<TemplateRef<any> | null>(null)
+
+ constructor() {}
+
+ showModal(template: TemplateRef<any>) {
+ console.log("got here", template)
+ this.modal.next(template)
+ }
+
+ hideModal() {
+ this.modal.next(null)
+ }
+
+ getModalBehaviorSubject() {
+ return this.modal;
+ }
+}
diff --git a/ufund-ui/src/app/services/users.service.ts b/ufund-ui/src/app/services/users.service.ts
index 080c394..35d080d 100644
--- a/ufund-ui/src/app/services/users.service.ts
+++ b/ufund-ui/src/app/services/users.service.ts
@@ -1,7 +1,7 @@
import { Injectable } from '@angular/core';
import {HttpClient, HttpHeaders} from '@angular/common/http';
import {BehaviorSubject, catchError, firstValueFrom, Observable, of} from 'rxjs';
-import {User} from '../models/User';
+import {User, userType} from '../models/User';
import { Need } from '../models/Need';
import { CupboardService } from './cupboard.service';
import {AuthService} from './auth.service';
@@ -34,7 +34,9 @@ export class UsersService {
private http: HttpClient,
private cupboardService: CupboardService,
private authService: AuthService
- ) {}
+ ) {
+ authService.getCurrentUserSubject().subscribe(() => this.refreshBasket())
+ }
async createUser(username:string, password:string) {
await firstValueFrom(this.http.post<User>(this.url, {username: username, password: password}, this.httpOptions()))
@@ -84,4 +86,16 @@ export class UsersService {
return this.basket;
}
+ isManager() {
+ return this.authService.getCurrentUser()?.type === userType.MANAGER
+ }
+
+ isHelper() {
+ return this.authService.getCurrentUser()?.type === userType.HELPER
+ }
+
+ inBasket(basket: Need[] | null, need: Need) {
+ return basket?.map(r => r.id).includes(need.id);
+ }
+
}
diff --git a/ufund-ui/src/styles.css b/ufund-ui/src/styles.css
index 75d6b36..527408a 100644
--- a/ufund-ui/src/styles.css
+++ b/ufund-ui/src/styles.css
@@ -19,7 +19,9 @@ body {
font-optical-sizing: auto;
}
-input {
+input, textarea {
+ resize: none;
+ font-family: Inter, sans-serif;
font-size: 14pt;
padding: 5px;
border-radius: 5px;
@@ -42,6 +44,7 @@ button, input[type=button], input[type=reset], input[type=submit], .button {
gap: 5px;
text-align: center;
justify-content: center;
+ align-items: center;
&:hover {
background-color: light-dark(#e1e1e1, #444444);