diff --git a/agrilink_vocpro/src/app/cores/interface/sensor-data.ts b/agrilink_vocpro/src/app/cores/interface/sensor-data.ts index f087e6c..6474dff 100644 --- a/agrilink_vocpro/src/app/cores/interface/sensor-data.ts +++ b/agrilink_vocpro/src/app/cores/interface/sensor-data.ts @@ -2,18 +2,18 @@ export interface ParameterSensor { hour: number; //for DHT sensor - vicitemperature_avg?: number; - vicihumidity_avg?: number; - viciluminosity_avg?: number; + vicitemperature?: number; + vicihumidity?: number; + viciluminosity?: number; // for NPK1 & NPK2 sensor - soiltemperature_avg?: number; - soilhumidity_avg?: number; - soilconductivity_avg?: number; - soilph_avg?: number; - soilnitrogen_avg?: number; - soilphosphorus_avg?: number; - soilpotassium_avg?: number; + soiltemperature?: number; + soilhumidity?: number; + soilconductivity?: number; + soilph?: number; + soilnitrogen?: number; + soilphosphorus?: number; + soilpotassium?: number; } export interface ApiResponse { @@ -49,3 +49,8 @@ export interface SensorData { npk2: NPKSensor; } +export interface StatusRelay { + number: number; + current_status: boolean; +} + diff --git a/agrilink_vocpro/src/app/cores/services/api.service.spec.ts b/agrilink_vocpro/src/app/cores/services/api.service.spec.ts deleted file mode 100644 index c0310ae..0000000 --- a/agrilink_vocpro/src/app/cores/services/api.service.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { TestBed } from '@angular/core/testing'; - -import { ApiService } from './api.service'; - -describe('ApiService', () => { - let service: ApiService; - - beforeEach(() => { - TestBed.configureTestingModule({}); - service = TestBed.inject(ApiService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); -}); diff --git a/agrilink_vocpro/src/app/cores/services/auth.service.spec.ts b/agrilink_vocpro/src/app/cores/services/auth.service.spec.ts deleted file mode 100644 index f1251ca..0000000 --- a/agrilink_vocpro/src/app/cores/services/auth.service.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { TestBed } from '@angular/core/testing'; - -import { AuthService } from './auth.service'; - -describe('AuthService', () => { - let service: AuthService; - - beforeEach(() => { - TestBed.configureTestingModule({}); - service = TestBed.inject(AuthService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); -}); diff --git a/agrilink_vocpro/src/app/cores/services/auth.service.ts b/agrilink_vocpro/src/app/cores/services/auth.service.ts index a8d8855..376456b 100644 --- a/agrilink_vocpro/src/app/cores/services/auth.service.ts +++ b/agrilink_vocpro/src/app/cores/services/auth.service.ts @@ -1,10 +1,12 @@ import { Injectable } from '@angular/core'; import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Observable } from 'rxjs'; -import { tap } from 'rxjs/operators'; +import { catchError, tap } from 'rxjs/operators'; import { ApiService } from './api.service'; import { LoginData } from '../interface/auth'; import {jwtDecode}from 'jwt-decode'; +import { StorageService } from './storage.service'; +import { ToastrService } from 'ngx-toastr'; @Injectable({ providedIn: 'root' @@ -14,10 +16,11 @@ export class AuthService extends ApiService { private logoutUrl = `${this.baseUrl}auth/logout`; private registerUrl = `${this.baseUrl}auth/register`; - constructor(http: HttpClient) { + constructor(http: HttpClient, + private storageService: StorageService, + private toast: ToastrService) { super(http); } - login(data: LoginData): Observable { const headers = new HttpHeaders({ @@ -30,20 +33,16 @@ export class AuthService extends ApiService { return this.http.post(this.authUrl, formData, { headers }).pipe( tap(response => { const accessToken = response.data.token; - this.saveTokens(accessToken); + this.storageService.saveToken(accessToken); const jwtToken = response.data.jwtToken; const decodedToken: any = jwtDecode(jwtToken); - this.saveUserDataToStorage(decodedToken.user.fullname, decodedToken.user.avatar); + this.storageService.saveUserData(decodedToken.user.fullname, decodedToken.user.avatar); }) ); } - saveTokens(token: string) { - localStorage.setItem('accessToken', token); - } - logout(): Observable { const token = localStorage.getItem('accessToken'); const headers = new HttpHeaders({ @@ -52,8 +51,12 @@ export class AuthService extends ApiService { return this.http.post(this.logoutUrl, {}, { headers }).pipe( tap(() => { - localStorage.removeItem('accessToken'); - this.clearUserDataFromStorage(); + this.storageService.clearToken(); + this.storageService.clearUserData(); + }), + catchError(error => { + this.toast.error('Failed to logout'); + return error; }) ); } @@ -61,27 +64,18 @@ export class AuthService extends ApiService { register(data: any): Observable { const headers = new HttpHeaders({}); return this.http.post(this.registerUrl, data, { headers }).pipe( - // tap(response => { - // console.log('Registration response:', response); - // }) + catchError(error => { + this.toast.error('Failed to register'); + return error; + }) ); } getUserFullName(): string | null { - return localStorage.getItem('userFullName'); + return this.storageService.getUserFullName(); } getAvatar(): string | null { - return localStorage.getItem('avatar'); - } - - private saveUserDataToStorage(fullName: string | null, avatar: string | null) { - localStorage.setItem('userFullName', fullName || ''); - localStorage.setItem('avatar', avatar || ''); - } - - private clearUserDataFromStorage() { - localStorage.removeItem('userFullName'); - localStorage.removeItem('avatar'); + return this.storageService.getAvatar(); } } diff --git a/agrilink_vocpro/src/app/cores/services/sensor.service.spec.ts b/agrilink_vocpro/src/app/cores/services/sensor.service.spec.ts deleted file mode 100644 index 1a2cdc0..0000000 --- a/agrilink_vocpro/src/app/cores/services/sensor.service.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { TestBed } from '@angular/core/testing'; - -import { SensorService } from './sensor.service'; - -describe('SensorService', () => { - let service: SensorService; - - beforeEach(() => { - TestBed.configureTestingModule({}); - service = TestBed.inject(SensorService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); -}); diff --git a/agrilink_vocpro/src/app/cores/services/sensor.service.ts b/agrilink_vocpro/src/app/cores/services/sensor.service.ts index 4a39720..239ac8b 100644 --- a/agrilink_vocpro/src/app/cores/services/sensor.service.ts +++ b/agrilink_vocpro/src/app/cores/services/sensor.service.ts @@ -3,53 +3,66 @@ import { HttpParams, HttpClient, HttpHeaders, HttpErrorResponse } from '@angular import { catchError, Observable, throwError, tap } from 'rxjs'; import { ApiService } from './api.service'; import { ApiResponse } from '../interface/sensor-data'; +import { StorageService } from './storage.service'; +import { ToastrService } from 'ngx-toastr'; @Injectable({ providedIn: 'root' }) export class SensorService extends ApiService { - constructor(http: HttpClient) { + constructor(http: HttpClient, + private storageService: StorageService, + private toast: ToastrService) { super(http); } + private getDataUrl = `${this.baseUrl}api/sensor/getData`; + private getLatestUrl = `${this.baseUrl}api/sensor/getLatest`; + private getStatusRelay = `${this.baseUrl}api/get-relay`; + + private createAuthHeaders(): HttpHeaders { + const token = this.storageService.getToken(); + return new HttpHeaders({ + Authorization: `Bearer ${token}` + }); + } + getSensorData(sensor: string, metric: string, startEnd: string, timeRange: string): Observable { - const url = `${this.baseUrl}api/sensor/getData`; const params = new HttpParams() .set('range[end]', startEnd) .set('range[time_range]', timeRange) .set('sensor', sensor) .set('metric', metric); - const token = localStorage.getItem('accessToken'); - const headers = new HttpHeaders({ - Authorization: 'Bearer ' + token, - }); + const headers= this.createAuthHeaders(); - return this.http.get(url, { params, headers }).pipe( - catchError((error: HttpErrorResponse) => { - if (error.error instanceof ErrorEvent) { - console.error('An error occurred:', error.error.message); - } else { - console.error(`Backend returned code ${error.status}, body was: ${error.error}`); - } - return throwError('Something went wrong; please try again later.'); - }) + return this.http.get(this.getDataUrl, { params, headers }).pipe( + catchError(error => { + this.toast.error('Failed to get sensor data, please try again'); + return throwError(error); + }) ); } getLatestData(): Observable { - const url = `${this.baseUrl}api/sensor/getLatest`; - const headers = new HttpHeaders({ - Authorization: 'Bearer ' + localStorage.getItem('accessToken'), + const headers = this.createAuthHeaders(); - }); - - return this.http.get(url, { headers }).pipe( + return this.http.get(this.getLatestUrl, { headers }).pipe( catchError(error => { - console.error('API Error:', error); + this.toast.error('Failed to get sensor data, please try again'); + return throwError(error); + }) + ); + } + + getRelayStatus(): Observable { + const headers = this.createAuthHeaders(); + + return this.http.get(this.getStatusRelay, { headers }).pipe( + catchError(error => { + this.toast.error('Failed to get relay status, please try again'); return throwError(error); }) ); - } } diff --git a/agrilink_vocpro/src/app/cores/services/storage.service.ts b/agrilink_vocpro/src/app/cores/services/storage.service.ts new file mode 100644 index 0000000..ab0ea84 --- /dev/null +++ b/agrilink_vocpro/src/app/cores/services/storage.service.ts @@ -0,0 +1,36 @@ +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root' +}) +export class StorageService { + saveToken(token: string): void { + localStorage.setItem('accessToken', token); + } + + getToken(): string | null { + return localStorage.getItem('accessToken'); + } + + clearToken(): void { + localStorage.removeItem('accessToken'); + } + + saveUserData(fullName: string | null, avatar: string | null): void { + localStorage.setItem('userFullName', fullName || ''); + localStorage.setItem('avatar', avatar || ''); + } + + getUserFullName(): string | null { + return localStorage.getItem('userFullName'); + } + + getAvatar(): string | null { + return localStorage.getItem('avatar'); + } + + clearUserData(): void { + localStorage.removeItem('userFullName'); + localStorage.removeItem('avatar'); + } +} diff --git a/agrilink_vocpro/src/app/pages/auth/auth.component.ts b/agrilink_vocpro/src/app/pages/auth/auth.component.ts index ab8a9cd..ec21a19 100644 --- a/agrilink_vocpro/src/app/pages/auth/auth.component.ts +++ b/agrilink_vocpro/src/app/pages/auth/auth.component.ts @@ -1,6 +1,7 @@ import { Component } from '@angular/core'; import { Router, RouterModule } from '@angular/router'; import { AuthService } from '../../cores/services/auth.service'; +import { StorageService } from '../../cores/services/storage.service'; import { FormsModule } from '@angular/forms'; import { LoginData } from '../../cores/interface/auth'; import { ToastrService } from 'ngx-toastr'; @@ -17,7 +18,7 @@ export class AuthComponent { password: string = ''; rememberMe: boolean = false; - constructor(private authService: AuthService, private router: Router, private toastr: ToastrService) {} + constructor(private authService: AuthService, private storageService: StorageService, private router: Router, private toastr: ToastrService) {} onSubmit() { const loginData: LoginData = { @@ -28,7 +29,7 @@ export class AuthComponent { this.authService.login(loginData).subscribe( (response) => { - this.authService.saveTokens(response.data.token); + this.storageService.saveToken(response.data.token); this.router.navigate(['/dashboard']); this.toastr.success('Login successful'); }, diff --git a/agrilink_vocpro/src/app/pages/dashboard/dashboard.component.html b/agrilink_vocpro/src/app/pages/dashboard/dashboard.component.html index 0f7b715..2e00c12 100644 --- a/agrilink_vocpro/src/app/pages/dashboard/dashboard.component.html +++ b/agrilink_vocpro/src/app/pages/dashboard/dashboard.component.html @@ -8,10 +8,10 @@
- + - +
@@ -33,7 +33,7 @@
-

{{sensorData.dht.humidity}}%

+

{{sensorData.dht.humidity}} %RH

Humidity
@@ -48,13 +48,13 @@
-

{{sensorData.npk1.moisture}}%

+

{{sensorData.npk1.moisture}} %RH

Moisture
-

{{sensorData.npk1.conductivity}}mS/cm

+

{{sensorData.npk1.conductivity}} μS/cm

Conductivity
@@ -66,19 +66,19 @@
-

{{sensorData.npk1.nitrogen}}PPM

+

{{sensorData.npk1.nitrogen}} PPM

Nitrogen
-

{{sensorData.npk1.phosphorus}}PPM

+

{{sensorData.npk1.phosphorus}} PPM

Phosphorus
-

{{sensorData.npk1.potassium}}PPM

+

{{sensorData.npk1.potassium}} PPM

Potassium
@@ -93,13 +93,13 @@
-

{{ sensorData.npk1.moisture }}%

+

{{ sensorData.npk1.moisture }} %RH

Moisture
-

{{ sensorData.npk1.conductivity }}mS/cm

+

{{ sensorData.npk1.conductivity }} μS/cm

Conductivity
@@ -111,39 +111,35 @@
-

{{ sensorData.npk1.nitrogen}}PPM

+

{{ sensorData.npk1.nitrogen}} PPM

Nitrogen
-

{{ sensorData.npk2.phosphorus}}PPM

+

{{ sensorData.npk2.phosphorus}} PPM

Phosphorus
-

{{ sensorData.npk2.potassium }}PPM

+

{{ sensorData.npk2.potassium }} PPM

Potassium
- + +
Monitoring
diff --git a/agrilink_vocpro/src/app/pages/dashboard/dashboard.component.scss b/agrilink_vocpro/src/app/pages/dashboard/dashboard.component.scss index daddac4..44ca4c0 100644 --- a/agrilink_vocpro/src/app/pages/dashboard/dashboard.component.scss +++ b/agrilink_vocpro/src/app/pages/dashboard/dashboard.component.scss @@ -99,3 +99,11 @@ button { text-align: center; color: #888; } + +.status-on { + color: #16423C; +} + +.status-off { + color: rgb(144, 6, 6); +} diff --git a/agrilink_vocpro/src/app/pages/dashboard/dashboard.component.ts b/agrilink_vocpro/src/app/pages/dashboard/dashboard.component.ts index 9d1beb9..5a47b28 100644 --- a/agrilink_vocpro/src/app/pages/dashboard/dashboard.component.ts +++ b/agrilink_vocpro/src/app/pages/dashboard/dashboard.component.ts @@ -4,7 +4,7 @@ import { SidebarComponent } from './layouts/sidebar/sidebar.component'; import { GraphComponent } from './page/graph/graph.component'; import { CommonModule } from '@angular/common'; import { SensorService } from '../../cores/services/sensor.service'; -import { SensorData } from '../../cores/interface/sensor-data'; +import { SensorData, StatusRelay } from '../../cores/interface/sensor-data'; import { interval } from 'rxjs'; @Component({ @@ -24,6 +24,7 @@ export class DashboardComponent implements OnInit { npk1: { temperature: 0, moisture: 0, conductivity: 0, ph: 0, nitrogen: 0, phosphorus: 0, potassium: 0 }, npk2: { temperature: 0, moisture: 0, conductivity: 0, ph: 0, nitrogen: 0, phosphorus: 0, potassium: 0 } }; + relayStatus: StatusRelay[] = []; constructor(private apiService: SensorService) {} @@ -63,7 +64,11 @@ export class DashboardComponent implements OnInit { selectSensor(param: string): void { this.selectedButton = param; - this.loadData(); + if(param==='relay'){ + this.loadRelayData(); + }else{ + this.loadData(); + } } loadData(): void { @@ -72,35 +77,36 @@ export class DashboardComponent implements OnInit { this.apiService.getLatestData().subscribe( (response) => { const data = response.data; + console.log('Data:', data); if (data.dht && data.dht.length > 0) { this.sensorData.dht = { - lightIntensity: data.dht[0].viciluminosity_avg ?? 0, - temperature: data.dht[0].vicitemperature_avg ?? 0, - humidity: data.dht[0].vicihumidity_avg ?? 0 + lightIntensity: data.dht[0].viciluminosity ?? 0, + temperature: data.dht[0].vicitemperature ?? 0, + humidity: data.dht[0].vicihumidity ?? 0 }; } if (data.npk1 && data.npk1.length > 0) { this.sensorData.npk1 = { - temperature: data.npk1[0].soiltemperature_avg ?? 0, - moisture: data.npk1[0].soilhumidity_avg ?? 0, - conductivity: data.npk1[0].soilconductivity_avg ?? 0, - ph: data.npk1[0].soilph_avg ?? 0, - nitrogen: data.npk1[0].soilnitrogen_avg ?? 0, - phosphorus: data.npk1[0].soilphosphorus_avg ?? 0, - potassium: data.npk1[0].soilpotassium_avg ?? 0 + temperature: data.npk1[0].soiltemperature ?? 0, + moisture: data.npk1[0].soilhumidity ?? 0, + conductivity: data.npk1[0].soilconductivity ?? 0, + ph: data.npk1[0].soilph ?? 0, + nitrogen: data.npk1[0].soilnitrogen ?? 0, + phosphorus: data.npk1[0].soilphosphorus ?? 0, + potassium: data.npk1[0].soilpotassium ?? 0 }; } if (data.npk2 && data.npk2.length > 0) { this.sensorData.npk2 = { - temperature: data.npk2[0].soiltemperature_avg ?? 0, - moisture: data.npk2[0].soilhumidity_avg ?? 0, - conductivity: data.npk2[0].soilconductivity_avg ?? 0, - ph: data.npk2[0].soilph_avg ?? 0, - nitrogen: data.npk2[0].soilnitrogen_avg ?? 0, - phosphorus: data.npk2[0].soilphosphorus_avg ?? 0, - potassium: data.npk2[0].soilpotassium_avg ?? 0 + temperature: data.npk2[0].soiltemperature ?? 0, + moisture: data.npk2[0].soilhumidity ?? 0, + conductivity: data.npk2[0].soilconductivity ?? 0, + ph: data.npk2[0].soilph ?? 0, + nitrogen: data.npk2[0].soilnitrogen ?? 0, + phosphorus: data.npk2[0].soilphosphorus ?? 0, + potassium: data.npk2[0].soilpotassium ?? 0 }; } @@ -113,5 +119,31 @@ export class DashboardComponent implements OnInit { } ); } + + loadRelayData(): void { + this.isLoaded = true; + + this.apiService.getRelayStatus().subscribe( + (response) => { + if (Array.isArray(response.data)) { + this.relayStatus = response.data.map((relay) => ({ + id: relay.id, + number: relay.number, + enabled_at: relay.enabled_at, + disabled_at: relay.disabled_at, + created_at: relay.created_at, + current_status: relay.current_status + })); + } + console.log('Relay Data:', response); + this.isLoaded = false; + }, + (error) => { + console.error('Error fetching relay data:', error); + this.isLoaded = false; + } + ); + } + } diff --git a/agrilink_vocpro/src/app/pages/dashboard/page/graph/graph.component.html b/agrilink_vocpro/src/app/pages/dashboard/page/graph/graph.component.html index 14671f9..90d7ea4 100644 --- a/agrilink_vocpro/src/app/pages/dashboard/page/graph/graph.component.html +++ b/agrilink_vocpro/src/app/pages/dashboard/page/graph/graph.component.html @@ -1,6 +1,6 @@
diff --git a/agrilink_vocpro/src/app/pages/dashboard/page/graph/graph.component.ts b/agrilink_vocpro/src/app/pages/dashboard/page/graph/graph.component.ts index b278b65..4fbf721 100644 --- a/agrilink_vocpro/src/app/pages/dashboard/page/graph/graph.component.ts +++ b/agrilink_vocpro/src/app/pages/dashboard/page/graph/graph.component.ts @@ -7,16 +7,16 @@ import { FormsModule } from '@angular/forms'; Chart.register(...registerables); const parameterColors: { [key: string]: string } = { - vicitemperature_avg: '#8F5A62', - vicihumidity_avg: '#16423C', - viciluminosity_avg: '#DF9B55', - soiltemperature_avg: '#8F5A62', - soilhumidity_avg: '#54909c', - soilconductivity_avg: '#661311', - soilph_avg: '#664735', - soilnitrogen_avg: '#3a6635', - soilphosphorus_avg: '#3f3566', - soilpotassium_avg: '#5f3566', + vicitemperature: '#8F5A62', + vicihumidity: '#16423C', + viciluminosity: '#DF9B55', + soiltemperature: '#8F5A62', + soilhumidity: '#54909c', + soilconductivity: '#661311', + soilph: '#664735', + soilnitrogen: '#3a6635', + soilphosphorus: '#3f3566', + soilpotassium: '#5f3566', }; @Component({ @@ -34,22 +34,22 @@ export class GraphComponent implements OnInit, AfterViewInit, OnDestroy { isLoading: boolean = true; sensorParameters: { [key: string]: string[] } = { - dht: ['vicitemperature_avg', 'vicihumidity_avg', 'viciluminosity_avg'], - npk1: ['soiltemperature_avg', 'soilhumidity_avg', 'soilconductivity_avg', 'soilph_avg', 'soilnitrogen_avg', 'soilphosphorus_avg', 'soilpotassium_avg'], - npk2: ['soiltemperature_avg', 'soilhumidity_avg', 'soilconductivity_avg', 'soilph_avg', 'soilnitrogen_avg', 'soilphosphorus_avg', 'soilpotassium_avg'], + dht: ['vicitemperature', 'vicihumidity', 'viciluminosity'], + npk1: ['soiltemperature', 'soilhumidity', 'soilconductivity', 'soilph', 'soilnitrogen', 'soilphosphorus', 'soilpotassium'], + npk2: ['soiltemperature', 'soilhumidity', 'soilconductivity', 'soilph', 'soilnitrogen', 'soilphosphorus', 'soilpotassium'], }; parameterDisplayNames: { [key: string]: string } = { - vicitemperature_avg: 'Temperature (°C)', - vicihumidity_avg: 'Humidity (%)', - viciluminosity_avg: 'Luminosity (lx)', - soiltemperature_avg: 'Soil Temperature (°C)', - soilhumidity_avg: 'Soil Humidity (%)', - soilconductivity_avg: 'Conductivity (mS/cm)', - soilph_avg: 'pH', - soilnitrogen_avg: 'Nitrogen (PPM)', - soilphosphorus_avg: 'Phosphorus (PPM)', - soilpotassium_avg: 'Potassium (PPM)', + vicitemperature: 'Temperature (°C)', + vicihumidity: 'Humidity (%)', + viciluminosity: 'Luminosity (lux)', + soiltemperature: 'Soil Temperature (°C)', + soilhumidity: 'Soil Humidity (%)', + soilconductivity: 'Conductivity (μS/cm)', + soilph: 'pH', + soilnitrogen: 'Nitrogen (PPM)', + soilphosphorus: 'Phosphorus (PPM)', + soilpotassium: 'Potassium (PPM)', }; chart: Chart | undefined; @@ -61,7 +61,7 @@ export class GraphComponent implements OnInit, AfterViewInit, OnDestroy { ngOnInit(): void { this.selectedSensor = 'dht'; this.updateParameters(); - this.selectedParameter = 'vicitemperature_avg'; + this.selectedParameter = 'vicitemperature'; this.updateChart(); this.resizeListener = this.onResize.bind(this); window.addEventListener('resize', this.resizeListener); @@ -168,9 +168,9 @@ export class GraphComponent implements OnInit, AfterViewInit, OnDestroy { this.selectedSensor = select.value; this.updateParameters(); if (this.selectedSensor === 'dht') { - this.selectedParameter = 'vicitemperature_avg'; + this.selectedParameter = 'vicitemperature'; } else if (this.selectedSensor === 'npk1' || this.selectedSensor === 'npk2') { - this.selectedParameter = 'soiltemperature_avg'; + this.selectedParameter = 'soiltemperature'; } this.updateChart(); }