Skip to content

Commit

Permalink
Implement recent activity and currency pipe.
Browse files Browse the repository at this point in the history
  • Loading branch information
cynavi committed Mar 21, 2024
1 parent 383a24c commit 96f442a
Show file tree
Hide file tree
Showing 16 changed files with 161 additions and 115 deletions.
2 changes: 2 additions & 0 deletions src/app/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { provideRouter, Route } from '@angular/router';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
import { authGuard } from './shared/util/guards';
import { CurrencyPipe } from '@angular/common';

const routes: Route[] = [
{
Expand All @@ -18,6 +19,7 @@ export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
importProvidersFrom(BrowserAnimationsModule),
CurrencyPipe,
provideAnimationsAsync()
]
};
18 changes: 15 additions & 3 deletions src/app/buxx/data-access/recent-activity.store.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { computed, Injectable, Signal, signal, WritableSignal } from '@angular/core';
import { DeleteTransaction, RecentTransaction, Transaction } from '../../shared/model/buxx.model';
import { RecentTransaction } from '../../shared/model/buxx.model';
import { Subject } from 'rxjs';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';

export type RecentActivityState = {
data: RecentTransaction[];
Expand All @@ -9,8 +10,19 @@ export type RecentActivityState = {
@Injectable()
export class RecentActivityStore {

restore$: Subject<Transaction> = new Subject<Transaction>();
delete$: Subject<DeleteTransaction> = new Subject<DeleteTransaction>();
private state: WritableSignal<RecentActivityState> = signal({data: []});
readonly data: Signal<RecentTransaction[]> = computed(() => this.state().data);

reset$: Subject<void> = new Subject<void>();
add$: Subject<RecentTransaction> = new Subject<RecentTransaction>();

constructor() {
this.reset$.pipe(takeUntilDestroyed())
.subscribe(() => this.state.set({data: []}));

this.add$.pipe(takeUntilDestroyed())
.subscribe((transaction: RecentTransaction) => this.state.update(state => ({
data: [...state.data, transaction]
})));
}
}
77 changes: 52 additions & 25 deletions src/app/buxx/data-access/transaction.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ import {
TRANSACTIONS,
UpdateTransaction
} from '../../shared/model/buxx.model';
import { from, Subject, switchMap } from 'rxjs';
import { from, map, Subject, switchMap } from 'rxjs';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { supabase } from '../../../supabase/supabase';
import { MatSnackBar } from '@angular/material/snack-bar';
import { AuthStore } from '../../shared/data-access/auth/auth.store';
import { PostgrestSingleResponse } from '@supabase/supabase-js';
import { buildFilter } from '../../shared/util/filter-builder';
import { RecentActivityStore } from './recent-activity.store';

type BuxxState = {
loaded: boolean;
Expand All @@ -38,6 +39,7 @@ export class TransactionStore {

private readonly snackBar = inject(MatSnackBar);
private readonly authStore = inject(AuthStore);
private readonly recentActivityStore = inject(RecentActivityStore);
private state: WritableSignal<BuxxState> = signal(initialState);

loaded: Signal<boolean> = computed(() => this.state().loaded);
Expand Down Expand Up @@ -78,15 +80,17 @@ export class TransactionStore {
private handleSave(): void {
this.save$.pipe(
takeUntilDestroyed(),
switchMap((transaction: SaveTransaction) => from(supabase.from(TRANSACTIONS).insert(transaction)))
).subscribe({
next: () => {
this.snackBar.open('Transaction has been saved.', undefined, { duration: 3000 });
},
error: err => {
this.state.update(state => ({ ...state, error: err }));
switchMap((transaction: SaveTransaction) => from(supabase.from(TRANSACTIONS).insert(transaction).select()))
).subscribe(response => {
if (response.error) {
this.openSnackBar('Unable to save transaction.');
this.state.update(state => ({ ...state, error: response.error.message }));
} else {
this.openSnackBar('Transaction has been saved.');
this.recentActivityStore.add$.next({ ...response.data[0], action: 'SAVED' });
}
}
});
);
}

private handleUpdate() {
Expand All @@ -96,28 +100,51 @@ export class TransactionStore {
.from(TRANSACTIONS)
.update(transaction)
.eq('id', transaction.id)
))
).subscribe({
next: () => {
this.snackBar.open('Transaction has been updated.', undefined, { duration: 3000 });
},
error: err => {
this.state.update(state => ({ ...state, error: err }));
).pipe(map(response => ({ response, transaction: transaction }))))
).subscribe(({ response, transaction }) => {
if (response.error) {
this.openSnackBar('Unable to update transaction.');
this.state.update(state => ({ ...state, error: response.error.message }));
} else {
this.snackBar.open('Transaction has been updated.');
this.state.update(state => ({
...state,
data: {
...state.data,
transactions: state.data.transactions.map(t => t.id === transaction.id ? transaction : t)
}
}));
}
}
});
);
}

private handleDelete() {
this.delete$.pipe(
takeUntilDestroyed(),
switchMap((id: DeleteTransaction) => from(supabase.from(TRANSACTIONS).delete().eq('id', id)))
).subscribe({
next: () => {
this.snackBar.open('Transaction has been deleted.', undefined, { duration: 3000 });
},
error: err => {
this.state.update(state => ({ ...state, error: err }));
switchMap((id: DeleteTransaction) => from(supabase.from(TRANSACTIONS).delete().eq('id', id))
.pipe(map(response => ({ response, id })))
)).subscribe(({ response, id }) => {
if (response.error) {
this.openSnackBar('Unable to delete transaction.');
this.state.update(state => ({ ...state, error: response.error.message }));
} else {
this.openSnackBar('Transaction has been deleted.');
const transaction: Transaction = this.data().transactions.find(t => t.id === id)!;
this.recentActivityStore.add$.next({ ...transaction, action: 'DELETED' });
this.state.update(state => ({
...state,
data: {
...state.data,
transactions: state.data.transactions.filter(t => t.id != id)
}
}));
}
}
});
);
}

private openSnackBar(message: string) {
this.snackBar.open(message, undefined, { duration: 3000 });
}
}
2 changes: 1 addition & 1 deletion src/app/buxx/feature/buxx.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<div class="flex flex-col items-center justify-center">
<as-summary [summary]="summaryStore.data()"></as-summary>
<div class="items-center justify-center w-3/4">
<as-recent-transaction></as-recent-transaction>
<as-recent-activity [transactions]="recentActivityStore.data()"></as-recent-activity>
</div>
<mat-accordion class="flex flex-col w-3/4 sm:min-w-11/12 sm:mx-14">
<mat-expansion-panel [expanded]="false" class="w-full sm:mx-14">
Expand Down
8 changes: 5 additions & 3 deletions src/app/buxx/feature/buxx.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ import { queryInitialState, QueryStore } from '../data-access/query.store';
import { environment } from '../../../environments/environment';
import { PositiveNumberOnlyDirective } from '../../shared/util/positive-number.directive';
import { MaskDateDirective } from '../../shared/util/date-mask.directive';
import { RecentTransactionComponent } from '../ui/recent-transaction/recent-transaction.component';
import { RecentActivityComponent } from '../ui/recent-activity/recent-activity.component';
import { RecentActivityStore } from '../data-access/recent-activity.store';

export const filterValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => {
const date = control.get('date');
Expand Down Expand Up @@ -81,17 +82,18 @@ export const filterValidator: ValidatorFn = (control: AbstractControl): Validati
SummaryComponent,
PositiveNumberOnlyDirective,
MaskDateDirective,
RecentTransactionComponent
RecentActivityComponent
],
templateUrl: './buxx.component.html',
styleUrl: './buxx.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [TransactionStore, SummaryStore, SummaryService, QueryStore]
providers: [TransactionStore, SummaryStore, SummaryService, QueryStore, RecentActivityStore]
})
export class BuxxComponent implements OnInit, OnDestroy {

@ViewChild(MatPaginator) paginator!: MatPaginator;
readonly summaryStore = inject(SummaryStore);
readonly recentActivityStore = inject(RecentActivityStore);
private readonly fb = inject(FormBuilder);
private readonly dialog = inject(MatDialog);
private readonly transactionStore = inject(TransactionStore);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@if (transactions.length) {
@if (transactions().length) {
<mat-accordion class="flex flex-col w-full mb-4 sm:min-w-11/12 sm:mx-14">
<mat-expansion-panel [hideToggle]="true">
<mat-expansion-panel-header>
Expand All @@ -9,7 +9,7 @@
</mat-expansion-panel-header>
</mat-expansion-panel>

@for (transaction of transactions; track transaction.id) {
@for (transaction of transactions(); track transaction.id) {
<mat-expansion-panel class="w-full">
<mat-expansion-panel-header>
<mat-panel-title>
Expand All @@ -32,13 +32,6 @@
<mat-panel-description>Rs. {{ transaction.amount }}</mat-panel-description>
<mat-panel-description>{{ transaction.date | date: 'mediumDate' }}</mat-panel-description>
</mat-expansion-panel-header>
<div class="flex flex-row float-right sm:flex-col sm:float-none">
@if (transaction.action === 'DELETED') {
<button mat-raised-button color="accent" class="sm:mb-2" (click)="restore(transaction)">Restore</button>
} @else if (transaction.action === 'SAVED') {
<button mat-raised-button color="warn" class="sm:mb-2" (click)="delete(transaction)">Delete</button>
}
</div>
<p>{{ transaction.details }}</p>
</mat-expansion-panel>
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RecentTransactionComponent } from './recent-transaction.component';
import { RecentActivityComponent } from './recent-activity.component';

describe('RecentTransactionComponent', () => {
let component: RecentTransactionComponent;
let fixture: ComponentFixture<RecentTransactionComponent>;
let component: RecentActivityComponent;
let fixture: ComponentFixture<RecentActivityComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [RecentTransactionComponent],
imports: [RecentActivityComponent],
}).compileComponents();

fixture = TestBed.createComponent(RecentTransactionComponent);
fixture = TestBed.createComponent(RecentActivityComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
Expand Down
49 changes: 49 additions & 0 deletions src/app/buxx/ui/recent-activity/recent-activity.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Component, input } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatExpansionModule } from '@angular/material/expansion';
import { MatIconModule } from '@angular/material/icon';
import { RecentTransaction } from '../../../shared/model/buxx.model';
import { MatChipsModule } from '@angular/material/chips';
import { DatePipe } from '@angular/common';
import { MatCardModule } from '@angular/material/card';

@Component({
selector: 'as-recent-activity',
standalone: true,
imports: [
MatButtonModule,
MatExpansionModule,
MatIconModule,
MatChipsModule,
DatePipe,
MatCardModule
],
templateUrl: './recent-activity.component.html',
styleUrl: './recent-activity.component.scss',
})
export class RecentActivityComponent {

transactions = input.required<RecentTransaction[]>();
// transactions: RecentTransaction[] = [
// {
// action: 'DELETED',
// date: (new Date()).toString(),
// id: 'sds',
// name: 'Test Name',
// isExpense: true,
// details: 'sdfd',
// amount: 222,
// userId: 'sdfsdf'
// },
// {
// action: 'SAVED',
// date: (new Date()).toString(),
// id: 'sds',
// name: 'Test Name Again',
// isExpense: true,
// details: 'sdfd',
// amount: 222,
// userId: 'sdfsdf'
// }
// ];
}
56 changes: 0 additions & 56 deletions src/app/buxx/ui/recent-transaction/recent-transaction.component.ts

This file was deleted.

6 changes: 3 additions & 3 deletions src/app/buxx/ui/summary/summary.component.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<div class="flex flex-row sm:flex-col sm:min-w-11/12">
<mat-card class="w-64 h-40 mb-2 mr-2 items-center justify-center sm:min-w-full sm:mr-0">
<mat-card-header>
<mat-card-title>Rs. {{summary().balance}}</mat-card-title>
<mat-card-title>{{summary().balance | nprCurrency }}</mat-card-title>
<mat-card-subtitle>
<mat-icon class="positive scale-150 mt-2 mr-2">arrow_upward_alt</mat-icon>balance
</mat-card-subtitle>
Expand All @@ -10,7 +10,7 @@

<mat-card class="w-64 h-40 mb-2 mr-2 items-center justify-center sm:min-w-full sm:mr-0">
<mat-card-header>
<mat-card-title>Rs. {{summary().income}}</mat-card-title>
<mat-card-title>{{summary().income | nprCurrency}}</mat-card-title>
<mat-card-subtitle>
<mat-icon class="positive scale-150 mt-2 mr-2">arrow_upward_alt</mat-icon>income
</mat-card-subtitle>
Expand All @@ -19,7 +19,7 @@

<mat-card class="w-64 h-40 mb-2 mr-2 items-center justify-center sm:min-w-full sm:mr-0">
<mat-card-header>
<mat-card-title>Rs. {{summary().expense}}</mat-card-title>
<mat-card-title>{{summary().expense | nprCurrency}}</mat-card-title>
<mat-card-subtitle>
<mat-icon class="negative scale-150 mt-2 mr-2">arrow_downward_alt</mat-icon>expense
</mat-card-subtitle>
Expand Down
Loading

0 comments on commit 96f442a

Please sign in to comment.