dev smartfarming #1
|
|
@ -1,9 +1,10 @@
|
|||
import { ApplicationConfig, provideZoneChangeDetection, importProvidersFrom } from '@angular/core';
|
||||
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 { ToastrModule } from 'ngx-toastr';
|
||||
import { routes } from './app.routes';
|
||||
import { httpErrorInterceptor } from './cores/interceptors/http-error-interceptor.service';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
|
|
@ -12,5 +13,6 @@ export const appConfig: ApplicationConfig = {
|
|||
provideHttpClient(),
|
||||
provideAnimations(),
|
||||
importProvidersFrom(ToastrModule.forRoot()),
|
||||
provideHttpClient(withInterceptors([httpErrorInterceptor])) // Register your HttpErrorInterceptor
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { Routes } from '@angular/router';
|
||||
import { DashboardComponent } from './pages/dashboard/dashboard.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 { AuthGuard } from './cores/guards/auth.guard';
|
||||
import { RegisterComponent } from './pages/register/register.component';
|
||||
|
|
@ -29,11 +28,6 @@ export const routes: Routes = [
|
|||
component: DashboardComponent,
|
||||
canActivate: [AuthGuard]
|
||||
},
|
||||
{
|
||||
path: 'graph',
|
||||
component: GraphComponent,
|
||||
canActivate: [AuthGuard]
|
||||
},
|
||||
]
|
||||
}
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1,16 +1,20 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { CanActivate, Router } from '@angular/router';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
|
||||
import { StorageService } from '../services/storage.service';
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class AuthGuard implements CanActivate {
|
||||
|
||||
constructor(private router: Router, private toast: ToastrService) {}
|
||||
constructor(
|
||||
private router: Router,
|
||||
private toast: ToastrService,
|
||||
private storageService: StorageService
|
||||
) {}
|
||||
|
||||
canActivate(): boolean {
|
||||
const token = localStorage.getItem('accessToken');
|
||||
const token = this.storageService.getToken();
|
||||
if (token) {
|
||||
return true;
|
||||
} 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;
|
||||
message: string;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
export interface DHTSensor {
|
||||
lightIntensity: number;
|
||||
temperature: number;
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||
import { Observable } from 'rxjs';
|
||||
import { catchError, tap } from 'rxjs/operators';
|
||||
import { Observable, tap } from 'rxjs';
|
||||
import { ApiService } from './api.service';
|
||||
import { LoginData } from '../interface/auth';
|
||||
import {jwtDecode}from 'jwt-decode';
|
||||
import { jwtDecode } from 'jwt-decode';
|
||||
import { StorageService } from './storage.service';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
|
|
@ -16,9 +14,10 @@ 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);
|
||||
}
|
||||
|
||||
|
|
@ -44,7 +43,7 @@ export class AuthService extends ApiService {
|
|||
}
|
||||
|
||||
logout(): Observable<any> {
|
||||
const token = localStorage.getItem('accessToken');
|
||||
const token = this.storageService.getToken();
|
||||
const headers = new HttpHeaders({
|
||||
Authorization: `Bearer ${token}`
|
||||
});
|
||||
|
|
@ -54,21 +53,12 @@ export class AuthService extends ApiService {
|
|||
this.storageService.clearToken();
|
||||
this.storageService.clearUserData();
|
||||
}),
|
||||
catchError(error => {
|
||||
this.toast.error('Failed to logout');
|
||||
return error;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
register(data: any): Observable<any> {
|
||||
const headers = new HttpHeaders({});
|
||||
return this.http.post<any>(this.registerUrl, data, { headers }).pipe(
|
||||
catchError(error => {
|
||||
this.toast.error('Failed to register');
|
||||
return error;
|
||||
})
|
||||
);
|
||||
return this.http.post<any>(this.registerUrl, data, { headers });
|
||||
}
|
||||
|
||||
getUserFullName(): string | null {
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ export class SensorService extends ApiService {
|
|||
|
||||
private getDataUrl = `${this.baseUrl}api/sensor/getData`;
|
||||
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 {
|
||||
const token = this.storageService.getToken();
|
||||
|
|
@ -38,7 +38,7 @@ export class SensorService extends ApiService {
|
|||
|
||||
return this.http.get<ApiResponse>(this.getDataUrl, { params, headers }).pipe(
|
||||
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);
|
||||
})
|
||||
);
|
||||
|
|
@ -49,7 +49,7 @@ export class SensorService extends ApiService {
|
|||
|
||||
return this.http.get<any>(this.getLatestUrl, { headers }).pipe(
|
||||
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);
|
||||
})
|
||||
);
|
||||
|
|
@ -60,7 +60,7 @@ export class SensorService extends ApiService {
|
|||
|
||||
return this.http.get<any>(this.getStatusRelay, { headers }).pipe(
|
||||
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);
|
||||
})
|
||||
);
|
||||
|
|
|
|||
|
|
@ -8,24 +8,52 @@
|
|||
<div class="col-md-9 col-lg-8 mx-auto">
|
||||
<h3 class="login-heading mb-4">Welcome back!</h3>
|
||||
|
||||
|
||||
<form (ngSubmit)="onSubmit()">
|
||||
<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>
|
||||
</div>
|
||||
<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>
|
||||
|
||||
<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 class="form-check mb-3">
|
||||
<input class="form-check-input" type="checkbox" [(ngModel)]="rememberMe" name="rememberMe" id="rememberPasswordCheck">
|
||||
<label class="form-check-label" for="rememberPasswordCheck">Remember Me? </label>
|
||||
<input class="form-check-input"
|
||||
type="checkbox"
|
||||
[(ngModel)]="rememberMe"
|
||||
name="rememberMe"
|
||||
id="rememberPasswordCheck">
|
||||
<label class="form-check-label" for="rememberPasswordCheck">Remember me</label>
|
||||
</div>
|
||||
|
||||
<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">
|
||||
<a class="small forgot" routerLink= '/register'>Register here</a>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
.login {
|
||||
min-height: 100vh;
|
||||
min-height: 100vh;
|
||||
font-family: "Onest", sans-serif;
|
||||
}
|
||||
|
||||
|
|
@ -19,11 +19,40 @@
|
|||
padding: 0.75rem 1rem;
|
||||
}
|
||||
|
||||
.color-btn{
|
||||
.color-btn {
|
||||
background-color: #16423C;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.forgot{
|
||||
.forgot {
|
||||
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 { LoginData } from '../../cores/interface/auth';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { catchError } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
selector: 'app-auth',
|
||||
standalone: true,
|
||||
imports: [FormsModule, RouterModule],
|
||||
imports: [FormsModule, RouterModule, CommonModule],
|
||||
templateUrl: './auth.component.html',
|
||||
styleUrls: ['./auth.component.scss']
|
||||
})
|
||||
|
|
@ -17,25 +19,43 @@ export class AuthComponent {
|
|||
email: string = '';
|
||||
password: string = '';
|
||||
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() {
|
||||
this.loading = true;
|
||||
if (!this.email || !this.password) {
|
||||
this.loading = false;
|
||||
this.toast.error('Please fill in all fields.');
|
||||
return;
|
||||
}
|
||||
|
||||
const loginData: LoginData = {
|
||||
email: this.email,
|
||||
password: this.password,
|
||||
rememberMe: this.rememberMe
|
||||
};
|
||||
|
||||
|
||||
this.authService.login(loginData).subscribe(
|
||||
(response) => {
|
||||
this.storageService.saveToken(response.data.token);
|
||||
this.router.navigate(['/dashboard']);
|
||||
this.toastr.success('Login successful');
|
||||
this.toast.success('Login successful');
|
||||
this.loading = false;
|
||||
},
|
||||
(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>
|
||||
<h1 class="title">Hello there</h1>
|
||||
<h3 class="description">Welcome back to your management system</h3>
|
||||
<h1 class="title">{{ greeting }}</h1>
|
||||
<h3 class="description">Welcome back to your management system</h3>
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="update">Latest Update: {{latestUpdate}}</h2>
|
||||
<h2 class="update">Latest Update: {{latestUpdate}}</h2>
|
||||
</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 === 'npk2'}" (click)="selectSensor('npk2')">NPK 2</button>
|
||||
<button [ngClass]="{'active-button': selectedButton === 'relay'}" (click)="selectSensor('relay')">Relay</button>
|
||||
</div>
|
||||
|
||||
<div *ngIf="isLoaded " class="loading">
|
||||
Loading...
|
||||
<div *ngIf="isLoaded" class="loading">
|
||||
<i class="fa fa-spinner fa-spin"></i>
|
||||
</div>
|
||||
|
||||
<div *ngIf="!isLoaded && selectedButton === 'dht'" class="card-container">
|
||||
<div class="card-parameter">
|
||||
<div>
|
||||
<h3>{{sensorData.dht.lightIntensity}} Lux</h3>
|
||||
<h6>Light Intensity</h6>
|
||||
<ng-template #noData>
|
||||
<div class="loading">No available data</div>
|
||||
</ng-template>
|
||||
|
||||
<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 class="card-parameter">
|
||||
<div>
|
||||
<h3>{{sensorData.dht.temperature}} °C</h3>
|
||||
<h6>Temperature</h6>
|
||||
<div class="card-parameter">
|
||||
<div>
|
||||
<h3>{{sensorData.dht.temperature}} °C</h3>
|
||||
<h6>Temperatur</h6>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-parameter">
|
||||
<div>
|
||||
<h3>{{sensorData.dht.humidity}} %RH</h3>
|
||||
<h6>Humidity</h6>
|
||||
<div class="card-parameter">
|
||||
<div>
|
||||
<h3>{{sensorData.dht.humidity}} %RH</h3>
|
||||
<h6>Kelembaban Udara</h6>
|
||||
</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 *ngIf="!isLoaded && selectedButton === 'npk2'" class="card-container">
|
||||
<div class="card-parameter">
|
||||
<div>
|
||||
<h3>{{ sensorData.npk2.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.npk2.phosphorus}} PPM</h3>
|
||||
<h6>Phosphorus</h6>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-parameter">
|
||||
<div>
|
||||
<h3>{{ sensorData.npk2.potassium }} PPM</h3>
|
||||
<h6>Potassium</h6>
|
||||
<div *ngIf="!isLoaded && selectedButton === 'npk1'">
|
||||
<div *ngIf="sensorData.npk1.temperature || sensorData.npk1.moisture || sensorData.npk1.conductivity; else noData">
|
||||
<div 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>Kelembaban Tanah</h6>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-parameter">
|
||||
<div>
|
||||
<h3>{{sensorData.npk1.conductivity}} μS/cm</h3>
|
||||
<h6>Conductivity</h6>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Additional parameters for NPK1... -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="!isLoaded && selectedButton === 'relay'" class="card-container">
|
||||
<div class="card-parameter" *ngFor="let relay of relayStatus;">
|
||||
<div>
|
||||
<h3 [ngClass]="relay.current_status ? 'status-on' : 'status-off'">
|
||||
{{ relay.current_status ? 'ON' : 'OFF' }}
|
||||
</h3>
|
||||
<h6>Relay {{ relay.number }}</h6>
|
||||
<div *ngIf="!isLoaded && selectedButton === 'npk2'">
|
||||
<div *ngIf="sensorData.npk2.temperature || sensorData.npk2.moisture || sensorData.npk2.conductivity; else noData">
|
||||
<div class="card-container">
|
||||
<div class="card-parameter">
|
||||
<div>
|
||||
<h3>{{sensorData.npk2.temperature}} °C</h3>
|
||||
<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 class="graph">
|
||||
<div class="title-graph">Monitoring</div>
|
||||
<div class="graph">
|
||||
<app-graph></app-graph>
|
||||
</div>
|
||||
<div class="title-graph">Monitoring Graphs</div>
|
||||
<div class="graph">
|
||||
<app-graph></app-graph>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -97,7 +97,6 @@ button {
|
|||
.loading{
|
||||
font-size: 18px;
|
||||
text-align: center;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.status-on {
|
||||
|
|
@ -107,3 +106,7 @@ button {
|
|||
.status-off {
|
||||
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 { SensorService } from '../../cores/services/sensor.service';
|
||||
import { SensorData, StatusRelay } from '../../cores/interface/sensor-data';
|
||||
import { interval } from 'rxjs';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
|
||||
@Component({
|
||||
selector: 'app-dashboard',
|
||||
|
|
@ -16,9 +16,12 @@ import { interval } from 'rxjs';
|
|||
})
|
||||
export class DashboardComponent implements OnInit {
|
||||
isLoaded: boolean = false;
|
||||
hasError: boolean = false;
|
||||
noData: boolean = false;
|
||||
selectedButton: string = '';
|
||||
latestUpdate: string = '';
|
||||
intervalId: any;
|
||||
greeting: string = '';
|
||||
sensorData: SensorData = {
|
||||
dht: { lightIntensity: 0, temperature: 0, humidity: 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[] = [];
|
||||
|
||||
constructor(private apiService: SensorService) {}
|
||||
constructor(private apiService: SensorService, private toast: ToastrService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.selectedButton = 'dht';
|
||||
this.startClock();
|
||||
this.updateGreeting();
|
||||
this.loadData();
|
||||
}
|
||||
|
||||
|
|
@ -61,68 +65,88 @@ export class DashboardComponent implements OnInit {
|
|||
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 {
|
||||
this.selectedButton = param;
|
||||
if(param==='relay'){
|
||||
if(param === 'relay'){
|
||||
this.loadRelayData();
|
||||
}else{
|
||||
this.loadData();
|
||||
} else {
|
||||
this.loadData();
|
||||
}
|
||||
}
|
||||
|
||||
loadData(): void {
|
||||
this.isLoaded = true;
|
||||
|
||||
this.hasError = false;
|
||||
this.noData = false;
|
||||
|
||||
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 ?? 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 ?? 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 ?? 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
|
||||
};
|
||||
|
||||
if ((!data.dht || data.dht.length === 0) &&
|
||||
(!data.npk1 || data.npk1.length === 0) &&
|
||||
(!data.npk2 || data.npk2.length === 0)) {
|
||||
this.noData = true;
|
||||
} else {
|
||||
if (data.dht && data.dht.length > 0) {
|
||||
this.sensorData.dht = {
|
||||
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 ?? 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 ?? 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.isLoaded = false;
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error fetching sensor data:', error);
|
||||
this.isLoaded = false;
|
||||
this.handleError(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
loadRelayData(): void {
|
||||
this.isLoaded = true;
|
||||
|
||||
this.hasError = false;
|
||||
|
||||
this.apiService.getRelayStatus().subscribe(
|
||||
(response) => {
|
||||
if (Array.isArray(response.data)) {
|
||||
|
|
@ -134,16 +158,26 @@ export class DashboardComponent implements OnInit {
|
|||
created_at: relay.created_at,
|
||||
current_status: relay.current_status
|
||||
}));
|
||||
this.relayStatus.sort((a, b) => a.number - b.number);
|
||||
}
|
||||
console.log('Relay Data:', response);
|
||||
this.isLoaded = false;
|
||||
},
|
||||
(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;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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">
|
||||
<select class="form-select" id="sensorSelect" (change)="onSensorChange($event)">
|
||||
<option value="dht">BHT</option>
|
||||
<option value="npk1">NPK 1</option>
|
||||
<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>
|
||||
<ng-container *ngIf="allNoData; else graphContent">
|
||||
<div class="no-data">No available data</div>
|
||||
</ng-container>
|
||||
|
||||
<div *ngIf="isLoading" class="loading">
|
||||
Loading...
|
||||
</div>
|
||||
<ng-template #graphContent>
|
||||
<div class="sensor-wrapper">
|
||||
<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 {
|
||||
width: 100%;
|
||||
max-width: 800px;
|
||||
margin: auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
canvas {
|
||||
width: 100%;
|
||||
height: 30vh;
|
||||
margin: 20px;
|
||||
align-items: center;
|
||||
}
|
||||
.container-graph {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
height: max-content;
|
||||
|
||||
.form-select{
|
||||
width: max-content;
|
||||
margin-top: 20px;
|
||||
display: flexbox;
|
||||
flex-direction: column;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.container-graph{
|
||||
.sensor-wrapper {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: left;
|
||||
height: max-content;
|
||||
}
|
||||
|
||||
|
||||
@media (max-width: 768px) {
|
||||
canvas{
|
||||
display: flex;
|
||||
width: max-content;
|
||||
flex: 1 1 45%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
canvas{
|
||||
display: flex;
|
||||
width: max-content;
|
||||
flex: 1 1 100%;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-bottom: 35px;
|
||||
background-color: #f1f1f1;
|
||||
border-radius: 10px;
|
||||
|
||||
h2 {
|
||||
font-size: 18px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.loading{
|
||||
font-size: 18px;
|
||||
text-align: center;
|
||||
color: #888;
|
||||
.title-with-dropdown {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
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 { Chart, registerables } from 'chart.js';
|
||||
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 { FormsModule } from '@angular/forms';
|
||||
|
||||
Chart.register(...registerables);
|
||||
|
||||
const parameterColors: { [key: string]: string } = {
|
||||
vicitemperature: '#8F5A62',
|
||||
vicihumidity: '#16423C',
|
||||
viciluminosity: '#DF9B55',
|
||||
soiltemperature: '#8F5A62',
|
||||
soilhumidity: '#54909c',
|
||||
soilconductivity: '#661311',
|
||||
soilph: '#664735',
|
||||
soilnitrogen: '#3a6635',
|
||||
soilphosphorus: '#3f3566',
|
||||
soilpotassium: '#5f3566',
|
||||
soiltemperature: '#FF6347',
|
||||
soilhumidity: '#00BFFF',
|
||||
soilconductivity: '#A52A2A',
|
||||
soilph: '#228B22',
|
||||
soilnitrogen: '#FEDC56',
|
||||
soilphosphorus: '#B80F0A',
|
||||
soilpotassium: '#6F2DA8',
|
||||
};
|
||||
|
||||
@Component({
|
||||
|
|
@ -27,11 +28,19 @@ const parameterColors: { [key: string]: string } = {
|
|||
styleUrls: ['./graph.component.scss']
|
||||
})
|
||||
export class GraphComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
@ViewChild('myChart', { static: false }) chartElement!: ElementRef<HTMLCanvasElement>;
|
||||
selectedSensor: string = '';
|
||||
selectedParameter: string = '';
|
||||
parameters: string[] = [];
|
||||
isLoading: boolean = true;
|
||||
@ViewChild('myChartDHT', { static: false }) dhtChartElement!: ElementRef<HTMLCanvasElement>;
|
||||
@ViewChild('myChartNPK1', { static: false }) npk1ChartElement!: ElementRef<HTMLCanvasElement>;
|
||||
@ViewChild('myChartNPK2', { static: false }) npk2ChartElement!: ElementRef<HTMLCanvasElement>;
|
||||
|
||||
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[] } = {
|
||||
dht: ['vicitemperature', 'vicihumidity', 'viciluminosity'],
|
||||
|
|
@ -47,28 +56,32 @@ export class GraphComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
soilhumidity: 'Soil Humidity (%)',
|
||||
soilconductivity: 'Conductivity (μS/cm)',
|
||||
soilph: 'pH',
|
||||
soilnitrogen: 'Nitrogen (PPM)',
|
||||
soilphosphorus: 'Phosphorus (PPM)',
|
||||
soilpotassium: 'Potassium (PPM)',
|
||||
soilnitrogen: 'Nitrogen (mg/l)',
|
||||
soilphosphorus: 'Phosphorus (mg/l)',
|
||||
soilpotassium: 'Potassium (mg/l)',
|
||||
};
|
||||
|
||||
chart: Chart | undefined;
|
||||
labelsHourly: string[] = [];
|
||||
charts: { [key: string]: Chart | undefined } = {
|
||||
dht: undefined,
|
||||
npk1: undefined,
|
||||
npk2: undefined,
|
||||
};
|
||||
|
||||
selectedNPK1: string = 'npk';
|
||||
selectedNPK2: string = 'npk';
|
||||
|
||||
private resizeListener!: () => void;
|
||||
|
||||
constructor(private sensorService: SensorService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.selectedSensor = 'dht';
|
||||
this.updateParameters();
|
||||
this.selectedParameter = 'vicitemperature';
|
||||
this.updateChart();
|
||||
this.resizeListener = this.onResize.bind(this);
|
||||
window.addEventListener('resize', this.resizeListener);
|
||||
this.updateCharts();
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
this.updateChart();
|
||||
this.onResize();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
|
|
@ -76,12 +89,204 @@ export class GraphComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
}
|
||||
|
||||
onResize(): void {
|
||||
if (this.chart) {
|
||||
this.chart.destroy();
|
||||
this.updateChart();
|
||||
}
|
||||
Object.values(this.charts).forEach(chart => {
|
||||
if (chart) {
|
||||
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[] } {
|
||||
const sensorData = response.data[sensor as keyof typeof response.data];
|
||||
|
||||
|
|
@ -100,112 +305,8 @@ export class GraphComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
return { data, labels };
|
||||
}
|
||||
|
||||
|
||||
createChart(data: number[], parameter: string, labels: string[]): void {
|
||||
const canvas = this.chartElement.nativeElement;
|
||||
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;
|
||||
}
|
||||
);
|
||||
getLabels(response: ApiResponse, sensor: string): string[] {
|
||||
const sensorData = response.data[sensor as keyof typeof response.data];
|
||||
return sensorData ? sensorData.map(item => `${item.hour}.00`) : [];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,25 +10,44 @@
|
|||
|
||||
<form (ngSubmit)="onSubmit()">
|
||||
<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>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
<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>
|
||||
|
||||
<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 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">
|
||||
<a class="small forgot" routerLink= '/auth'>Log In</a>
|
||||
<a class="small forgot" routerLink='/auth'>Sign In here</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -27,3 +27,22 @@
|
|||
.forgot{
|
||||
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 { AuthService } from '../../cores/services/auth.service';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
|
@ -13,15 +13,35 @@ import { CommonModule } from '@angular/common';
|
|||
templateUrl: './register.component.html',
|
||||
styleUrls: ['./register.component.scss']
|
||||
})
|
||||
export class RegisterComponent {
|
||||
export class RegisterComponent implements OnInit {
|
||||
username: string = '';
|
||||
password: string = '';
|
||||
email: string = '';
|
||||
fullname: string = '';
|
||||
loading: boolean = false;
|
||||
passwordVisible: boolean = false;
|
||||
|
||||
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() {
|
||||
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 = {
|
||||
username: this.username,
|
||||
pwd: this.password,
|
||||
|
|
@ -33,10 +53,12 @@ export class RegisterComponent {
|
|||
|
||||
this.authService.register(registrationData).subscribe(
|
||||
(response) => {
|
||||
this.loading = false;
|
||||
this.toast.success('Registration successful');
|
||||
this.router.navigate(['/auth']);
|
||||
},
|
||||
(error) => {
|
||||
this.loading = false;
|
||||
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/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">
|
||||
</head>
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user