From 567127a27552850540c4a9860700b59ed27b5e3c Mon Sep 17 00:00:00 2001 From: MDBBee Date: Fri, 16 Jan 2026 16:16:15 +0200 Subject: [PATCH] Completed! --- src/app/app.component.css | 39 ++ src/app/app.component.html | 356 +----------------- src/app/app.component.ts | 14 +- .../components/balance/balance.component.css | 40 ++ .../components/balance/balance.component.html | 9 + .../balance/balance.component.spec.ts | 23 ++ .../components/balance/balance.component.ts | 33 ++ src/app/components/model/transaction.type.ts | 1 + .../components/saving/saving.component.css | 53 +++ .../components/saving/saving.component.html | 23 ++ .../saving/saving.component.spec.ts | 23 ++ src/app/components/saving/saving.component.ts | 33 ++ .../transaction/transaction.component.css | 47 +++ .../transaction/transaction.component.html | 22 ++ .../transaction/transaction.component.spec.ts | 23 ++ .../transaction/transaction.component.ts | 37 ++ .../savings-target.directive.spec.ts | 8 + .../directives/savings-target.directive.ts | 28 ++ src/app/services/transaction.service.spec.ts | 16 + src/app/services/transaction.service.ts | 41 ++ 20 files changed, 526 insertions(+), 343 deletions(-) create mode 100644 src/app/components/balance/balance.component.css create mode 100644 src/app/components/balance/balance.component.html create mode 100644 src/app/components/balance/balance.component.spec.ts create mode 100644 src/app/components/balance/balance.component.ts create mode 100644 src/app/components/model/transaction.type.ts create mode 100644 src/app/components/saving/saving.component.css create mode 100644 src/app/components/saving/saving.component.html create mode 100644 src/app/components/saving/saving.component.spec.ts create mode 100644 src/app/components/saving/saving.component.ts create mode 100644 src/app/components/transaction/transaction.component.css create mode 100644 src/app/components/transaction/transaction.component.html create mode 100644 src/app/components/transaction/transaction.component.spec.ts create mode 100644 src/app/components/transaction/transaction.component.ts create mode 100644 src/app/directives/savings-target.directive.spec.ts create mode 100644 src/app/directives/savings-target.directive.ts create mode 100644 src/app/services/transaction.service.spec.ts create mode 100644 src/app/services/transaction.service.ts diff --git a/src/app/app.component.css b/src/app/app.component.css index e69de29..66b9b24 100644 --- a/src/app/app.component.css +++ b/src/app/app.component.css @@ -0,0 +1,39 @@ +/* General app layout */ +.app-container { + max-width: 1000px; + margin: 0 auto; + padding: 20px; + font-family: Arial, sans-serif; +} + +/* Header */ +header { + text-align: center; + margin-bottom: 30px; +} + +header h1 { + font-size: 2rem; + color: #2d3748; +} + +/* Main content layout */ +main { + display: flex; + gap: 20px; +} + +/* Left panel (Transactions) */ +.left-panel { + flex: 1; + display: flex; + flex-direction: column; +} + +/* Right panel (Balance + Saving) */ +.right-panel { + flex: 1; + display: flex; + flex-direction: column; + gap: 20px; +} diff --git a/src/app/app.component.html b/src/app/app.component.html index 36093e1..d660a6e 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,336 +1,20 @@ - - - - - - - - - - - -
-
-
- -

Hello, {{ title }}

-

Congratulations! Your app is running. 🎉

-
- -
-
- @for (item of [ - { title: 'Explore the Docs', link: 'https://angular.dev' }, - { title: 'Learn with Tutorials', link: 'https://angular.dev/tutorials' }, - { title: 'CLI Docs', link: 'https://angular.dev/tools/cli' }, - { title: 'Angular Language Service', link: 'https://angular.dev/tools/language-service' }, - { title: 'Angular DevTools', link: 'https://angular.dev/tools/devtools' }, - ]; track item.title) { - - {{ item.title }} - - - - - } -
- -
-
-
- - - - - - - - - - - +
+
+

Budget App

+
+ +
+
+ + +
+ +
+ + + + + +
+
+
diff --git a/src/app/app.component.ts b/src/app/app.component.ts index ece0ed9..9e0d5ae 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,12 +1,12 @@ -import { Component } from '@angular/core'; -import { RouterOutlet } from '@angular/router'; +import { Component, computed, signal, WritableSignal } from '@angular/core'; +import { TransactionComponent } from './components/transaction/transaction.component'; +import { BalanceComponent } from './components/balance/balance.component'; +import { SavingComponent } from './components/saving/saving.component'; @Component({ selector: 'app-root', - imports: [RouterOutlet], + imports: [TransactionComponent, BalanceComponent, SavingComponent], templateUrl: './app.component.html', - styleUrl: './app.component.css' + styleUrl: './app.component.css', }) -export class AppComponent { - title = 'angular-budget-app'; -} +export class AppComponent {} diff --git a/src/app/components/balance/balance.component.css b/src/app/components/balance/balance.component.css new file mode 100644 index 0000000..21b6b71 --- /dev/null +++ b/src/app/components/balance/balance.component.css @@ -0,0 +1,40 @@ +.balance-card { + border: 2px solid #4a5568; + padding: 15px; + border-radius: 8px; + width: 300px; + margin-bottom: 15px; +} + +.balance-card h3 { + text-align: center; + margin-bottom: 10px; +} + +.balance-card p { + text-align: center; + font-size: 1.5rem; + margin-bottom: 10px; +} + +.balance-card input { + width: 100%; + padding: 5px; + margin-bottom: 10px; + border-radius: 4px; + border: 1px solid #4a5568; +} + +.balance-card button { + width: 100%; + padding: 7px; + background-color: #4a5568; + color: white; + border: none; + border-radius: 5px; + cursor: pointer; +} + +.balance-card button:hover { + background-color: #2d3748; +} diff --git a/src/app/components/balance/balance.component.html b/src/app/components/balance/balance.component.html new file mode 100644 index 0000000..87730fa --- /dev/null +++ b/src/app/components/balance/balance.component.html @@ -0,0 +1,9 @@ +
+

Current Balance

+

€{{ bal() }}

+ + + + + +
diff --git a/src/app/components/balance/balance.component.spec.ts b/src/app/components/balance/balance.component.spec.ts new file mode 100644 index 0000000..83025a8 --- /dev/null +++ b/src/app/components/balance/balance.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { BalanceComponent } from './balance.component'; + +describe('BalanceComponent', () => { + let component: BalanceComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [BalanceComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(BalanceComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/balance/balance.component.ts b/src/app/components/balance/balance.component.ts new file mode 100644 index 0000000..a1aadef --- /dev/null +++ b/src/app/components/balance/balance.component.ts @@ -0,0 +1,33 @@ +import { Component, computed, inject } from '@angular/core'; +import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { TransactionService } from '../../services/transaction.service'; + +@Component({ + selector: 'app-balance', + imports: [ReactiveFormsModule], + templateUrl: './balance.component.html', + styleUrl: './balance.component.css', +}) +export class BalanceComponent { + transaction = inject(TransactionService); + bal = computed(() => this.transaction.balance()); + + savings = new FormControl(0); + + transferToSavings() { + console.log(this.savings.value); + if ((this.savings.value as number) <= 0) { + alert(`Invalid transfer amount! ${this.savings.value as number}`); + this.savings.setValue(0); + return; + } + if (this.bal() <= 0 || this.bal() < (this.savings.value as number)) { + alert(`Insufficient funds! Current Bal: €${this.bal()}`); + this.savings.setValue(0); + return; + } + + this.transaction.transferToSavings(this.savings.value as number); + this.savings.setValue(0); + } +} diff --git a/src/app/components/model/transaction.type.ts b/src/app/components/model/transaction.type.ts new file mode 100644 index 0000000..ea62b7f --- /dev/null +++ b/src/app/components/model/transaction.type.ts @@ -0,0 +1 @@ +export type TransactionType = 'income' | 'expense'; diff --git a/src/app/components/saving/saving.component.css b/src/app/components/saving/saving.component.css new file mode 100644 index 0000000..29263cc --- /dev/null +++ b/src/app/components/saving/saving.component.css @@ -0,0 +1,53 @@ +.saving-card { + border: 2px solid #2f855a; + padding: 15px; + border-radius: 8px; + width: 300px; + margin-bottom: 15px; +} + +.saving-card h3 { + text-align: center; + margin-bottom: 10px; +} + +.saving-card p { + margin: 5px 0; +} + +.saving-card input { + width: 100%; + padding: 5px; + margin-bottom: 10px; + border-radius: 4px; + border: 1px solid #2f855a; +} + +.saving-card button { + width: 100%; + padding: 7px; + background-color: #2f855a; + color: white; + border: none; + border-radius: 5px; + cursor: pointer; + margin-bottom: 10px; +} + +.saving-card button:hover { + background-color: #276749; +} + +/* Progress Bar */ +.progress-bar { + background-color: #c6f6d5; + border-radius: 5px; + height: 20px; + width: 100%; +} + +.progress { + background-color: #2f855a; + height: 100%; + border-radius: 5px; +} diff --git a/src/app/components/saving/saving.component.html b/src/app/components/saving/saving.component.html new file mode 100644 index 0000000..5754e29 --- /dev/null +++ b/src/app/components/saving/saving.component.html @@ -0,0 +1,23 @@ +
+

Saving Goal

+

Target: €{{ savingTarget() }}

+

Saved: €{{ savings() }}

+ + + + +
+
+
+

+ Achieved {{ ((savings() / savingTarget()) * 100).toFixed(0) }}% of Savings + Goal:😎 +

+
diff --git a/src/app/components/saving/saving.component.spec.ts b/src/app/components/saving/saving.component.spec.ts new file mode 100644 index 0000000..32c0af7 --- /dev/null +++ b/src/app/components/saving/saving.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SavingComponent } from './saving.component'; + +describe('SavingComponent', () => { + let component: SavingComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [SavingComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(SavingComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/saving/saving.component.ts b/src/app/components/saving/saving.component.ts new file mode 100644 index 0000000..b384e67 --- /dev/null +++ b/src/app/components/saving/saving.component.ts @@ -0,0 +1,33 @@ +import { + Component, + computed, + inject, + input, + output, + signal, +} from '@angular/core'; +import { TransactionService } from '../../services/transaction.service'; +import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { SavingsTargetDirective } from '../../directives/savings-target.directive'; + +@Component({ + selector: 'app-saving', + imports: [ReactiveFormsModule, SavingsTargetDirective], + templateUrl: './saving.component.html', + styleUrl: './saving.component.css', +}) +export class SavingComponent { + transaction = inject(TransactionService); + savings = computed(() => this.transaction.saved()); + savingTarget = computed(() => this.transaction.savingsTarget()); + + targetControl = new FormControl(1); + + updateSavingsTraget(value: number | null) { + if (!value) { + alert('Invalid input for savings target!'); + return; + } + this.transaction.savingsTarget.set(value); + } +} diff --git a/src/app/components/transaction/transaction.component.css b/src/app/components/transaction/transaction.component.css new file mode 100644 index 0000000..9270182 --- /dev/null +++ b/src/app/components/transaction/transaction.component.css @@ -0,0 +1,47 @@ +.transaction-card { + border: 2px solid #2c7a7b; + padding: 15px; + border-radius: 8px; + width: 250px; + margin-bottom: 15px; +} + +.select { + display: flex; + flex-direction: column; + gap: 4px; + margin-bottom: 1rem; + width: 100%; +} + +.select select { + padding: 5px; + width: 100%; + border-radius: 5px; +} +.transaction-card h3 { + text-align: center; + margin-bottom: 10px; +} + +.transaction-card input { + width: 100%; + padding: 5px; + margin-bottom: 10px; + border-radius: 4px; + border: 1px solid #2c7a7b; +} + +.transaction-card button { + width: 100%; + padding: 7px; + background-color: #2c7a7b; + color: white; + border: none; + border-radius: 5px; + cursor: pointer; +} + +.transaction-card button:hover { + background-color: #285e61; +} diff --git a/src/app/components/transaction/transaction.component.html b/src/app/components/transaction/transaction.component.html new file mode 100644 index 0000000..f572dd0 --- /dev/null +++ b/src/app/components/transaction/transaction.component.html @@ -0,0 +1,22 @@ +
+
+

Transaction

+
+ + +
+
+ + +
+ + +
+
diff --git a/src/app/components/transaction/transaction.component.spec.ts b/src/app/components/transaction/transaction.component.spec.ts new file mode 100644 index 0000000..c4968f4 --- /dev/null +++ b/src/app/components/transaction/transaction.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TransactionComponent } from './transaction.component'; + +describe('TransactionComponent', () => { + let component: TransactionComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [TransactionComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(TransactionComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/transaction/transaction.component.ts b/src/app/components/transaction/transaction.component.ts new file mode 100644 index 0000000..2620fb5 --- /dev/null +++ b/src/app/components/transaction/transaction.component.ts @@ -0,0 +1,37 @@ +import { Component, inject } from '@angular/core'; +import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; +import { TransactionService } from '../../services/transaction.service'; +import { TransactionType } from '../model/transaction.type'; + +@Component({ + selector: 'app-transaction', + imports: [ReactiveFormsModule], + templateUrl: './transaction.component.html', + styleUrl: './transaction.component.css', +}) +export class TransactionComponent { + transaction = inject(TransactionService); + + transForm = new FormGroup({ + type: new FormControl('income'), + amount: new FormControl(0), + }); + + onSubmit() { + if ((this.transForm.value.amount as number) <= 0) { + alert( + `Invalid selection! Amount must be positive, you inputed: ${this.transForm.value.amount}` + ); + this.transForm.value.amount = 0; + return; + } + + if (!this.transForm.value.type) return; + + this.transaction.executeTransaction({ + type: this.transForm.value.type as TransactionType, + amount: this.transForm.value.amount as number, + }); + this.transForm.value.amount = 0; + } +} diff --git a/src/app/directives/savings-target.directive.spec.ts b/src/app/directives/savings-target.directive.spec.ts new file mode 100644 index 0000000..52ab046 --- /dev/null +++ b/src/app/directives/savings-target.directive.spec.ts @@ -0,0 +1,8 @@ +import { SavingsTargetDirective } from './savings-target.directive'; + +describe('SavingsTargetDirective', () => { + it('should create an instance', () => { + const directive = new SavingsTargetDirective(); + expect(directive).toBeTruthy(); + }); +}); diff --git a/src/app/directives/savings-target.directive.ts b/src/app/directives/savings-target.directive.ts new file mode 100644 index 0000000..edb3a86 --- /dev/null +++ b/src/app/directives/savings-target.directive.ts @@ -0,0 +1,28 @@ +import { + computed, + Directive, + effect, + ElementRef, + inject, + input, +} from '@angular/core'; +import { TransactionService } from '../services/transaction.service'; + +@Directive({ + selector: '[appSavingsTarget]', +}) +export class SavingsTargetDirective { + el = inject(ElementRef); + + savings = input(0); + savingsTarget = input(0); + + styleEffect = effect(() => { + const progress = Math.round((this.savings() / this.savingsTarget()) * 100); + if (progress / 100 > 1) { + this.el.nativeElement.style.width = 100 + '%'; + } else { + this.el.nativeElement.style.width = progress + '%'; + } + }); +} diff --git a/src/app/services/transaction.service.spec.ts b/src/app/services/transaction.service.spec.ts new file mode 100644 index 0000000..82b19f8 --- /dev/null +++ b/src/app/services/transaction.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { TransactionService } from './transaction.service'; + +describe('TransactionService', () => { + let service: TransactionService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(TransactionService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/services/transaction.service.ts b/src/app/services/transaction.service.ts new file mode 100644 index 0000000..43f15a3 --- /dev/null +++ b/src/app/services/transaction.service.ts @@ -0,0 +1,41 @@ +import { Injectable, signal } from '@angular/core'; +import { TransactionType } from '../components/model/transaction.type'; + +@Injectable({ + providedIn: 'root', +}) +export class TransactionService { + balance = signal(0); + savingsTarget = signal(1); + saved = signal(0); + + executeTransaction({ + amount, + type, + }: { + amount: number | null; + type: TransactionType | null; + }) { + if (type === 'income' && amount) { + this.balance.update((prevBal) => prevBal + amount); + return; + } + + if (amount && this.balance() < amount) { + alert( + `Insufficient Balance! Current Balance: ${this.balance()}, but you trying to withdraw: ${amount}` + ); + return; + } else if (amount) { + this.balance.update((prevBal) => prevBal - amount); + return; + } + } + + transferToSavings(amount: number) { + if (!amount) return; + this.saved.update((saved) => saved + amount); + this.balance.update((prevBal) => prevBal - amount); + return; + } +}