This commit is contained in:
2023-08-03 08:58:53 +03:00
parent c82f8e3537
commit ad31d3ffce
30 changed files with 697 additions and 24 deletions
+5 -6
View File
@@ -9,12 +9,11 @@ public static class GuidExtensions
public static string ToShort(this Guid guid) public static string ToShort(this Guid guid)
{ {
/*string encoded = Convert.ToBase64String(guid.ToByteArray());
encoded = encoded
.Replace("/", "_")
.Replace("+", "-");
return encoded.Substring(0, 22);*/
return guid.ToString("N"); return guid.ToString("N");
} }
public static string? ToShort(this Guid? guid)
{
return guid?.ToString("N");
}
} }
+1 -1
View File
@@ -54,7 +54,7 @@ public static class StringExtensions
return Guid.Parse(str); return Guid.Parse(str);
} }
public static Guid? AsGuidNull(this string str) public static Guid? AsGuidNull(this string? str)
{ {
if (Guid.TryParse(str, out var guid)) if (Guid.TryParse(str, out var guid))
{ {
+11 -1
View File
@@ -25,7 +25,7 @@ public class AccountDetailed
public decimal Rest => TotalPlus - TotalMinus; public decimal Rest => TotalPlus - TotalMinus;
} }
public struct AccountSimple public class AccountSimple
{ {
public Guid Id { get; set; } public Guid Id { get; set; }
public string Name { get; set; } public string Name { get; set; }
@@ -38,3 +38,13 @@ public struct AccountSimple
public decimal? TotalMinus { get; set; } public decimal? TotalMinus { get; set; }
public decimal? Balance { get; set; } public decimal? Balance { get; set; }
} }
public class MotionTotalSimple
{
public Guid? Id { get; set; }
public string Name { get; set; }
public string CurrencyId { get; set; } = null!;
public string CurrencyName { get; set; } = null!;
public decimal CurrencyRate { get; set; }
public decimal Amount { get; set; }
}
@@ -194,4 +194,66 @@ public class AccountRepository : AppRepository<Account>, IAccountRepository
}) })
.ToList(); .ToList();
} }
public List<MotionTotalSimple> GetIncomeByCategories(Guid userId, DateTime from, DateTime to)
{
return _context.Motions
.Where(x => x.DateTime >= from && x.DateTime <= to)
.Where(x => x.Item.Category!.UserId == userId)
.Where(x => !x.Item.Category!.IsInternal)
.GroupBy(x => new { x.Item.CategoryId, x.Item.Category!.Name })
.Select(x => new MotionTotalSimple
{
Id = x.Key.CategoryId,
Name = x.Key.Name,
Amount = x.Sum(a => a.AmountPlus)
}).ToList();
}
public List<MotionTotalSimple> GetIncomeByCategory(Guid userId, Guid categoryId, DateTime from, DateTime to)
{
return _context.Motions
.Where(x => x.DateTime >= from && x.DateTime <= to)
.Where(x => x.Item.CategoryId == categoryId)
.Where(x => !x.Item.Category!.IsInternal)
.Where(x => x.Item.Category!.UserId == userId)
.GroupBy(x => new { Id = x.Item.ItemGlobalId, x.Item.ItemGlobal.Name })
.Select(x => new MotionTotalSimple
{
Name = x.Key.Name,
Amount = x.Sum(a => a.AmountPlus),
}).ToList();
}
public List<MotionTotalSimple> GetOutcomeByCategories(Guid userId, DateTime from, DateTime to)
{
return _context.Motions
.Where(x => x.DateTime >= from && x.DateTime <= to)
.Where(x => x.Item.Category!.UserId == userId)
.Where(x => !x.Item.Category!.IsInternal)
.GroupBy(x => new { x.Item.CategoryId, x.Item.Category!.Name })
.Select(x => new MotionTotalSimple
{
Id = x.Key.CategoryId,
Name = x.Key.Name,
Amount = x.Sum(a => a.AmountMinus)
}).ToList();
}
public List<MotionTotalSimple> GetOutcomeByCategory(Guid userId, Guid categoryId, DateTime from, DateTime to)
{
return _context.Motions
.Where(x => x.DateTime >= from && x.DateTime <= to)
.Where(x => x.Item.CategoryId == categoryId)
.Where(x => !x.Item.Category!.IsInternal)
.Where(x => x.Item.Category!.UserId == userId)
.GroupBy(x => new { Id = x.Item.ItemGlobalId, x.Item.ItemGlobal.Name })
.Select(x => new MotionTotalSimple
{
Name = x.Key.Name,
Amount = x.Sum(a => a.AmountMinus)
}).ToList();
}
} }
@@ -18,4 +18,8 @@ public interface IAccountRepository
decimal GetPeriodIncome(Guid userId, DateTime from, DateTime to); decimal GetPeriodIncome(Guid userId, DateTime from, DateTime to);
decimal GetPeriodOutcome(Guid userId, DateTime from, DateTime to); decimal GetPeriodOutcome(Guid userId, DateTime from, DateTime to);
List<AccountSimple> GetRestAtDate(Guid userId, DateTime date); List<AccountSimple> GetRestAtDate(Guid userId, DateTime date);
List<MotionTotalSimple> GetIncomeByCategories(Guid userId, DateTime from, DateTime to);
List<MotionTotalSimple> GetIncomeByCategory(Guid userId, Guid categoryId, DateTime from, DateTime to);
List<MotionTotalSimple> GetOutcomeByCategories(Guid userId, DateTime from, DateTime to);
List<MotionTotalSimple> GetOutcomeByCategory(Guid userId, Guid categoryId, DateTime from, DateTime to);
} }
+1 -1
View File
@@ -8,7 +8,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Folder Include="src\app\layout\app-layout\" /> <Folder Include="src\app\layout\app-layout\" />
<Folder Include="src\app\shared\" /> <Folder Include="src\app\shared\components\date-period\" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<TypeScriptConfiguration Remove="dist\**" /> <TypeScriptConfiguration Remove="dist\**" />
+2
View File
@@ -37,4 +37,6 @@ export class ApiRoutes {
static Items = '/api/items'; static Items = '/api/items';
static Dashboard = '/api/dashboard'; static Dashboard = '/api/dashboard';
static DashboardIncome = '/api/dashboard/income';
static DashboardOutcome = '/api/dashboard/outcome';
} }
@@ -44,7 +44,7 @@ export class ForgotPasswordComponent implements OnInit {
if (this.authForm.invalid) { if (this.authForm.invalid) {
return; return;
} else { } else {
this.router.navigate(['/dashboard/main']); this.router.navigate(['/dashboard']);
} }
} }
} }
@@ -51,7 +51,7 @@ implements OnInit {
this.authService.isAuthenticated$.subscribe(isAuthenticated => { this.authService.isAuthenticated$.subscribe(isAuthenticated => {
if (isAuthenticated) { if (isAuthenticated) {
this.router.navigate([this.getRedirect() || '/dashboard/dashboard']); this.router.navigate([this.getRedirect() || '/dashboard']);
} }
}); });
} }
@@ -46,7 +46,7 @@ export class SignupComponent implements OnInit {
if (this.authForm.invalid) { if (this.authForm.invalid) {
return; return;
} }
//this.router.navigate(['/admin/dashboard/main']);
this.authService.register({ this.authService.register({
username: this.authForm.get('email')!.value!, username: this.authForm.get('email')!.value!,
password: this.authForm.get('password')!.value!, password: this.authForm.get('password')!.value!,
@@ -85,7 +85,7 @@ export class AuthService {
this.setCurrentUserValue(userProfile.id, userProfile); this.setCurrentUserValue(userProfile.id, userProfile);
if (authState.action === AuthenticatedActionEnum.loggedIn) { if (authState.action === AuthenticatedActionEnum.loggedIn) {
this.router.navigate([redirectUrl || '/dashboard/dashboard']); this.router.navigate([redirectUrl || '/dashboard']);
} }
}); });
} }
@@ -1,7 +1,12 @@
// angular
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router'; import { RouterModule, Routes } from '@angular/router';
// app
import { Page404Component } from '../authentication/page404/page404.component'; import { Page404Component } from '../authentication/page404/page404.component';
import { Dashboard2Component } from './dashboard2/dashboard2.component'; import { Dashboard2Component } from './dashboard2/dashboard2.component';
import { DashboardIncomeComponent } from './income/dashboard.component';
import { DashboardOutcomeComponent } from './outcome/dashboard.component';
const routes: Routes = [ const routes: Routes = [
{ {
@@ -10,9 +15,17 @@ const routes: Routes = [
pathMatch: 'full', pathMatch: 'full',
}, },
{ {
path: 'dashboard', path: 'rests',
component: Dashboard2Component, component: Dashboard2Component,
}, },
{
path: 'income',
component: DashboardIncomeComponent,
},
{
path: 'outcome',
component: DashboardOutcomeComponent,
},
{ path: '**', component: Page404Component }, { path: '**', component: Page404Component },
]; ];
@@ -15,15 +15,22 @@ import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatTooltipModule } from '@angular/material/tooltip'; import { MatTooltipModule } from '@angular/material/tooltip';
import { NgApexchartsModule } from 'ng-apexcharts'; import { NgApexchartsModule } from 'ng-apexcharts';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { MAT_DATE_LOCALE } from '@angular/material/core';
// app // app
import { DashboardRoutingModule } from './dashboard-routing.module'; import { DashboardRoutingModule } from './dashboard-routing.module';
import { Dashboard2Component } from './dashboard2/dashboard2.component'; import { Dashboard2Component } from './dashboard2/dashboard2.component';
import { DashboardIncomeComponent } from './income/dashboard.component';
import { DashboardOutcomeComponent } from './outcome/dashboard.component';
import { ComponentsModule } from 'src/app/shared/components/components.module'; import { ComponentsModule } from 'src/app/shared/components/components.module';
import { SharedModule } from '../shared/shared.module'; import { SharedModule } from '../shared/shared.module';
@NgModule({ @NgModule({
declarations: [Dashboard2Component], declarations: [
Dashboard2Component,
DashboardIncomeComponent,
DashboardOutcomeComponent,
],
imports: [ imports: [
CommonModule, CommonModule,
DashboardRoutingModule, DashboardRoutingModule,
@@ -42,6 +49,7 @@ import { SharedModule } from '../shared/shared.module';
TranslateModule, TranslateModule,
], ],
providers: [ providers: [
{ provide: MAT_DATE_LOCALE, useValue: 'en-GB' },
DecimalPipe, DecimalPipe,
] ]
}) })
@@ -0,0 +1,85 @@
<section class="content">
<div class="content-block">
<div class="block-header">
<!-- breadcrumb -->
<app-breadcrumb [title]="'MENUITEMS.DASHBOARD.LIST.INCOME'" [items]="['HOME']" [active_item]="'MENUITEMS.DASHBOARD.LIST.INCOME'"></app-breadcrumb>
</div>
<!--
<div class="row">
<div class="col-4 col-sm-4 col-md-4">
<div class="card">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h5>{{'BALANCE' | translate}}</h5>
</div>
<h3 class="text-danger">{{dashboardModel?.balance | number: '0.2-2'}}</h3>
</div>
</div>
</div>
</div>
<div class="col-4 col-sm-4 col-md-4">
<div class="card">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h5>{{'DEBIT.BALANCE' | translate}}</h5>
<p class="text-muted"></p>
</div>
<h3 class="text-success">{{dashboardModel?.balanceDebit | number: '0.2-2'}}</h3>
</div>
</div>
</div>
</div>
<div class="col-4 col-sm-4 col-md-4">
<div class="card">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h5>{{'CREDIT.BALANCE' | translate}}</h5>
</div>
<h3 class="text-danger">{{dashboardModel?.balanceCredit | number: '0.2-2'}}</h3>
</div>
</div>
</div>
</div>
</div>
-->
<div class="row clearfix">
<!-- Bar chart with line -->
<div class="col-xl-12 col-lg-12 col-md-12 col-sm-12">
<div class="card">
<div class="header">
<h2>{{'INCOME' | translate}}</h2>
<button mat-icon-button [matMenuTriggerFor]="menu" class="header-dropdown">
<mat-icon>more_vert</mat-icon>
</button>
<mat-menu #menu="matMenu">
<button mat-menu-item>Action</button>
<button mat-menu-item>Another action</button>
<button mat-menu-item>Something else here</button>
</mat-menu>
</div>
<div class="body">
<div id="chart">
<apx-chart #chart *ngIf="chartOptions" class="apex-pie-center"
[series]="chartOptions.series!"
[chart]="chartOptions.chart!"
[dataLabels]="chartOptions.dataLabels!"
[plotOptions]="chartOptions.plotOptions!"
[title]="chartOptions.title!"
[legend]="chartOptions.legend!"
>
</apx-chart>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
@@ -0,0 +1,27 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { Dashboard2Component } from './dashboard2.component';
describe('Dashboard2Component',
() => {
let component: Dashboard2Component;
let fixture: ComponentFixture<Dashboard2Component>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [Dashboard2Component]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(Dashboard2Component);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create',
() => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,104 @@
// angular
import { Component } from '@angular/core';
import { OnInit } from '@angular/core';
import { ViewChild } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { HttpParams } from '@angular/common/http';
import { DecimalPipe } from '@angular/common';
// libs
import {
ChartComponent,
ApexAxisChartSeries,
ApexTitleSubtitle,
ApexDataLabels,
ApexChart,
ApexPlotOptions,
ApexLegend
} from "ng-apexcharts";
// app
import { ApiRoutes } from '../../api-routes';
import { DashboardInOutModel } from '../../model/dashboard.model';
export type ChartOptions = {
series: ApexAxisChartSeries;
chart: ApexChart;
dataLabels: ApexDataLabels;
title: ApexTitleSubtitle;
plotOptions: ApexPlotOptions;
legend: ApexLegend;
};
@Component({
selector: 'app-dashboard-income',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.scss'],
})
export class DashboardIncomeComponent implements OnInit {
@ViewChild("chart") chart!: ChartComponent;
public chartOptions!: Partial<ChartOptions>;
dashboardModel?: DashboardInOutModel;
colors: string[] = ["#fd7f6f", "#7eb0d5", "#b2e061", "#bd7ebe", "#ffb55a", "#ffee65", "#beb9db", "#fdcce5", "#8bd3c7"];
constructor(
private httpClient: HttpClient,
private _decimalPipe: DecimalPipe
) {
}
ngOnInit() {
let params = new HttpParams()
.set('from', '2023-07-01')
.set('to', '2023-08-01');
this.httpClient
.get<DashboardInOutModel>(ApiRoutes.DashboardIncome, { params: params })
.subscribe(response => {
var series = response.data.map(x => ({
x: x.name + '(' + this._decimalPipe.transform(x.value, '1.2-2') || "" + ')',
y: x.value
}));
var min = Math.min(...response.data.map(x => x.value));
var max = Math.max(...response.data.map(x => x.value));
var step = max / 10;
var ranges = [];
for (var i = 0; i < this.colors.length; i++) {
ranges.push({
from: i === 0 ? min : step * i,
to: i === this.colors.length - 1 ? max + 10 : step * (i + 1),
color: this.colors[this.colors.length - i - 1],
});
}
this.setChartOptions(series, ranges);
});
}
private setChartOptions(data: any[], ranges: any[]) {
var self = this;
this.chartOptions = {
series: [{
data: data
}],
chart: {
type: 'treemap',
width: 600,
height: 600,
},
plotOptions: {
treemap: {
enableShades: true,
shadeIntensity: 0.5,
reverseNegativeShade: true,
colorScale: {
ranges: ranges
}
}
},
};
}
}
@@ -0,0 +1,80 @@
<section class="content">
<div class="content-block">
<div class="block-header">
<!-- breadcrumb -->
<app-breadcrumb [title]="'MENUITEMS.DASHBOARD.LIST.OUTCOME'" [items]="['HOME']" [active_item]="'MENUITEMS.DASHBOARD.LIST.OUTCOME'"></app-breadcrumb>
</div>
<div class="row">
<div class="col-xl-12 col-lg-12 col-md-12 col-sm-12">
<div class="card">
<div class="header">
<h2>{{'PERIOD' | translate}}</h2>
</div>
<div class="body">
<div class="example-container">
<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>
</form>
</div>
</div>
</div>
</div>
</div>
<div class="row clearfix">
<!-- Bar chart with line -->
<div class="col-xl-12 col-lg-12 col-md-12 col-sm-12">
<div class="card">
<div class="header">
<h2>{{'OUTCOME' | translate}}</h2>
<!--
<button mat-icon-button [matMenuTriggerFor]="menu" class="header-dropdown">
<mat-icon>more_vert</mat-icon>
</button>
<mat-menu #menu="matMenu">
<button mat-menu-item>Action</button>
<button mat-menu-item>Another action</button>
<button mat-menu-item>Something else here</button>
</mat-menu>
-->
</div>
<div class="body">
<apx-chart #chart *ngIf="chartOptions" class="apex-pie-center"
[series]="chartOptions.series!"
[chart]="chartOptions.chart!"
[dataLabels]="chartOptions.dataLabels!"
[plotOptions]="chartOptions.plotOptions!"
[title]="chartOptions.title!"
[legend]="chartOptions.legend!">
</apx-chart>
</div>
</div>
</div>
</div>
</div>
</section>
@@ -0,0 +1,27 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { Dashboard2Component } from './dashboard2.component';
describe('Dashboard2Component',
() => {
let component: Dashboard2Component;
let fixture: ComponentFixture<Dashboard2Component>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [Dashboard2Component]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(Dashboard2Component);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create',
() => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,148 @@
// angular
import { Component } from '@angular/core';
import { OnInit } from '@angular/core';
import { ViewChild } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { HttpParams } from '@angular/common/http';
import { DecimalPipe } from '@angular/common';
import { UntypedFormBuilder } from '@angular/forms';
import { UntypedFormGroup } from '@angular/forms';
import { Validators } from '@angular/forms';
import { Inject } from '@angular/core';
// libs
import {
ChartComponent,
ApexAxisChartSeries,
ApexTitleSubtitle,
ApexDataLabels,
ApexChart,
ApexPlotOptions,
ApexLegend
} from "ng-apexcharts";
import * as moment from 'moment'
import { MAT_DATE_FORMATS } from '@angular/material/core';
// app
import { ApiRoutes } from '../../api-routes';
import { DashboardInOutModel } from '../../model/dashboard.model';
export type ChartOptions = {
series: ApexAxisChartSeries;
chart: ApexChart;
dataLabels: ApexDataLabels;
title: ApexTitleSubtitle;
plotOptions: ApexPlotOptions;
legend: ApexLegend;
};
@Component({
selector: 'app-dashboard-outcome',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.scss'],
})
export class DashboardOutcomeComponent implements OnInit {
@ViewChild("chart") chart!: ChartComponent;
public chartOptions!: Partial<ChartOptions>;
dashboardModel?: DashboardInOutModel;
public form!: UntypedFormGroup;
public dateFrom: Date = moment(new Date).add(-30, 'days').toDate();
public dateTo: Date = moment(new Date).add(0, 'days').toDate();
colors: string[] = ["#fd7f6f", "#7eb0d5", "#b2e061", "#bd7ebe", "#ffb55a", "#ffee65", "#beb9db", "#fdcce5", "#8bd3c7"];
constructor(
private fb: UntypedFormBuilder,
private httpClient: HttpClient,
private _decimalPipe: DecimalPipe
) {
}
ngOnInit() {
this.form = this.fb.group({
dateFrom: [
this.dateFrom,
[Validators.required],
],
dateTo: [
this.dateTo,
[Validators.required],
],
});
this.loadData();
}
onSubmitClick() {
}
private loadData(update?: boolean, category?: string) {
let params = new HttpParams()
.set('from', '2023-07-01')
.set('to', '2023-08-01')
.set('category', category || '')
;
this.httpClient
.get<DashboardInOutModel>(ApiRoutes.DashboardOutcome, { params: params })
.subscribe(response => {
this.dashboardModel = response;
var series = response.data.map(x => ({
x: x.name + ' (' + (this._decimalPipe.transform(x.value, '1.2-2') || '') + ')',
y: x.value
}));
var min = Math.min(...response.data.map(x => x.value));
var max = Math.max(...response.data.map(x => x.value));
var step = max / 10;
var ranges = [];
for (var i = 0; i < this.colors.length; i++) {
ranges.push({
from: i === 0 ? min : step * i,
to: i === this.colors.length - 1 ? max + 10 : step * (i + 1),
color: this.colors[this.colors.length - i - 1],
});
}
if (update) {
this.chart.updateSeries([{
data: series
}]);
} else {
this.setChartOptions(series, ranges);
}
});
}
private setChartOptions(data: any[], ranges: any[]) {
var self = this;
this.chartOptions = {
series: [{
data: data
}],
chart: {
type: 'treemap',
width: 600,
height: 600,
events: {
click: function (event, chartContext, config) {
var data = self.dashboardModel!.data[config.dataPointIndex];
self.loadData(true, data.id);
}
}
},
plotOptions: {
treemap: {
enableShades: true,
shadeIntensity: 0.5,
reverseNegativeShade: true,
colorScale: {
ranges: ranges
}
}
},
};
}
}
@@ -23,7 +23,7 @@ export const ROUTES: RouteInfo[] = [
badgeClass: '', badgeClass: '',
submenu: [ submenu: [
{ {
path: 'dashboard/dashboard', path: 'dashboard/rests',
title: 'MENUITEMS.DASHBOARD.LIST.DASHBOARD', title: 'MENUITEMS.DASHBOARD.LIST.DASHBOARD',
iconType: '', iconType: '',
icon: '', icon: '',
@@ -33,6 +33,28 @@ export const ROUTES: RouteInfo[] = [
badgeClass: '', badgeClass: '',
submenu: [], submenu: [],
}, },
{
path: 'dashboard/income',
title: 'MENUITEMS.DASHBOARD.LIST.INCOME',
iconType: '',
icon: '',
class: 'ml-menu',
groupTitle: false,
badge: '',
badgeClass: '',
submenu: [],
},
{
path: 'dashboard/outcome',
title: 'MENUITEMS.DASHBOARD.LIST.OUTCOME',
iconType: '',
icon: '',
class: 'ml-menu',
groupTitle: false,
badge: '',
badgeClass: '',
submenu: [],
},
], ],
}, },
@@ -23,3 +23,13 @@ export interface DashboardRestModel {
currencyQuantity: number; currencyQuantity: number;
balanceAtRate: number; balanceAtRate: number;
} }
export interface DashboardInOutModel {
data: DashboardInOutItemModel[];
}
export interface DashboardInOutItemModel {
id: string;
name: string;
value: number;
}
+1 -1
View File
@@ -3,7 +3,6 @@ import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { ReactiveFormsModule } from '@angular/forms'; import { ReactiveFormsModule } from '@angular/forms';
import { MAT_DATE_LOCALE } from '@angular/material/core';
// libs // libs
import { MatFormFieldModule } from '@angular/material/form-field'; import { MatFormFieldModule } from '@angular/material/form-field';
@@ -22,6 +21,7 @@ import { MatExpansionModule } from '@angular/material/expansion';
import { MatAutocompleteModule } from '@angular/material/autocomplete'; import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatMenuModule } from '@angular/material/menu'; import { MatMenuModule } from '@angular/material/menu';
import { MAT_DATE_LOCALE } from '@angular/material/core';
// app // app
import { PagesRoutingModule } from './pages-routing.module'; import { PagesRoutingModule } from './pages-routing.module';
+3 -1
View File
@@ -14,7 +14,9 @@
"DASHBOARD": { "DASHBOARD": {
"TEXT": "Dashboard", "TEXT": "Dashboard",
"LIST": { "LIST": {
"DASHBOARD": "Dashboard" "DASHBOARD": "Rests",
"INCOME": "Incomes",
"OUTCOME": "Outcomes"
} }
}, },
"ACCOUNTS": { "ACCOUNTS": {
+6 -2
View File
@@ -14,7 +14,9 @@
"DASHBOARD": { "DASHBOARD": {
"TEXT": "Панелі", "TEXT": "Панелі",
"LIST": { "LIST": {
"DASHBOARD": "Панель" "DASHBOARD": "Залишки",
"INCOME": "Надходження",
"OUTCOME": "Витрати"
} }
}, },
"ACCOUNTS": { "ACCOUNTS": {
@@ -99,5 +101,7 @@
"CURRENT.BALANCE": "Поточний баланс", "CURRENT.BALANCE": "Поточний баланс",
"HOME": "Головна", "HOME": "Головна",
"OTHER": "Інші", "OTHER": "Інші",
"TOTAL": "Всього" "TOTAL": "Всього",
"INCOME": "Надходження",
"OUTCOME": "Витрати"
} }
@@ -4,6 +4,7 @@ using MyOffice.Services.Dashboard.Domain;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Core.Extensions; using Core.Extensions;
@@ -62,4 +63,38 @@ public class DashboardService
return result; return result;
} }
public DashboardIncomeData GetDashboardIncomeData(Guid userId, DateTime from, DateTime to, Guid? category)
{
var data = category.HasValue
? _accountRepository.GetIncomeByCategory(userId, category.Value, from.ToUtc(), to.ToUtc())
: _accountRepository.GetIncomeByCategories(userId, from.ToUtc(), to.ToUtc());
return new DashboardIncomeData
{
Data = data.Select(x => new DashboardIncomeDataItem
{
Id = x.Id?.ToShort(),
Name = x.Name,
Value = x.Amount,
}).ToList()
};
}
public DashboardIncomeData GetDashboardOutcomeData(Guid userId, DateTime from, DateTime to, Guid? category)
{
var data = category.HasValue
? _accountRepository.GetOutcomeByCategory(userId, category.Value, from.ToUtc(), to.ToUtc())
: _accountRepository.GetOutcomeByCategories(userId, from.ToUtc(), to.ToUtc());
return new DashboardIncomeData
{
Data = data.Select(x => new DashboardIncomeDataItem
{
Id = x.Id?.ToShort(),
Name = x.Name,
Value = x.Amount,
}).ToList()
};
}
} }
@@ -28,3 +28,15 @@ public class DashboardRestData
public decimal? BalanceAtRate => Balance * (CurrencyRate * CurrencyQuantity); public decimal? BalanceAtRate => Balance * (CurrencyRate * CurrencyQuantity);
} }
public class DashboardIncomeData
{
public List<DashboardIncomeDataItem> Data { get; set; } = null!;
}
public class DashboardIncomeDataItem
{
public string? Id { get; set; } = null!;
public string Name { get; set; } = null!;
public decimal Value { get; set; }
}
@@ -1,9 +1,9 @@
namespace MyOffice.Web.Controllers; namespace MyOffice.Web.Controllers;
using Core.Extensions;
using Infrastructure.Attributes;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using MyOffice.Services.Account;
using MyOffice.Services.Item;
using Services.Dashboard; using Services.Dashboard;
[Authorize] [Authorize]
@@ -31,4 +31,21 @@ public class DashboardController : BaseApiController
return data; return data;
} }
[HttpGet("~/api/dashboard/income")]
public object Income(DateTime from, DateTime to, [AsGuid(true)] string? category)
{
var data = _dashboardService.GetDashboardIncomeData(UserId, from.StartOfDay(), to.EndOfDay(), category?.AsGuidNull());
return data;
}
[HttpGet("~/api/dashboard/outcome")]
public object Outcome(DateTime from, DateTime to, [AsGuid(true)] string? category)
{
var data = _dashboardService.GetDashboardOutcomeData(UserId, from.StartOfDay(), to.EndOfDay(), category?.AsGuidNull());
return data;
}
} }
+2
View File
@@ -69,6 +69,8 @@ using MyOffice.Services.Mapper;
//TODO: SPA all http requests -> services //TODO: SPA all http requests -> services
//TODO: Items, select category -> save url to allow refresh //TODO: Items, select category -> save url to allow refresh
//TODO: Accounts, select category -> save url to allow refresh //TODO: Accounts, select category -> save url to allow refresh
//TODO: report income
//TODO: report outcome
public class Program public class Program
{ {