From e7adad24f0a8095510d5cf46180e74b0ba933270 Mon Sep 17 00:00:00 2001 From: technbuzz Date: Sun, 31 Jul 2022 16:41:30 +0400 Subject: [PATCH] chart js is added added chart data remove the clutter used html5 datetime fixed the date padding and current date --- .../doughnut/doughnut.component.spec.ts | 24 ++++ .../summary/doughnut/doughnut.component.ts | 101 +++++++++++++++ .../src/app/summary/summary.module.ts | 9 +- .../src/app/summary/summary.page.html | 27 ++-- apps/kharchay/src/app/summary/summary.page.ts | 121 +++++++++--------- package-lock.json | 89 +++++++------ package.json | 5 +- 7 files changed, 252 insertions(+), 124 deletions(-) create mode 100644 apps/kharchay/src/app/summary/doughnut/doughnut.component.spec.ts create mode 100644 apps/kharchay/src/app/summary/doughnut/doughnut.component.ts diff --git a/apps/kharchay/src/app/summary/doughnut/doughnut.component.spec.ts b/apps/kharchay/src/app/summary/doughnut/doughnut.component.spec.ts new file mode 100644 index 0000000..69c7e28 --- /dev/null +++ b/apps/kharchay/src/app/summary/doughnut/doughnut.component.spec.ts @@ -0,0 +1,24 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DoughnutComponent } from './doughnut.component'; + +describe('DoughnutComponent', () => { + let component: DoughnutComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [DoughnutComponent], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(DoughnutComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/apps/kharchay/src/app/summary/doughnut/doughnut.component.ts b/apps/kharchay/src/app/summary/doughnut/doughnut.component.ts new file mode 100644 index 0000000..d0c506e --- /dev/null +++ b/apps/kharchay/src/app/summary/doughnut/doughnut.component.ts @@ -0,0 +1,101 @@ +import { AfterViewInit, Component, ElementRef, Input, OnInit, ViewChild } from '@angular/core'; +import { ArcElement, Chart, Legend, PieController, Tooltip } from 'chart.js'; + +@Component({ + selector: 'kh-doughnut', + template: ` +
+ + +
+ ` +}) +export class DoughnutComponent implements OnInit, AfterViewInit { + @Input() set chartData(data: number[]) { + this.updateData(this.chart, data) + } + + @Input() set chartLabel(label: string[]) { + this.updateLabels(this.chart, label) + } + + + @ViewChild('container') container!: ElementRef; + chart!: Chart; + + ngOnInit(): void {} + + ngAfterViewInit(): void { + Chart.register(PieController, ArcElement, Legend, Tooltip); + this.chart = new Chart(this.container.nativeElement, { + type: 'doughnut', + data: { + labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'], + datasets: [ + { + label: '# of Votes', + hoverOffset: 4, + data: [12, 19, 3, 5, 2, 3], + backgroundColor: [ + 'tomato', + '#FF9020', + '#059BFF', + 'rebeccapurple', + 'gold', + '#FF6384', + 'indigo', + '#FFC234', + ] + }, + ], + }, + options: { + layout: { + padding: 10, + }, + plugins: { + legend: { + display: true, + position: 'bottom' + } + } + } + }); + + } + + updateLabels(chart: Chart, label: string[]) { + if(!chart) return; + chart.data.labels = []; + chart.data.labels = [...label] + chart.update(); + } + + updateData(chart: Chart, data:number[]) { + if(!chart) return; + chart.data.datasets.forEach((dataset) => { + dataset.data == []; + }); + chart.data.datasets.forEach((dataset) => { + dataset.data = [...data]; + }); + chart.update(); + + } + + addData(chart: Chart, label: string[], data: number[]) { + chart.data.labels = [...label] + chart.data.datasets.forEach((dataset) => { + dataset.data = [...data]; + }); + chart.update(); + } + + removeData(chart: Chart) { + chart.data.labels = []; + chart.data.datasets.forEach((dataset) => { + dataset.data == []; + }); + chart.update(); + } +} diff --git a/apps/kharchay/src/app/summary/summary.module.ts b/apps/kharchay/src/app/summary/summary.module.ts index 4fad6c8..6fedf90 100644 --- a/apps/kharchay/src/app/summary/summary.module.ts +++ b/apps/kharchay/src/app/summary/summary.module.ts @@ -7,12 +7,13 @@ import { IonicModule } from '@ionic/angular'; import { SummaryPage } from './summary.page'; import { PieComponent } from './pie/pie'; +import { DoughnutComponent } from './doughnut/doughnut.component'; const routes: Routes = [ { path: '', - component: SummaryPage - } + component: SummaryPage, + }, ]; @NgModule({ @@ -20,8 +21,8 @@ const routes: Routes = [ CommonModule, FormsModule, IonicModule, - RouterModule.forChild(routes) + RouterModule.forChild(routes), ], - declarations: [SummaryPage, PieComponent], + declarations: [SummaryPage, PieComponent, DoughnutComponent], }) export class SummaryPageModule {} diff --git a/apps/kharchay/src/app/summary/summary.page.html b/apps/kharchay/src/app/summary/summary.page.html index c5733cf..1c84bef 100644 --- a/apps/kharchay/src/app/summary/summary.page.html +++ b/apps/kharchay/src/app/summary/summary.page.html @@ -9,26 +9,17 @@ - Month - {{ month | date : 'MMMM y'}} + - + + + + - + -
+
diff --git a/apps/kharchay/src/app/summary/summary.page.ts b/apps/kharchay/src/app/summary/summary.page.ts index 404643b..a7feae9 100644 --- a/apps/kharchay/src/app/summary/summary.page.ts +++ b/apps/kharchay/src/app/summary/summary.page.ts @@ -1,114 +1,113 @@ -import { Component, OnInit, ViewChild, ViewContainerRef, ComponentFactoryResolver, AfterViewInit } from '@angular/core'; -import { Stepper } from '../shared/stepper'; +import { + AfterViewInit, + Component, ElementRef, OnInit, + ViewChild +} from '@angular/core'; import { collection, Firestore } from '@angular/fire/firestore'; -import { Observable } from 'rxjs'; -import { BaseExpense } from '../home/expense-base.model'; +import { fromEvent, Observable } from 'rxjs'; +import { Stepper } from '../shared/stepper'; +import { + DocumentData, + query, + where +} from '@firebase/firestore'; +import endOfMonth from 'date-fns/esm/endOfMonth'; +import startOfMonth from 'date-fns/esm/startOfMonth'; -// import { groupBy, forIn, reduce } from 'lodash'; +import forIn from 'lodash-es/forIn'; import groupBy from 'lodash-es/groupBy'; -import forIn from 'lodash-es/forIn'; import reduce from 'lodash-es/reduce'; -import startOfMonth from 'date-fns/esm/startOfMonth'; -import endOfMonth from 'date-fns/esm/endOfMonth'; -import { GestureController, IonDatetime } from '@ionic/angular'; import { collectionData } from 'rxfire/firestore'; -import { CollectionReference, DocumentData, query, where } from '@firebase/firestore'; +import { map, switchMap } from 'rxjs/operators'; +import format from 'date-fns/esm/format' + @Component({ selector: 'app-summary', templateUrl: './summary.page.html', styleUrls: ['./summary.page.scss'], }) -export class SummaryPage extends Stepper implements OnInit, AfterViewInit { - @ViewChild('container', { read: ViewContainerRef, static: true }) - container!: ViewContainerRef; +export class SummaryPage extends Stepper implements AfterViewInit { @ViewChild('dateItem') dateItem: any; - @ViewChild('expenseMonth', { static: true }) - expenseMonth!: IonDatetime; + @ViewChild('expenseDate', { static: true }) + expenseDate!: ElementRef; chartData: number[] = []; chartLabels: string[] = []; - month = new Date().toISOString(); - loading = true; + month = format(new Date(), 'yyyy-MM') + // month = new Date().toISOString(); total = 0; - expRef!: CollectionReference; expenses$!: Observable; - constructor( - private afs: Firestore, - private gestureCtrl: GestureController - ) { + private expensesRef = collection(this.afs, 'expense'); + constructor(private afs: Firestore) { super(); } - - ngOnInit() { - this.loadBasic(); - } - + ngAfterViewInit() { - const gesture = this.gestureCtrl.create({ - el: this.dateItem.el, - gestureName: 'move', - onEnd: detail => { - const type = detail.type; - const currentX = detail.currentX; - const deltaX = detail.deltaX; - const velocityX = detail.velocityX; - if (deltaX > 0) { - // this.addMonth(this.month, this.expenseMonth); - } else { - // this.subMonth(this.month, this.expenseMonth); - } - } - }); + console.log('month', format(new Date(), 'yyyy-MM')) + fromEvent(this.expenseDate.nativeElement, 'change').pipe( + map(( value: any ) => value.currentTarget.value), + map(value => { + const start = startOfMonth(new Date(value)); + const end = endOfMonth(new Date(this.month)); + return { start, end } + }), + map(value => this.buildQuery(value)), + switchMap(value => collectionData(value)) + ).subscribe((event) => { + // + this.generateDataForChart(event) + }) + } - gesture.enable(); + private buildQuery(value: {start: Date, end: Date}) { + const expensesQuery = query( + this.expensesRef, + where('date', '>=', value.start), + where('date', '<=', value.end) + ); +// 8001717, + return expensesQuery } + loadBasic() { const basicStartMonth = startOfMonth(new Date(this.month)); const basicEndMonth = endOfMonth(new Date(this.month)); - this.loading = true; - const expensesRef = collection(this.afs, 'expense'); - const expensesQuery = query(expensesRef, where('date', '>=', basicStartMonth),where('date', '<=', basicEndMonth)); - - // this.expRef = collection(this.afs, 'expense', ref => - // ref - // .where('date', '>=', basicStartMonth) - // .where('date', '<=', basicEndMonth) - // ); + const expensesQuery = query( + this.expensesRef, + where('date', '>=', basicStartMonth), + where('date', '<=', basicEndMonth) + ); // Finding Total this.expenses$ = collectionData(expensesQuery); - // this.expenses$ = this.expRef.valueChanges(); - this.expenses$.forEach(values => { + this.expenses$.forEach((values) => { this.generateDataForChart(values); }); } generateDataForChart(values: any) { - this.chartData = []; this.chartLabels = []; - // Previously for format like {category:'food'} // grouped = lodash.groupBy(values, ('category.title')); // FIXME: Replace lodash with groupBy rxjs function // Backward compat becuse new format is {category:{title:'food'}} - const grouped = groupBy(values, (item) => item.category.title ? item.category.title : item.category); + const grouped = groupBy(values, (item) => + item.category.title ? item.category.title : item.category + ); forIn(grouped, (value, key, item) => { this.chartLabels.push(key.toUpperCase()); - this.chartData.push(reduce( - value, (sum, n) => sum + Number(n.price), 0) - ); + this.chartData.push(reduce(value, (sum, n) => sum + Number(n.price), 0)); }); - } } diff --git a/package-lock.json b/package-lock.json index d611321..83de1c6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,8 +22,9 @@ "@angular/router": "~13.3.0", "@capacitor/android": "^3.4.3", "@capacitor/core": "^3.4.3", - "@ionic/angular": "^6.1.0", + "@ionic/angular": "^6.2.0", "@nrwl/angular": "13.10.2", + "chart.js": "^3.8.2", "date-fns": "^2.28.0", "firebase": "^9.6.11", "lodash-es": "^4.17.21", @@ -4015,11 +4016,11 @@ "devOptional": true }, "node_modules/@ionic/angular": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@ionic/angular/-/angular-6.1.0.tgz", - "integrity": "sha512-Kf/C/6wSM0xwYRWYNlc7bU4oYwPoq8ID7nMuysY0K8oyLycM8XokujqBqbJXCbbfWiyrbxrlKceMTJUuGj5Tfg==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@ionic/angular/-/angular-6.2.0.tgz", + "integrity": "sha512-gdK25B5l0DKY0+fiC2vBytb4WnA2bgQacYgLCAJ6aB2w4GhXLWcZsZCh+raTx3JC1OrewDSCJkgM/m2k9ozv1w==", "dependencies": { - "@ionic/core": "^6.1.0", + "@ionic/core": "^6.2.0", "jsonc-parser": "^3.0.0", "tslib": "^2.0.0" }, @@ -4046,12 +4047,12 @@ } }, "node_modules/@ionic/core": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.1.0.tgz", - "integrity": "sha512-Sx64Ue0oGSlBRcBSkIUNHbhz4k+33+VLjaIFGo4ko99yTpD+1B2/GAPwkb1DRHcoipRcPoyq37FakHjZsnolyg==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.2.0.tgz", + "integrity": "sha512-3qUNsVcSAdrjhIhPr5M2RRnh+1xuc9RFaxoeUgI9xQ0YjTmn2FWiH4urDCXuE/rZAwnvHF4X17P0L2EqCPSfWA==", "dependencies": { - "@stencil/core": "^2.14.2", - "ionicons": "^6.0.0", + "@stencil/core": "^2.16.0", + "ionicons": "^6.0.2", "tslib": "^2.1.0" } }, @@ -5516,9 +5517,9 @@ } }, "node_modules/@stencil/core": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/@stencil/core/-/core-2.15.0.tgz", - "integrity": "sha512-58+FPFpJCJScd5nmqVsZN+qk7aui57wFcMHWzySr1SQzoY8Efst9OPG7XRf27UsNj1DNeEdYWTtdrTfJyn3mZg==", + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/@stencil/core/-/core-2.17.1.tgz", + "integrity": "sha512-ErjQsNALgZQ9SYeBHhqwL1UO+Zbptwl3kwrRJC2tGlc3G/T6UvPuaKr+PGsqI+CZGia+0+R5EELQvFu74mYeIg==", "bin": { "stencil": "bin/stencil" }, @@ -16561,6 +16562,11 @@ "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" }, + "node_modules/chart.js": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.8.2.tgz", + "integrity": "sha512-7rqSlHWMUKFyBDOJvmFGW2lxULtcwaPLegDjX/Nu5j6QybY+GCiQkEY+6cqHw62S5tcwXMD8Y+H5OBGoR7d+ZQ==" + }, "node_modules/check-more-types": { "version": "2.24.0", "resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz", @@ -22427,17 +22433,17 @@ } }, "node_modules/ionicons": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ionicons/-/ionicons-6.0.1.tgz", - "integrity": "sha512-xQekOJsxH82O7oB+3F60zeRggCdND9pJ/k0E6IJDVUGGlCj5mlyFqNgxUimytKgstPGv3S+3EmCxjefvtGgWUg==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/ionicons/-/ionicons-6.0.2.tgz", + "integrity": "sha512-AyKfFaUKVoBz4eB8XkU7H1R5HFnVsgq5ijqSdbXC0lES9PDK/J6LUQz6XUJq0mVVQF5k9kczSPOLMW3mszG0mQ==", "dependencies": { - "@stencil/core": "~2.12.0" + "@stencil/core": "~2.16.0" } }, "node_modules/ionicons/node_modules/@stencil/core": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/@stencil/core/-/core-2.12.1.tgz", - "integrity": "sha512-u24TZ+FEvjnZt5ZgIkLjLpUNsO6Ml3mUZqwmqk81w6RWWz75hgB5p4RFI5rvuErFeh2xvMIGo+pNdG24XUBz1A==", + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/@stencil/core/-/core-2.16.1.tgz", + "integrity": "sha512-s/UJp9qxExL3DyQPT70kiuWeb3AdjbUZM+5lEIXn30I2DLcLYPOPXfsoWJODieQywq+3vPiLZeIdkoqjf6jcSw==", "bin": { "stencil": "bin/stencil" }, @@ -37253,11 +37259,11 @@ "devOptional": true }, "@ionic/angular": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@ionic/angular/-/angular-6.1.0.tgz", - "integrity": "sha512-Kf/C/6wSM0xwYRWYNlc7bU4oYwPoq8ID7nMuysY0K8oyLycM8XokujqBqbJXCbbfWiyrbxrlKceMTJUuGj5Tfg==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@ionic/angular/-/angular-6.2.0.tgz", + "integrity": "sha512-gdK25B5l0DKY0+fiC2vBytb4WnA2bgQacYgLCAJ6aB2w4GhXLWcZsZCh+raTx3JC1OrewDSCJkgM/m2k9ozv1w==", "requires": { - "@ionic/core": "^6.1.0", + "@ionic/core": "^6.2.0", "jsonc-parser": "^3.0.0", "tslib": "^2.0.0" } @@ -37274,12 +37280,12 @@ } }, "@ionic/core": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.1.0.tgz", - "integrity": "sha512-Sx64Ue0oGSlBRcBSkIUNHbhz4k+33+VLjaIFGo4ko99yTpD+1B2/GAPwkb1DRHcoipRcPoyq37FakHjZsnolyg==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.2.0.tgz", + "integrity": "sha512-3qUNsVcSAdrjhIhPr5M2RRnh+1xuc9RFaxoeUgI9xQ0YjTmn2FWiH4urDCXuE/rZAwnvHF4X17P0L2EqCPSfWA==", "requires": { - "@stencil/core": "^2.14.2", - "ionicons": "^6.0.0", + "@stencil/core": "^2.16.0", + "ionicons": "^6.0.2", "tslib": "^2.1.0" } }, @@ -38471,9 +38477,9 @@ } }, "@stencil/core": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/@stencil/core/-/core-2.15.0.tgz", - "integrity": "sha512-58+FPFpJCJScd5nmqVsZN+qk7aui57wFcMHWzySr1SQzoY8Efst9OPG7XRf27UsNj1DNeEdYWTtdrTfJyn3mZg==" + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/@stencil/core/-/core-2.17.1.tgz", + "integrity": "sha512-ErjQsNALgZQ9SYeBHhqwL1UO+Zbptwl3kwrRJC2tGlc3G/T6UvPuaKr+PGsqI+CZGia+0+R5EELQvFu74mYeIg==" }, "@storybook/addon-actions": { "version": "6.4.22", @@ -47033,6 +47039,11 @@ "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" }, + "chart.js": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.8.2.tgz", + "integrity": "sha512-7rqSlHWMUKFyBDOJvmFGW2lxULtcwaPLegDjX/Nu5j6QybY+GCiQkEY+6cqHw62S5tcwXMD8Y+H5OBGoR7d+ZQ==" + }, "check-more-types": { "version": "2.24.0", "resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz", @@ -51545,17 +51556,17 @@ } }, "ionicons": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ionicons/-/ionicons-6.0.1.tgz", - "integrity": "sha512-xQekOJsxH82O7oB+3F60zeRggCdND9pJ/k0E6IJDVUGGlCj5mlyFqNgxUimytKgstPGv3S+3EmCxjefvtGgWUg==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/ionicons/-/ionicons-6.0.2.tgz", + "integrity": "sha512-AyKfFaUKVoBz4eB8XkU7H1R5HFnVsgq5ijqSdbXC0lES9PDK/J6LUQz6XUJq0mVVQF5k9kczSPOLMW3mszG0mQ==", "requires": { - "@stencil/core": "~2.12.0" + "@stencil/core": "~2.16.0" }, "dependencies": { "@stencil/core": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/@stencil/core/-/core-2.12.1.tgz", - "integrity": "sha512-u24TZ+FEvjnZt5ZgIkLjLpUNsO6Ml3mUZqwmqk81w6RWWz75hgB5p4RFI5rvuErFeh2xvMIGo+pNdG24XUBz1A==" + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/@stencil/core/-/core-2.16.1.tgz", + "integrity": "sha512-s/UJp9qxExL3DyQPT70kiuWeb3AdjbUZM+5lEIXn30I2DLcLYPOPXfsoWJODieQywq+3vPiLZeIdkoqjf6jcSw==" } } }, diff --git a/package.json b/package.json index 36048e2..971a00f 100644 --- a/package.json +++ b/package.json @@ -30,8 +30,9 @@ "@angular/router": "~13.3.0", "@capacitor/android": "^3.4.3", "@capacitor/core": "^3.4.3", - "@ionic/angular": "^6.1.0", + "@ionic/angular": "^6.2.0", "@nrwl/angular": "13.10.2", + "chart.js": "^3.8.2", "date-fns": "^2.28.0", "firebase": "^9.6.11", "lodash-es": "^4.17.21", @@ -82,4 +83,4 @@ "typescript": "~4.6.2", "webpack": "^5.64.0" } -} \ No newline at end of file +}