dev smartfarming #1
|
|
@ -1,9 +1,10 @@
|
||||||
import { ApplicationConfig, provideZoneChangeDetection, importProvidersFrom } from '@angular/core';
|
import { ApplicationConfig, provideZoneChangeDetection, importProvidersFrom } from '@angular/core';
|
||||||
import { provideRouter } from '@angular/router';
|
import { provideRouter } from '@angular/router';
|
||||||
import { provideHttpClient } from '@angular/common/http';
|
import { provideHttpClient, withInterceptors } from '@angular/common/http';
|
||||||
import { provideAnimations } from '@angular/platform-browser/animations';
|
import { provideAnimations } from '@angular/platform-browser/animations';
|
||||||
import { ToastrModule } from 'ngx-toastr';
|
import { ToastrModule } from 'ngx-toastr';
|
||||||
import { routes } from './app.routes';
|
import { routes } from './app.routes';
|
||||||
|
import { httpErrorInterceptor } from './cores/interceptors/http-error-interceptor.service';
|
||||||
|
|
||||||
export const appConfig: ApplicationConfig = {
|
export const appConfig: ApplicationConfig = {
|
||||||
providers: [
|
providers: [
|
||||||
|
|
@ -12,5 +13,6 @@ export const appConfig: ApplicationConfig = {
|
||||||
provideHttpClient(),
|
provideHttpClient(),
|
||||||
provideAnimations(),
|
provideAnimations(),
|
||||||
importProvidersFrom(ToastrModule.forRoot()),
|
importProvidersFrom(ToastrModule.forRoot()),
|
||||||
|
provideHttpClient(withInterceptors([httpErrorInterceptor])) // Register your HttpErrorInterceptor
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { Routes } from '@angular/router';
|
import { Routes } from '@angular/router';
|
||||||
import { DashboardComponent } from './pages/dashboard/dashboard.component';
|
import { DashboardComponent } from './pages/dashboard/dashboard.component';
|
||||||
import { LayoutsComponent } from './pages/dashboard/layouts/layouts.component';
|
import { LayoutsComponent } from './pages/dashboard/layouts/layouts.component';
|
||||||
import { GraphComponent } from './pages/dashboard/page/graph/graph.component';
|
|
||||||
import { AuthComponent } from './pages/auth/auth.component';
|
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';
|
||||||
|
|
@ -29,11 +28,6 @@ export const routes: Routes = [
|
||||||
component: DashboardComponent,
|
component: DashboardComponent,
|
||||||
canActivate: [AuthGuard]
|
canActivate: [AuthGuard]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: 'graph',
|
|
||||||
component: GraphComponent,
|
|
||||||
canActivate: [AuthGuard]
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,20 @@
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { CanActivate, Router } from '@angular/router';
|
import { CanActivate, Router } from '@angular/router';
|
||||||
import { ToastrService } from 'ngx-toastr';
|
import { ToastrService } from 'ngx-toastr';
|
||||||
|
import { StorageService } from '../services/storage.service';
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class AuthGuard implements CanActivate {
|
export class AuthGuard implements CanActivate {
|
||||||
|
|
||||||
constructor(private router: Router, private toast: ToastrService) {}
|
constructor(
|
||||||
|
private router: Router,
|
||||||
|
private toast: ToastrService,
|
||||||
|
private storageService: StorageService
|
||||||
|
) {}
|
||||||
|
|
||||||
canActivate(): boolean {
|
canActivate(): boolean {
|
||||||
const token = localStorage.getItem('accessToken');
|
const token = this.storageService.getToken();
|
||||||
if (token) {
|
if (token) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
import { HttpInterceptorFn } from '@angular/common/http';
|
||||||
|
import { ToastrService } from 'ngx-toastr';
|
||||||
|
import { inject } from '@angular/core';
|
||||||
|
import { catchError, throwError } from 'rxjs';
|
||||||
|
import { HttpErrorResponse } from '@angular/common/http';
|
||||||
|
|
||||||
|
export const httpErrorInterceptor: HttpInterceptorFn = (req, next) => {
|
||||||
|
const toast = inject(ToastrService);
|
||||||
|
|
||||||
|
return next(req).pipe(
|
||||||
|
catchError((error: HttpErrorResponse) => {
|
||||||
|
let errorMessage = '';
|
||||||
|
|
||||||
|
if (error.error instanceof ErrorEvent) {
|
||||||
|
errorMessage = `Client error: ${error.error.message}`;
|
||||||
|
} else {
|
||||||
|
switch (error.status) {
|
||||||
|
case 0:
|
||||||
|
errorMessage = 'Network error: Please check your internet connection.';
|
||||||
|
break;
|
||||||
|
case 401:
|
||||||
|
errorMessage = 'Unauthorized: Please log in again.';
|
||||||
|
break;
|
||||||
|
case 403:
|
||||||
|
errorMessage = 'Forbidden: You do not have permission to access this resource.';
|
||||||
|
break;
|
||||||
|
case 404:
|
||||||
|
errorMessage = 'Resource not found: The requested resource does not exist.';
|
||||||
|
break;
|
||||||
|
case 500:
|
||||||
|
errorMessage = 'Server error: Please try again later.';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
errorMessage = `Unexpected error: ${error.message}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.error(errorMessage, 'Error', { timeOut: 3000 });
|
||||||
|
|
||||||
|
return throwError(() => new Error(errorMessage));
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -24,9 +24,7 @@ export interface ApiResponse {
|
||||||
};
|
};
|
||||||
statusCode: number;
|
statusCode: number;
|
||||||
message: string;
|
message: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface DHTSensor {
|
export interface DHTSensor {
|
||||||
lightIntensity: number;
|
lightIntensity: number;
|
||||||
temperature: number;
|
temperature: number;
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,10 @@
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable, tap } from 'rxjs';
|
||||||
import { catchError, tap } from 'rxjs/operators';
|
|
||||||
import { ApiService } from './api.service';
|
import { ApiService } from './api.service';
|
||||||
import { LoginData } from '../interface/auth';
|
import { LoginData } from '../interface/auth';
|
||||||
import {jwtDecode}from 'jwt-decode';
|
import { jwtDecode } from 'jwt-decode';
|
||||||
import { StorageService } from './storage.service';
|
import { StorageService } from './storage.service';
|
||||||
import { ToastrService } from 'ngx-toastr';
|
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
|
|
@ -16,9 +14,10 @@ export class AuthService extends ApiService {
|
||||||
private logoutUrl = `${this.baseUrl}auth/logout`;
|
private logoutUrl = `${this.baseUrl}auth/logout`;
|
||||||
private registerUrl = `${this.baseUrl}auth/register`;
|
private registerUrl = `${this.baseUrl}auth/register`;
|
||||||
|
|
||||||
constructor(http: HttpClient,
|
constructor(
|
||||||
|
http: HttpClient,
|
||||||
private storageService: StorageService,
|
private storageService: StorageService,
|
||||||
private toast: ToastrService) {
|
) {
|
||||||
super(http);
|
super(http);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -44,7 +43,7 @@ export class AuthService extends ApiService {
|
||||||
}
|
}
|
||||||
|
|
||||||
logout(): Observable<any> {
|
logout(): Observable<any> {
|
||||||
const token = localStorage.getItem('accessToken');
|
const token = this.storageService.getToken();
|
||||||
const headers = new HttpHeaders({
|
const headers = new HttpHeaders({
|
||||||
Authorization: `Bearer ${token}`
|
Authorization: `Bearer ${token}`
|
||||||
});
|
});
|
||||||
|
|
@ -54,21 +53,12 @@ export class AuthService extends ApiService {
|
||||||
this.storageService.clearToken();
|
this.storageService.clearToken();
|
||||||
this.storageService.clearUserData();
|
this.storageService.clearUserData();
|
||||||
}),
|
}),
|
||||||
catchError(error => {
|
|
||||||
this.toast.error('Failed to logout');
|
|
||||||
return error;
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
register(data: any): Observable<any> {
|
register(data: any): Observable<any> {
|
||||||
const headers = new HttpHeaders({});
|
const headers = new HttpHeaders({});
|
||||||
return this.http.post<any>(this.registerUrl, data, { headers }).pipe(
|
return this.http.post<any>(this.registerUrl, data, { headers });
|
||||||
catchError(error => {
|
|
||||||
this.toast.error('Failed to register');
|
|
||||||
return error;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getUserFullName(): string | null {
|
getUserFullName(): string | null {
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ export class SensorService extends ApiService {
|
||||||
|
|
||||||
private getDataUrl = `${this.baseUrl}api/sensor/getData`;
|
private getDataUrl = `${this.baseUrl}api/sensor/getData`;
|
||||||
private getLatestUrl = `${this.baseUrl}api/sensor/getLatest`;
|
private getLatestUrl = `${this.baseUrl}api/sensor/getLatest`;
|
||||||
private getStatusRelay = `${this.baseUrl}api/get-relay`;
|
private getStatusRelay = `${this.baseUrl}api/relay/get-relay`;
|
||||||
|
|
||||||
private createAuthHeaders(): HttpHeaders {
|
private createAuthHeaders(): HttpHeaders {
|
||||||
const token = this.storageService.getToken();
|
const token = this.storageService.getToken();
|
||||||
|
|
@ -38,7 +38,7 @@ export class SensorService extends ApiService {
|
||||||
|
|
||||||
return this.http.get<ApiResponse>(this.getDataUrl, { params, headers }).pipe(
|
return this.http.get<ApiResponse>(this.getDataUrl, { params, headers }).pipe(
|
||||||
catchError(error => {
|
catchError(error => {
|
||||||
this.toast.error('Failed to get sensor data, please try again');
|
// this.toast.error('Failed to get sensor data for graphic, please try again');
|
||||||
return throwError(error);
|
return throwError(error);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
@ -49,7 +49,7 @@ export class SensorService extends ApiService {
|
||||||
|
|
||||||
return this.http.get<any>(this.getLatestUrl, { headers }).pipe(
|
return this.http.get<any>(this.getLatestUrl, { headers }).pipe(
|
||||||
catchError(error => {
|
catchError(error => {
|
||||||
this.toast.error('Failed to get sensor data, please try again');
|
// this.toast.error('Failed to get latest sensor data, please try again');
|
||||||
return throwError(error);
|
return throwError(error);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
@ -60,7 +60,7 @@ export class SensorService extends ApiService {
|
||||||
|
|
||||||
return this.http.get<any>(this.getStatusRelay, { headers }).pipe(
|
return this.http.get<any>(this.getStatusRelay, { headers }).pipe(
|
||||||
catchError(error => {
|
catchError(error => {
|
||||||
this.toast.error('Failed to get relay status, please try again');
|
// this.toast.error('Failed to get relay status, please try again');
|
||||||
return throwError(error);
|
return throwError(error);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -8,24 +8,52 @@
|
||||||
<div class="col-md-9 col-lg-8 mx-auto">
|
<div class="col-md-9 col-lg-8 mx-auto">
|
||||||
<h3 class="login-heading mb-4">Welcome back!</h3>
|
<h3 class="login-heading mb-4">Welcome back!</h3>
|
||||||
|
|
||||||
|
|
||||||
<form (ngSubmit)="onSubmit()">
|
<form (ngSubmit)="onSubmit()">
|
||||||
<div class="form-floating mb-3">
|
<div class="form-floating mb-3">
|
||||||
<input type="email" class="form-control" [(ngModel)]="email" name="email" id="floatingInput" placeholder="name@example.com" required>
|
<input type="email"
|
||||||
|
class="form-control"
|
||||||
|
[(ngModel)]="email"
|
||||||
|
name="email"
|
||||||
|
id="floatingInput"
|
||||||
|
placeholder="name@example.com"
|
||||||
|
required>
|
||||||
<label for="floatingInput">Email address</label>
|
<label for="floatingInput">Email address</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-floating mb-3">
|
<div class="form-floating mb-3">
|
||||||
<input type="password" class="form-control" [(ngModel)]="password" name="password" id="floatingPassword" placeholder="Password" required>
|
<input [type]="passwordVisible ? 'text' : 'password'"
|
||||||
|
class="form-control"
|
||||||
|
[(ngModel)]="password"
|
||||||
|
name="password"
|
||||||
|
id="floatingPassword"
|
||||||
|
placeholder="Password"
|
||||||
|
required>
|
||||||
<label for="floatingPassword">Password</label>
|
<label for="floatingPassword">Password</label>
|
||||||
|
|
||||||
|
<button type="button"
|
||||||
|
class="btn btn-link position-absolute end-0 me-3"
|
||||||
|
(click)="togglePasswordVisibility()">
|
||||||
|
<i class="fa" [ngClass]="passwordVisible ? 'fa-eye' : 'fa-eye-slash'"></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-check mb-3">
|
<div class="form-check mb-3">
|
||||||
<input class="form-check-input" type="checkbox" [(ngModel)]="rememberMe" name="rememberMe" id="rememberPasswordCheck">
|
<input class="form-check-input"
|
||||||
<label class="form-check-label" for="rememberPasswordCheck">Remember Me? </label>
|
type="checkbox"
|
||||||
|
[(ngModel)]="rememberMe"
|
||||||
|
name="rememberMe"
|
||||||
|
id="rememberPasswordCheck">
|
||||||
|
<label class="form-check-label" for="rememberPasswordCheck">Remember me</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="d-grid">
|
<div class="d-grid">
|
||||||
<button class="btn btn-lg color-btn btn-login text-uppercase fw-bold mb-2" type="submit">Sign in</button>
|
<button class="btn btn-lg color-btn btn-login text-uppercase fw-bold mb-2"
|
||||||
|
type="submit"
|
||||||
|
[disabled]="loading">
|
||||||
|
<span *ngIf="!loading">Sign in</span>
|
||||||
|
<span *ngIf="loading">
|
||||||
|
<i class="fa fa-spinner fa-spin"></i>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<a class="small forgot" routerLink= '/register'>Register here</a>
|
<a class="small forgot" routerLink= '/register'>Register here</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
.login {
|
.login {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
font-family: "Onest", sans-serif;
|
font-family: "Onest", sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -19,11 +19,40 @@
|
||||||
padding: 0.75rem 1rem;
|
padding: 0.75rem 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.color-btn{
|
.color-btn {
|
||||||
background-color: #16423C;
|
background-color: #16423C;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.forgot{
|
.forgot {
|
||||||
color: #16423C;
|
color: #16423C;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form-floating {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.btn-link {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
right: 10px;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #16423C;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #16423C;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-check-input:checked {
|
||||||
|
background-color: #16423C;
|
||||||
|
border-color: #16423C;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-check-input:checked::before {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,13 @@ import { StorageService } from '../../cores/services/storage.service';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
import { LoginData } from '../../cores/interface/auth';
|
import { LoginData } from '../../cores/interface/auth';
|
||||||
import { ToastrService } from 'ngx-toastr';
|
import { ToastrService } from 'ngx-toastr';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { catchError } from 'rxjs';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-auth',
|
selector: 'app-auth',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [FormsModule, RouterModule],
|
imports: [FormsModule, RouterModule, CommonModule],
|
||||||
templateUrl: './auth.component.html',
|
templateUrl: './auth.component.html',
|
||||||
styleUrls: ['./auth.component.scss']
|
styleUrls: ['./auth.component.scss']
|
||||||
})
|
})
|
||||||
|
|
@ -17,25 +19,43 @@ export class AuthComponent {
|
||||||
email: string = '';
|
email: string = '';
|
||||||
password: string = '';
|
password: string = '';
|
||||||
rememberMe: boolean = false;
|
rememberMe: boolean = false;
|
||||||
|
loading: boolean = false;
|
||||||
|
passwordVisible: boolean = false;
|
||||||
|
|
||||||
constructor(private authService: AuthService, private storageService: StorageService, private router: Router, private toastr: ToastrService) {}
|
constructor(private authService: AuthService,
|
||||||
|
private storageService: StorageService,
|
||||||
|
private router: Router,
|
||||||
|
private toast: ToastrService) {}
|
||||||
|
|
||||||
onSubmit() {
|
onSubmit() {
|
||||||
|
this.loading = true;
|
||||||
|
if (!this.email || !this.password) {
|
||||||
|
this.loading = false;
|
||||||
|
this.toast.error('Please fill in all fields.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const loginData: LoginData = {
|
const loginData: LoginData = {
|
||||||
email: this.email,
|
email: this.email,
|
||||||
password: this.password,
|
password: this.password,
|
||||||
rememberMe: this.rememberMe
|
rememberMe: this.rememberMe
|
||||||
};
|
};
|
||||||
|
|
||||||
this.authService.login(loginData).subscribe(
|
this.authService.login(loginData).subscribe(
|
||||||
(response) => {
|
(response) => {
|
||||||
this.storageService.saveToken(response.data.token);
|
this.storageService.saveToken(response.data.token);
|
||||||
this.router.navigate(['/dashboard']);
|
this.router.navigate(['/dashboard']);
|
||||||
this.toastr.success('Login successful');
|
this.toast.success('Login successful');
|
||||||
|
this.loading = false;
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
this.toastr.error(error.error.message);
|
this.loading = false;
|
||||||
|
this.toast.error(error.error.message);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
togglePasswordVisibility() {
|
||||||
|
this.passwordVisible = !this.passwordVisible; // Toggle password visibility
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,150 +1,124 @@
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="title">Hello there</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>
|
<div>
|
||||||
<h2 class="update">Latest Update: {{latestUpdate}}</h2>
|
<h2 class="update">Latest Update: {{latestUpdate}}</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<button [ngClass]="{'active-button': selectedButton === 'dht'}" (click)="selectSensor('dht')">BHT</button>
|
<button [ngClass]="{'active-button': selectedButton === 'dht'}" (click)="selectSensor('dht')">DHT</button>
|
||||||
<button [ngClass]="{'active-button': selectedButton === 'npk1'}" (click)="selectSensor('npk1')">NPK 1</button>
|
<button [ngClass]="{'active-button': selectedButton === 'npk1'}" (click)="selectSensor('npk1')">NPK 1</button>
|
||||||
<button [ngClass]="{'active-button': selectedButton === 'npk2'}" (click)="selectSensor('npk2')">NPK 2</button>
|
<button [ngClass]="{'active-button': selectedButton === 'npk2'}" (click)="selectSensor('npk2')">NPK 2</button>
|
||||||
<button [ngClass]="{'active-button': selectedButton === 'relay'}" (click)="selectSensor('relay')">Relay</button>
|
<button [ngClass]="{'active-button': selectedButton === 'relay'}" (click)="selectSensor('relay')">Relay</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="isLoaded " class="loading">
|
<div *ngIf="isLoaded" class="loading">
|
||||||
Loading...
|
<i class="fa fa-spinner fa-spin"></i>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="!isLoaded && selectedButton === 'dht'" class="card-container">
|
<ng-template #noData>
|
||||||
<div class="card-parameter">
|
<div class="loading">No available data</div>
|
||||||
<div>
|
</ng-template>
|
||||||
<h3>{{sensorData.dht.lightIntensity}} Lux</h3>
|
|
||||||
<h6>Light Intensity</h6>
|
<div *ngIf="!isLoaded && selectedButton === 'dht'">
|
||||||
|
<div *ngIf="sensorData.dht.lightIntensity || sensorData.dht.temperature || sensorData.dht.humidity; else noData">
|
||||||
|
<div class="card-container">
|
||||||
|
<div class="card-parameter">
|
||||||
|
<div>
|
||||||
|
<h3>{{sensorData.dht.lightIntensity}} Lux</h3>
|
||||||
|
<h6>Cahaya</h6>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="card-parameter">
|
||||||
<div class="card-parameter">
|
<div>
|
||||||
<div>
|
<h3>{{sensorData.dht.temperature}} °C</h3>
|
||||||
<h3>{{sensorData.dht.temperature}} °C</h3>
|
<h6>Temperatur</h6>
|
||||||
<h6>Temperature</h6>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="card-parameter">
|
||||||
<div class="card-parameter">
|
<div>
|
||||||
<div>
|
<h3>{{sensorData.dht.humidity}} %RH</h3>
|
||||||
<h3>{{sensorData.dht.humidity}} %RH</h3>
|
<h6>Kelembaban Udara</h6>
|
||||||
<h6>Humidity</h6>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div *ngIf="!isLoaded && selectedButton === 'npk1'" class="card-container">
|
|
||||||
<div class="card-parameter">
|
|
||||||
<div>
|
|
||||||
<h3>{{ sensorData.npk1.temperature }} °C</h3>
|
|
||||||
<h6>Temperature</h6>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card-parameter">
|
|
||||||
<div>
|
|
||||||
<h3>{{sensorData.npk1.moisture}} %RH</h3>
|
|
||||||
<h6>Moisture</h6>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card-parameter">
|
|
||||||
<div>
|
|
||||||
<h3>{{sensorData.npk1.conductivity}} μS/cm</h3>
|
|
||||||
<h6>Conductivity</h6>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card-parameter">
|
|
||||||
<div>
|
|
||||||
<h3>{{sensorData.npk1.ph}}</h3>
|
|
||||||
<h6>pH</h6>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card-parameter">
|
|
||||||
<div>
|
|
||||||
<h3>{{sensorData.npk1.nitrogen}} PPM</h3>
|
|
||||||
<h6>Nitrogen</h6>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card-parameter">
|
|
||||||
<div>
|
|
||||||
<h3>{{sensorData.npk1.phosphorus}} PPM</h3>
|
|
||||||
<h6>Phosphorus</h6>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card-parameter">
|
|
||||||
<div>
|
|
||||||
<h3>{{sensorData.npk1.potassium}} PPM</h3>
|
|
||||||
<h6>Potassium</h6>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="!isLoaded && selectedButton === 'npk2'" class="card-container">
|
<div *ngIf="!isLoaded && selectedButton === 'npk1'">
|
||||||
<div class="card-parameter">
|
<div *ngIf="sensorData.npk1.temperature || sensorData.npk1.moisture || sensorData.npk1.conductivity; else noData">
|
||||||
<div>
|
<div class="card-container">
|
||||||
<h3>{{ sensorData.npk2.temperature }}°C</h3>
|
<div class="card-parameter">
|
||||||
<h6>Temperature</h6>
|
<div>
|
||||||
</div>
|
<h3>{{sensorData.npk1.temperature}} °C</h3>
|
||||||
</div>
|
<h6>Temperature</h6>
|
||||||
<div class="card-parameter">
|
</div>
|
||||||
<div>
|
</div>
|
||||||
<h3>{{ sensorData.npk1.moisture }} %RH</h3>
|
<div class="card-parameter">
|
||||||
<h6>Moisture</h6>
|
<div>
|
||||||
</div>
|
<h3>{{sensorData.npk1.moisture}} %RH</h3>
|
||||||
</div>
|
<h6>Kelembaban Tanah</h6>
|
||||||
<div class="card-parameter">
|
</div>
|
||||||
<div>
|
</div>
|
||||||
<h3>{{ sensorData.npk1.conductivity }} μS/cm</h3>
|
<div class="card-parameter">
|
||||||
<h6>Conductivity</h6>
|
<div>
|
||||||
</div>
|
<h3>{{sensorData.npk1.conductivity}} μS/cm</h3>
|
||||||
</div>
|
<h6>Conductivity</h6>
|
||||||
<div class="card-parameter">
|
</div>
|
||||||
<div>
|
</div>
|
||||||
<h3>{{ sensorData.npk1.ph }}</h3>
|
<!-- Additional parameters for NPK1... -->
|
||||||
<h6>pH</h6>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card-parameter">
|
|
||||||
<div>
|
|
||||||
<h3>{{ sensorData.npk1.nitrogen}} PPM</h3>
|
|
||||||
<h6>Nitrogen</h6>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card-parameter">
|
|
||||||
<div>
|
|
||||||
<h3>{{ sensorData.npk2.phosphorus}} PPM</h3>
|
|
||||||
<h6>Phosphorus</h6>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card-parameter">
|
|
||||||
<div>
|
|
||||||
<h3>{{ sensorData.npk2.potassium }} PPM</h3>
|
|
||||||
<h6>Potassium</h6>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="!isLoaded && selectedButton === 'relay'" class="card-container">
|
<div *ngIf="!isLoaded && selectedButton === 'npk2'">
|
||||||
<div class="card-parameter" *ngFor="let relay of relayStatus;">
|
<div *ngIf="sensorData.npk2.temperature || sensorData.npk2.moisture || sensorData.npk2.conductivity; else noData">
|
||||||
<div>
|
<div class="card-container">
|
||||||
<h3 [ngClass]="relay.current_status ? 'status-on' : 'status-off'">
|
<div class="card-parameter">
|
||||||
{{ relay.current_status ? 'ON' : 'OFF' }}
|
<div>
|
||||||
</h3>
|
<h3>{{sensorData.npk2.temperature}} °C</h3>
|
||||||
<h6>Relay {{ relay.number }}</h6>
|
<h6>Temperature</h6>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-parameter">
|
||||||
|
<div>
|
||||||
|
<h3>{{sensorData.npk2.moisture}} %RH</h3>
|
||||||
|
<h6>Kelembaban Tanah</h6>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-parameter">
|
||||||
|
<div>
|
||||||
|
<h3>{{sensorData.npk2.conductivity}} μS/cm</h3>
|
||||||
|
<h6>Conductivity</h6>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Additional parameters for NPK2... -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="!isLoaded && selectedButton === 'relay'">
|
||||||
|
<div *ngIf="relayStatus.length > 0; else noData">
|
||||||
|
<div class="card-container" *ngFor="let relay of relayStatus;">
|
||||||
|
<div class="card-parameter">
|
||||||
|
<div>
|
||||||
|
<h3 [ngClass]="relay.current_status ? 'status-on' : 'status-off'">
|
||||||
|
{{ relay.current_status ? 'ON' : 'OFF' }}
|
||||||
|
</h3>
|
||||||
|
<h6>Relay {{ relay.number }}</h6>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="graph">
|
<div class="graph">
|
||||||
<div class="title-graph">Monitoring</div>
|
<div class="title-graph">Monitoring Graphs</div>
|
||||||
<div class="graph">
|
<div class="graph">
|
||||||
<app-graph></app-graph>
|
<app-graph></app-graph>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,6 @@ button {
|
||||||
.loading{
|
.loading{
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: #888;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-on {
|
.status-on {
|
||||||
|
|
@ -107,3 +106,7 @@ button {
|
||||||
.status-off {
|
.status-off {
|
||||||
color: rgb(144, 6, 6);
|
color: rgb(144, 6, 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
color: #16423C
|
||||||
|
}
|
||||||
|
|
@ -5,7 +5,7 @@ import { GraphComponent } from './page/graph/graph.component';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { SensorService } from '../../cores/services/sensor.service';
|
import { SensorService } from '../../cores/services/sensor.service';
|
||||||
import { SensorData, StatusRelay } from '../../cores/interface/sensor-data';
|
import { SensorData, StatusRelay } from '../../cores/interface/sensor-data';
|
||||||
import { interval } from 'rxjs';
|
import { ToastrService } from 'ngx-toastr';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-dashboard',
|
selector: 'app-dashboard',
|
||||||
|
|
@ -16,9 +16,12 @@ import { interval } from 'rxjs';
|
||||||
})
|
})
|
||||||
export class DashboardComponent implements OnInit {
|
export class DashboardComponent implements OnInit {
|
||||||
isLoaded: boolean = false;
|
isLoaded: boolean = false;
|
||||||
|
hasError: boolean = false;
|
||||||
|
noData: boolean = false;
|
||||||
selectedButton: string = '';
|
selectedButton: string = '';
|
||||||
latestUpdate: string = '';
|
latestUpdate: string = '';
|
||||||
intervalId: any;
|
intervalId: any;
|
||||||
|
greeting: string = '';
|
||||||
sensorData: SensorData = {
|
sensorData: SensorData = {
|
||||||
dht: { lightIntensity: 0, temperature: 0, humidity: 0 },
|
dht: { lightIntensity: 0, temperature: 0, humidity: 0 },
|
||||||
npk1: { temperature: 0, moisture: 0, conductivity: 0, ph: 0, nitrogen: 0, phosphorus: 0, potassium: 0 },
|
npk1: { temperature: 0, moisture: 0, conductivity: 0, ph: 0, nitrogen: 0, phosphorus: 0, potassium: 0 },
|
||||||
|
|
@ -26,11 +29,12 @@ export class DashboardComponent implements OnInit {
|
||||||
};
|
};
|
||||||
relayStatus: StatusRelay[] = [];
|
relayStatus: StatusRelay[] = [];
|
||||||
|
|
||||||
constructor(private apiService: SensorService) {}
|
constructor(private apiService: SensorService, private toast: ToastrService) {}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.selectedButton = 'dht';
|
this.selectedButton = 'dht';
|
||||||
this.startClock();
|
this.startClock();
|
||||||
|
this.updateGreeting();
|
||||||
this.loadData();
|
this.loadData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -61,68 +65,88 @@ export class DashboardComponent implements OnInit {
|
||||||
this.latestUpdate = now.toLocaleString('en-GB', options);
|
this.latestUpdate = now.toLocaleString('en-GB', options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateGreeting(): void {
|
||||||
|
const hour = new Date().getHours();
|
||||||
|
if (hour < 12) {
|
||||||
|
this.greeting = 'Good Morning';
|
||||||
|
} else if (hour < 18) {
|
||||||
|
this.greeting = 'Good Afternoon';
|
||||||
|
} else {
|
||||||
|
this.greeting = 'Good Evening';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
selectSensor(param: string): void {
|
selectSensor(param: string): void {
|
||||||
this.selectedButton = param;
|
this.selectedButton = param;
|
||||||
if(param==='relay'){
|
if(param === 'relay'){
|
||||||
this.loadRelayData();
|
this.loadRelayData();
|
||||||
}else{
|
} else {
|
||||||
this.loadData();
|
this.loadData();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
loadData(): void {
|
loadData(): void {
|
||||||
this.isLoaded = true;
|
this.isLoaded = true;
|
||||||
|
this.hasError = false;
|
||||||
|
this.noData = false;
|
||||||
|
|
||||||
this.apiService.getLatestData().subscribe(
|
this.apiService.getLatestData().subscribe(
|
||||||
(response) => {
|
(response) => {
|
||||||
const data = response.data;
|
const data = response.data;
|
||||||
console.log('Data:', data);
|
console.log('Data:', data);
|
||||||
if (data.dht && data.dht.length > 0) {
|
|
||||||
this.sensorData.dht = {
|
if ((!data.dht || data.dht.length === 0) &&
|
||||||
lightIntensity: data.dht[0].viciluminosity ?? 0,
|
(!data.npk1 || data.npk1.length === 0) &&
|
||||||
temperature: data.dht[0].vicitemperature ?? 0,
|
(!data.npk2 || data.npk2.length === 0)) {
|
||||||
humidity: data.dht[0].vicihumidity ?? 0
|
this.noData = true;
|
||||||
};
|
} else {
|
||||||
}
|
if (data.dht && data.dht.length > 0) {
|
||||||
|
this.sensorData.dht = {
|
||||||
if (data.npk1 && data.npk1.length > 0) {
|
lightIntensity: data.dht[0].viciluminosity ?? 0,
|
||||||
this.sensorData.npk1 = {
|
temperature: data.dht[0].vicitemperature ?? 0,
|
||||||
temperature: data.npk1[0].soiltemperature ?? 0,
|
humidity: data.dht[0].vicihumidity ?? 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,
|
if (data.npk1 && data.npk1.length > 0) {
|
||||||
phosphorus: data.npk1[0].soilphosphorus ?? 0,
|
this.sensorData.npk1 = {
|
||||||
potassium: data.npk1[0].soilpotassium ?? 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,
|
||||||
if (data.npk2 && data.npk2.length > 0) {
|
nitrogen: data.npk1[0].soilnitrogen ?? 0,
|
||||||
this.sensorData.npk2 = {
|
phosphorus: data.npk1[0].soilphosphorus ?? 0,
|
||||||
temperature: data.npk2[0].soiltemperature ?? 0,
|
potassium: data.npk1[0].soilpotassium ?? 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,
|
if (data.npk2 && data.npk2.length > 0) {
|
||||||
phosphorus: data.npk2[0].soilphosphorus ?? 0,
|
this.sensorData.npk2 = {
|
||||||
potassium: data.npk2[0].soilpotassium ?? 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
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateLatestTime();
|
this.updateLatestTime();
|
||||||
this.isLoaded = false;
|
this.isLoaded = false;
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
console.error('Error fetching sensor data:', error);
|
|
||||||
this.isLoaded = false;
|
this.isLoaded = false;
|
||||||
|
this.handleError(error);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
loadRelayData(): void {
|
loadRelayData(): void {
|
||||||
this.isLoaded = true;
|
this.isLoaded = true;
|
||||||
|
this.hasError = false;
|
||||||
|
|
||||||
this.apiService.getRelayStatus().subscribe(
|
this.apiService.getRelayStatus().subscribe(
|
||||||
(response) => {
|
(response) => {
|
||||||
if (Array.isArray(response.data)) {
|
if (Array.isArray(response.data)) {
|
||||||
|
|
@ -134,16 +158,26 @@ 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);
|
||||||
}
|
}
|
||||||
console.log('Relay Data:', response);
|
|
||||||
this.isLoaded = false;
|
this.isLoaded = false;
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
console.error('Error fetching relay data:', error);
|
this.toast.error('Failed to get relay status, please try again');
|
||||||
|
this.hasError = true;
|
||||||
this.isLoaded = false;
|
this.isLoaded = false;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
handleError(error: any): void {
|
||||||
|
if (this.selectedButton === 'dht') {
|
||||||
|
this.toast.error('Error fetching DHT sensor data. Please try again.', 'Error', { timeOut: 3000 });
|
||||||
|
} else if (this.selectedButton === 'npk1') {
|
||||||
|
this.toast.error('Error fetching NPK1 sensor data. Please try again.', 'Error', { timeOut: 3000 });
|
||||||
|
} else if (this.selectedButton === 'npk2') {
|
||||||
|
this.toast.error('Error fetching NPK2 sensor data. Please try again.', 'Error', { timeOut: 3000 });
|
||||||
|
}
|
||||||
|
this.hasError = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,58 @@
|
||||||
<div class="container-graph">
|
<div class="container-graph">
|
||||||
<select class="form-select" id="sensorSelect" (change)="onSensorChange($event)">
|
<ng-container *ngIf="allNoData; else graphContent">
|
||||||
<option value="dht">BHT</option>
|
<div class="no-data">No available data</div>
|
||||||
<option value="npk1">NPK 1</option>
|
</ng-container>
|
||||||
<option value="npk2">NPK 2</option>
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<select class="form-select" id="parameterSelect" (change)="updateChart()" [(ngModel)]="selectedParameter">
|
|
||||||
<option *ngFor="let parameter of parameters" [value]="getParameterKey(parameter)">{{ parameter }}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div *ngIf="isLoading" class="loading">
|
<ng-template #graphContent>
|
||||||
Loading...
|
<div class="sensor-wrapper">
|
||||||
</div>
|
<div class="title">Sensor BHT</div>
|
||||||
|
<ng-container *ngIf="isLoadingDHT; else dhtData">
|
||||||
|
<div class="d-flex align-items-center" style="padding: 50px 0px 50px 0px">
|
||||||
|
<i class="fa fa-spinner fa-spin"></i>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
<ng-template #dhtData>
|
||||||
|
<canvas #myChartDHT id="myChartDHT" width="600" height="300" style="height: 300px;"></canvas>
|
||||||
|
<p *ngIf="isNoDataDHT" class="no-data">No available data</p>
|
||||||
|
</ng-template>
|
||||||
|
</div>
|
||||||
|
|
||||||
<canvas #myChart id="myChart"></canvas>
|
<div class="sensor-wrapper">
|
||||||
|
<div class="title-with-dropdown">
|
||||||
|
<div class="title">Sensor NPK 1</div>
|
||||||
|
<select class="form-select" style="margin-top: 10px" [(ngModel)]="selectedNPK1" (change)="updateCharts()">
|
||||||
|
<option value="npk">NPK</option>
|
||||||
|
<option value="others">Lainnya</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<ng-container *ngIf="isLoadingNPK1; else npk1Data">
|
||||||
|
<div class="d-flex align-items-center" style="padding: 50px 0px 50px 0px">
|
||||||
|
<i class="fa fa-spinner fa-spin"></i>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
<ng-template #npk1Data>
|
||||||
|
<canvas #myChartNPK1 id="myChartNPK1"></canvas>
|
||||||
|
<p *ngIf="isNoDataNPK1" class="no-data">No available data</p>
|
||||||
|
</ng-template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sensor-wrapper">
|
||||||
|
<div class="title-with-dropdown">
|
||||||
|
<div class="title">Sensor NPK 2</div>
|
||||||
|
<select class="form-select" style="margin-top: 10px" [(ngModel)]="selectedNPK2" (change)="updateCharts()">
|
||||||
|
<option value="npk">NPK</option>
|
||||||
|
<option value="others">Lainnya</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<ng-container *ngIf="isLoadingNPK2; else npk2Data">
|
||||||
|
<div class="d-flex align-items-center" style="padding: 50px 0px 50px 0px;">
|
||||||
|
<i class="fa fa-spinner fa-spin"></i>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
<ng-template #npk2Data>
|
||||||
|
<canvas #myChartNPK2 id="myChartNPK2"></canvas>
|
||||||
|
<p *ngIf="isNoDataNPK2" class="no-data">No available data</p>
|
||||||
|
</ng-template>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,54 +1,74 @@
|
||||||
.chart-container {
|
.container-graph {
|
||||||
width: 100%;
|
display: flex;
|
||||||
max-width: 800px;
|
flex-direction: column;
|
||||||
margin: auto;
|
justify-content: flex-start;
|
||||||
text-align: center;
|
height: max-content;
|
||||||
}
|
|
||||||
|
|
||||||
canvas {
|
|
||||||
width: 100%;
|
|
||||||
height: 30vh;
|
|
||||||
margin: 20px;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-select{
|
.sensor-wrapper {
|
||||||
width: max-content;
|
|
||||||
margin-top: 20px;
|
|
||||||
display: flexbox;
|
|
||||||
flex-direction: column;
|
|
||||||
margin-right: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container-graph{
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-direction: column;
|
||||||
justify-content: left;
|
align-items: center;
|
||||||
height: max-content;
|
margin-bottom: 35px;
|
||||||
}
|
background-color: #f1f1f1;
|
||||||
|
border-radius: 10px;
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
h2 {
|
||||||
canvas{
|
font-size: 18px;
|
||||||
display: flex;
|
margin-bottom: 10px;
|
||||||
width: max-content;
|
|
||||||
flex: 1 1 45%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 576px) {
|
|
||||||
canvas{
|
|
||||||
display: flex;
|
|
||||||
width: max-content;
|
|
||||||
flex: 1 1 100%;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading{
|
.title-with-dropdown {
|
||||||
font-size: 18px;
|
display: flex;
|
||||||
text-align: center;
|
align-items: center;
|
||||||
color: #888;
|
justify-content: center;
|
||||||
|
width: 50%;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
flex: 0 0 25%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-select {
|
||||||
|
flex: 0 0 25%;
|
||||||
|
margin: 18px 0px 0px 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 20px;
|
||||||
|
margin: 18px 0px 0px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas {
|
||||||
|
height: 500px !important;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 2000px;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
padding-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
canvas {
|
||||||
|
height: 300px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
font-size: 18px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
color: #16423C
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-data {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100%;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,23 @@
|
||||||
import { Component, OnInit, ElementRef, ViewChild, AfterViewInit, OnDestroy } from '@angular/core';
|
import { Component, OnInit, ElementRef, ViewChild, AfterViewInit, OnDestroy } from '@angular/core';
|
||||||
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 } from '@angular/common';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
|
|
||||||
Chart.register(...registerables);
|
Chart.register(...registerables);
|
||||||
|
|
||||||
const parameterColors: { [key: string]: string } = {
|
const parameterColors: { [key: string]: string } = {
|
||||||
vicitemperature: '#8F5A62',
|
vicitemperature: '#8F5A62',
|
||||||
vicihumidity: '#16423C',
|
vicihumidity: '#16423C',
|
||||||
viciluminosity: '#DF9B55',
|
viciluminosity: '#DF9B55',
|
||||||
soiltemperature: '#8F5A62',
|
soiltemperature: '#FF6347',
|
||||||
soilhumidity: '#54909c',
|
soilhumidity: '#00BFFF',
|
||||||
soilconductivity: '#661311',
|
soilconductivity: '#A52A2A',
|
||||||
soilph: '#664735',
|
soilph: '#228B22',
|
||||||
soilnitrogen: '#3a6635',
|
soilnitrogen: '#FEDC56',
|
||||||
soilphosphorus: '#3f3566',
|
soilphosphorus: '#B80F0A',
|
||||||
soilpotassium: '#5f3566',
|
soilpotassium: '#6F2DA8',
|
||||||
};
|
};
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
|
@ -27,11 +28,19 @@ const parameterColors: { [key: string]: string } = {
|
||||||
styleUrls: ['./graph.component.scss']
|
styleUrls: ['./graph.component.scss']
|
||||||
})
|
})
|
||||||
export class GraphComponent implements OnInit, AfterViewInit, OnDestroy {
|
export class GraphComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||||
@ViewChild('myChart', { static: false }) chartElement!: ElementRef<HTMLCanvasElement>;
|
@ViewChild('myChartDHT', { static: false }) dhtChartElement!: ElementRef<HTMLCanvasElement>;
|
||||||
selectedSensor: string = '';
|
@ViewChild('myChartNPK1', { static: false }) npk1ChartElement!: ElementRef<HTMLCanvasElement>;
|
||||||
selectedParameter: string = '';
|
@ViewChild('myChartNPK2', { static: false }) npk2ChartElement!: ElementRef<HTMLCanvasElement>;
|
||||||
parameters: string[] = [];
|
|
||||||
isLoading: boolean = true;
|
isLoadingDHT: boolean = true;
|
||||||
|
isLoadingNPK1: boolean = true;
|
||||||
|
isLoadingNPK2: boolean = true;
|
||||||
|
|
||||||
|
isNoDataDHT: boolean = false;
|
||||||
|
isNoDataNPK1: boolean = false;
|
||||||
|
isNoDataNPK2: boolean = false;
|
||||||
|
|
||||||
|
allNoData: boolean = false;
|
||||||
|
|
||||||
sensorParameters: { [key: string]: string[] } = {
|
sensorParameters: { [key: string]: string[] } = {
|
||||||
dht: ['vicitemperature', 'vicihumidity', 'viciluminosity'],
|
dht: ['vicitemperature', 'vicihumidity', 'viciluminosity'],
|
||||||
|
|
@ -47,28 +56,32 @@ export class GraphComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||||
soilhumidity: 'Soil Humidity (%)',
|
soilhumidity: 'Soil Humidity (%)',
|
||||||
soilconductivity: 'Conductivity (μS/cm)',
|
soilconductivity: 'Conductivity (μS/cm)',
|
||||||
soilph: 'pH',
|
soilph: 'pH',
|
||||||
soilnitrogen: 'Nitrogen (PPM)',
|
soilnitrogen: 'Nitrogen (mg/l)',
|
||||||
soilphosphorus: 'Phosphorus (PPM)',
|
soilphosphorus: 'Phosphorus (mg/l)',
|
||||||
soilpotassium: 'Potassium (PPM)',
|
soilpotassium: 'Potassium (mg/l)',
|
||||||
};
|
};
|
||||||
|
|
||||||
chart: Chart | undefined;
|
charts: { [key: string]: Chart | undefined } = {
|
||||||
labelsHourly: string[] = [];
|
dht: undefined,
|
||||||
|
npk1: undefined,
|
||||||
|
npk2: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
selectedNPK1: string = 'npk';
|
||||||
|
selectedNPK2: string = 'npk';
|
||||||
|
|
||||||
private resizeListener!: () => void;
|
private resizeListener!: () => void;
|
||||||
|
|
||||||
constructor(private sensorService: SensorService) {}
|
constructor(private sensorService: SensorService) {}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.selectedSensor = 'dht';
|
|
||||||
this.updateParameters();
|
|
||||||
this.selectedParameter = 'vicitemperature';
|
|
||||||
this.updateChart();
|
|
||||||
this.resizeListener = this.onResize.bind(this);
|
this.resizeListener = this.onResize.bind(this);
|
||||||
window.addEventListener('resize', this.resizeListener);
|
window.addEventListener('resize', this.resizeListener);
|
||||||
|
this.updateCharts();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngAfterViewInit(): void {
|
ngAfterViewInit(): void {
|
||||||
this.updateChart();
|
this.onResize();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
|
|
@ -76,12 +89,204 @@ export class GraphComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
onResize(): void {
|
onResize(): void {
|
||||||
if (this.chart) {
|
Object.values(this.charts).forEach(chart => {
|
||||||
this.chart.destroy();
|
if (chart) {
|
||||||
this.updateChart();
|
chart.destroy();
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
this.updateCharts();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateCharts(): void {
|
||||||
|
this.isLoadingDHT = this.isLoadingNPK1 = this.isLoadingNPK2 = true;
|
||||||
|
|
||||||
|
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');
|
||||||
|
|
||||||
|
const startEnd = `${year}-${month}-${day}`;
|
||||||
|
// const startEnd = '2024-10-24';
|
||||||
|
const timeRange = 'HOURLY';
|
||||||
|
|
||||||
|
// Fetch data for DHT
|
||||||
|
this.sensorService.getSensorData('dht', '', startEnd, timeRange).subscribe({
|
||||||
|
next: (response) => {
|
||||||
|
if (response.statusCode === 200 && response.data.dht?.length > 0) {
|
||||||
|
this.createChart(this.dhtChartElement.nativeElement, response, 'dht', 'npk');
|
||||||
|
this.isNoDataDHT = false;
|
||||||
|
this.allNoData = false;
|
||||||
|
} else {
|
||||||
|
this.isNoDataDHT = true;
|
||||||
|
}
|
||||||
|
this.isLoadingDHT = false;
|
||||||
|
},
|
||||||
|
error: () => {
|
||||||
|
this.isLoadingDHT = false;
|
||||||
|
this.isNoDataDHT = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fetch data for NPK1
|
||||||
|
this.sensorService.getSensorData('npk1', '', startEnd, timeRange).subscribe({
|
||||||
|
next: (response) => {
|
||||||
|
if (response.statusCode === 200 && response.data.npk1?.length > 0) {
|
||||||
|
this.createChart(this.npk1ChartElement.nativeElement, response, 'npk1', this.selectedNPK1);
|
||||||
|
this.isNoDataNPK1 = false;
|
||||||
|
this.allNoData = false;
|
||||||
|
} else {
|
||||||
|
this.isNoDataNPK1 = true;
|
||||||
|
}
|
||||||
|
this.isLoadingNPK1 = false;
|
||||||
|
},
|
||||||
|
error: () => {
|
||||||
|
this.isLoadingNPK1 = false;
|
||||||
|
this.isNoDataNPK1 = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fetch data for NPK2
|
||||||
|
this.sensorService.getSensorData('npk2', '', startEnd, timeRange).subscribe({
|
||||||
|
next: (response) => {
|
||||||
|
if (response.statusCode === 200 && response.data.npk2?.length > 0) {
|
||||||
|
this.createChart(this.npk2ChartElement.nativeElement, response, 'npk2', this.selectedNPK2);
|
||||||
|
this.isNoDataNPK2 = false;
|
||||||
|
this.allNoData = false;
|
||||||
|
} else {
|
||||||
|
this.isNoDataNPK2 = true;
|
||||||
|
}
|
||||||
|
this.isLoadingNPK2 = false;
|
||||||
|
},
|
||||||
|
error: () => {
|
||||||
|
this.isLoadingNPK2 = false;
|
||||||
|
this.isNoDataNPK2 = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createChart(canvas: HTMLCanvasElement, response: ApiResponse, sensor: string, selectedOption: string): void {
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
const parameters = this.sensorParameters[sensor];
|
||||||
|
|
||||||
|
if (!ctx) {
|
||||||
|
console.error('Failed to get canvas context for sensor:', sensor);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let datasets: any[] = [];
|
||||||
|
|
||||||
|
if (sensor === 'dht') {
|
||||||
|
// Handle DHT parameters directly
|
||||||
|
datasets = ['vicitemperature', 'vicihumidity', 'viciluminosity'].map(parameter => {
|
||||||
|
const { data, labels } = this.getDataFromResponse(response, sensor, parameter);
|
||||||
|
|
||||||
|
if (data.length === 0) {
|
||||||
|
console.warn(`No data found for parameter: ${parameter}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const displayName = this.parameterDisplayNames[parameter] || parameter;
|
||||||
|
const borderColor = parameterColors[parameter] || '#000000';
|
||||||
|
const backgroundColor = `${borderColor}4D`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
label: displayName,
|
||||||
|
data,
|
||||||
|
borderColor,
|
||||||
|
borderWidth: 1.5,
|
||||||
|
fill: true,
|
||||||
|
backgroundColor,
|
||||||
|
tension: 0.5,
|
||||||
|
pointRadius: 0,
|
||||||
|
pointHoverRadius: 0,
|
||||||
|
};
|
||||||
|
}).filter(dataset => dataset !== null);
|
||||||
|
} else {
|
||||||
|
// Handle NPK1 and NPK2 as before
|
||||||
|
datasets = parameters.map(parameter => {
|
||||||
|
const { data, labels } = this.getDataFromResponse(response, sensor, parameter);
|
||||||
|
|
||||||
|
if (data.length === 0) {
|
||||||
|
console.warn(`No data found for parameter: ${parameter}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const displayName = this.parameterDisplayNames[parameter] || parameter;
|
||||||
|
const borderColor = parameterColors[parameter] || '#000000';
|
||||||
|
const backgroundColor = `${borderColor}4D`;
|
||||||
|
|
||||||
|
// Filter datasets based on the selected option
|
||||||
|
if (selectedOption === 'npk' && ['soilphosphorus', 'soilnitrogen', 'soilpotassium'].includes(parameter)) {
|
||||||
|
return {
|
||||||
|
label: displayName,
|
||||||
|
data,
|
||||||
|
borderColor,
|
||||||
|
borderWidth: 1.5,
|
||||||
|
fill: true,
|
||||||
|
backgroundColor,
|
||||||
|
tension: 0.5,
|
||||||
|
pointRadius: 0,
|
||||||
|
pointHoverRadius: 0,
|
||||||
|
};
|
||||||
|
} else if (selectedOption === 'others' && !['soilphosphorus', 'soilnitrogen', 'soilpotassium'].includes(parameter)) {
|
||||||
|
return {
|
||||||
|
label: displayName,
|
||||||
|
data,
|
||||||
|
borderColor,
|
||||||
|
borderWidth: 1.5,
|
||||||
|
fill: true,
|
||||||
|
backgroundColor,
|
||||||
|
tension: 0.5,
|
||||||
|
pointRadius: 0,
|
||||||
|
pointHoverRadius: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}).filter(dataset => dataset !== null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (datasets.length === 0) {
|
||||||
|
console.warn('No valid datasets to render for sensor:', sensor);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.charts[sensor]) {
|
||||||
|
this.charts[sensor]?.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.charts[sensor] = new Chart(ctx, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: this.getLabels(response, sensor),
|
||||||
|
datasets,
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
plugins: {
|
||||||
|
tooltip: {
|
||||||
|
enabled: true,
|
||||||
|
mode: 'nearest',
|
||||||
|
intersect: false,
|
||||||
|
callbacks: {
|
||||||
|
label: (tooltipItem) => {
|
||||||
|
const paramLabel = tooltipItem.dataset.label;
|
||||||
|
const value = tooltipItem.formattedValue;
|
||||||
|
return `${paramLabel}: ${value}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: { display: true }
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
x: { grid: { display: false }, beginAtZero: true },
|
||||||
|
y: { grid: { display: false }, beginAtZero: true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
getDataFromResponse(response: ApiResponse, sensor: string, parameter: string): { data: number[], labels: string[] } {
|
getDataFromResponse(response: ApiResponse, sensor: string, parameter: string): { data: number[], labels: string[] } {
|
||||||
const sensorData = response.data[sensor as keyof typeof response.data];
|
const sensorData = response.data[sensor as keyof typeof response.data];
|
||||||
|
|
||||||
|
|
@ -100,112 +305,8 @@ export class GraphComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||||
return { data, labels };
|
return { data, labels };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getLabels(response: ApiResponse, sensor: string): string[] {
|
||||||
createChart(data: number[], parameter: string, labels: string[]): void {
|
const sensorData = response.data[sensor as keyof typeof response.data];
|
||||||
const canvas = this.chartElement.nativeElement;
|
return sensorData ? sensorData.map(item => `${item.hour}.00`) : [];
|
||||||
const ctx = canvas.getContext('2d');
|
|
||||||
|
|
||||||
if (this.chart) {
|
|
||||||
this.chart.destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ctx) {
|
|
||||||
const displayName = this.parameterDisplayNames[parameter] || parameter;
|
|
||||||
const borderColor = parameterColors[parameter] || '#000000';
|
|
||||||
const backgroundColor = `${borderColor}4D`;
|
|
||||||
|
|
||||||
this.chart = new Chart(ctx, {
|
|
||||||
type: 'line',
|
|
||||||
data: {
|
|
||||||
labels: labels,
|
|
||||||
datasets: [{
|
|
||||||
label: displayName,
|
|
||||||
data,
|
|
||||||
borderColor,
|
|
||||||
borderWidth: 1.5,
|
|
||||||
fill: true,
|
|
||||||
backgroundColor,
|
|
||||||
tension: 0.5,
|
|
||||||
pointRadius: 0,
|
|
||||||
pointHoverRadius: 0,
|
|
||||||
pointBackgroundColor: borderColor,
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
responsive: true,
|
|
||||||
maintainAspectRatio: false,
|
|
||||||
plugins: {
|
|
||||||
tooltip: {
|
|
||||||
enabled: true,
|
|
||||||
mode: 'nearest',
|
|
||||||
intersect: false,
|
|
||||||
callbacks: {
|
|
||||||
label: (tooltipItem) => {
|
|
||||||
const paramLabel = displayName;
|
|
||||||
const value = tooltipItem.formattedValue;
|
|
||||||
return `${paramLabel}: ${value}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
legend: { display: false }
|
|
||||||
},
|
|
||||||
scales: {
|
|
||||||
x: { grid: { display: false }, beginAtZero: true },
|
|
||||||
y: { grid: { display: false }, beginAtZero: true }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
getParameterKey(displayName: string): string {
|
|
||||||
return Object.keys(this.parameterDisplayNames).find(key => this.parameterDisplayNames[key] === displayName) || '';
|
|
||||||
}
|
|
||||||
|
|
||||||
onSensorChange(event: Event): void {
|
|
||||||
const select = event.target as HTMLSelectElement;
|
|
||||||
this.selectedSensor = select.value;
|
|
||||||
this.updateParameters();
|
|
||||||
if (this.selectedSensor === 'dht') {
|
|
||||||
this.selectedParameter = 'vicitemperature';
|
|
||||||
} else if (this.selectedSensor === 'npk1' || this.selectedSensor === 'npk2') {
|
|
||||||
this.selectedParameter = 'soiltemperature';
|
|
||||||
}
|
|
||||||
this.updateChart();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
updateParameters(): void {
|
|
||||||
this.parameters = this.sensorParameters[this.selectedSensor].map(param => this.parameterDisplayNames[param]);
|
|
||||||
this.selectedParameter = this.parameters[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
updateChart(): void {
|
|
||||||
this.isLoading = true;
|
|
||||||
|
|
||||||
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');
|
|
||||||
|
|
||||||
const startEnd= `${year}-${month}-${day}`;
|
|
||||||
const timeRange = 'HOURLY';
|
|
||||||
|
|
||||||
this.sensorService.getSensorData(this.selectedSensor, this.selectedParameter, startEnd, timeRange).subscribe(
|
|
||||||
(response: ApiResponse) => {
|
|
||||||
if (response.statusCode === 200) {
|
|
||||||
const { data, labels } = this.getDataFromResponse(response, this.selectedSensor, this.selectedParameter);
|
|
||||||
this.createChart(data, this.selectedParameter, labels);
|
|
||||||
} else {
|
|
||||||
console.error('Error fetching data:', response.message);
|
|
||||||
}
|
|
||||||
this.isLoading = false;
|
|
||||||
},
|
|
||||||
(error) => {
|
|
||||||
console.error('API Error:', error);
|
|
||||||
this.isLoading = false;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,25 +10,44 @@
|
||||||
|
|
||||||
<form (ngSubmit)="onSubmit()">
|
<form (ngSubmit)="onSubmit()">
|
||||||
<div class="form-floating mb-3">
|
<div class="form-floating mb-3">
|
||||||
<input type="text" class="form-control" [(ngModel)]="fullname" name="fullname" id="floatingFullname" placeholder="Full Name" required>
|
<input type="text" class="form-control" [(ngModel)]="fullname" name="fullname" id="floatingFullname" placeholder="Full Name" required autocomplete="off">
|
||||||
<label for="floatingFullname">Full Name</label>
|
<label for="floatingFullname">Full Name</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-floating mb-3">
|
<div class="form-floating mb-3">
|
||||||
<input type="text" class="form-control" [(ngModel)]="username" name="username" id="floatingUsername" placeholder="Username" required>
|
<input type="text" class="form-control" [(ngModel)]="username" name="username" id="floatingUsername" placeholder="Username" required autocomplete="off">
|
||||||
<label for="floatingUsername">Username</label>
|
<label for="floatingUsername">Username</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-floating mb-3">
|
<div class="form-floating mb-3">
|
||||||
<input type="email" class="form-control" [(ngModel)]="email" name="email" id="floatingInput" placeholder="name@example.com" required>
|
<input type="email" class="form-control" [(ngModel)]="email" name="email" id="floatingInput" placeholder="name@example.com" required autocomplete="off">
|
||||||
<label for="floatingInput">Email address</label>
|
<label for="floatingInput">Email address</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-floating mb-3">
|
<div class="form-floating mb-3">
|
||||||
<input type="password" class="form-control" [(ngModel)]="password" name="password" id="floatingPassword" placeholder="Password" required>
|
<input [type]="passwordVisible ? 'text' : 'password'"
|
||||||
|
class="form-control"
|
||||||
|
[(ngModel)]="password"
|
||||||
|
name="password"
|
||||||
|
id="floatingPassword"
|
||||||
|
placeholder="Password"
|
||||||
|
required autocomplete="off">
|
||||||
<label for="floatingPassword">Password</label>
|
<label for="floatingPassword">Password</label>
|
||||||
|
|
||||||
|
<button type="button"
|
||||||
|
class="btn btn-link position-absolute end-0 me-3"
|
||||||
|
(click)="togglePasswordVisibility()">
|
||||||
|
<i class="fa" [ngClass]="passwordVisible ? 'fa-eye' : 'fa-eye-slash'"></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-grid">
|
<div class="d-grid">
|
||||||
<button class="btn btn-lg color-btn btn-login text-uppercase fw-bold mb-2" type="submit">Sign Up</button>
|
<button class="btn btn-lg color-btn btn-login text-uppercase fw-bold mb-2"
|
||||||
|
type="submit"
|
||||||
|
[disabled]="loading">
|
||||||
|
<span *ngIf="!loading">Sign up</span>
|
||||||
|
<span *ngIf="loading">
|
||||||
|
<i class="fa fa-spinner fa-spin"></i>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<a class="small forgot" routerLink= '/auth'>Log In</a>
|
<a class="small forgot" routerLink='/auth'>Sign In here</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
||||||
|
|
@ -27,3 +27,22 @@
|
||||||
.forgot{
|
.forgot{
|
||||||
color: #16423C;
|
color: #16423C;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form-floating {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.btn-link {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
right: 10px;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #16423C;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #16423C;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Component } from '@angular/core';
|
import { Component, OnInit } from '@angular/core'; // Import OnInit
|
||||||
import { Router, RouterModule } from '@angular/router';
|
import { Router, RouterModule } from '@angular/router';
|
||||||
import { AuthService } from '../../cores/services/auth.service';
|
import { AuthService } from '../../cores/services/auth.service';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
|
|
@ -13,15 +13,35 @@ import { CommonModule } from '@angular/common';
|
||||||
templateUrl: './register.component.html',
|
templateUrl: './register.component.html',
|
||||||
styleUrls: ['./register.component.scss']
|
styleUrls: ['./register.component.scss']
|
||||||
})
|
})
|
||||||
export class RegisterComponent {
|
export class RegisterComponent implements OnInit {
|
||||||
username: string = '';
|
username: string = '';
|
||||||
password: string = '';
|
password: string = '';
|
||||||
email: string = '';
|
email: string = '';
|
||||||
fullname: string = '';
|
fullname: string = '';
|
||||||
|
loading: boolean = false;
|
||||||
|
passwordVisible: boolean = false;
|
||||||
|
|
||||||
constructor(private authService: AuthService, private router: Router, private toast: ToastrService) {}
|
constructor(private authService: AuthService, private router: Router, private toast: ToastrService) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.username = '';
|
||||||
|
this.password = '';
|
||||||
|
this.email = '';
|
||||||
|
this.fullname = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
togglePasswordVisibility() {
|
||||||
|
this.passwordVisible = !this.passwordVisible;
|
||||||
|
}
|
||||||
|
|
||||||
onSubmit() {
|
onSubmit() {
|
||||||
|
this.loading = true;
|
||||||
|
if (!this.username || !this.password || !this.email || !this.fullname) {
|
||||||
|
this.loading = false;
|
||||||
|
this.toast.error('Please fill in all fields.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const registrationData: RegistrationData = {
|
const registrationData: RegistrationData = {
|
||||||
username: this.username,
|
username: this.username,
|
||||||
pwd: this.password,
|
pwd: this.password,
|
||||||
|
|
@ -33,10 +53,12 @@ export class RegisterComponent {
|
||||||
|
|
||||||
this.authService.register(registrationData).subscribe(
|
this.authService.register(registrationData).subscribe(
|
||||||
(response) => {
|
(response) => {
|
||||||
|
this.loading = false;
|
||||||
this.toast.success('Registration successful');
|
this.toast.success('Registration successful');
|
||||||
this.router.navigate(['/auth']);
|
this.router.navigate(['/auth']);
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
|
this.loading = false;
|
||||||
this.toast.error(error.error.message);
|
this.toast.error(error.error.message);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
<script src="https://code.highcharts.com/modules/exporting.js"></script>
|
<script src="https://code.highcharts.com/modules/exporting.js"></script>
|
||||||
<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">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<app-root></app-root>
|
<app-root></app-root>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user