Feature: Voucher tags. This will eventually help in filtering ledger based on tags and also to group vouchers.

This commit is contained in:
tanshu committed 2024-05-31 07:30:02 +05:30
1 parent 3dd6384fd0
commit 6f433ef203
38 files changed
+768 -23

No files matched your search

@@ -113,6 +113,33 @@
<mat-label>Narration</mat-label>
<textarea matInput matAutosizeMinRows="5" formControlName="narration"></textarea>
</mat-form-field>
<mat-form-field class="flex-auto">
<mat-label>Tags</mat-label>
<mat-chip-grid #chipGrid aria-label="Tag selection">
@for (tag of voucher.tags; track tag) {
<mat-chip-row (removed)="removeTag(tag)">
{{ tag.name }}
<button matChipRemove [attr.aria-label]="'remove ' + tag.name">
<mat-icon>cancel</mat-icon>
</button>
</mat-chip-row>
}
</mat-chip-grid>
<input
placeholder="New Tag..."
#tagInput
formControlName="tags"
[matChipInputFor]="chipGrid"
[matAutocomplete]="autoTag"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
(matChipInputTokenEnd)="addTag($event)"
/>
<mat-autocomplete #autoTag="matAutocomplete" (optionSelected)="selectedTag($event)">
@for (tag of tags | async; track tag) {
<mat-option [value]="tag">{{ tag.name }}</mat-option>
}
</mat-autocomplete>
</mat-form-field>
<div class="flex flex-row justify-center items-stretch">
@for (item of voucher.files; track item) {
<div class="img-container" class="flex-auto mr-5 basis-1/5">
+52 -1
View File
@@ -1,13 +1,15 @@
import { COMMA, ENTER } from '@angular/cdk/keycodes';
import { AfterViewInit, Component, ElementRef, OnInit, OnDestroy, ViewChild } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';
import { MatChipInputEvent } from '@angular/material/chips';
import { MatDialog } from '@angular/material/dialog';
import { ActivatedRoute, Router } from '@angular/router';
import { Hotkey, HotkeysService } from 'angular2-hotkeys';
import { round } from 'mathjs';
import moment from 'moment';
import { BehaviorSubject, Observable, of as observableOf } from 'rxjs';
import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators';
import { debounceTime, distinctUntilChanged, map, switchMap } from 'rxjs/operators';
import { AuthService } from '../auth/auth.service';
import { Account } from '../core/account';
@@ -15,6 +17,8 @@ import { AccountBalance } from '../core/account-balance';
import { AccountService } from '../core/account.service';
import { DbFile } from '../core/db-file';
import { Journal } from '../core/journal';
import { Tag } from '../core/tag';
import { TagService } from '../core/tag.service';
import { ToasterService } from '../core/toaster.service';
import { User } from '../core/user';
import { Voucher } from '../core/voucher';
@@ -35,6 +39,8 @@ import { PaymentDialogComponent } from './payment-dialog.component';
export class PaymentComponent implements OnInit, AfterViewInit, OnDestroy {
@ViewChild('accountElement', { static: true }) accountElement?: ElementRef;
@ViewChild('dateElement', { static: true }) dateElement?: ElementRef;
@ViewChild('tagInput') tagInput?: ElementRef<HTMLInputElement>;
separatorKeysCodes: number[] = [ENTER, COMMA];
public journalObservable = new BehaviorSubject<Journal[]>([]);
dataSource: PaymentDataSource = new PaymentDataSource(this.journalObservable);
form: FormGroup<{
@@ -46,6 +52,7 @@ export class PaymentComponent implements OnInit, AfterViewInit, OnDestroy {
amount: FormControl<string>;
}>;
narration: FormControl<string>;
tags: FormControl<string | null>;
}>;
paymentAccounts: Account[] = [];
@@ -57,6 +64,7 @@ export class PaymentComponent implements OnInit, AfterViewInit, OnDestroy {
displayedColumns = ['account', 'amount', 'action'];
accounts: Observable<Account[]>;
tags: Observable<Tag[]>;
constructor(
private route: ActivatedRoute,
@@ -69,6 +77,7 @@ export class PaymentComponent implements OnInit, AfterViewInit, OnDestroy {
public image: ImageService,
private ser: VoucherService,
private accountSer: AccountService,
private tagSer: TagService,
) {
this.account = null;
this.form = new FormGroup({
@@ -80,6 +89,7 @@ export class PaymentComponent implements OnInit, AfterViewInit, OnDestroy {
amount: new FormControl('', { nonNullable: true }),
}),
narration: new FormControl('', { nonNullable: true }),
tags: new FormControl(''),
});
this.accBal = null;
// Listen to Account Autocomplete Change
@@ -88,6 +98,12 @@ export class PaymentComponent implements OnInit, AfterViewInit, OnDestroy {
distinctUntilChanged(),
switchMap((x) => (x === null ? observableOf([]) : this.accountSer.autocomplete(x))),
);
this.tags = this.form.controls.tags.valueChanges.pipe(
debounceTime(150),
distinctUntilChanged(),
map((tag: string | Tag | null) => (tag === null ? '' : typeof tag !== 'string' ? tag.name.toLowerCase() : tag)),
switchMap((tag: string) => this.tagSer.autocomplete(tag)),
);
// Listen to Payment Account Change
this.form.controls.paymentAccount.valueChanges.subscribe((x) =>
this.router.navigate([], {
@@ -165,6 +181,7 @@ export class PaymentComponent implements OnInit, AfterViewInit, OnDestroy {
amount: '',
},
narration: this.voucher.narration,
tags: '',
});
this.dataSource = new PaymentDataSource(this.journalObservable);
this.updateView();
@@ -349,4 +366,38 @@ export class PaymentComponent implements OnInit, AfterViewInit, OnDestroy {
const index = this.voucher.files.indexOf(file);
this.voucher.files.splice(index, 1);
}
removeTag(tag: Tag): void {
const index = this.voucher.tags.indexOf(tag);
if (index >= 0) {
this.voucher.tags.splice(index, 1);
}
}
addTag(event: MatChipInputEvent): void {
const value = (event.value || '').trim();
// Add our tag
if (value) {
this.voucher.tags.push(new Tag({ name: value }));
}
// Clear the input value
event.chipInput!.clear();
this.form.controls.tags.setValue(null);
}
selectedTag(event: MatAutocompleteSelectedEvent): void {
const tag = event.option.value as Tag;
const index = this.voucher.tags.findIndex((t) => (tag.id === null ? t.name === tag.name : t.id === tag.id));
if (index === -1) {
this.voucher.tags.push(tag);
}
if (this.tagInput) {
this.tagInput.nativeElement.value = '';
}
this.form.controls.tags.setValue(null);
}
}
@@ -7,6 +7,7 @@ import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatChipsModule } from '@angular/material/chips';
import { DateAdapter, MAT_DATE_FORMATS, MAT_DATE_LOCALE, MatNativeDateModule } from '@angular/material/core';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatDialogModule } from '@angular/material/dialog';
@@ -49,6 +50,7 @@ export const MY_FORMATS = {
MatButtonModule,
MatCardModule,
MatCheckboxModule,
MatChipsModule,
MatDatepickerModule,
MatDialogModule,
MatFormFieldModule,