From 9862e5af251982a355271bd8ede6a270628cad23 Mon Sep 17 00:00:00 2001 From: Peter Date: Sun, 24 Sep 2023 19:23:52 +0200 Subject: [PATCH] Add chart, adjust html, logic and remove unused code --- .vscode/settings.json | 2 +- api/pom.xml | 6 + .../main/java/com/rossa/api/config/data.sql | 12 +- rossa-tech-cli/package-lock.json | 40 +++++ rossa-tech-cli/package.json | 4 +- rossa-tech-cli/src/app/app.module.ts | 8 +- .../dashboard/dashboard.component.html | 22 ++- .../dashboard/dashboard.component.ts | 74 ++++---- .../subcomponents/chart/chart.component.html | 5 + .../chart.component.scss} | 0 .../chart/chart.component.spec.ts | 21 +++ .../subcomponents/chart/chart.component.ts | 53 ++++++ .../consumption-last-year.component.html | 49 ----- .../consumption-last-year.component.spec.ts | 23 --- .../consumption-last-year.component.ts | 145 --------------- .../meter-data-list.component.html | 2 + .../meter-data-list.component.ts | 20 ++- .../meter-data-wrapper.component.html | 3 - .../meter-data-wrapper.component.scss | 0 .../meter-data-wrapper.component.spec.ts | 23 --- .../meter-data-wrapper.component.ts | 26 --- .../src/app/core/dataModels/Meter.ts | 12 +- .../src/app/core/dataModels/Meterdata.ts | 65 +++++-- .../src/app/core/services/global.service.ts | 16 +- .../app/core/services/meterData.service.ts | 168 ++++++++++++++++++ .../meter-data-add-dialog.component.html | 2 +- rossa-tech-cli/src/assets/icon.png | Bin 0 -> 22842 bytes 27 files changed, 442 insertions(+), 359 deletions(-) create mode 100644 rossa-tech-cli/src/app/components/subcomponents/chart/chart.component.html rename rossa-tech-cli/src/app/components/subcomponents/{consumption-last-year/consumption-last-year.component.scss => chart/chart.component.scss} (100%) create mode 100644 rossa-tech-cli/src/app/components/subcomponents/chart/chart.component.spec.ts create mode 100644 rossa-tech-cli/src/app/components/subcomponents/chart/chart.component.ts delete mode 100644 rossa-tech-cli/src/app/components/subcomponents/consumption-last-year/consumption-last-year.component.html delete mode 100644 rossa-tech-cli/src/app/components/subcomponents/consumption-last-year/consumption-last-year.component.spec.ts delete mode 100644 rossa-tech-cli/src/app/components/subcomponents/consumption-last-year/consumption-last-year.component.ts delete mode 100644 rossa-tech-cli/src/app/components/subcomponents/meter-data-wrapper/meter-data-wrapper.component.html delete mode 100644 rossa-tech-cli/src/app/components/subcomponents/meter-data-wrapper/meter-data-wrapper.component.scss delete mode 100644 rossa-tech-cli/src/app/components/subcomponents/meter-data-wrapper/meter-data-wrapper.component.spec.ts delete mode 100644 rossa-tech-cli/src/app/components/subcomponents/meter-data-wrapper/meter-data-wrapper.component.ts create mode 100644 rossa-tech-cli/src/app/core/services/meterData.service.ts create mode 100644 rossa-tech-cli/src/assets/icon.png diff --git a/.vscode/settings.json b/.vscode/settings.json index c851379..f4578e7 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -18,6 +18,6 @@ "titleBar.activeForeground": "#e7e7e7", "titleBar.inactiveBackground": "#4990b599", "titleBar.inactiveForeground": "#e7e7e799" - }, +}, "peacock.color": "#4990b5" } diff --git a/api/pom.xml b/api/pom.xml index 3eb979f..4f3b258 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -24,6 +24,12 @@ 1.8 + + + org.springframework.boot + spring-boot-starter-actuator + + org.springframework.boot spring-boot-starter-web diff --git a/api/src/main/java/com/rossa/api/config/data.sql b/api/src/main/java/com/rossa/api/config/data.sql index ea2cfd5..34d6229 100644 --- a/api/src/main/java/com/rossa/api/config/data.sql +++ b/api/src/main/java/com/rossa/api/config/data.sql @@ -23,11 +23,11 @@ INSERT INTO meter_data (type, date, amount, meter_id) VALUES ('WATER', '2022-01- INSERT INTO meter_data (type, date, amount, meter_id) VALUES ('WATER', '2022-12-31 00:00:00', 464.00, 3); INSERT INTO meter_data (type, date, amount, meter_id) VALUES ('WATER', '2021-01-01 00:00:00', 153.00, 3); INSERT INTO meter_data (type, date, amount, meter_id) VALUES ('WATER', '2021-12-31 00:00:00', 288.00, 3); -INSERT INTO meter_data (type, date, amount, meter_id) VALUES ('WATER', '2020-03-03 00:00:00', 153.00, 3); -INSERT INTO meter_data (type, date, amount, meter_id) VALUES ('WATER', '2020-12-31 00:00:00', 288.00, 3); +INSERT INTO meter_data (type, date, amount, meter_id) VALUES ('WATER', '2020-03-03 00:00:00', 149.00, 3); +INSERT INTO meter_data (type, date, amount, meter_id) VALUES ('WATER', '2020-12-31 00:00:00', 153.00, 3); -INSERT INTO meter_data (type, date, amount, meter_id) VALUES ('ENERGY', '2023-05-31 00:00:00', 524.00, 3); -INSERT INTO meter_data (type, date, amount, meter_id) VALUES ('ENERGY', '2023-04-26 00:00:00', 496.00, 3); +INSERT INTO meter_data (type, date, amount, meter_id) VALUES ('WATER', '2023-05-31 00:00:00', 524.00, 3); +INSERT INTO meter_data (type, date, amount, meter_id) VALUES ('WATER', '2023-04-26 00:00:00', 496.00, 3); INSERT INTO meter_data (type, date, amount, meter_id) VALUES ('WATER', '2022-01-01 00:00:00', 116.00, 4); INSERT INTO meter_data (type, date, amount, meter_id) VALUES ('WATER', '2022-12-31 00:00:00', 189.00, 4); @@ -37,6 +37,10 @@ INSERT INTO meter_data (type, date, amount, meter_id) VALUES ('WATER', '2020-03- INSERT INTO meter_data (type, date, amount, meter_id) VALUES ('WATER', '2020-12-31 00:00:00', 81.00, 4); +INSERT INTO meter_data (type, date, amount, meter_id) VALUES ('ENERGY', '2023-08-31 00:00:00', 13944.00, 1); +INSERT INTO meter_data (type, date, amount, meter_id) VALUES ('ENERGY', '2023-08-31 00:00:00', 14496.00, 2); + + /*************** diff --git a/rossa-tech-cli/package-lock.json b/rossa-tech-cli/package-lock.json index 5469558..57a60a1 100644 --- a/rossa-tech-cli/package-lock.json +++ b/rossa-tech-cli/package-lock.json @@ -19,6 +19,8 @@ "@angular/platform-browser-dynamic": "^16.0.1", "@angular/router": "^16.0.1", "@mdi/angular-material": "^7.2.96", + "chart.js": "^4.3.0", + "ng2-charts": "^5.0.3", "rxjs": "~7.8.0", "tslib": "^2.3.0", "zone.js": "~0.13.0" @@ -2929,6 +2931,11 @@ "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", "dev": true }, + "node_modules/@kurkle/color": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", + "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==" + }, "node_modules/@leichtgewicht/ip-codec": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", @@ -5156,6 +5163,17 @@ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, + "node_modules/chart.js": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.3.0.tgz", + "integrity": "sha512-ynG0E79xGfMaV2xAHdbhwiPLczxnNNnasrmPEXriXsPJGjmhOBYzFVEsB65w2qMDz+CaBJJuJD0inE/ab/h36g==", + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=7" + } + }, "node_modules/chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -8353,6 +8371,11 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" + }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -9164,6 +9187,23 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, + "node_modules/ng2-charts": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/ng2-charts/-/ng2-charts-5.0.3.tgz", + "integrity": "sha512-/lTY64tiCN/pJPx+oIWRWOhtCk+ZbAU9yAUDNnRJwhe+a8ajcO5yS0tVOm5k7pj3doVp9+UdBRahyt6woJ95Rw==", + "dependencies": { + "lodash-es": "^4.17.15", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/cdk": ">=16.0.0", + "@angular/common": ">=16.0.0", + "@angular/core": ">=16.0.0", + "@angular/platform-browser": ">=16.0.0", + "chart.js": "^3.4.0 || ^4.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, "node_modules/nice-napi": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz", diff --git a/rossa-tech-cli/package.json b/rossa-tech-cli/package.json index 72b8c35..2908724 100644 --- a/rossa-tech-cli/package.json +++ b/rossa-tech-cli/package.json @@ -21,6 +21,8 @@ "@angular/platform-browser-dynamic": "^16.0.1", "@angular/router": "^16.0.1", "@mdi/angular-material": "^7.2.96", + "chart.js": "^4.3.0", + "ng2-charts": "^5.0.3", "rxjs": "~7.8.0", "tslib": "^2.3.0", "zone.js": "~0.13.0" @@ -38,4 +40,4 @@ "karma-jasmine-html-reporter": "~2.0.0", "typescript": "~4.9.5" } -} \ No newline at end of file +} diff --git a/rossa-tech-cli/src/app/app.module.ts b/rossa-tech-cli/src/app/app.module.ts index ceb01ae..edef613 100644 --- a/rossa-tech-cli/src/app/app.module.ts +++ b/rossa-tech-cli/src/app/app.module.ts @@ -10,8 +10,6 @@ import { AppComponent } from './app.component'; import { LoginComponent } from './components/loginPage/login.component'; import { DashboardComponent } from './components/dashboard/dashboard.component'; import { MeterDataListComponent } from './components/subcomponents/meter-data-list/meter-data-list.component'; -import { ConsumptionLastYearComponent } from './components/subcomponents/consumption-last-year/consumption-last-year.component'; -import { MeterDataWrapperComponent } from './components/subcomponents/meter-data-wrapper/meter-data-wrapper.component'; import { HeaderComponent } from './components/header/header.component'; import { LoaderComponent } from './components/loader/loader.component'; import { MeterDataAddDialogComponent } from './dialogs/meter-data-add-dialog/meter-data-add-dialog.component'; @@ -35,6 +33,8 @@ import { MatButtonModule } from '@angular/material/button'; import { MatDatepickerModule } from '@angular/material/datepicker'; import { MatNativeDateModule } from '@angular/material/core'; import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { ChartComponent } from './components/subcomponents/chart/chart.component'; +import { NgChartsModule } from 'ng2-charts'; @NgModule({ declarations: [ @@ -44,10 +44,9 @@ import { MatSnackBarModule } from '@angular/material/snack-bar'; LoaderComponent, HeaderComponent, MeterDataListComponent, - ConsumptionLastYearComponent, - MeterDataWrapperComponent, MeterDataAddDialogComponent, SnackbarComponent, + ChartComponent, ], imports: [ BrowserModule, @@ -72,6 +71,7 @@ import { MatSnackBarModule } from '@angular/material/snack-bar'; MatDatepickerModule, MatNativeDateModule, MatSnackBarModule, + NgChartsModule, ], providers: [ MatDatepickerModule, diff --git a/rossa-tech-cli/src/app/components/dashboard/dashboard.component.html b/rossa-tech-cli/src/app/components/dashboard/dashboard.component.html index 0c6ff71..ea7679a 100644 --- a/rossa-tech-cli/src/app/components/dashboard/dashboard.component.html +++ b/rossa-tech-cli/src/app/components/dashboard/dashboard.component.html @@ -3,16 +3,26 @@ - - + - + - + + + + + + + diff --git a/rossa-tech-cli/src/app/components/dashboard/dashboard.component.ts b/rossa-tech-cli/src/app/components/dashboard/dashboard.component.ts index ff6b3b4..44f5531 100644 --- a/rossa-tech-cli/src/app/components/dashboard/dashboard.component.ts +++ b/rossa-tech-cli/src/app/components/dashboard/dashboard.component.ts @@ -1,12 +1,16 @@ import { HttpErrorResponse } from '@angular/common/http'; -import { Component } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; import { LoaderService } from 'src/app/components/loader/loader.service'; -import { MeterData } from 'src/app/core/dataModels/Meterdata'; +import { + MeterData, + PreparedMeterData, +} from 'src/app/core/dataModels/Meterdata'; import { UsageType } from 'src/app/core/dataModels/UsageType'; import { DatabaseService } from 'src/app/core/services/database.service'; import { ErrorService } from 'src/app/core/services/error.service'; import { GlobalService } from 'src/app/core/services/global.service'; +import { MeterDataService } from 'src/app/core/services/meterData.service'; import { MeterDataAddDialogComponent } from 'src/app/dialogs/meter-data-add-dialog/meter-data-add-dialog.component'; @Component({ @@ -17,35 +21,45 @@ import { MeterDataAddDialogComponent } from 'src/app/dialogs/meter-data-add-dial export class DashboardComponent { usageTypes = UsageType; - meterDataEnergy: MeterData[] = []; + preparedMeterData: PreparedMeterData[] = []; + + meterDataEnergy: PreparedMeterData[] = []; meterDataWater: MeterData[] = []; - meterDataEnergyDTO: MeterData[] = []; - meterDataWaterDTO: MeterData[] = []; - - energyAverageAmountLastYear1Log: number = 0; - energyAverageAmountLastYear1Hem: number = 0; - waterAverageAmountLastYear: number = 0; - displayedColumns: string[] = ['date', 'amount', 'meter']; constructor( - private dataService: DatabaseService, + private dataBaseService: DatabaseService, + public meterDataService: MeterDataService, private loaderService: LoaderService, private globalService: GlobalService, private errorService: ErrorService, private dialog: MatDialog - ) { + ) {} + + ngOnInit(): void { this.loadMeterData(); } loadMeterData(): void { this.loaderService.show(); - this.dataService.getMeterData().subscribe({ + this.dataBaseService.getMeterData().subscribe({ next: (data) => { - this.splitMeterData(data); + this.meterDataService.prepareMeterData(data); + + this.meterDataService.energyMeterData.subscribe({ + next: (data: PreparedMeterData[]) => { + this.meterDataEnergy = data; + }, + }); + this.meterDataService.preparedMeterData.subscribe({ + next: (data: PreparedMeterData[]) => { + this.preparedMeterData = data; + console.log({ data }); + }, + }); + this.loaderService.hide(); - console.log('Meter data:', data); }, error: (error: HttpErrorResponse) => { this.errorService.handleError(error); @@ -54,31 +68,8 @@ export class DashboardComponent { }); } - splitMeterData(meterData: MeterData[]): void { - if (meterData) { - meterData.forEach((data) => { - if (data.type === this.usageTypes.ENERGY) { - this.meterDataEnergy.push(data); - } else if (data.type === this.usageTypes.WATER) { - this.meterDataWater.push(data); - } - }); - this.initEnergyMeterData(); - this.initWaterMeterDate(); - } - } - - initEnergyMeterData(): void { - this.meterDataEnergy = this.globalService.sortMeterData( - this.meterDataEnergy - ); - this.meterDataEnergyDTO = this.meterDataEnergy; - } - - initWaterMeterDate(): void { - this.meterDataWater = this.globalService.sortMeterData(this.meterDataWater); - - this.meterDataWaterDTO = this.meterDataWater; + test(): void { + this.meterDataService.getMeterDataByType(this.usageTypes.ENERGY); } addMeterData(): void { @@ -92,8 +83,9 @@ export class DashboardComponent { console.log({ newMeterData }); if (newMeterData) { - this.dataService.addMeterData(newMeterData).subscribe({ + this.dataBaseService.addMeterData(newMeterData).subscribe({ next: (meterData: MeterData) => { + // todo: refresh data console.log({ meterData }); }, error: (error: HttpErrorResponse) => { diff --git a/rossa-tech-cli/src/app/components/subcomponents/chart/chart.component.html b/rossa-tech-cli/src/app/components/subcomponents/chart/chart.component.html new file mode 100644 index 0000000..6fbdfdc --- /dev/null +++ b/rossa-tech-cli/src/app/components/subcomponents/chart/chart.component.html @@ -0,0 +1,5 @@ +
+ + +
\ No newline at end of file diff --git a/rossa-tech-cli/src/app/components/subcomponents/consumption-last-year/consumption-last-year.component.scss b/rossa-tech-cli/src/app/components/subcomponents/chart/chart.component.scss similarity index 100% rename from rossa-tech-cli/src/app/components/subcomponents/consumption-last-year/consumption-last-year.component.scss rename to rossa-tech-cli/src/app/components/subcomponents/chart/chart.component.scss diff --git a/rossa-tech-cli/src/app/components/subcomponents/chart/chart.component.spec.ts b/rossa-tech-cli/src/app/components/subcomponents/chart/chart.component.spec.ts new file mode 100644 index 0000000..defe7fa --- /dev/null +++ b/rossa-tech-cli/src/app/components/subcomponents/chart/chart.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ChartComponent } from './chart.component'; + +describe('ChartComponent', () => { + let component: ChartComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [ChartComponent] + }); + fixture = TestBed.createComponent(ChartComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/rossa-tech-cli/src/app/components/subcomponents/chart/chart.component.ts b/rossa-tech-cli/src/app/components/subcomponents/chart/chart.component.ts new file mode 100644 index 0000000..2d8eb0f --- /dev/null +++ b/rossa-tech-cli/src/app/components/subcomponents/chart/chart.component.ts @@ -0,0 +1,53 @@ +import { + Component, + ElementRef, + Input, + SimpleChanges, + ViewChild, +} from '@angular/core'; +import { + MeterData, + MeterDataForYear, + PreparedMeterData, +} from 'src/app/core/dataModels/Meterdata'; +import { ChartType, ChartOptions, ChartDataset } from 'chart.js'; + +// import Chart from 'chart.js/auto'; +import { BaseChartDirective } from 'ng2-charts'; + +@Component({ + selector: 'app-chart', + templateUrl: './chart.component.html', + styleUrls: ['./chart.component.scss'], +}) +export class ChartComponent { + @Input() meterData: any[] = []; + + // Chart properties + public barChartOptions: ChartOptions = { + responsive: true, + scales: { x: { stacked: false }, y: { stacked: false } }, + maintainAspectRatio: false, + }; + public barChartLabels: string[] = []; + public barChartType: ChartType = 'bar'; + public barChartLegend = true; + public barChartData: ChartDataset[] = []; + + ngOnChanges(): void { + this.updateChart(); + } + + private updateChart(): void { + this.barChartLabels = this.meterData[0]?.meterDataForYear + ?.map((entry: MeterDataForYear) => entry.year.toString()) + .reverse(); + + this.barChartData = this.meterData?.map((meter) => ({ + data: meter.meterDataForYear + .map((entry: MeterDataForYear) => entry.amount) + .reverse(), + label: meter.meter.name, + })); + } +} diff --git a/rossa-tech-cli/src/app/components/subcomponents/consumption-last-year/consumption-last-year.component.html b/rossa-tech-cli/src/app/components/subcomponents/consumption-last-year/consumption-last-year.component.html deleted file mode 100644 index 5a9517f..0000000 --- a/rossa-tech-cli/src/app/components/subcomponents/consumption-last-year/consumption-last-year.component.html +++ /dev/null @@ -1,49 +0,0 @@ - -
- -

{{type}}

- - - - 1LOG - Hausstrom - - - 1HEM - Heizstrom - - -
{{averageEnergy1Log}} kWh
-
- -
{{averageEnergy1Hem}} kWh
-
-
-
-
- - -
- -

{{type}}

- - - - Wasser - - - Gartenwasser - - - Summe - - -
{{averageWater}} m³
-
- -
{{averageGarden}} m³
-
- -
{{averageWater - averageGarden}} m³
-
-
-
-
\ No newline at end of file diff --git a/rossa-tech-cli/src/app/components/subcomponents/consumption-last-year/consumption-last-year.component.spec.ts b/rossa-tech-cli/src/app/components/subcomponents/consumption-last-year/consumption-last-year.component.spec.ts deleted file mode 100644 index f7a9e53..0000000 --- a/rossa-tech-cli/src/app/components/subcomponents/consumption-last-year/consumption-last-year.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { ConsumptionLastYearComponent } from './consumption-last-year.component'; - -describe('ConsumptionLastYearComponent', () => { - let component: ConsumptionLastYearComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ ConsumptionLastYearComponent ] - }) - .compileComponents(); - - fixture = TestBed.createComponent(ConsumptionLastYearComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/rossa-tech-cli/src/app/components/subcomponents/consumption-last-year/consumption-last-year.component.ts b/rossa-tech-cli/src/app/components/subcomponents/consumption-last-year/consumption-last-year.component.ts deleted file mode 100644 index 77f0b1a..0000000 --- a/rossa-tech-cli/src/app/components/subcomponents/consumption-last-year/consumption-last-year.component.ts +++ /dev/null @@ -1,145 +0,0 @@ -import { Component, Input, OnChanges } from '@angular/core'; -import { Meters } from 'src/app/core/dataModels/Meter'; -import { MeterData } from 'src/app/core/dataModels/Meterdata'; -import { UsageType } from 'src/app/core/dataModels/UsageType'; -import { GlobalService } from 'src/app/core/services/global.service'; - -@Component({ - selector: 'app-consumption-last-year', - templateUrl: './consumption-last-year.component.html', - styleUrls: ['./consumption-last-year.component.scss'], -}) -export class ConsumptionLastYearComponent implements OnChanges { - @Input() - meterData!: MeterData[]; - @Input() - type!: UsageType; - - usageTypes = UsageType; - meters = Meters; - - meterDataEnergy1Hem: MeterData[] = []; - averageEnergy1Hem!: number; - - meterDataEnergy1Log: MeterData[] = []; - averageEnergy1Log!: number; - - meterDataWasser: MeterData[] = []; - averageWater!: number; - - meterDataAbwasser: MeterData[] = []; - averageGarden!: number; - - currentYear = new Date().getFullYear(); - - constructor(private globalService: GlobalService) {} - - ngOnChanges(): void { - if (this.type && this.type === this.usageTypes.ENERGY) { - console.log('count energy'); - - if (this.meterData) { - this.meterData.forEach((data) => { - if (data.meter.name === this.meters['1LOG']) { - this.meterDataEnergy1Log.push(data); - } else if (data.meter.name === this.meters['1HEM']) { - this.meterDataEnergy1Hem.push(data); - } - }); - - this.meterDataEnergy1Log = this.globalService.sortMeterData( - this.meterDataEnergy1Log - ); - this.averageEnergy1Log = this.countAverageAmountForYear( - this.meterDataEnergy1Log, - this.currentYear - 1 - ); - - this.meterDataEnergy1Hem = this.globalService.sortMeterData( - this.meterDataEnergy1Hem - ); - this.averageEnergy1Hem = this.countAverageAmountForYear( - this.meterDataEnergy1Hem, - this.currentYear - 1 - ); - } - } else if (this.type && this.type === this.usageTypes.WATER) { - console.log('count water'); - - if (this.meterData) { - this.meterData.forEach((data) => { - if (data.meter.name === this.meters['WATER']) { - this.meterDataWasser.push(data); - } else if (data.meter.name === this.meters['GARDEN']) { - this.meterDataAbwasser.push(data); - } - }); - - this.meterDataWasser = this.globalService.sortMeterData( - this.meterDataWasser - ); - this.averageWater = this.countAverageAmountForYear( - this.meterDataWasser, - this.currentYear - 1 - ); - - this.meterDataAbwasser = this.globalService.sortMeterData( - this.meterDataAbwasser - ); - this.averageGarden = this.countAverageAmountForYear( - this.meterDataAbwasser, - this.currentYear - 1 - ); - } - } - } - - // countAverageAmountForLastYear(meterData: MeterData[]): number { - // const currentYear = new Date().getFullYear(); - - // let lastYear = meterData.filter( - // (data) => new Date(data.date).getFullYear() === currentYear - 1 - // ); - - // lastYear = this.globalService.sortMeterData(lastYear); - // if (lastYear.length > 0) { - // const end = lastYear[0]; - - // const startIdx = - // meterData.findIndex((el) => el === lastYear[lastYear.length - 1]) + 1; - // const start = meterData[startIdx]; - - // console.log({ end: end.amount, startIdx, startAm: start.amount, start }); - - // return end.amount - start.amount; - // } - - // return 0; - // } - - /* FOR TEST*/ - countAverageAmountForYear(meterData: MeterData[], year: number): number { - // const currentYear = new Date().getFullYear(); - - let filteredData = meterData.filter( - (data) => new Date(data.date).getFullYear() === year - ); - - filteredData = this.globalService.sortMeterData(filteredData); - if (filteredData.length > 0) { - const end = filteredData[0]; - - const startIdx = - meterData.findIndex( - (el) => el === filteredData[filteredData.length - 1] - ) + 1; - const start = meterData[startIdx]; - - console.log({ end: end.amount, startIdx, startAm: start.amount, start }); - - return end.amount - start.amount; - } - - return 0; - } -} diff --git a/rossa-tech-cli/src/app/components/subcomponents/meter-data-list/meter-data-list.component.html b/rossa-tech-cli/src/app/components/subcomponents/meter-data-list/meter-data-list.component.html index 1ad852d..e3207ba 100644 --- a/rossa-tech-cli/src/app/components/subcomponents/meter-data-list/meter-data-list.component.html +++ b/rossa-tech-cli/src/app/components/subcomponents/meter-data-list/meter-data-list.component.html @@ -1,3 +1,5 @@ +amount: {{amount}} +
diff --git a/rossa-tech-cli/src/app/components/subcomponents/meter-data-list/meter-data-list.component.ts b/rossa-tech-cli/src/app/components/subcomponents/meter-data-list/meter-data-list.component.ts index ab7a6cb..a57db5f 100644 --- a/rossa-tech-cli/src/app/components/subcomponents/meter-data-list/meter-data-list.component.ts +++ b/rossa-tech-cli/src/app/components/subcomponents/meter-data-list/meter-data-list.component.ts @@ -1,7 +1,10 @@ import { Component, Input, OnChanges, OnInit } from '@angular/core'; import { MatTableDataSource } from '@angular/material/table'; import { UsageType } from 'src/app/core/dataModels/UsageType'; -import { MeterData } from 'src/app/core/dataModels/Meterdata'; +import { + MeterData, + PreparedMeterData, +} from 'src/app/core/dataModels/Meterdata'; @Component({ selector: 'app-meter-data-list', @@ -10,9 +13,13 @@ import { MeterData } from 'src/app/core/dataModels/Meterdata'; }) export class MeterDataListComponent implements OnInit, OnChanges { @Input() - meterData: MeterData[] = []; + meterData!: MeterData[]; @Input() - type: UsageType = UsageType.ENERGY; + type!: UsageType; + amount: number | undefined; + // meterData: PreparedMeterData = new PreparedMeterData(); + // @Input() + // type: UsageType = UsageType.ENERGY; displayedColumns: string[] = ['id', 'date', 'amount', 'meter']; @@ -105,6 +112,11 @@ export class MeterDataListComponent implements OnInit, OnChanges { ngOnInit(): void {} ngOnChanges(): void { - this.dataSource = new MatTableDataSource(this.meterData); + this.dataSource = new MatTableDataSource( + this.meterData + // this.meterData?.meterDataForYear[0].meterData + ); + + // this.amount = this.meterData?.meterDataForYear[0].amount; } } diff --git a/rossa-tech-cli/src/app/components/subcomponents/meter-data-wrapper/meter-data-wrapper.component.html b/rossa-tech-cli/src/app/components/subcomponents/meter-data-wrapper/meter-data-wrapper.component.html deleted file mode 100644 index 033d1f7..0000000 --- a/rossa-tech-cli/src/app/components/subcomponents/meter-data-wrapper/meter-data-wrapper.component.html +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/rossa-tech-cli/src/app/components/subcomponents/meter-data-wrapper/meter-data-wrapper.component.scss b/rossa-tech-cli/src/app/components/subcomponents/meter-data-wrapper/meter-data-wrapper.component.scss deleted file mode 100644 index e69de29..0000000 diff --git a/rossa-tech-cli/src/app/components/subcomponents/meter-data-wrapper/meter-data-wrapper.component.spec.ts b/rossa-tech-cli/src/app/components/subcomponents/meter-data-wrapper/meter-data-wrapper.component.spec.ts deleted file mode 100644 index fc5609c..0000000 --- a/rossa-tech-cli/src/app/components/subcomponents/meter-data-wrapper/meter-data-wrapper.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { MeterDataWrapperComponent } from './meter-data-wrapper.component'; - -describe('MeterDataWrapperComponent', () => { - let component: MeterDataWrapperComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ MeterDataWrapperComponent ] - }) - .compileComponents(); - - fixture = TestBed.createComponent(MeterDataWrapperComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/rossa-tech-cli/src/app/components/subcomponents/meter-data-wrapper/meter-data-wrapper.component.ts b/rossa-tech-cli/src/app/components/subcomponents/meter-data-wrapper/meter-data-wrapper.component.ts deleted file mode 100644 index e61309b..0000000 --- a/rossa-tech-cli/src/app/components/subcomponents/meter-data-wrapper/meter-data-wrapper.component.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Component, Input, OnChanges } from '@angular/core'; -import { MeterData } from 'src/app/core/dataModels/Meterdata'; -import { UsageType } from 'src/app/core/dataModels/UsageType'; - -@Component({ - selector: 'app-meter-data-wrapper', - templateUrl: './meter-data-wrapper.component.html', - styleUrls: ['./meter-data-wrapper.component.scss'], -}) -export class MeterDataWrapperComponent /* implements OnChanges */ { - @Input() - meterData: MeterData[] = []; - @Input() - type: UsageType = UsageType.ENERGY; - - // usageTypes = UsageType; - // averageConsumptionLastYear: number; - - constructor() {} - - // ngOnChanges(): void { - // this.averageConsumptionLastYear = this.countAverageAmountForLastYear( - // this.meterData - // ); - // } -} diff --git a/rossa-tech-cli/src/app/core/dataModels/Meter.ts b/rossa-tech-cli/src/app/core/dataModels/Meter.ts index 8139b18..3581f41 100644 --- a/rossa-tech-cli/src/app/core/dataModels/Meter.ts +++ b/rossa-tech-cli/src/app/core/dataModels/Meter.ts @@ -1,10 +1,16 @@ export class Meter { id: number; name: '1LOG' | '1HEM' | 'WATER' | 'GARDEN'; + type?: 'ENERGY' | 'WATER'; - constructor(id: number, name: '1LOG' | '1HEM' | 'WATER' | 'GARDEN') { - this.id = id; - this.name = name; + constructor( + id: number, + name?: '1LOG' | '1HEM' | 'WATER' | 'GARDEN', + type?: 'ENERGY' | 'WATER' + ) { + this.id = id || 0; + this.name = name || '1LOG'; + this.type = type || 'ENERGY'; } } diff --git a/rossa-tech-cli/src/app/core/dataModels/Meterdata.ts b/rossa-tech-cli/src/app/core/dataModels/Meterdata.ts index 8766848..62dbfdb 100644 --- a/rossa-tech-cli/src/app/core/dataModels/Meterdata.ts +++ b/rossa-tech-cli/src/app/core/dataModels/Meterdata.ts @@ -1,23 +1,66 @@ -import { Meter } from "./Meter"; +export class Meter { + id: number; + name: '1LOG' | '1HEM' | 'WATER' | 'GARDEN'; + type?: 'ENERGY' | 'WATER'; + + constructor( + id: number, + name?: '1LOG' | '1HEM' | 'WATER' | 'GARDEN', + type?: 'ENERGY' | 'WATER' + ) { + this.id = id || 0; + this.name = name || '1LOG'; + this.type = type || 'ENERGY'; + } +} + +export enum Meters { + '1LOG' = '1LOG', + '1HEM' = '1HEM', + 'WATER' = 'WATER', + 'GARDEN' = 'GARDEN', +} export class MeterData { id?: number; - type: "ENERGY" | "WATER"; + type: 'ENERGY' | 'WATER'; date: Date; amount: number; meter: Meter; constructor( - type: "ENERGY" | "WATER", - date: Date, - amount: number, - meter: Meter, + type?: 'ENERGY' | 'WATER', + date?: Date, + amount?: number, + meter?: Meter, id?: number ) { - this.id = id; - this.type = type; - this.date = date; - this.amount = amount; - this.meter = meter; + this.id = id || undefined; + this.type = type || 'ENERGY'; + this.date = date || new Date(); + this.amount = amount || 0; + this.meter = meter || new Meter(0); + } +} + +export class PreparedMeterData { + meter: Meter; + meterDataForYear: MeterDataForYear[] = []; + + constructor(meter?: Meter, meterDataForYear?: MeterDataForYear[]) { + this.meter = meter || new Meter(0); + this.meterDataForYear = meterDataForYear || []; + } +} + +export class MeterDataForYear { + year: number; + amount: number; + meterData: MeterData[] = []; + + constructor(year?: number, amount?: number, meterData?: MeterData[]) { + this.amount = amount || 0; + this.year = year || 0; + this.meterData = meterData || []; } } diff --git a/rossa-tech-cli/src/app/core/services/global.service.ts b/rossa-tech-cli/src/app/core/services/global.service.ts index 1dd41c2..4e064fc 100644 --- a/rossa-tech-cli/src/app/core/services/global.service.ts +++ b/rossa-tech-cli/src/app/core/services/global.service.ts @@ -1,20 +1,8 @@ -import { Injectable } from "@angular/core"; -import { MeterData } from "../dataModels/Meterdata"; +import { Injectable } from '@angular/core'; @Injectable({ - providedIn: "root", + providedIn: 'root', }) export class GlobalService { constructor() {} - - sortMeterData(meterData: MeterData[]): MeterData[] { - if (meterData.length > 0) { - meterData.sort( - (a, b) => new Date(b.date).getTime() - new Date(a.date).getTime() - ); - return meterData; - } else { - return []; - } - } } diff --git a/rossa-tech-cli/src/app/core/services/meterData.service.ts b/rossa-tech-cli/src/app/core/services/meterData.service.ts new file mode 100644 index 0000000..7527b0e --- /dev/null +++ b/rossa-tech-cli/src/app/core/services/meterData.service.ts @@ -0,0 +1,168 @@ +import { Injectable } from '@angular/core'; +import { + MeterData, + MeterDataForYear, + PreparedMeterData, +} from '../dataModels/Meterdata'; +import { UsageType } from '../dataModels/UsageType'; +import { Observable, of } from 'rxjs'; + +@Injectable({ + providedIn: 'root', +}) +export class MeterDataService { + public preparedMeterDataObject: PreparedMeterData[] = []; + private meterDataObject: MeterData[] = new Array(); + // public energyMeterDataObject: MeterData[] = []; + + constructor() {} + + setMeterData(meterData: MeterData[]) { + this.meterDataObject = meterData; + } + + // get meterData(): Observable { + // return + // } + + get preparedMeterData(): Observable { + return of(this.preparedMeterDataObject); + } + + get energyMeterData(): Observable { + // console.log(this.energyMeterDataObject); + return of(this.preparedMeterDataObject.slice(0, 2)); + // return of(this.energyMeterDataObject); + } + + getMeterAmoutforYear(meterName: string, year: number): number { + const amount: number = 0; + // todo: fill amount + return amount; + } + getMeterDataByMeter(meterName: string): MeterData[] { + const meterData: MeterData[] = []; + //todo: fill meterData + return this.sortMeterData(meterData); + } + getMeterDataByType(type: string): MeterData[] { + let meterData: MeterData[] = []; + meterData = this.meterDataObject.filter( + (md: MeterData) => md.type === type + ); + return this.sortMeterData(meterData); + } + + getMeterDataByMeterAndYear(meterName: string, year: number) {} + + sortMeterData(meterData: MeterData[]): MeterData[] { + if (meterData.length > 0) { + meterData.sort( + (a, b) => new Date(b.date).getTime() - new Date(a.date).getTime() + ); + return meterData; + } else { + return []; + } + } + + prepareMeterData(rawMeterData: MeterData[]) { + this.setMeterData(rawMeterData); + this.preparedMeterDataObject = []; + if (rawMeterData) { + rawMeterData.forEach((data) => { + // if (data.type === UsageType.ENERGY) { + // this.energyMeterDataObject.push(data); + // } + const meterExist = this.preparedMeterDataObject.find( + (el) => el.meter.name === data.meter.name + ); + + if (!meterExist) { + const newPreparedMeterData: PreparedMeterData = { + meter: data.meter, + meterDataForYear: [ + { + year: new Date(data.date).getFullYear(), + amount: data.amount, + meterData: [data], + }, + ], + }; + this.preparedMeterDataObject.push(newPreparedMeterData); + } else { + // meter exist, check year + const yearExist = meterExist.meterDataForYear.find( + (meterByYear) => + meterByYear.year === new Date(data.date).getFullYear() + ); + + if (yearExist) { + yearExist.meterData.push(data); + yearExist.meterData = this.sortMeterData(yearExist.meterData); + } else { + // add new year + const newMeterDataForYear: MeterDataForYear = { + year: new Date(data.date).getFullYear(), + amount: data.amount, + meterData: [data], + }; + + meterExist.meterDataForYear.push(newMeterDataForYear); + + meterExist.meterDataForYear.sort( + (a, b) => new Date(b.year).getTime() - new Date(a.year).getTime() + ); + } + } + }); + + console.log('----PREPARE BY YEAR DONE-----'); + this.countAmountByYear(); + } + } + + countAmountByYear() { + this.preparedMeterDataObject.forEach((prepMeterData) => { + prepMeterData.meterDataForYear.forEach((year) => { + // check if prev year + const previousYear = prepMeterData.meterDataForYear.find( + (meterByYear) => meterByYear.year === year.year - 1 + ); + if (previousYear) { + year.amount = + year.meterData[0].amount - previousYear.meterData[0].amount; + } else { + if (year.meterData.length > 1) { + year.amount = + year.meterData[0].amount - + year.meterData[year.meterData.length - 1].amount; + } else { + console.log('nothing'); + } + } + }); + }); + + console.log('----AMOUNT PER YEAR DONE-----'); + } + + // getDataSummarizedByYear(meterData: MeterData[]):any { + // const yearSummary: YearSummary[] = [] + + // meterData.forEach(data => { + // const year: string = data.date.getFullYear().toString(); + // // const exist = yearSummary.find(el => el[year] === undefined) + // if (!yearSummary.hasOwnProperty(year)) { + // const newYearSummary: YearSummary = { + // // year, + // amount: 0, + // meterData: [data] + // } + // yearSummary.add(newYearSummary) + // } else { + + // } + // }) + // } +} diff --git a/rossa-tech-cli/src/app/dialogs/meter-data-add-dialog/meter-data-add-dialog.component.html b/rossa-tech-cli/src/app/dialogs/meter-data-add-dialog/meter-data-add-dialog.component.html index 41b8101..a89ba52 100644 --- a/rossa-tech-cli/src/app/dialogs/meter-data-add-dialog/meter-data-add-dialog.component.html +++ b/rossa-tech-cli/src/app/dialogs/meter-data-add-dialog/meter-data-add-dialog.component.html @@ -23,6 +23,6 @@
- +
\ No newline at end of file diff --git a/rossa-tech-cli/src/assets/icon.png b/rossa-tech-cli/src/assets/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..4ce820c06882d43854e5b48ab73bd11671f1e23f GIT binary patch literal 22842 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%0Dsf#;P#j68m@r$`^_gJLC3ro2yZ`yg z_cy7|@XzE#Sy_>RNZJZ>i;xnCsIW^U85b12c!y(l|t6BH{i)tV_V)b87Ug z@2#%KkH?HnS3duhvI57wR~E(OW-@Z0MrJi6Xy2+QWM{| z+Jv}|w3YuZKor@qwwQeik(;@(3rskoTFte(=cW3rXHTXTfI-P0YGoQU4RuB%A=3|L z^h6Lvr?!NGVwqB2pljYaN9pqxlYG7V#1EvRIu8D8ofBq|zT0blF9SEPMn&2y!L&#M zgL$SpU-arZu3mQG`HDbMpC3d{q|#WGS{tX9yIFoH_D5yCPtSrxihQjVBuM2X)9Xwu zAoWgaH1WwmEcx{Q;Zy&d3zzzGo5j4LgZHQs6-u*JTUI4IQ4QqS2}M!dRa4I)t*gpt zVn`?Umuo;G9oLuOX`mvhZeMN!?QP8z9<7NjXP08PRdB&q`%LEKfcV;LGoAn8t2JE5fXSrx%eVuYAZr6w2&}1d}kICi|(*_3u^gQi6V|iJ+_1a zs)zu8^4GkjWyJp$^%2ea8%DH_Z~iH1)*nqYYy!+f3y%lzWAHmQ! zgv^h+y^vhn6ozU)sJq0O;Xl+wYKtCbLxRIyK5#eTaa{7Q)5T~diTr!HAHUP6!3vUu z&>qZ+o?sv&(ni-0w7}4`+`{rsxT)l|LR0OtsK^KtFiQ9@6%rvRaY?a}ddpii=H(Y; z9j&17Qwgza&~@jFC0=J$$hlN~5;${aHO7^OceW00!c zWR=`O>y~ClyZyP1GUa7Q&EIa7=WVRAeN7ba#>nERsLQ(cbn|}xLZvzAz3|OpTKgGC z5@yQsZ>dqr^MeY_0}7h)V^BJ{(1~{eg5c%>NRnI-{-KIFX0a9wZ{H?H`KrDm6@cX`sBYU6XxiU zb`!Giwj=7#uuMrpw}~A@*4OCjdfZs!Oy3*5Y8S>)&~gry{kH4hz3~$$P_loTjWk$w zR=&Z^Oq1d&65{=kG>j7RSUxu3WzMYB#HZ4ZIX@5x?_ZBG!vTA;< z%YJqr*EX0WARorHmnhAr(w*pqcAvOGKiG#*i1ZrE#;sUHJuz|#SHH`7+rZVQdXg(( zPry*LO{W*p=shbbF{>2?9OiV4OBCr=sd!$ojh87X_A@21KOj>Qz8Z9|MqN+#?x_21RS>@V z^>RDWV;7^hp&tG&iFB@zJQ4?*(eM*L9f%C#_KR`)#qwxAJ~K$5Zj|{=&+JnH#|2ZL z3FH8HumWM&<4DcO%ZXz6CG@pTfahVbLev)HmlVw;@irT&Hw)=-@64Oe;KNu`UY=%q zN23MMxcVloOe1-l^R7M1y>VFGu`INOH*l4R=o~DvIGUG^V6}zJ2M-z7k45#-HH00z zaezn2ku)pLv=npJu1j@~gMs%`-moYeKyr{ZPR8jq*cFi|WmczsC_?j(kU0 zK(7nfMD27?RV5`A7o1L3IfG@24-~0%cjJ81Czvz?Ll$W-z5)?6TMJ2Y4Uul(f9UBG zn)N^acbp)G$P?}FWUa~CMBCWL=W-3*?T%L+vjd;CPxs*`V&JGpP(UUkCKUJ)B6pxF zS?4|X<8}FO5h4{ky_opsu!y2P;Q4b6_#ni;xiT~2Qy0`K@D{ouM2~$3*f@Gw(wd?} zp27U=CWU$svxL#qnBYF_JetrLPO&TAstb}z$q-H)g4y;~oyr?o`p7qnyI%Hy76nUG z9pf6t#Rs}z^GNPoC4 zbb)fva3ZdN^xpY5{_v!T``^dzC)f>ZhV*l