10
									
								
								frontend/allowance-planner-v2/src/app/models/allowance.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								frontend/allowance-planner-v2/src/app/models/allowance.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
			
		||||
export interface Allowance {
 | 
			
		||||
    id: number;
 | 
			
		||||
    name: string;
 | 
			
		||||
    target: number;
 | 
			
		||||
    // Current allowance value
 | 
			
		||||
    progress: number;
 | 
			
		||||
    // Can be any positive number (backend checks for number relative to each other)
 | 
			
		||||
    weight: number;
 | 
			
		||||
    colour: string;
 | 
			
		||||
}
 | 
			
		||||
@@ -5,14 +5,22 @@ import { FormsModule } from '@angular/forms';
 | 
			
		||||
import { AllowancePage } from './allowance.page';
 | 
			
		||||
 | 
			
		||||
import { AllowancePageRoutingModule } from './allowance-routing.module';
 | 
			
		||||
import { AllowanceService } from 'src/app/services/allowance.service';
 | 
			
		||||
import { provideHttpClient } from '@angular/common/http';
 | 
			
		||||
import { MatIconModule } from '@angular/material/icon';
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
  imports: [
 | 
			
		||||
    IonicModule,
 | 
			
		||||
    CommonModule,
 | 
			
		||||
    FormsModule,
 | 
			
		||||
    AllowancePageRoutingModule
 | 
			
		||||
    AllowancePageRoutingModule,
 | 
			
		||||
    MatIconModule
 | 
			
		||||
  ],
 | 
			
		||||
  declarations: [AllowancePage]
 | 
			
		||||
  declarations: [AllowancePage],
 | 
			
		||||
  providers: [
 | 
			
		||||
    provideHttpClient(),
 | 
			
		||||
    AllowanceService
 | 
			
		||||
  ]
 | 
			
		||||
})
 | 
			
		||||
export class AllowancePageModule {}
 | 
			
		||||
 
 | 
			
		||||
@@ -7,4 +7,63 @@
 | 
			
		||||
</ion-header>
 | 
			
		||||
 | 
			
		||||
<ion-content>
 | 
			
		||||
  <div class="content" *ngIf="allowance$ | async as allowance">
 | 
			
		||||
    <div class="bar">
 | 
			
		||||
      <div class="distribution">Allowance distribution</div>
 | 
			
		||||
      <div class="allowance-bar">
 | 
			
		||||
        <span
 | 
			
		||||
          *ngFor="let goal of allowance"
 | 
			
		||||
          class="partition"
 | 
			
		||||
          [style.--partition-color]="goal.colour"
 | 
			
		||||
          [style.width.%]="getPartitionSize(goal, allowance)"
 | 
			
		||||
        ></span>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="legend">
 | 
			
		||||
        <div class="legend-item" [style.--legend-color]="goal.colour" *ngFor="let goal of allowance">
 | 
			
		||||
          <div class="circle"></div>
 | 
			
		||||
          <div class="title">{{ goal.name }}</div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div
 | 
			
		||||
      class="goal"
 | 
			
		||||
      [style.--used-color]="goal.colour"
 | 
			
		||||
      [ngClass]="{'other-goals': goal.id !== 0}"
 | 
			
		||||
      *ngFor="let goal of allowance"
 | 
			
		||||
    >
 | 
			
		||||
      <div class="main" *ngIf="goal.id === 0; else other_goal">
 | 
			
		||||
        <div class="title">
 | 
			
		||||
          <div class="name">Main Allowance</div>
 | 
			
		||||
          <div class="icon">
 | 
			
		||||
            <mat-icon>settings</mat-icon>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="progress">{{ goal.progress }} SP</div>
 | 
			
		||||
        <div class="buttons">
 | 
			
		||||
          <button class="add-button">Add</button>
 | 
			
		||||
          <!-- <button class="move-button">Move</button> -->
 | 
			
		||||
          <button class="spend-button">Spend</button>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      <ng-template #other_goal>
 | 
			
		||||
        <div class="color-wrapper">
 | 
			
		||||
          <div>
 | 
			
		||||
            <div class="title">
 | 
			
		||||
              <div class="name">{{ goal.name }}</div>
 | 
			
		||||
              <div class="icon">
 | 
			
		||||
                <mat-icon>settings</mat-icon>
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="progress">{{ goal.progress }} / {{ goal.target }} SP</div>
 | 
			
		||||
            <div class="buttons">
 | 
			
		||||
              <button class="add-button">Add</button>
 | 
			
		||||
              <!-- <button class="move-button">Move</button> -->
 | 
			
		||||
              <button class="spend-button" [disabled]="!canFinishGoal(goal)">Finish goal</button>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
          <div class="color" [style.--background]="hexToRgb(goal.colour)" [style.width.%]="getPercentage(goal)"></div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </ng-template>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</ion-content>
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,130 @@
 | 
			
		||||
.goal {
 | 
			
		||||
    border: 1px solid var(--used-color);
 | 
			
		||||
    border-radius: 10px;
 | 
			
		||||
    padding: 10px;
 | 
			
		||||
    margin-bottom: 20px;
 | 
			
		||||
    margin-left: 10px;
 | 
			
		||||
    margin-right: 10px;
 | 
			
		||||
    color: var(--used-color);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.name {
 | 
			
		||||
    font-size: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.progress {
 | 
			
		||||
    color: var(--font-color);
 | 
			
		||||
    margin-left: 15px;
 | 
			
		||||
    margin-top: 8px;
 | 
			
		||||
    margin-bottom: 15px;
 | 
			
		||||
    font-size: 16px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.bar {
 | 
			
		||||
    margin-top: 20px;
 | 
			
		||||
    margin-bottom: 20px;
 | 
			
		||||
    margin-left: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.distribution {
 | 
			
		||||
    color: var(--ion-color-primary);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.allowance-bar {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    width: 95%;
 | 
			
		||||
    height: 15px !important;
 | 
			
		||||
    border-radius: 15px;
 | 
			
		||||
    background-color: var(--font-color);
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.partition {
 | 
			
		||||
    --partition-color: white;
 | 
			
		||||
    background-color: var(--partition-color);
 | 
			
		||||
    width: 25%;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    //border-radius: 15px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.buttons,
 | 
			
		||||
.title {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    gap: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
button {
 | 
			
		||||
    height: 30px;
 | 
			
		||||
    padding-inline: 30px;
 | 
			
		||||
    border-radius: 10px;
 | 
			
		||||
    color: white;
 | 
			
		||||
    font-size: 16px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
button:disabled,
 | 
			
		||||
button[disabled]{
 | 
			
		||||
    opacity: 0.5;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.add-button {
 | 
			
		||||
    background-color: var(--confirm-button-color);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.move-button {
 | 
			
		||||
    background-color: var(--ion-color-primary);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.spend-button {
 | 
			
		||||
    background-color: var(--negative-amount-color);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.icon {
 | 
			
		||||
    margin-left: auto;
 | 
			
		||||
    color: var(--font-color);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.color-wrapper {
 | 
			
		||||
    padding: 10px;
 | 
			
		||||
    border-radius: 9px;
 | 
			
		||||
    position: relative;
 | 
			
		||||
    z-index: 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.color {
 | 
			
		||||
    --background: rgba(0, 0, 0, 0.3);
 | 
			
		||||
    background-color: var(--background);
 | 
			
		||||
    border-radius: 9px;
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    top: 0;
 | 
			
		||||
    bottom: 0;
 | 
			
		||||
    left: 0;
 | 
			
		||||
    z-index: -1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.other-goals {
 | 
			
		||||
    padding: unset;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.legend {
 | 
			
		||||
    width: 95%;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    font-size: 13px;
 | 
			
		||||
    gap: 8px;
 | 
			
		||||
    margin-top: 5px;
 | 
			
		||||
    flex-wrap: wrap;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.legend-item {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    --legend-color: white;
 | 
			
		||||
    color: var(--legend-color);
 | 
			
		||||
    align-items: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.circle {
 | 
			
		||||
    width: 12px;
 | 
			
		||||
    height: 12px;
 | 
			
		||||
    background-color: var(--legend-color);
 | 
			
		||||
    border-radius: 20px;
 | 
			
		||||
    margin-right: 2px;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,5 +1,10 @@
 | 
			
		||||
import { Component } from '@angular/core';
 | 
			
		||||
import { UserService } from 'src/app/services/user.service';
 | 
			
		||||
import { ActivatedRoute } from '@angular/router';
 | 
			
		||||
import { BehaviorSubject } from 'rxjs';
 | 
			
		||||
import { Allowance } from 'src/app/models/allowance';
 | 
			
		||||
import { AllowanceService } from 'src/app/services/allowance.service';
 | 
			
		||||
import hexRgb from 'hex-rgb';
 | 
			
		||||
import { ViewWillEnter } from '@ionic/angular';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-allowance',
 | 
			
		||||
@@ -7,8 +12,64 @@ import { UserService } from 'src/app/services/user.service';
 | 
			
		||||
  styleUrls: ['allowance.page.scss'],
 | 
			
		||||
  standalone: false,
 | 
			
		||||
})
 | 
			
		||||
export class AllowancePage {
 | 
			
		||||
export class AllowancePage implements ViewWillEnter {
 | 
			
		||||
  private id: number;
 | 
			
		||||
  // Move to add/edit page later
 | 
			
		||||
  private possibleColors: Array<string> = [
 | 
			
		||||
    '#6199D9',
 | 
			
		||||
    '#D98B61',
 | 
			
		||||
    '#DBC307',
 | 
			
		||||
    '#13DEB5',
 | 
			
		||||
    '#7DCB7D',
 | 
			
		||||
    '#CF1DBD',
 | 
			
		||||
    '#F53311',
 | 
			
		||||
    '#2F00FF',
 | 
			
		||||
    '#098B0D',
 | 
			
		||||
    '#1BC2E8'
 | 
			
		||||
  ];
 | 
			
		||||
  public allowance$: BehaviorSubject<Array<Allowance>> = new BehaviorSubject<Array<Allowance>>([]);
 | 
			
		||||
 | 
			
		||||
  constructor(private userService: UserService) {}
 | 
			
		||||
  constructor(
 | 
			
		||||
    private route: ActivatedRoute,
 | 
			
		||||
    private allowanceService: AllowanceService
 | 
			
		||||
  ) {
 | 
			
		||||
    this.id = this.route.snapshot.params['id'];
 | 
			
		||||
    this.getAllowance();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ionViewWillEnter(): void {
 | 
			
		||||
    this.getAllowance();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getAllowance() {
 | 
			
		||||
    setTimeout(() => {
 | 
			
		||||
      this.allowanceService.getAllowanceList(this.id).subscribe(allowance => {
 | 
			
		||||
        allowance[0].colour = '#9C4BE4';
 | 
			
		||||
        allowance[0].name = 'Main Allowance';
 | 
			
		||||
        console.log('Allowance list: ', allowance);
 | 
			
		||||
        this.allowance$.next(allowance);
 | 
			
		||||
      })
 | 
			
		||||
    }, 10);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  canFinishGoal(allowance: Allowance): boolean {
 | 
			
		||||
    return allowance.progress >= allowance.target;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  hexToRgb(color: string) {
 | 
			
		||||
    return hexRgb(color, { alpha: 0.3, format: 'css' })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getPercentage(allowance: Allowance): number {
 | 
			
		||||
    return allowance.progress / allowance.target * 100;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Returns number in percent
 | 
			
		||||
  getPartitionSize(goal: Allowance, allowanceList: Array<Allowance>): number {
 | 
			
		||||
    let allowanceTotal = 0;
 | 
			
		||||
    for (let allowance of allowanceList) {
 | 
			
		||||
      allowanceTotal += allowance.progress;
 | 
			
		||||
    }
 | 
			
		||||
    return goal.progress / allowanceTotal * 100;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,7 @@
 | 
			
		||||
    <input id="name" type="text" formControlName="name"/>
 | 
			
		||||
 | 
			
		||||
    <label>Reward</label>
 | 
			
		||||
    <input id="name" type="number" formControlName="reward"/>
 | 
			
		||||
    <input id="name" type="number" placeholder="0.00" name="price" min="0" value="0" step="0.01" formControlName="reward"/>
 | 
			
		||||
 | 
			
		||||
    <label>Assigned</label>
 | 
			
		||||
    <select formControlName="assigned">
 | 
			
		||||
 
 | 
			
		||||
@@ -30,7 +30,7 @@ export class EditTaskPage implements OnInit {
 | 
			
		||||
 | 
			
		||||
    this.form = this.formBuilder.group({
 | 
			
		||||
      name: ['', Validators.required],
 | 
			
		||||
      reward: ['', [Validators.required, Validators.pattern("^[0-9]*$")]],
 | 
			
		||||
      reward: ['', Validators.required],
 | 
			
		||||
      assigned: [0, Validators.required]
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,17 @@
 | 
			
		||||
import { HttpClient } from '@angular/common/http';
 | 
			
		||||
import { Injectable } from '@angular/core';
 | 
			
		||||
import { Observable } from 'rxjs';
 | 
			
		||||
import { Allowance } from '../models/allowance';
 | 
			
		||||
 | 
			
		||||
@Injectable({
 | 
			
		||||
    providedIn: 'root'
 | 
			
		||||
})
 | 
			
		||||
export class AllowanceService {
 | 
			
		||||
    private url = 'http://localhost:8080/api';
 | 
			
		||||
 | 
			
		||||
    constructor(private http: HttpClient) {}
 | 
			
		||||
 | 
			
		||||
    getAllowanceList(userId: number): Observable<Array<Allowance>> {
 | 
			
		||||
        return this.http.get<Allowance[]>(`${this.url}/user/${userId}/allowance`);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user