feat(historygraph+actualgraph): adding date range & actual range, fix interface
This commit is contained in:
parent
6ad5951413
commit
a68c0f045b
985
agrilink_vocpro/package-lock.json
generated
985
agrilink_vocpro/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
|
|
@ -10,17 +10,19 @@
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "^18.2.8",
|
"@angular/animations": "^18.2.4",
|
||||||
"@angular/common": "^18.2.0",
|
"@angular/common": "^18.2.0",
|
||||||
"@angular/compiler": "^18.2.0",
|
"@angular/compiler": "^18.2.0",
|
||||||
"@angular/core": "^18.2.0",
|
"@angular/core": "^18.2.0",
|
||||||
"@angular/forms": "^18.2.0",
|
"@angular/forms": "^18.2.0",
|
||||||
|
"@angular/material": "^17.3.6",
|
||||||
"@angular/platform-browser": "^18.2.0",
|
"@angular/platform-browser": "^18.2.0",
|
||||||
"@angular/platform-browser-dynamic": "^18.2.0",
|
"@angular/platform-browser-dynamic": "^18.2.0",
|
||||||
"@angular/router": "^18.2.0",
|
"@angular/router": "^18.2.0",
|
||||||
"bootstrap": "^5.3.3",
|
"bootstrap": "^5.3.3",
|
||||||
"bootstrap-icons": "^1.11.3",
|
"bootstrap-icons": "^1.11.3",
|
||||||
"chart.js": "^4.4.4",
|
"chart.js": "^4.4.4",
|
||||||
|
"date-fns": "^4.1.0",
|
||||||
"highcharts-angular": "^4.0.1",
|
"highcharts-angular": "^4.0.1",
|
||||||
"jwt-decode": "^4.0.0",
|
"jwt-decode": "^4.0.0",
|
||||||
"ngx-toastr": "^19.0.0",
|
"ngx-toastr": "^19.0.0",
|
||||||
|
|
@ -32,6 +34,7 @@
|
||||||
"@angular-devkit/build-angular": "^18.2.4",
|
"@angular-devkit/build-angular": "^18.2.4",
|
||||||
"@angular/cli": "^18.2.4",
|
"@angular/cli": "^18.2.4",
|
||||||
"@angular/compiler-cli": "^18.2.0",
|
"@angular/compiler-cli": "^18.2.0",
|
||||||
|
"@types/date-fns": "^2.5.3",
|
||||||
"@types/jasmine": "~5.1.0",
|
"@types/jasmine": "~5.1.0",
|
||||||
"jasmine-core": "~5.2.0",
|
"jasmine-core": "~5.2.0",
|
||||||
"karma": "~6.4.0",
|
"karma": "~6.4.0",
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { AuthComponent } from './pages/auth/auth.component';
|
||||||
import { AuthGuard } from './cores/guards/auth.guard';
|
import { AuthGuard } from './cores/guards/auth.guard';
|
||||||
import { RegisterComponent } from './pages/register/register.component';
|
import { RegisterComponent } from './pages/register/register.component';
|
||||||
import { HistorygraphComponent } from './pages/dashboard/page/historygraph/historygraph.component';
|
import { HistorygraphComponent } from './pages/dashboard/page/historygraph/historygraph.component';
|
||||||
|
import { ActualgraphComponent } from './pages/dashboard/page/actualgraph/actualgraph.component';
|
||||||
|
|
||||||
export const routes: Routes = [
|
export const routes: Routes = [
|
||||||
{
|
{
|
||||||
|
|
@ -33,6 +34,11 @@ export const routes: Routes = [
|
||||||
path: 'historygraph',
|
path: 'historygraph',
|
||||||
component: HistorygraphComponent,
|
component: HistorygraphComponent,
|
||||||
canActivate: [AuthGuard]
|
canActivate: [AuthGuard]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'actualgraph',
|
||||||
|
component: ActualgraphComponent,
|
||||||
|
canActivate: [AuthGuard]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
export interface ParameterSensor {
|
export interface ParameterSensor {
|
||||||
hour: number;
|
hour: number;
|
||||||
day: number;
|
day: number;
|
||||||
|
date: number;
|
||||||
|
|
||||||
//for DHT sensor
|
//for DHT sensor
|
||||||
vicitemperature?: number;
|
vicitemperature?: number;
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,6 @@
|
||||||
<h1 class="title">{{ greeting }}</h1>
|
<h1 class="title">{{ greeting }}</h1>
|
||||||
<h3 class="description">Welcome back to your management system</h3>
|
<h3 class="description">Welcome back to your management system</h3>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<h2 class="update">Latest Update: {{latestUpdate}}</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<button [ngClass]="{'active-button': selectedButton === 'dht'}" (click)="selectSensor('dht')">BHT</button>
|
<button [ngClass]="{'active-button': selectedButton === 'dht'}" (click)="selectSensor('dht')">BHT</button>
|
||||||
|
|
@ -158,10 +155,15 @@
|
||||||
Katup Nutrisi
|
Katup Nutrisi
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-template #checkRelay>
|
<ng-template #checkRelay>
|
||||||
<ng-container *ngIf="relay.number === 2; else defaultRelay">
|
<ng-container *ngIf="relay.number === 2; else checkRelay2">
|
||||||
Katup Air
|
Katup Air
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
<ng-template #checkRelay2>
|
||||||
|
<ng-container *ngIf="relay.number === 3; else defaultRelay">
|
||||||
|
Pompa Air
|
||||||
|
</ng-container>
|
||||||
|
</ng-template>
|
||||||
<ng-template #defaultRelay>
|
<ng-template #defaultRelay>
|
||||||
Relay {{ relay.number }}
|
Relay {{ relay.number }}
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
@ -170,9 +172,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="graph">
|
<div class="graph">
|
||||||
<div class="title-graph">Monitoring Graphs</div>
|
<div class="title-graph">Monitoring Graphs</div>
|
||||||
|
|
|
||||||
|
|
@ -16,13 +16,7 @@
|
||||||
.description {
|
.description {
|
||||||
color: #49473C;
|
color: #49473C;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
margin-top: 10px;
|
margin: 10px 0px 15px 0px;
|
||||||
}
|
|
||||||
|
|
||||||
.update{
|
|
||||||
color: #49473C;
|
|
||||||
font-size: 15px;
|
|
||||||
margin-top: 18px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-container {
|
.card-container {
|
||||||
|
|
@ -102,6 +96,10 @@
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.relay-container{
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 344px) {
|
@media (max-width: 344px) {
|
||||||
|
|
@ -126,6 +124,10 @@
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.relay-container{
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,6 @@ export class DashboardComponent implements OnInit {
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.selectedButton = 'dht';
|
this.selectedButton = 'dht';
|
||||||
this.startClock();
|
|
||||||
this.updateGreeting();
|
this.updateGreeting();
|
||||||
this.loadData();
|
this.loadData();
|
||||||
}
|
}
|
||||||
|
|
@ -44,26 +43,6 @@ export class DashboardComponent implements OnInit {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
startClock(): void {
|
|
||||||
this.updateLatestTime();
|
|
||||||
|
|
||||||
this.intervalId = setInterval(() => {
|
|
||||||
this.updateLatestTime();
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateLatestTime(): void {
|
|
||||||
const now = new Date();
|
|
||||||
const options: Intl.DateTimeFormatOptions = {
|
|
||||||
year: 'numeric',
|
|
||||||
month: '2-digit',
|
|
||||||
day: '2-digit',
|
|
||||||
hour: '2-digit',
|
|
||||||
minute: '2-digit',
|
|
||||||
second: '2-digit'
|
|
||||||
};
|
|
||||||
this.latestUpdate = now.toLocaleString('en-GB', options);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateGreeting(): void {
|
updateGreeting(): void {
|
||||||
const hour = new Date().getHours();
|
const hour = new Date().getHours();
|
||||||
|
|
@ -131,8 +110,6 @@ export class DashboardComponent implements OnInit {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateLatestTime();
|
|
||||||
this.isLoaded = false;
|
this.isLoaded = false;
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
|
|
@ -157,10 +134,10 @@ export class DashboardComponent implements OnInit {
|
||||||
created_at: relay.created_at,
|
created_at: relay.created_at,
|
||||||
current_status: relay.current_status
|
current_status: relay.current_status
|
||||||
}));
|
}));
|
||||||
this.relayStatus.sort((a, b) => a.number - b.number);
|
this.relayStatus.sort((a, b) => b.number - a.number);
|
||||||
this.noData = false; // Data available
|
|
||||||
|
this.noData = false;
|
||||||
} else {
|
} else {
|
||||||
// Throw an error if relay data is empty
|
|
||||||
this.handleError({ message: 'No available relay data.' });
|
this.handleError({ message: 'No available relay data.' });
|
||||||
this.noData = true;
|
this.noData = true;
|
||||||
}
|
}
|
||||||
|
|
@ -172,8 +149,7 @@ export class DashboardComponent implements OnInit {
|
||||||
this.hasError = true;
|
this.hasError = true;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
handleError(error: any): void {
|
handleError(error: any): void {
|
||||||
if (this.selectedButton === 'dht') {
|
if (this.selectedButton === 'dht') {
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,10 @@
|
||||||
<a routerLink='/historygraph' data-bs-toggle="collapse" class="nav-link px-0 align-middle">
|
<a routerLink='/historygraph' data-bs-toggle="collapse" class="nav-link px-0 align-middle">
|
||||||
<i class="bi bi-file-earmark-text title"></i> <span class="ms-1 d-none d-sm-inline menu">History Graph</span> </a>
|
<i class="bi bi-file-earmark-text title"></i> <span class="ms-1 d-none d-sm-inline menu">History Graph</span> </a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a routerLink='/actualgraph' data-bs-toggle="collapse" class="nav-link px-0 align-middle">
|
||||||
|
<i class="bi bi-bar-chart title"></i> <span class="ms-1 d-none d-sm-inline menu">Actual Graph</span> </a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
<div class="container">
|
||||||
|
<div>
|
||||||
|
<h1 class="title">{{ greeting }}</h1>
|
||||||
|
<h3 class="description">Compare your actual data with the standard data</h3>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button (click)="loadData('npk1')" [ngClass]="{'active-button': selectedButton === 'npk1'}">NPK 1</button>
|
||||||
|
<button (click)="loadData('npk2')" [ngClass]="{'active-button': selectedButton === 'npk2'}">NPK 2</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="graph loading">
|
||||||
|
<div class="title-graph" *ngIf="selectedButton === 'npk1'">Actual Graph Sensor NPK 1</div>
|
||||||
|
<div class="title-graph" *ngIf="selectedButton === 'npk2'">Actual Graph Sensor NPK 2</div>
|
||||||
|
<div class="graph-content">
|
||||||
|
<canvas *ngIf="!isLoading && !isNoData" #chartCanvas></canvas>
|
||||||
|
<i *ngIf="isLoading" class="fa fa-spinner fa-spin spinner"></i>
|
||||||
|
<p *ngIf="isNoData" class="no-data">No available data</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
.container {
|
||||||
|
font-family: "Onest", sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
color: #49473C;
|
||||||
|
font-size: 30px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
color: #49473C;
|
||||||
|
font-size: 15px;
|
||||||
|
margin: 10px 0px 15px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.graph {
|
||||||
|
width: 100%;
|
||||||
|
height: 400px;
|
||||||
|
position: relative;
|
||||||
|
max-width: 100%;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid #E5E5E5;
|
||||||
|
padding: 20px 20px 0px 20px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-graph {
|
||||||
|
color: #49473C;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 400;
|
||||||
|
margin-top: 15px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.graph-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
font-size: 30px;
|
||||||
|
color: #16423C;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.graph {
|
||||||
|
height: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-graph {
|
||||||
|
margin: auto;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 344px) {
|
||||||
|
button {
|
||||||
|
color: #49473C;
|
||||||
|
margin-right: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-graph {
|
||||||
|
font-size: 15px;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
font-size: 15px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active-button {
|
||||||
|
background-color: #cad849;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { ActualgraphComponent } from './actualgraph.component';
|
||||||
|
|
||||||
|
describe('ActualgraphComponent', () => {
|
||||||
|
let component: ActualgraphComponent;
|
||||||
|
let fixture: ComponentFixture<ActualgraphComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [ActualgraphComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(ActualgraphComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,201 @@
|
||||||
|
import { Component, OnInit, ViewChild, ElementRef, ChangeDetectorRef } from '@angular/core';
|
||||||
|
import { Chart, registerables } from 'chart.js';
|
||||||
|
import { SensorService } from '../../../../cores/services/sensor.service';
|
||||||
|
import { ApiResponse, ParameterSensor } from '../../../../cores/interface/sensor-data';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
|
||||||
|
Chart.register(...registerables);
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-actualgraph',
|
||||||
|
standalone: true,
|
||||||
|
imports: [CommonModule],
|
||||||
|
templateUrl: './actualgraph.component.html',
|
||||||
|
styleUrls: ['./actualgraph.component.scss']
|
||||||
|
})
|
||||||
|
export class ActualgraphComponent implements OnInit {
|
||||||
|
@ViewChild('chartCanvas', { static: false }) chartCanvas?: ElementRef<HTMLCanvasElement>;
|
||||||
|
private chart: Chart | undefined;
|
||||||
|
selectedButton: string = 'npk1';
|
||||||
|
isLoading: boolean = true;
|
||||||
|
isNoData: boolean = false;
|
||||||
|
greeting = '';
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private sensorService: SensorService,
|
||||||
|
private cdr: ChangeDetectorRef
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.updateGreeting();
|
||||||
|
this.loadData('npk1');
|
||||||
|
}
|
||||||
|
|
||||||
|
loadData(sensorType: string): void {
|
||||||
|
console.log('Loading data for sensor:', sensorType);
|
||||||
|
this.selectedButton = sensorType;
|
||||||
|
this.isLoading = true;
|
||||||
|
this.isNoData = false;
|
||||||
|
|
||||||
|
this.sensorService.getLatestData().subscribe(
|
||||||
|
(response: ApiResponse) => {
|
||||||
|
console.log('Data received:', response);
|
||||||
|
if (response.statusCode === 200) {
|
||||||
|
const selectedData = (response.data as { [key: string]: any[] })[sensorType];
|
||||||
|
|
||||||
|
if (selectedData && selectedData.length > 0) {
|
||||||
|
const npkData: ParameterSensor = selectedData[0];
|
||||||
|
|
||||||
|
if (sensorType === 'npk1' || sensorType === 'npk2') {
|
||||||
|
const actualData = [
|
||||||
|
{ x: npkData.soilnitrogen ?? 0, y: 0 },
|
||||||
|
{ x: npkData.soilphosphorus ?? 0, y: 1 },
|
||||||
|
{ x: npkData.soilpotassium ?? 0, y: 2 }
|
||||||
|
];
|
||||||
|
|
||||||
|
this.chartData.datasets[0].data = actualData;
|
||||||
|
this.isLoading = false;
|
||||||
|
this.cdr.detectChanges();
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.initializeChart();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.isNoData = true;
|
||||||
|
this.isLoading = false;
|
||||||
|
this.cdr.detectChanges();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error('Error loading data: ', response.message);
|
||||||
|
this.isLoading = false;
|
||||||
|
this.cdr.detectChanges();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
console.error('Error loading data:', error);
|
||||||
|
this.isLoading = false;
|
||||||
|
this.cdr.detectChanges();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize Chart
|
||||||
|
private initializeChart(): void {
|
||||||
|
if (this.chartCanvas?.nativeElement) {
|
||||||
|
console.log('Initializing chart...');
|
||||||
|
const ctx = this.chartCanvas.nativeElement.getContext('2d');
|
||||||
|
if (ctx) {
|
||||||
|
this.chart = new Chart(ctx, {
|
||||||
|
type: 'bar',
|
||||||
|
data: this.chartData,
|
||||||
|
options: this.chartOptions
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn('Chart canvas is not available');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateGreeting(): void {
|
||||||
|
const hour = new Date().getHours();
|
||||||
|
this.greeting = hour < 12 ? 'Good Morning' : hour < 18 ? 'Good Afternoon' : 'Good Evening';
|
||||||
|
}
|
||||||
|
|
||||||
|
public chartData = {
|
||||||
|
labels: ['Nitrogen', 'Phosphor', 'Kalium'],
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'Actual Data',
|
||||||
|
data: [],
|
||||||
|
backgroundColor: 'rgb(18, 55, 42)',
|
||||||
|
borderColor: 'rgb(18, 55, 42)',
|
||||||
|
borderWidth: 1,
|
||||||
|
type: 'bubble'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Start Data Range',
|
||||||
|
data: [
|
||||||
|
{ x: 100, y: 0 },
|
||||||
|
{ x: 90, y: 1 },
|
||||||
|
{ x: 220, y: 2 }
|
||||||
|
],
|
||||||
|
backgroundColor: 'rgba(0,0,0,0)',
|
||||||
|
borderWidth: 0,
|
||||||
|
type: 'bar',
|
||||||
|
stack: 'range',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Standard Data Range',
|
||||||
|
data: [
|
||||||
|
{ x: 100, y: 0 },
|
||||||
|
{ x: 35, y: 1 },
|
||||||
|
{ x: 200, y: 2 }
|
||||||
|
],
|
||||||
|
backgroundColor: 'rgb(212, 231, 197)',
|
||||||
|
borderWidth: 1,
|
||||||
|
type: 'bar',
|
||||||
|
stack: 'range'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
public chartOptions = {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
indexAxis: 'y',
|
||||||
|
scales: {
|
||||||
|
x: {
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Value (mg/L)',
|
||||||
|
},
|
||||||
|
min: 0,
|
||||||
|
max: 300,
|
||||||
|
type: 'linear',
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Sensor Parameter',
|
||||||
|
},
|
||||||
|
stacked: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
tooltip: {
|
||||||
|
callbacks: {
|
||||||
|
title: function (tooltipItem: any) {
|
||||||
|
// Display the label (Nitrogen, Phosphorus, or Kalium)
|
||||||
|
return tooltipItem[0].label;
|
||||||
|
},
|
||||||
|
label: function (tooltipItem: any) {
|
||||||
|
const dataset = tooltipItem.dataset;
|
||||||
|
const dataIndex = tooltipItem.dataIndex;
|
||||||
|
const data = dataset.data[dataIndex];
|
||||||
|
|
||||||
|
let tooltipText = `Actual Value: ${data.x} mg/L`;
|
||||||
|
|
||||||
|
if (dataset.type === 'bar') {
|
||||||
|
const startRangeDataset = tooltipItem.chart.data.datasets.find((ds: any) => ds.label === 'Start Data Range');
|
||||||
|
const standardRangeDataset = tooltipItem.chart.data.datasets.find((ds: any) => ds.label === 'Standard Data Range');
|
||||||
|
|
||||||
|
const startRangeValue = startRangeDataset?.data.find((point: any) => point.y === data.y)?.x;
|
||||||
|
const standardRangeValue = standardRangeDataset?.data.find((point: any) => point.y === data.y)?.x;
|
||||||
|
|
||||||
|
if (startRangeValue !== undefined && standardRangeValue !== undefined) {
|
||||||
|
const minValue = startRangeValue;
|
||||||
|
const maxValue = startRangeValue + standardRangeValue;
|
||||||
|
tooltipText = `(Standard Range: ${minValue} - ${maxValue} mg/L)`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tooltipText;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,18 @@
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-template #graphContent>
|
<ng-template #graphContent>
|
||||||
|
<div class="date-picker">
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>Enter a date range</mat-label>
|
||||||
|
<mat-date-range-input [rangePicker]="picker">
|
||||||
|
<input matStartDate placeholder="Start date" [(ngModel)]="startDate" (dateChange)="dateChange()">
|
||||||
|
<input matEndDate placeholder="End date" [(ngModel)]="endDate" (dateChange)="dateChange()">
|
||||||
|
</mat-date-range-input>
|
||||||
|
<mat-hint>MM/DD/YYYY – MM/DD/YYYY</mat-hint>
|
||||||
|
<mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
|
||||||
|
<mat-date-range-picker #picker></mat-date-range-picker>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
<div class="sensor-wrapper">
|
<div class="sensor-wrapper">
|
||||||
<div class="title">Sensor BHT</div>
|
<div class="title">Sensor BHT</div>
|
||||||
<ng-container *ngIf="isLoadingDHT; else dhtData">
|
<ng-container *ngIf="isLoadingDHT; else dhtData">
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,26 @@
|
||||||
.container-graph {
|
.container-graph {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: flex-start;
|
position: relative;
|
||||||
height: max-content;
|
height: max-content;
|
||||||
|
|
||||||
|
.date-picker{
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding: 10px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-form-field{
|
||||||
|
font-family: 'Onest', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-datepicker-toggle, mat-date-range-input{
|
||||||
|
color: #16423C;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-form-field.mat-focused .mat-date-range-input {
|
||||||
|
color: #16423C;
|
||||||
|
}
|
||||||
|
|
||||||
.sensor-wrapper {
|
.sensor-wrapper {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
@ -62,6 +79,17 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.date-picker{
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 10px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-hint{
|
||||||
|
font-size: 10px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 344px) {
|
@media (max-width: 344px) {
|
||||||
|
|
@ -70,6 +98,17 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.date-picker{
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 10px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-hint{
|
||||||
|
font-size: 10px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading {
|
.loading {
|
||||||
|
|
@ -88,3 +127,5 @@
|
||||||
height: 100%;
|
height: 100%;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,13 @@ import { Component, OnInit, ElementRef, ViewChild, AfterViewInit, OnDestroy, OnC
|
||||||
import { Chart, registerables } from 'chart.js';
|
import { Chart, registerables } from 'chart.js';
|
||||||
import { SensorService } from '../../../../cores/services/sensor.service';
|
import { SensorService } from '../../../../cores/services/sensor.service';
|
||||||
import { ApiResponse, ParameterSensor } from '../../../../cores/interface/sensor-data';
|
import { ApiResponse, ParameterSensor } from '../../../../cores/interface/sensor-data';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule, formatDate } from '@angular/common';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { provideNativeDateAdapter} from '@angular/material/core';
|
||||||
|
import { MatDatepickerModule} from '@angular/material/datepicker';
|
||||||
|
import { MatFormFieldModule} from '@angular/material/form-field';
|
||||||
|
import { format } from 'date-fns';
|
||||||
|
import { ToastrService } from 'ngx-toastr';
|
||||||
|
|
||||||
Chart.register(...registerables);
|
Chart.register(...registerables);
|
||||||
|
|
||||||
|
|
@ -23,7 +28,8 @@ const parameterColors: { [key: string]: string } = {
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-graph',
|
selector: 'app-graph',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [CommonModule, FormsModule],
|
imports: [CommonModule, FormsModule, MatDatepickerModule, MatFormFieldModule],
|
||||||
|
providers: [provideNativeDateAdapter()],
|
||||||
templateUrl: './graph.component.html',
|
templateUrl: './graph.component.html',
|
||||||
styleUrls: ['./graph.component.scss']
|
styleUrls: ['./graph.component.scss']
|
||||||
})
|
})
|
||||||
|
|
@ -34,6 +40,9 @@ export class GraphComponent implements OnInit, AfterViewInit, OnDestroy, OnChang
|
||||||
@Input() interval: string = 'hourly';
|
@Input() interval: string = 'hourly';
|
||||||
selectedInterval: string = 'HOURLY';
|
selectedInterval: string = 'HOURLY';
|
||||||
|
|
||||||
|
startDate: Date | null = null;
|
||||||
|
endDate: Date | null = null;
|
||||||
|
|
||||||
isLoadingDHT: boolean = true;
|
isLoadingDHT: boolean = true;
|
||||||
isLoadingNPK1: boolean = true;
|
isLoadingNPK1: boolean = true;
|
||||||
isLoadingNPK2: boolean = true;
|
isLoadingNPK2: boolean = true;
|
||||||
|
|
@ -73,15 +82,16 @@ export class GraphComponent implements OnInit, AfterViewInit, OnDestroy, OnChang
|
||||||
selectedNPK2: string = 'npk';
|
selectedNPK2: string = 'npk';
|
||||||
|
|
||||||
private resizeListener!: () => void;
|
private resizeListener!: () => void;
|
||||||
|
private updateTimeout: any;
|
||||||
|
|
||||||
constructor(private sensorService: SensorService, private cdr: ChangeDetectorRef) {}
|
constructor(private sensorService: SensorService, private toast: ToastrService) {}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.resizeListener = this.onResize.bind(this);
|
this.resizeListener = this.onResize.bind(this);
|
||||||
window.addEventListener('resize', this.resizeListener);
|
window.addEventListener('resize', this.resizeListener);
|
||||||
this.updateCharts();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ngOnChanges(changes: SimpleChanges): void {
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
if (changes['interval'] && changes['interval'].previousValue !== changes['interval'].currentValue) {
|
if (changes['interval'] && changes['interval'].previousValue !== changes['interval'].currentValue) {
|
||||||
this.selectedInterval = changes['interval'].currentValue;
|
this.selectedInterval = changes['interval'].currentValue;
|
||||||
|
|
@ -92,6 +102,18 @@ export class GraphComponent implements OnInit, AfterViewInit, OnDestroy, OnChang
|
||||||
this.onResize();
|
this.onResize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dateChange(): void {
|
||||||
|
if(!this.startDate || !this.endDate){
|
||||||
|
this.toast.warning('Select both start and end dates');
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
clearTimeout(this.updateTimeout);
|
||||||
|
this.updateTimeout = setTimeout(() => {
|
||||||
|
this.updateCharts();
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
window.removeEventListener('resize', this.resizeListener);
|
window.removeEventListener('resize', this.resizeListener);
|
||||||
}
|
}
|
||||||
|
|
@ -105,13 +127,21 @@ export class GraphComponent implements OnInit, AfterViewInit, OnDestroy, OnChang
|
||||||
this.updateCharts();
|
this.updateCharts();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
formatDate(date: Date): string {
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||||
|
const day = String(date.getDate()).padStart(2, '0');
|
||||||
|
return `${year}-${month}-${day}`;
|
||||||
|
}
|
||||||
|
|
||||||
getDate(): string {
|
getDate(): string {
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
const year = today.getFullYear();
|
const year = today.getFullYear();
|
||||||
const month = String(today.getMonth() + 1).padStart(2, '0');
|
const month = String(today.getMonth() + 1).padStart(2, '0');
|
||||||
const day = String(today.getDate()).padStart(2, '0');
|
const day = String(today.getDate()).padStart(2, '0');
|
||||||
|
|
||||||
return `${year}-${month}-${day}`;
|
const hasil = `${year}-${month}-${day}`;
|
||||||
|
return hasil;
|
||||||
}
|
}
|
||||||
|
|
||||||
getDateAgo():string{
|
getDateAgo():string{
|
||||||
|
|
@ -125,13 +155,13 @@ export class GraphComponent implements OnInit, AfterViewInit, OnDestroy, OnChang
|
||||||
return `${year}-${month}-${day}`;
|
return `${year}-${month}-${day}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchDHTData(timeRange: string): void {
|
fetchDHTData(timeRange: string, startDate?: Date, endDate?: Date): void {
|
||||||
const hEnd = this.getDate();
|
const hStart = startDate ? this.formatDate(startDate) : this.getDate();
|
||||||
const hStart = this.getDate();
|
const hEnd = endDate ? this.formatDate(endDate) : this.getDate();
|
||||||
const dEnd = this.getDate();
|
const dStart= startDate ? this.formatDate(startDate) : this.getDateAgo();
|
||||||
const dStart = this.getDateAgo();
|
const dEnd = endDate ? this.formatDate(endDate) : this.getDate();
|
||||||
|
|
||||||
if(timeRange === 'HOURLY'){
|
if (timeRange === 'HOURLY') {
|
||||||
this.sensorService.getSensorDataHourly('dht', 'npk', hStart, hEnd, timeRange).subscribe({
|
this.sensorService.getSensorDataHourly('dht', 'npk', hStart, hEnd, timeRange).subscribe({
|
||||||
next: (response) => {
|
next: (response) => {
|
||||||
this.isLoadingDHT = false;
|
this.isLoadingDHT = false;
|
||||||
|
|
@ -147,7 +177,7 @@ export class GraphComponent implements OnInit, AfterViewInit, OnDestroy, OnChang
|
||||||
this.isNoDataDHT = true;
|
this.isNoDataDHT = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if(timeRange === 'DAILY'){
|
} else if (timeRange === 'DAILY') {
|
||||||
this.sensorService.getSensorDataDaily('dht', 'npk', dStart, dEnd, timeRange).subscribe({
|
this.sensorService.getSensorDataDaily('dht', 'npk', dStart, dEnd, timeRange).subscribe({
|
||||||
next: (response) => {
|
next: (response) => {
|
||||||
this.isLoadingDHT = false;
|
this.isLoadingDHT = false;
|
||||||
|
|
@ -166,14 +196,14 @@ export class GraphComponent implements OnInit, AfterViewInit, OnDestroy, OnChang
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchNPK1Data(timeRange: string): void {
|
fetchNPK1Data(timeRange: string, startDate?: Date, endDate?: Date): void {
|
||||||
const hEnd = this.getDate();
|
const hStart = startDate ? this.formatDate(startDate) : this.getDate();
|
||||||
const hStart = this.getDate();
|
const hEnd = endDate ? this.formatDate(endDate) : this.getDate();
|
||||||
const dEnd = this.getDate();
|
const dStart = startDate ? this.formatDate(startDate) : this.getDateAgo();
|
||||||
const dStart = this.getDateAgo();
|
const dEnd= endDate ? this.formatDate(endDate) : this.getDate();
|
||||||
|
|
||||||
if(timeRange === 'HOURLY'){
|
if(timeRange === 'HOURLY'){
|
||||||
this.sensorService.getSensorDataHourly('npk1', this.selectedNPK1, hEnd, hStart, timeRange).subscribe({
|
this.sensorService.getSensorDataHourly('npk1', this.selectedNPK1, hStart, hEnd, timeRange).subscribe({
|
||||||
next: (response) => {
|
next: (response) => {
|
||||||
this.isLoadingNPK1 = false;
|
this.isLoadingNPK1 = false;
|
||||||
if (response.statusCode === 200 && response.data.npk1?.length > 0) {
|
if (response.statusCode === 200 && response.data.npk1?.length > 0) {
|
||||||
|
|
@ -187,7 +217,7 @@ export class GraphComponent implements OnInit, AfterViewInit, OnDestroy, OnChang
|
||||||
this.isLoadingNPK1 = false;
|
this.isLoadingNPK1 = false;
|
||||||
this.isNoDataNPK1 = true;
|
this.isNoDataNPK1 = true;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}else if(timeRange === 'DAILY'){
|
}else if(timeRange === 'DAILY'){
|
||||||
this.sensorService.getSensorDataDaily('npk1', this.selectedNPK1, dStart, dEnd, timeRange).subscribe({
|
this.sensorService.getSensorDataDaily('npk1', this.selectedNPK1, dStart, dEnd, timeRange).subscribe({
|
||||||
next: (response) => {
|
next: (response) => {
|
||||||
|
|
@ -207,17 +237,18 @@ export class GraphComponent implements OnInit, AfterViewInit, OnDestroy, OnChang
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchNPK2Data(savedTimeRange: string): void {
|
fetchNPK2Data(savedTimeRange: string, startDate?: Date, endDate?: Date): void {
|
||||||
const hEnd = this.getDate();
|
const hStart = startDate ? this.formatDate(startDate) : this.getDate();
|
||||||
const hStart = this.getDate();
|
const hEnd = endDate ? this.formatDate(endDate) : this.getDate();
|
||||||
const dEnd = this.getDate();
|
const dStart = startDate ? this.formatDate(startDate) : this.getDateAgo();
|
||||||
const dStart = this.getDateAgo();
|
const dEnd = endDate ? this.formatDate(endDate) : this.getDate();
|
||||||
|
|
||||||
if(savedTimeRange === 'HOURLY'){
|
if(savedTimeRange === 'HOURLY'){
|
||||||
this.sensorService.getSensorDataHourly('npk2', this.selectedNPK2, hStart, hEnd, savedTimeRange).subscribe({
|
this.sensorService.getSensorDataHourly('npk2', this.selectedNPK2, hStart, hEnd, savedTimeRange).subscribe({
|
||||||
next: (response) => {
|
next: (response) => {
|
||||||
this.isLoadingNPK2 = false;
|
this.isLoadingNPK2 = false;
|
||||||
if (response.statusCode === 200 && response.data.npk2?.length > 0) {
|
if (response.statusCode === 200 && response.data.npk2?.length > 0) {
|
||||||
|
console.log('NPK2 Data:', response);
|
||||||
this.createChart(this.npk2ChartElement.nativeElement, response, 'npk2', this.selectedNPK2);
|
this.createChart(this.npk2ChartElement.nativeElement, response, 'npk2', this.selectedNPK2);
|
||||||
this.isNoDataNPK2 = false;
|
this.isNoDataNPK2 = false;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -254,13 +285,20 @@ export class GraphComponent implements OnInit, AfterViewInit, OnDestroy, OnChang
|
||||||
Object.keys(this.charts).forEach(key => {
|
Object.keys(this.charts).forEach(key => {
|
||||||
if (this.charts[key]) {
|
if (this.charts[key]) {
|
||||||
this.charts[key]?.destroy();
|
this.charts[key]?.destroy();
|
||||||
|
this.charts[key].data.labels = [];
|
||||||
|
this.charts[key].data.datasets = [];
|
||||||
this.charts[key] = undefined;
|
this.charts[key] = undefined;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
if (this.startDate && this.endDate) {
|
||||||
this.fetchDHTData(interval);
|
this.fetchDHTData(interval, this.startDate, this.endDate);
|
||||||
this.fetchNPK1Data(interval);
|
this.fetchNPK1Data(interval, this.startDate, this.endDate);
|
||||||
this.fetchNPK2Data(interval);
|
this.fetchNPK2Data(interval, this.startDate, this.endDate);
|
||||||
|
} else {
|
||||||
|
this.fetchDHTData(interval);
|
||||||
|
this.fetchNPK1Data(interval);
|
||||||
|
this.fetchNPK2Data(interval);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -369,7 +407,7 @@ export class GraphComponent implements OnInit, AfterViewInit, OnDestroy, OnChang
|
||||||
mode: 'nearest',
|
mode: 'nearest',
|
||||||
intersect: false,
|
intersect: false,
|
||||||
callbacks: {
|
callbacks: {
|
||||||
label: (tooltipItem) => {
|
label: (tooltipItem: any) => {
|
||||||
const paramLabel = tooltipItem.dataset.label;
|
const paramLabel = tooltipItem.dataset.label;
|
||||||
const value = tooltipItem.formattedValue;
|
const value = tooltipItem.formattedValue;
|
||||||
return `${paramLabel}: ${value}`;
|
return `${paramLabel}: ${value}`;
|
||||||
|
|
@ -383,7 +421,7 @@ export class GraphComponent implements OnInit, AfterViewInit, OnDestroy, OnChang
|
||||||
grid: { display: false },
|
grid: { display: false },
|
||||||
beginAtZero: true,
|
beginAtZero: true,
|
||||||
ticks: {
|
ticks: {
|
||||||
callback: (value, index, values) => {
|
callback: (value: string | number, index: number, values: any) => {
|
||||||
const labels = this.getLabels(response, sensor);
|
const labels = this.getLabels(response, sensor);
|
||||||
return labels[index] || value;
|
return labels[index] || value;
|
||||||
}
|
}
|
||||||
|
|
@ -406,7 +444,8 @@ export class GraphComponent implements OnInit, AfterViewInit, OnDestroy, OnChang
|
||||||
sensorData.forEach(item => {
|
sensorData.forEach(item => {
|
||||||
data.push(item[parameter as keyof ParameterSensor] ?? 0);
|
data.push(item[parameter as keyof ParameterSensor] ?? 0);
|
||||||
if(this.interval === 'HOURLY') {
|
if(this.interval === 'HOURLY') {
|
||||||
labels.push(`${item.hour}.00`);
|
labels.push(item.date);
|
||||||
|
labels.push(item.hour);
|
||||||
}
|
}
|
||||||
else if (this.interval === 'DAILY') {
|
else if (this.interval === 'DAILY') {
|
||||||
labels.push(item.day);
|
labels.push(item.day);
|
||||||
|
|
@ -423,11 +462,14 @@ export class GraphComponent implements OnInit, AfterViewInit, OnDestroy, OnChang
|
||||||
const sensorData = response.data[sensor as keyof typeof response.data];
|
const sensorData = response.data[sensor as keyof typeof response.data];
|
||||||
|
|
||||||
return sensorData.map(item => {
|
return sensorData.map(item => {
|
||||||
|
const formatDate = format(new Date(item.date), 'MMM dd');
|
||||||
|
|
||||||
if (this.interval === 'HOURLY' || this.selectedInterval === 'HOURLY') {
|
if (this.interval === 'HOURLY' || this.selectedInterval === 'HOURLY') {
|
||||||
return `${item.hour}.00`;
|
return `${formatDate}, ${item.hour}.00`;
|
||||||
} else if (this.interval === 'DAILY') {
|
} else if (this.interval === 'DAILY') {
|
||||||
const day = item.day;
|
const day = item.date;
|
||||||
return this.convertDateToDay(day);
|
const convertDateToDay = this.convertDateToDay(day);
|
||||||
|
return `${convertDateToDay}, ${formatDate}`;
|
||||||
} else {
|
} else {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
@ -436,7 +478,7 @@ export class GraphComponent implements OnInit, AfterViewInit, OnDestroy, OnChang
|
||||||
|
|
||||||
convertDateToDay(dateString: string): string {
|
convertDateToDay(dateString: string): string {
|
||||||
const date = new Date(dateString);
|
const date = new Date(dateString);
|
||||||
const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thrusday', 'Friday', 'Saturday'];
|
const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thr', 'Fri', 'Sat'];
|
||||||
return days[date.getDay()];
|
return days[date.getDay()];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,6 @@
|
||||||
<h1 class="title">{{ greeting }}</h1>
|
<h1 class="title">{{ greeting }}</h1>
|
||||||
<h3 class="description">View your historical data with customizable time ranges</h3>
|
<h3 class="description">View your historical data with customizable time ranges</h3>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<h2 class="update">Latest Update: {{ latestUpdate }}</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<button (click)="updateInterval('HOURLY')" [class.active-button]="selectedButton === 'hourly'">Hourly</button>
|
<button (click)="updateInterval('HOURLY')" [class.active-button]="selectedButton === 'hourly'">Hourly</button>
|
||||||
<button (click)="updateInterval('DAILY')" [class.active-button]="selectedButton === 'daily'">Daily</button>
|
<button (click)="updateInterval('DAILY')" [class.active-button]="selectedButton === 'daily'">Daily</button>
|
||||||
|
|
|
||||||
|
|
@ -11,13 +11,7 @@
|
||||||
.description {
|
.description {
|
||||||
color: #49473C;
|
color: #49473C;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
margin-top: 10px;
|
margin: 10px 0px 15px 0px;
|
||||||
}
|
|
||||||
|
|
||||||
.update{
|
|
||||||
color: #49473C;
|
|
||||||
font-size: 15px;
|
|
||||||
margin-top: 18px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-container {
|
.card-container {
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ import { ChangeDetectorRef } from '@angular/core';
|
||||||
export class HistorygraphComponent implements OnInit, OnDestroy {
|
export class HistorygraphComponent implements OnInit, OnDestroy {
|
||||||
selectedButton: string = '';
|
selectedButton: string = '';
|
||||||
selectedInterval: string = '';
|
selectedInterval: string = '';
|
||||||
latestUpdate: string = '';
|
|
||||||
intervalId: any;
|
intervalId: any;
|
||||||
greeting: string = '';
|
greeting: string = '';
|
||||||
|
|
||||||
|
|
@ -22,7 +21,6 @@ export class HistorygraphComponent implements OnInit, OnDestroy {
|
||||||
@ViewChild(GraphComponent) graphComponent!: GraphComponent;
|
@ViewChild(GraphComponent) graphComponent!: GraphComponent;
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.startClock();
|
|
||||||
this.updateGreeting();
|
this.updateGreeting();
|
||||||
this.selectedButton = 'hourly';
|
this.selectedButton = 'hourly';
|
||||||
this.selectedInterval = 'HOURLY';
|
this.selectedInterval = 'HOURLY';
|
||||||
|
|
@ -49,23 +47,6 @@ export class HistorygraphComponent implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
startClock(): void {
|
|
||||||
this.updateLatestTime();
|
|
||||||
this.intervalId = setInterval(() => this.updateLatestTime(), 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateLatestTime(): void {
|
|
||||||
const now = new Date();
|
|
||||||
this.latestUpdate = now.toLocaleString('en-GB', {
|
|
||||||
year: 'numeric',
|
|
||||||
month: '2-digit',
|
|
||||||
day: '2-digit',
|
|
||||||
hour: '2-digit',
|
|
||||||
minute: '2-digit',
|
|
||||||
second: '2-digit'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
updateGreeting(): void {
|
updateGreeting(): void {
|
||||||
const hour = new Date().getHours();
|
const hour = new Date().getHours();
|
||||||
if (hour < 12) {
|
if (hour < 12) {
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
<script src="https://code.highcharts.com/modules/export-data.js"></script>
|
<script src="https://code.highcharts.com/modules/export-data.js"></script>
|
||||||
<script src="https://code.highcharts.com/modules/accessibility.js"></script>
|
<script src="https://code.highcharts.com/modules/accessibility.js"></script>
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
|
||||||
|
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<app-root></app-root>
|
<app-root></app-root>
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,30 @@
|
||||||
|
@import '@angular/material/prebuilt-themes/indigo-pink.css';
|
||||||
body{
|
body{
|
||||||
font-family: "Onest", sans-serif;
|
font-family: "Onest", sans-serif;
|
||||||
|
|
||||||
|
}
|
||||||
|
.mat-form-field .mat-input-element:focus {
|
||||||
|
border-color: #16423C !important;
|
||||||
|
box-shadow: 0 1px 5px rgba(0, 128, 0, 0.3) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-form-field .mat-label.mat-focus-indicator {
|
||||||
|
color: #16423C !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-form-field .mat-input-element:focus {
|
||||||
|
caret-color: #16423C !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-form-field .mat-icon {
|
||||||
|
color: #16423C !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.mat-datepicker-toggle.mat-accent {
|
||||||
|
color: #16423C !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-calendar .mat-calendar-body-selected {
|
||||||
|
background-color: #16423C !important;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user