This commit is contained in:
2023-06-21 17:56:00 +03:00
parent fa8852a32a
commit e9d4053e65
55 changed files with 3883 additions and 262 deletions
+1
View File
@@ -7,6 +7,7 @@ export class ApiRoutes {
static GeneralCurrencies = '/api/general/currencies';
static SettingsCurrencies = '/api/settings/currencies';
static SettingsCurrency = '/api/settings/currencies/:id';
static SettingsCurrenciesRate = '/api/settings/currencies/:id/rate';
static SettingsAccountCategories = '/api/settings/account-categories';
@@ -24,10 +24,10 @@ export class ErrorInterceptor implements HttpInterceptor {
this.authenticationService.logout();
location.reload();
}
const error = err.error.message || err.statusText;
//return throwError(error);
return throwError(err.error || err);
console.log(err);
const error = err.message || err.error.message || err.statusText;
return throwError(error);
//return throwError(err.error || err);
})
);
}
@@ -1,3 +1,4 @@
// angular
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { HttpInterceptor } from '@angular/common/http';
@@ -5,6 +6,9 @@ import { HttpRequest } from '@angular/common/http';
import { HttpHandler } from '@angular/common/http';
import { HttpEvent } from '@angular/common/http';
// libs
import * as moment from 'moment';
@Injectable()
export class UpdateDateHttpInterceptor implements HttpInterceptor {
@@ -29,12 +33,7 @@ export class UpdateDateHttpInterceptor implements HttpInterceptor {
for (const key of Object.keys(body)) {
const value = body[key];
if (value instanceof Date) {
body[key] = new Date(Date.UTC(value.getFullYear(),
value.getMonth(),
value.getDate(),
value.getHours(),
value.getMinutes(),
value.getSeconds()));
body[key] = moment(value).utcOffset(0, true).format();
} else if (typeof value === 'object') {
this.shiftDates(value);
}
@@ -86,6 +86,7 @@ export class SidebarComponent implements OnInit, OnDestroy {
.getCategories()
.subscribe(categories => {
var accounts = this.sidebarItems.filter(x => x.id === 'accounts');
accounts[0].submenu = [];
for (var category of categories) {
accounts[0].submenu.push({
path: '/account-category/' + category.id,
@@ -1,30 +1,44 @@
<section class="content">
<div class="content-block">
<div class="block-header">
<!-- breadcrumb -->
<app-breadcrumb [title]="'Blank'" [items]="['Home','Settings']" [active_item]="'Accounts'">
</app-breadcrumb>
</div>
<div class="row clearfix">
<div class="col-xs-12 col-sm-12 col-md-12 col-lg-12">
<mat-accordion class="main-headers-align" multi>
<mat-expansion-panel *ngFor="let account of accounts; let i = index" [expanded]="i === 0" >
<mat-expansion-panel-header>
<mat-panel-description>
<div>{{account.account.name}}</div>
<div>
{{account.rest}}
({{account.account.currencyId}})
</div>
</mat-panel-description>
</mat-expansion-panel-header>
<account-motion [motion]="newMotion">
</account-motion>
<account-motion *ngFor="let motion of motions" [motion]="motion">
</account-motion>
</mat-expansion-panel>
</mat-accordion>
<mat-expansion-panel [expanded]="expanded">
<mat-expansion-panel-header>
<mat-panel-description>
<h4>{{account.account.name}}</h4>
<h4>
{{account.rest | number: '1.2-6'}}
({{account.account.currencyId}})
</h4>
</mat-panel-description>
</mat-expansion-panel-header>
<form [formGroup]="form!" (ngSubmit)="onSubmitClick()">
<div class="row">
<div class="col-md-6">
<mat-form-field class="example-full-width" appearance="fill">
<mat-label>From</mat-label>
<input matInput [matDatepicker]="pickerFrom" (focus)="pickerFrom.open()" formControlName="dateFrom" required>
<mat-hint>YYYY/MM/DD</mat-hint>
<mat-datepicker-toggle tabindex="-1" matSuffix [for]="pickerFrom"></mat-datepicker-toggle>
<mat-datepicker #pickerFrom></mat-datepicker>
<mat-error *ngIf="form.controls?.['dateFrom']?.hasError('required')">
Please enter rate date
</mat-error>
</mat-form-field>
</div>
<div class="col-md-6">
<mat-form-field class="example-full-width" appearance="fill">
<mat-label>To</mat-label>
<input matInput [matDatepicker]="pickerTo" (focus)="pickerTo.open()" formControlName="dateTo" required>
<mat-hint>YYYY/MM/DD</mat-hint>
<mat-datepicker-toggle tabindex="-1" matSuffix [for]="pickerTo"></mat-datepicker-toggle>
<mat-datepicker #pickerTo></mat-datepicker>
<mat-error *ngIf="form.controls?.['dateTo']?.hasError('required')">
Please enter rate date
</mat-error>
</mat-form-field>
</div>
</div>
</div>
</section>
</form>
<hr/>
<account-motion [motion]="newMotion" [account]="account.account" (onAdd)="handlerOnAdd($event)">
</account-motion>
<account-motion *ngFor="let motion of motions" [motion]="motion" [account]="account.account">
</account-motion>
</mat-expansion-panel>
@@ -1,91 +1,99 @@
// angular
import { Component } from '@angular/core';
import { Input } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { ActivatedRoute } from '@angular/router';
import { UntypedFormGroup } from '@angular/forms';
import { Validators } from '@angular/forms';
import { UntypedFormBuilder } from '@angular/forms';
import { Output } from '@angular/core';
import { EventEmitter } from '@angular/core';
// libs
import Swal from 'sweetalert2';
import { MatDialog } from '@angular/material/dialog';
import * as moment from 'moment';
// app
import { AccountCategoryService } from '../../services/account.category.service';
import { ApiRoutes } from '../../api-routes';
import { AccountCategoryModel } from '../../model/account.category.model';
import { SettingsAccountAddComponent } from '../settings/account/add.account.component';
import { SettingsAccountEditComponent } from '../settings/account/edit.account.component';
import { AccountModel } from '../../model/account.model';
import { AccountDetailedModel } from '../../model/account.detailed.model';
import { AccountMotionModel } from '../../model/account.motion';
@Component({
templateUrl: './account.component.html',
styleUrls: ['./account.component.scss'],
selector: 'account'
})
export class AccountComponent {
public accounts?: AccountDetailedModel[];
public motions?: AccountMotionModel[];
public newMotion: AccountMotionModel;
public form!: UntypedFormGroup;
@Input('account') account!: AccountDetailedModel;
@Input('expanded') expanded!: boolean;
@Output() onUpdate: EventEmitter<AccountMotionModel> = new EventEmitter<AccountMotionModel>();
constructor(
private fb: UntypedFormBuilder,
private httpClient: HttpClient,
private dialogModel: MatDialog,
private activatedRoute: ActivatedRoute
) {
this.newMotion = {
date: new Date(),
plus: 0,
minus: 0,
};
}
ngOnInit(): void {
this.form = this.fb.group({
dateFrom: [
moment(new Date).add(-7, 'days').toDate(),
[Validators.required],
],
dateTo: [
new Date,
[Validators.required],
],
});
this.loadMotions();
}
onSubmitClick() {
}
handlerOnAdd(accountMotion: AccountMotionModel) {
this.loadMotions();
this.httpClient
.get<AccountDetailedModel>(ApiRoutes.Account.replace(':id', this.account.account!.id!))
.subscribe(data => {
this.account = data;
});
/*var md = moment(accountMotion.date);
var mdf = moment(this.form.value.dateFrom);
var mdt = moment(this.form.value.dateTo);
if (md.isBetween(mdf, mdt, 'days', '[]')) {
this.motions?.push(accountMotion);
//this.motions?.sort((a, b) => (b.date!.getMilliseconds() - a.date!.getMilliseconds()));
this.motions?.sort((a, b) => {
var aa = moment(a.date).toDate();
var bb = moment(b.date).toDate();
return (bb.getMilliseconds() - aa.getMilliseconds());
});
}*/
}
private loadMotions() {
var url = ApiRoutes.Motions.replace(':id', this.account.account.id!);
url += '?from=' + moment(this.form.value.dateFrom).format('yyyy-MM-DD');
url += '&to=' + moment(this.form.value.dateTo).format('yyyy-MM-DD');
this.activatedRoute.params.subscribe(params => {
this.httpClient
.get<AccountDetailedModel[]>(ApiRoutes.Accounts + "?category=" + params['id'])
.get<AccountMotionModel[]>(url)
.subscribe(data => {
this.accounts = data;
this.motions = data;
});
});
}
/*private loadAccounts() {
}*/
/*private loadCategories() {
this.accountCategoryService
.getCategories()
.subscribe(x => {
this.accountCategories = x;
this.loadAccounts();
});
}*/
/*public accountInCategory(category?: AccountCategoryModel) {
if (this.accounts) {
return this.accounts.filter(acc => (!category && (!acc.categories || acc.categories.length === 0)) || (category && acc.categories && acc.categories.filter(cat => cat.id === category.id).length > 0));
}
return null;
}*/
/*public add() {
this.dialogModel.open(SettingsAccountAddComponent, {
width: '640px',
disableClose: true,
data: this.accounts,
}).afterClosed().subscribe(x => {
if (x && x.refresh) {
this.loadAccounts();
}
});
}*/
/*public edit(account: AccountModel) {
this.dialogModel.open(SettingsAccountEditComponent, {
width: '640px',
disableClose: true,
data: account,
}).afterClosed().subscribe(x => {
if (x && x.refresh) {
this.loadAccounts();
}
});
}*/
}
@@ -0,0 +1,18 @@
<section class="content">
<div class="content-block">
<div class="block-header">
<!-- breadcrumb -->
<app-breadcrumb [title]="'Blank'" [items]="['Home', 'Accounts']" [active_item]="category">
</app-breadcrumb>
</div>
<div class="row clearfix">
<div class="col-xs-12 col-sm-12 col-md-12 col-lg-12">
<mat-accordion class="main-headers-align" multi>
<div *ngFor="let account of accounts; let i = index" >
<account [account]="account" />
</div>
</mat-accordion>
</div>
</div>
</div>
</section>
@@ -0,0 +1,51 @@
// angular
import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { ActivatedRoute } from '@angular/router';
// libs
import Swal from 'sweetalert2';
import { MatDialog } from '@angular/material/dialog';
// app
import { AccountCategoryService } from '../../services/account.category.service';
import { ApiRoutes } from '../../api-routes';
import { AccountCategoryModel } from '../../model/account.category.model';
import { SettingsAccountAddComponent } from '../settings/account/add.account.component';
import { SettingsAccountEditComponent } from '../settings/account/edit.account.component';
import { AccountModel } from '../../model/account.model';
import { AccountDetailedModel } from '../../model/account.detailed.model';
import { AccountMotionModel } from '../../model/account.motion';
@Component({
templateUrl: './account.list.component.html',
styleUrls: ['./account.list.component.scss'],
})
export class AccountListComponent {
public accounts?: AccountDetailedModel[];
public category = '';
constructor(
private httpClient: HttpClient,
private activatedRoute: ActivatedRoute,
private accountCategoryService: AccountCategoryService,
) {
}
ngOnInit(): void {
this.activatedRoute.params.subscribe(params => {
var categoryId = params['id'];
this.httpClient
.get<AccountDetailedModel[]>(ApiRoutes.Accounts + '?category=' + categoryId)
.subscribe(data => {
this.accounts = data;
});
this.accountCategoryService
.getCategory(categoryId)
.subscribe(data => {
this.category = data.name!;
});
});
}
}
@@ -1,6 +1,6 @@
<form [formGroup]="form!" (ngSubmit)="onSubmitClick()">
<div class="row">
<div class="col-md-4">
<form [formGroup]="form" (ngSubmit)="onSubmitClick()" novalidate autocomplete="off">
<div class="" style="display: flex;">
<div class="" style="width: 310px; min-width: 310px;">
<div style="display: flex; align-items: center;">
<a href="javascript:void(0)" matSuffix (click)="addDay(-1)">
<i class="material-icons font-40">arrow_back</i>
@@ -21,15 +21,19 @@
</a>
</div>
</div>
<div class="col-md-4">
<div class="" style="flex-grow: 1;">
<mat-form-field class="example-full-width" appearance="fill">
<input matInput formControlName="motion">
<mat-label>Motion</mat-label>
<input #motionInput matInput formControlName="motion" required>
<mat-error *ngIf="form.controls['motion'].hasError('required')">
Please enter motion
</mat-error>
</mat-form-field>
<mat-form-field class="example-full-width" appearance="fill">
<input matInput formControlName="description">
</mat-form-field>
</div>
<div class="col-md-2">
<div class="" style="width: 100px; min-width: 100px;">
<mat-form-field class="example-full-width" appearance="fill">
<input matInput [value]="motion.plus | number:'0.2-2'" formControlName="plus" currencyMask [options]="{ prefix: '', precision: 4, inputMode: 1 }">
</mat-form-field>
@@ -37,19 +41,20 @@
<input matInput [value]="motion.minus | number:'0.2-2'" formControlName="minus" currencyMask [options]="{ prefix: '', precision: 4, inputMode: 1 }">
</mat-form-field>
</div>
<div class="col-md-2 mb-2">
<a *ngIf="!motion.id" href="javascript:void(0)" (click)="onSubmitClick()">
<div class="" style="display: inline-block; width: 150px; min-width: 150px;">
<button type="button" mat-stroked-button color="primary" *ngIf="!motion.id" (click)="onSubmitClick()">
<i class="material-icons font-40">add</i>
</a>
<a *ngIf="!motion.id" href="javascript:void(0)" (click)="onSubmitClick()">
</button>
<button mat-stroked-button color="primary" *ngIf="!motion.id" (click)="onSubmitClick()">
<i class="material-icons font-40">import_export</i>
</a>
<a *ngIf="motion.id" href="javascript:void(0)" (click)="onSubmitClick()">
</button>
<button mat-stroked-button color="primary" *ngIf="motion.id" (click)="onSubmitClick()">
<i class="material-icons font-40">save</i>
</a>
<a *ngIf="motion.id" href="javascript:void(0)" (click)="onSubmitClick()">
</button>
<button mat-stroked-button color="primary" *ngIf="motion.id" (click)="onSubmitClick()">
<i class="material-icons font-40">delete</i>
</a>
</button>
</div>
</div>
<hr/>
</form>
@@ -1,29 +1,22 @@
// angular
import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { ActivatedRoute } from '@angular/router';
import { UntypedFormBuilder } from '@angular/forms';
import { FormBuilder } from '@angular/forms';
import { FormGroup } from '@angular/forms';
import { Validators } from '@angular/forms';
import { UntypedFormGroup } from '@angular/forms';
import { Input } from '@angular/core';
import { Output } from '@angular/core';
import { EventEmitter } from '@angular/core';
import { ViewChild } from '@angular/core';
import { ElementRef } from '@angular/core';
// libs
import Swal from 'sweetalert2';
import { MatDialog } from '@angular/material/dialog';
import * as moment from 'moment';
// app
import { AccountCategoryService } from '../../services/account.category.service';
import { ApiRoutes } from '../../api-routes';
import { AccountCategoryModel } from '../../model/account.category.model';
import { SettingsAccountAddComponent } from '../settings/account/add.account.component';
import { SettingsAccountEditComponent } from '../settings/account/edit.account.component';
import { AccountModel } from '../../model/account.model';
import { AccountDetailedModel } from '../../model/account.detailed.model';
import { AccountMotionModel } from '../../model/account.motion';
import { atLeastOne } from '../../core/validators/atleastone.validator';
import { atLeastOneNumber } from '../../core/validators/atleastonenumber.validator';
@Component({
@@ -32,16 +25,19 @@ import { atLeastOneNumber } from '../../core/validators/atleastonenumber.validat
styleUrls: ['./motion.component.scss'],
})
export class MotionComponent {
public form!: UntypedFormGroup;
public form!: FormGroup;
public errorMessage?: string;
@Input() motion!: AccountMotionModel;
@Input('motion') motion!: AccountMotionModel;
@Input('account') account!: AccountModel;
@Output() onAdd: EventEmitter<AccountMotionModel> = new EventEmitter<AccountMotionModel>();
@ViewChild('motionInput') motionInput?: ElementRef;
constructor(
private fb: UntypedFormBuilder,
private fb: FormBuilder,
private httpClient: HttpClient,
private dialogModel: MatDialog,
private activatedRoute: ActivatedRoute
) {
}
@@ -67,14 +63,29 @@ export class MotionComponent {
minus: [
this.motion.minus,
],
}, { validator: atLeastOneNumber(Validators.required, ['plus', 'minus']) });
}, {
validator: atLeastOneNumber(Validators.required, ['plus', 'minus'])
});
}
onSubmitClick() {
this.form.markAllAsTouched();
if (this.form.valid) {
this.httpClient
.post<AccountMotionModel[]>(ApiRoutes.SettingsCurrencies, this.form.value)
.post<AccountMotionModel>(ApiRoutes.Motions.replace(':id', this.account.id!), this.form.value)
.subscribe(response => {
if (!this.form.value.id) {
this.onAdd?.emit(response);
this.form.patchValue({
motion: '',
plus: 0,
minus: 0,
});
this.form.markAsUntouched();
this.motionInput?.nativeElement.focus();
}
}, error => {
this.errorMessage = error.detail;
});
@@ -6,7 +6,7 @@ import { SettingsAccountCategoryComponent } from './settings/account/account.cat
import { SettingsAccountComponent } from './settings/account/account.component';
import { SettingsMotionCategoryComponent } from './settings/motion/motion.category.component';
import { SettingsMotionComponent } from './settings/motion/motion.component';
import { AccountComponent } from './accounts/account.component';
import { AccountListComponent as AccountComponent } from './accounts/account.list.component';
const routes: Routes = [
{
@@ -36,6 +36,7 @@ import { MotionService } from '../services/motion.service';
import { SettingsMotionCategoryComponent } from './settings/motion/motion.category.component';
import { SettingsMotionComponent } from './settings/motion/motion.component';
import { SettingsMotionEditComponent } from './settings/motion/edit.motion.component';
import { AccountListComponent } from './accounts/account.list.component';
import { AccountComponent } from './accounts/account.component';
import { MotionComponent } from './accounts/motion.component';
@@ -56,6 +57,7 @@ import { MotionComponent } from './accounts/motion.component';
SettingsMotionComponent,
SettingsMotionEditComponent,
AccountListComponent,
AccountComponent,
MotionComponent,
],
@@ -16,10 +16,22 @@
<mat-tab label="My currencies">
<div class="body table-responsive">
<table class="table">
<thead>
<tr>
<th>Code</th>
<th>Name</th>
<th>Short Name</th>
<th>Quantity</th>
<th>Rate</th>
<th>Rate Date</th>
<th></th>
</tr>
</thead>
<tbody>
<tr *ngFor="let item of myCurrencies">
<td>{{item.code}}</td>
<td>{{item.name}}</td>
<td>{{item.shortName}}</td>
<td style="text-align: end">
{{item.quantity}}
</td>
@@ -27,10 +39,10 @@
{{item.rate | number: '1.2-6'}}
{{item.symbol}}
</td>
<td>{{item.rateDate | date:'yyyy-MM-dd'}}</td>
<td>{{item.rateDate | date:'yyyy-MM-dd'}}</td>
<td>
<button class="btn-space" (click)="setRate(item)" mat-raised-button color="primary">
Rate
Edit
</button>
</td>
</tr>
@@ -74,16 +74,16 @@ export class SettingsCurrencyComponent {
.subscribe(data => {
this.myCurrencies = data.map(myCurrency => {
var globalCurrency = this.currencies?.find(x => x.id === myCurrency.code);
return ({
return {
id: myCurrency.id,
code: myCurrency.code,
name: myCurrency.name,
quantity: myCurrency.quantity,
rate: myCurrency.rate,
rateDate: myCurrency.rateDate,
shortName: globalCurrency?.name,
shortName: myCurrency.shortName,
symbol: globalCurrency?.symbol,
});
};
});
});
}
@@ -1,9 +1,9 @@
<div>
<h2 mat-dialog-title>Add currency</h2>
<h2 mat-dialog-title>Edit currency</h2>
<div mat-dialog-content>
<form [formGroup]="addForm!" (ngSubmit)="onSubmitClick()">
<div class="row">
<div class="col-md-4">
<div class="col-md-3">
<div class="text-inside">
<mat-form-field class="example-full-width">
<mat-label>Symbol</mat-label>
@@ -11,7 +11,7 @@
</mat-form-field>
</div>
</div>
<div class="col-md-4">
<div class="col-md-3">
<div class="text-inside">
<mat-form-field class="example-full-width">
<mat-label>Code</mat-label>
@@ -19,11 +19,19 @@
</mat-form-field>
</div>
</div>
<div class="col-md-4">
<div class="col-md-3">
<div class="text-inside">
<mat-form-field class="example-full-width">
<mat-label>Name</mat-label>
<input matInput value={{currency.name}} formControlName="name" readonly="">
<input matInput value={{currency.name}} formControlName="name">
</mat-form-field>
</div>
</div>
<div class="col-md-3">
<div class="text-inside">
<mat-form-field class="example-full-width">
<mat-label>Short name</mat-label>
<input matInput value={{currency.shortName}} formControlName="shortName">
</mat-form-field>
</div>
</div>
@@ -45,6 +45,9 @@ export class SettingsRateCurrencyComponent {
name: [
this.currency.name,
],
shortName: [
this.currency.shortName,
],
quantity: [
this.currency.quantity || 1,
[Validators.required],
@@ -68,9 +71,16 @@ export class SettingsRateCurrencyComponent {
if (this.addForm.valid) {
this.errorMessage = undefined;
this.httpClient
.post<CurrencyModel[]>(ApiRoutes.SettingsCurrenciesRate.replace(':id', this.addForm.value.id), this.addForm.value)
.put<CurrencyModel>(ApiRoutes.SettingsCurrency.replace(':id', this.addForm.value.id), this.addForm.value)
.subscribe(response => {
this.dialogRef.close({ refresh: true });
console.log(this.addForm.value);
this.httpClient
.post<CurrencyModel[]>(ApiRoutes.SettingsCurrenciesRate.replace(':id', this.addForm.value.id), this.addForm.value)
.subscribe(response => {
this.dialogRef.close({ refresh: true });
}, error => {
this.errorMessage = error.detail;
});
}, error => {
this.errorMessage = error.detail;
});
@@ -21,4 +21,9 @@ export class AccountCategoryService {
return this.httpClient
.get<AccountCategoryModel[]>(ApiRoutes.SettingsAccountCategories);
}
public getCategory(id: string): Observable<AccountCategoryModel> {
return this.httpClient
.get<AccountCategoryModel>(ApiRoutes.SettingsAccountCategory.replace(':id', id));
}
}