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