feat(historygraph+actualgraph): adding date range & actual range, fix interface

This commit is contained in:
Desy Ayurianti 2024-11-11 22:29:57 +07:00
parent 6ad5951413
commit a68c0f045b
20 changed files with 1492 additions and 139 deletions

File diff suppressed because it is too large Load Diff

View File

@ -10,17 +10,19 @@
},
"private": true,
"dependencies": {
"@angular/animations": "^18.2.8",
"@angular/animations": "^18.2.4",
"@angular/common": "^18.2.0",
"@angular/compiler": "^18.2.0",
"@angular/core": "^18.2.0",
"@angular/forms": "^18.2.0",
"@angular/material": "^17.3.6",
"@angular/platform-browser": "^18.2.0",
"@angular/platform-browser-dynamic": "^18.2.0",
"@angular/router": "^18.2.0",
"bootstrap": "^5.3.3",
"bootstrap-icons": "^1.11.3",
"chart.js": "^4.4.4",
"date-fns": "^4.1.0",
"highcharts-angular": "^4.0.1",
"jwt-decode": "^4.0.0",
"ngx-toastr": "^19.0.0",
@ -32,6 +34,7 @@
"@angular-devkit/build-angular": "^18.2.4",
"@angular/cli": "^18.2.4",
"@angular/compiler-cli": "^18.2.0",
"@types/date-fns": "^2.5.3",
"@types/jasmine": "~5.1.0",
"jasmine-core": "~5.2.0",
"karma": "~6.4.0",

View File

@ -5,6 +5,7 @@ import { AuthComponent } from './pages/auth/auth.component';
import { AuthGuard } from './cores/guards/auth.guard';
import { RegisterComponent } from './pages/register/register.component';
import { HistorygraphComponent } from './pages/dashboard/page/historygraph/historygraph.component';
import { ActualgraphComponent } from './pages/dashboard/page/actualgraph/actualgraph.component';
export const routes: Routes = [
{
@ -33,6 +34,11 @@ export const routes: Routes = [
path: 'historygraph',
component: HistorygraphComponent,
canActivate: [AuthGuard]
},
{
path: 'actualgraph',
component: ActualgraphComponent,
canActivate: [AuthGuard]
}
]
}

View File

@ -1,6 +1,7 @@
export interface ParameterSensor {
hour: number;
day: number;
date: number;
//for DHT sensor
vicitemperature?: number;

View File

@ -3,9 +3,6 @@
<h1 class="title">{{ greeting }}</h1>
<h3 class="description">Welcome back to your management system</h3>
</div>
<div>
<h2 class="update">Latest Update: {{latestUpdate}}</h2>
</div>
<div>
<button [ngClass]="{'active-button': selectedButton === 'dht'}" (click)="selectSensor('dht')">BHT</button>
@ -158,10 +155,15 @@
Katup Nutrisi
</ng-container>
<ng-template #checkRelay>
<ng-container *ngIf="relay.number === 2; else defaultRelay">
<ng-container *ngIf="relay.number === 2; else checkRelay2">
Katup Air
</ng-container>
</ng-template>
<ng-template #checkRelay2>
<ng-container *ngIf="relay.number === 3; else defaultRelay">
Pompa Air
</ng-container>
</ng-template>
<ng-template #defaultRelay>
Relay {{ relay.number }}
</ng-template>
@ -170,9 +172,7 @@
</div>
</div>
</div>
</div>
</div>
<div class="graph">
<div class="title-graph">Monitoring Graphs</div>

View File

@ -16,13 +16,7 @@
.description {
color: #49473C;
font-size: 15px;
margin-top: 10px;
}
.update{
color: #49473C;
font-size: 15px;
margin-top: 18px;
margin: 10px 0px 15px 0px;
}
.card-container {
@ -102,6 +96,10 @@
margin-top: 20px;
justify-content: center;
}
.relay-container{
justify-content: center;
}
}
@media (max-width: 344px) {
@ -126,6 +124,10 @@
margin-top: 20px;
justify-content: center;
}
.relay-container{
justify-content: center;
}
}
button {

View File

@ -33,7 +33,6 @@ export class DashboardComponent implements OnInit {
ngOnInit(): void {
this.selectedButton = 'dht';
this.startClock();
this.updateGreeting();
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 {
const hour = new Date().getHours();
@ -131,8 +110,6 @@ export class DashboardComponent implements OnInit {
};
}
}
this.updateLatestTime();
this.isLoaded = false;
},
(error) => {
@ -157,10 +134,10 @@ export class DashboardComponent implements OnInit {
created_at: relay.created_at,
current_status: relay.current_status
}));
this.relayStatus.sort((a, b) => a.number - b.number);
this.noData = false; // Data available
this.relayStatus.sort((a, b) => b.number - a.number);
this.noData = false;
} else {
// Throw an error if relay data is empty
this.handleError({ message: 'No available relay data.' });
this.noData = true;
}
@ -172,8 +149,7 @@ export class DashboardComponent implements OnInit {
this.hasError = true;
}
);
}
}
handleError(error: any): void {
if (this.selectedButton === 'dht') {

View File

@ -11,6 +11,10 @@
<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>
</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>
<hr>

View File

@ -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>

View File

@ -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;
}

View File

@ -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();
});
});

View File

@ -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;
},
},
},
},
};
}

View File

@ -4,6 +4,18 @@
</ng-container>
<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="title">Sensor BHT</div>
<ng-container *ngIf="isLoadingDHT; else dhtData">

View File

@ -1,9 +1,26 @@
.container-graph {
display: flex;
flex-direction: column;
justify-content: flex-start;
position: relative;
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 {
position: relative;
@ -62,6 +79,17 @@
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) {
@ -70,6 +98,17 @@
width: 100%;
max-width: 100%;
}
.date-picker{
display: flex;
justify-content: center;
padding: 10px 20px;
}
mat-hint{
font-size: 10px;
align-items: center;
}
}
.loading {
@ -88,3 +127,5 @@
height: 100%;
font-size: 18px;
}

View File

@ -2,8 +2,13 @@ import { Component, OnInit, ElementRef, ViewChild, AfterViewInit, OnDestroy, OnC
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';
import { CommonModule, formatDate } from '@angular/common';
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);
@ -23,7 +28,8 @@ const parameterColors: { [key: string]: string } = {
@Component({
selector: 'app-graph',
standalone: true,
imports: [CommonModule, FormsModule],
imports: [CommonModule, FormsModule, MatDatepickerModule, MatFormFieldModule],
providers: [provideNativeDateAdapter()],
templateUrl: './graph.component.html',
styleUrls: ['./graph.component.scss']
})
@ -34,6 +40,9 @@ export class GraphComponent implements OnInit, AfterViewInit, OnDestroy, OnChang
@Input() interval: string = 'hourly';
selectedInterval: string = 'HOURLY';
startDate: Date | null = null;
endDate: Date | null = null;
isLoadingDHT: boolean = true;
isLoadingNPK1: boolean = true;
isLoadingNPK2: boolean = true;
@ -73,15 +82,16 @@ export class GraphComponent implements OnInit, AfterViewInit, OnDestroy, OnChang
selectedNPK2: string = 'npk';
private resizeListener!: () => void;
private updateTimeout: any;
constructor(private sensorService: SensorService, private cdr: ChangeDetectorRef) {}
constructor(private sensorService: SensorService, private toast: ToastrService) {}
ngOnInit(): void {
this.resizeListener = this.onResize.bind(this);
window.addEventListener('resize', this.resizeListener);
this.updateCharts();
}
ngOnChanges(changes: SimpleChanges): void {
if (changes['interval'] && changes['interval'].previousValue !== changes['interval'].currentValue) {
this.selectedInterval = changes['interval'].currentValue;
@ -92,6 +102,18 @@ export class GraphComponent implements OnInit, AfterViewInit, OnDestroy, OnChang
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 {
window.removeEventListener('resize', this.resizeListener);
}
@ -105,13 +127,21 @@ export class GraphComponent implements OnInit, AfterViewInit, OnDestroy, OnChang
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 {
const today = new Date();
const year = today.getFullYear();
const month = String(today.getMonth() + 1).padStart(2, '0');
const day = String(today.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
const hasil = `${year}-${month}-${day}`;
return hasil;
}
getDateAgo():string{
@ -125,13 +155,13 @@ export class GraphComponent implements OnInit, AfterViewInit, OnDestroy, OnChang
return `${year}-${month}-${day}`;
}
fetchDHTData(timeRange: string): void {
const hEnd = this.getDate();
const hStart = this.getDate();
const dEnd = this.getDate();
const dStart = this.getDateAgo();
fetchDHTData(timeRange: string, startDate?: Date, endDate?: Date): void {
const hStart = startDate ? this.formatDate(startDate) : this.getDate();
const hEnd = endDate ? this.formatDate(endDate) : this.getDate();
const dStart= startDate ? this.formatDate(startDate) : 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({
next: (response) => {
this.isLoadingDHT = false;
@ -147,7 +177,7 @@ export class GraphComponent implements OnInit, AfterViewInit, OnDestroy, OnChang
this.isNoDataDHT = true;
}
});
} else if(timeRange === 'DAILY'){
} else if (timeRange === 'DAILY') {
this.sensorService.getSensorDataDaily('dht', 'npk', dStart, dEnd, timeRange).subscribe({
next: (response) => {
this.isLoadingDHT = false;
@ -166,14 +196,14 @@ export class GraphComponent implements OnInit, AfterViewInit, OnDestroy, OnChang
}
}
fetchNPK1Data(timeRange: string): void {
const hEnd = this.getDate();
const hStart = this.getDate();
const dEnd = this.getDate();
const dStart = this.getDateAgo();
fetchNPK1Data(timeRange: string, startDate?: Date, endDate?: Date): void {
const hStart = startDate ? this.formatDate(startDate) : this.getDate();
const hEnd = endDate ? this.formatDate(endDate) : this.getDate();
const dStart = startDate ? this.formatDate(startDate) : this.getDateAgo();
const dEnd= endDate ? this.formatDate(endDate) : this.getDate();
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) => {
this.isLoadingNPK1 = false;
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.isNoDataNPK1 = true;
}
})
});
}else if(timeRange === 'DAILY'){
this.sensorService.getSensorDataDaily('npk1', this.selectedNPK1, dStart, dEnd, timeRange).subscribe({
next: (response) => {
@ -207,17 +237,18 @@ export class GraphComponent implements OnInit, AfterViewInit, OnDestroy, OnChang
}
}
fetchNPK2Data(savedTimeRange: string): void {
const hEnd = this.getDate();
const hStart = this.getDate();
const dEnd = this.getDate();
const dStart = this.getDateAgo();
fetchNPK2Data(savedTimeRange: string, startDate?: Date, endDate?: Date): void {
const hStart = startDate ? this.formatDate(startDate) : this.getDate();
const hEnd = endDate ? this.formatDate(endDate) : this.getDate();
const dStart = startDate ? this.formatDate(startDate) : this.getDateAgo();
const dEnd = endDate ? this.formatDate(endDate) : this.getDate();
if(savedTimeRange === 'HOURLY'){
this.sensorService.getSensorDataHourly('npk2', this.selectedNPK2, hStart, hEnd, savedTimeRange).subscribe({
next: (response) => {
this.isLoadingNPK2 = false;
if (response.statusCode === 200 && response.data.npk2?.length > 0) {
console.log('NPK2 Data:', response);
this.createChart(this.npk2ChartElement.nativeElement, response, 'npk2', this.selectedNPK2);
this.isNoDataNPK2 = false;
} else {
@ -254,13 +285,20 @@ export class GraphComponent implements OnInit, AfterViewInit, OnDestroy, OnChang
Object.keys(this.charts).forEach(key => {
if (this.charts[key]) {
this.charts[key]?.destroy();
this.charts[key].data.labels = [];
this.charts[key].data.datasets = [];
this.charts[key] = undefined;
}
});
this.fetchDHTData(interval);
this.fetchNPK1Data(interval);
this.fetchNPK2Data(interval);
if (this.startDate && this.endDate) {
this.fetchDHTData(interval, this.startDate, this.endDate);
this.fetchNPK1Data(interval, this.startDate, this.endDate);
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',
intersect: false,
callbacks: {
label: (tooltipItem) => {
label: (tooltipItem: any) => {
const paramLabel = tooltipItem.dataset.label;
const value = tooltipItem.formattedValue;
return `${paramLabel}: ${value}`;
@ -383,7 +421,7 @@ export class GraphComponent implements OnInit, AfterViewInit, OnDestroy, OnChang
grid: { display: false },
beginAtZero: true,
ticks: {
callback: (value, index, values) => {
callback: (value: string | number, index: number, values: any) => {
const labels = this.getLabels(response, sensor);
return labels[index] || value;
}
@ -406,7 +444,8 @@ export class GraphComponent implements OnInit, AfterViewInit, OnDestroy, OnChang
sensorData.forEach(item => {
data.push(item[parameter as keyof ParameterSensor] ?? 0);
if(this.interval === 'HOURLY') {
labels.push(`${item.hour}.00`);
labels.push(item.date);
labels.push(item.hour);
}
else if (this.interval === 'DAILY') {
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];
return sensorData.map(item => {
const formatDate = format(new Date(item.date), 'MMM dd');
if (this.interval === 'HOURLY' || this.selectedInterval === 'HOURLY') {
return `${item.hour}.00`;
return `${formatDate}, ${item.hour}.00`;
} else if (this.interval === 'DAILY') {
const day = item.day;
return this.convertDateToDay(day);
const day = item.date;
const convertDateToDay = this.convertDateToDay(day);
return `${convertDateToDay}, ${formatDate}`;
} else {
return '';
}
@ -436,7 +478,7 @@ export class GraphComponent implements OnInit, AfterViewInit, OnDestroy, OnChang
convertDateToDay(dateString: string): string {
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()];
}

View File

@ -3,10 +3,6 @@
<h1 class="title">{{ greeting }}</h1>
<h3 class="description">View your historical data with customizable time ranges</h3>
</div>
<div>
<h2 class="update">Latest Update: {{ latestUpdate }}</h2>
</div>
<div>
<button (click)="updateInterval('HOURLY')" [class.active-button]="selectedButton === 'hourly'">Hourly</button>
<button (click)="updateInterval('DAILY')" [class.active-button]="selectedButton === 'daily'">Daily</button>

View File

@ -11,13 +11,7 @@
.description {
color: #49473C;
font-size: 15px;
margin-top: 10px;
}
.update{
color: #49473C;
font-size: 15px;
margin-top: 18px;
margin: 10px 0px 15px 0px;
}
.card-container {

View File

@ -13,7 +13,6 @@ import { ChangeDetectorRef } from '@angular/core';
export class HistorygraphComponent implements OnInit, OnDestroy {
selectedButton: string = '';
selectedInterval: string = '';
latestUpdate: string = '';
intervalId: any;
greeting: string = '';
@ -22,7 +21,6 @@ export class HistorygraphComponent implements OnInit, OnDestroy {
@ViewChild(GraphComponent) graphComponent!: GraphComponent;
ngOnInit(): void {
this.startClock();
this.updateGreeting();
this.selectedButton = '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 {
const hour = new Date().getHours();
if (hour < 12) {

View File

@ -18,6 +18,7 @@
<script src="https://code.highcharts.com/modules/export-data.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 href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</head>
<body>
<app-root></app-root>

View File

@ -1,3 +1,30 @@
@import '@angular/material/prebuilt-themes/indigo-pink.css';
body{
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;
}