Skip to content

Commit

Permalink
Merge branch 'feature/ra' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
cynavi authored Mar 21, 2024
2 parents da1bd51 + 96f442a commit 3a79d9e
Show file tree
Hide file tree
Showing 16 changed files with 235 additions and 43 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()
]
};
28 changes: 28 additions & 0 deletions src/app/buxx/data-access/recent-activity.store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { computed, Injectable, Signal, signal, WritableSignal } from '@angular/core';
import { RecentTransaction } from '../../shared/model/buxx.model';
import { Subject } from 'rxjs';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';

export type RecentActivityState = {
data: RecentTransaction[];
}

@Injectable()
export class RecentActivityStore {

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 @@ -79,15 +81,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 @@ -97,28 +101,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 });
}
}
7 changes: 5 additions & 2 deletions src/app/buxx/feature/buxx.component.html
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
<as-toolbar (newTransactionEmitter)="saveTransaction($event)"></as-toolbar>
<div class="flex flex-col items-center justify-center">
<as-summary [loaded]="summaryStore.loaded()" [summary]="summaryStore.data()"></as-summary>
<as-summary [loaded]="summaryStore.loaded()" [summary]="summaryStore.data()"></as-summary>
<div class="items-center justify-center w-3/4">
<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 class="w-full sm:mx-14" [expanded]="false">
<mat-expansion-panel [expanded]="false" class="w-full sm:mx-14">
<mat-expansion-panel-header>
<mat-panel-title class="text-lg">
<mat-icon class="mr-2">tune</mat-icon>
Expand Down
8 changes: 6 additions & 2 deletions src/app/buxx/feature/buxx.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ import { PositiveNumberOnlyDirective } from '../../shared/util/positive-number.d
import { MaskDateDirective } from '../../shared/util/date-mask.directive';
import { MatProgressSpinner } from '@angular/material/progress-spinner';
import { MatProgressBar } from '@angular/material/progress-bar';
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 @@ -83,17 +85,19 @@ export const filterValidator: ValidatorFn = (control: AbstractControl): Validati
PositiveNumberOnlyDirective,
MaskDateDirective,
MatProgressSpinner,
MatProgressBar
MatProgressBar,
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);
readonly transactionStore = inject(TransactionStore);
Expand Down
39 changes: 39 additions & 0 deletions src/app/buxx/ui/recent-activity/recent-activity.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
@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>
<mat-panel-title class="text-lg">
<mat-icon class="mr-2">layers</mat-icon>
Recent Activity
</mat-panel-title>
</mat-expansion-panel-header>
</mat-expansion-panel>

@for (transaction of transactions(); track transaction.id) {
<mat-expansion-panel class="w-full">
<mat-expansion-panel-header>
<mat-panel-title>
<span class="mr-4 font-bold">
@if (transaction.isExpense) {
<mat-icon class="negative">trending_down</mat-icon>
} @else {
<mat-icon class="positive">trending_up</mat-icon>
}
</span>
{{ transaction.name }}
<span class="ml-2">
@if (transaction.action === 'DELETED') {
<mat-chip style="background: #6f1a07">deleted</mat-chip>
} @else if (transaction.action === 'SAVED') {
<mat-chip style="background: #607d8b">saved</mat-chip>
}
</span>
</mat-panel-title>
<mat-panel-description>Rs. {{ transaction.amount }}</mat-panel-description>
<mat-panel-description>{{ transaction.date | date: 'mediumDate' }}</mat-panel-description>
</mat-expansion-panel-header>
<p>{{ transaction.details }}</p>
</mat-expansion-panel>
}
</mat-accordion>
}
Empty file.
21 changes: 21 additions & 0 deletions src/app/buxx/ui/recent-activity/recent-activity.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RecentActivityComponent } from './recent-activity.component';

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

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

fixture = TestBed.createComponent(RecentActivityComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
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'
// }
// ];
}
6 changes: 3 additions & 3 deletions src/app/buxx/ui/summary/summary.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,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>
@if (loaded()) {
<mat-card-title>Rs. {{ summary().balance }}</mat-card-title>
<mat-card-title>Rs. {{ summary().balance | nprCurrency }}</mat-card-title>
} @else {
<mat-card-title class="animate-pulse">
<div class="h-4 bg-neutral-800 rounded-full w-28"></div>
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>
@if (loaded()) {
<mat-card-title>Rs. {{ summary().income }}</mat-card-title>
<mat-card-title>Rs. {{ summary().income | nprCurrency }}</mat-card-title>
} @else {
<mat-card-title class="animate-pulse">
<div class="h-4 bg-neutral-800 rounded-full w-28"></div>
Expand All @@ -35,7 +35,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>
@if (loaded()) {
<mat-card-title>Rs. {{ summary().expense }}</mat-card-title>
<mat-card-title>Rs. {{ summary().expense | nprCurrency }}</mat-card-title>
} @else {
<mat-card-title class="animate-pulse">
<div class="h-4 bg-neutral-800 rounded-full w-28"></div>
Expand Down
3 changes: 2 additions & 1 deletion src/app/buxx/ui/summary/summary.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ import { MatCardModule } from '@angular/material/card';
import { MatIconModule } from '@angular/material/icon';
import { Summary } from '../../../shared/model/buxx.model';
import { NgClass } from '@angular/common';
import { NprCurrencyPipe } from '../../../shared/util/npr-currency.pipe';

@Component({
selector: 'as-summary',
standalone: true,
imports: [MatCardModule, MatIconModule, NgClass],
imports: [MatCardModule, MatIconModule, NgClass, NprCurrencyPipe],
templateUrl: './summary.component.html',
styleUrl: './summary.component.scss'
})
Expand Down
4 changes: 3 additions & 1 deletion src/app/shared/model/buxx.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,6 @@ export type Summary = {
balance: number,
income: number,
expense: number
};
};

export type RecentTransaction = Transaction & { action: 'DELETED' | 'SAVED' };
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ <h2 mat-dialog-title>{{ data ? 'Update Transaction' : 'Add New Transaction' }}</
</div>
</mat-dialog-content>
<mat-dialog-actions>
<button mat-button (click)="closDialog(false)">Cancel</button>
<button mat-button (click)="closDialog(true)" [disabled]="!transactionForm.valid">
<button (click)="closeDialog(true)" mat-button>Cancel</button>
<button (click)="closeDialog(false)" [disabled]="!transactionForm.valid" mat-button>
{{ data ? 'Update' : 'Save' }}
</button>
</mat-dialog-actions>
Loading

0 comments on commit 3a79d9e

Please sign in to comment.