dev smartfarming #1
|
|
@ -34,7 +34,8 @@
|
||||||
"styles": [
|
"styles": [
|
||||||
"src/styles.scss",
|
"src/styles.scss",
|
||||||
"node_modules/bootstrap/dist/css/bootstrap.min.css",
|
"node_modules/bootstrap/dist/css/bootstrap.min.css",
|
||||||
"node_modules/bootstrap-icons/font/bootstrap-icons.css"
|
"node_modules/bootstrap-icons/font/bootstrap-icons.css",
|
||||||
|
"node_modules/ngx-toastr/toastr.css"
|
||||||
],
|
],
|
||||||
"scripts": [
|
"scripts": [
|
||||||
"node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"
|
"node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"
|
||||||
|
|
@ -97,7 +98,8 @@
|
||||||
"styles": [
|
"styles": [
|
||||||
"src/styles.scss",
|
"src/styles.scss",
|
||||||
"node_modules/bootstrap/dist/css/bootstrap.min.css",
|
"node_modules/bootstrap/dist/css/bootstrap.min.css",
|
||||||
"node_modules/bootstrap-icons/font/bootstrap-icons.css"
|
"node_modules/bootstrap-icons/font/bootstrap-icons.css",
|
||||||
|
"node_modules/ngx-toastr/toastr.css"
|
||||||
],
|
],
|
||||||
"scripts": [
|
"scripts": [
|
||||||
"node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"
|
"node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"
|
||||||
|
|
|
||||||
53
agrilink_vocpro/package-lock.json
generated
53
agrilink_vocpro/package-lock.json
generated
|
|
@ -8,7 +8,7 @@
|
||||||
"name": "agrilink-vocpro",
|
"name": "agrilink-vocpro",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "^18.2.0",
|
"@angular/animations": "^18.2.8",
|
||||||
"@angular/common": "^18.2.0",
|
"@angular/common": "^18.2.0",
|
||||||
"@angular/compiler": "^18.2.0",
|
"@angular/compiler": "^18.2.0",
|
||||||
"@angular/core": "^18.2.0",
|
"@angular/core": "^18.2.0",
|
||||||
|
|
@ -20,6 +20,8 @@
|
||||||
"bootstrap-icons": "^1.11.3",
|
"bootstrap-icons": "^1.11.3",
|
||||||
"chart.js": "^4.4.4",
|
"chart.js": "^4.4.4",
|
||||||
"highcharts-angular": "^4.0.1",
|
"highcharts-angular": "^4.0.1",
|
||||||
|
"jwt-decode": "^4.0.0",
|
||||||
|
"ngx-toastr": "^19.0.0",
|
||||||
"rxjs": "~7.8.0",
|
"rxjs": "~7.8.0",
|
||||||
"tslib": "^2.3.0",
|
"tslib": "^2.3.0",
|
||||||
"zone.js": "~0.14.10"
|
"zone.js": "~0.14.10"
|
||||||
|
|
@ -272,9 +274,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@angular/animations": {
|
"node_modules/@angular/animations": {
|
||||||
"version": "18.2.4",
|
"version": "18.2.8",
|
||||||
"resolved": "https://registry.npmjs.org/@angular/animations/-/animations-18.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/@angular/animations/-/animations-18.2.8.tgz",
|
||||||
"integrity": "sha512-ajjXpLD+SyxbHnmhj2ZvYpXneviOjcBgU9L2UX4OVS0jVWxCNHLhJrcMqtqFHA6U5fPnhPNR9cmnt6tmqri0rA==",
|
"integrity": "sha512-dMSn2hg70siv3lhP+vqhMbgc923xw6XBUvnpCPEzhZqFHvPXfh/LubmsD5RtqHmjWebXtgVcgS+zg3Gq3jB2lg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "^2.3.0"
|
"tslib": "^2.3.0"
|
||||||
|
|
@ -283,7 +285,7 @@
|
||||||
"node": "^18.19.1 || ^20.11.1 || >=22.0.0"
|
"node": "^18.19.1 || ^20.11.1 || >=22.0.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@angular/core": "18.2.4"
|
"@angular/core": "18.2.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@angular/build": {
|
"node_modules/@angular/build": {
|
||||||
|
|
@ -3862,17 +3864,6 @@
|
||||||
"node": ">=14"
|
"node": ">=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@popperjs/core": {
|
|
||||||
"version": "2.11.8",
|
|
||||||
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
|
|
||||||
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true,
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/popperjs"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||||
"version": "4.20.0",
|
"version": "4.20.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.20.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.20.0.tgz",
|
||||||
|
|
@ -7632,13 +7623,6 @@
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/highcharts": {
|
|
||||||
"version": "11.4.8",
|
|
||||||
"resolved": "https://registry.npmjs.org/highcharts/-/highcharts-11.4.8.tgz",
|
|
||||||
"integrity": "sha512-5Tke9LuzZszC4osaFisxLIcw7xgNGz4Sy3Jc9pRMV+ydm6sYqsPYdU8ELOgpzGNrbrRNDRBtveoR5xS3SzneEA==",
|
|
||||||
"license": "https://www.highcharts.com/license",
|
|
||||||
"peer": true
|
|
||||||
},
|
|
||||||
"node_modules/highcharts-angular": {
|
"node_modules/highcharts-angular": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/highcharts-angular/-/highcharts-angular-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/highcharts-angular/-/highcharts-angular-4.0.1.tgz",
|
||||||
|
|
@ -8633,6 +8617,15 @@
|
||||||
],
|
],
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/jwt-decode": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/karma": {
|
"node_modules/karma": {
|
||||||
"version": "6.4.4",
|
"version": "6.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/karma/-/karma-6.4.4.tgz",
|
"resolved": "https://registry.npmjs.org/karma/-/karma-6.4.4.tgz",
|
||||||
|
|
@ -10063,6 +10056,20 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/ngx-toastr": {
|
||||||
|
"version": "19.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ngx-toastr/-/ngx-toastr-19.0.0.tgz",
|
||||||
|
"integrity": "sha512-6pTnktwwWD+kx342wuMOWB4+bkyX9221pAgGz3SHOJH0/MI9erLucS8PeeJDFwbUYyh75nQ6AzVtolgHxi52dQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.3.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@angular/common": ">=16.0.0-0",
|
||||||
|
"@angular/core": ">=16.0.0-0",
|
||||||
|
"@angular/platform-browser": ">=16.0.0-0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/nice-napi": {
|
"node_modules/nice-napi": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz",
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "^18.2.0",
|
"@angular/animations": "^18.2.8",
|
||||||
"@angular/common": "^18.2.0",
|
"@angular/common": "^18.2.0",
|
||||||
"@angular/compiler": "^18.2.0",
|
"@angular/compiler": "^18.2.0",
|
||||||
"@angular/core": "^18.2.0",
|
"@angular/core": "^18.2.0",
|
||||||
|
|
@ -22,6 +22,8 @@
|
||||||
"bootstrap-icons": "^1.11.3",
|
"bootstrap-icons": "^1.11.3",
|
||||||
"chart.js": "^4.4.4",
|
"chart.js": "^4.4.4",
|
||||||
"highcharts-angular": "^4.0.1",
|
"highcharts-angular": "^4.0.1",
|
||||||
|
"jwt-decode": "^4.0.0",
|
||||||
|
"ngx-toastr": "^19.0.0",
|
||||||
"rxjs": "~7.8.0",
|
"rxjs": "~7.8.0",
|
||||||
"tslib": "^2.3.0",
|
"tslib": "^2.3.0",
|
||||||
"zone.js": "~0.14.10"
|
"zone.js": "~0.14.10"
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,16 @@
|
||||||
import { ApplicationConfig, provideZoneChangeDetection } 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 } from '@angular/common/http';
|
||||||
|
import { provideAnimations } from '@angular/platform-browser/animations';
|
||||||
|
import { ToastrModule } from 'ngx-toastr';
|
||||||
import { routes } from './app.routes';
|
import { routes } from './app.routes';
|
||||||
|
|
||||||
export const appConfig: ApplicationConfig = {
|
export const appConfig: ApplicationConfig = {
|
||||||
providers: [
|
providers: [
|
||||||
provideZoneChangeDetection({ eventCoalescing: true }),
|
provideZoneChangeDetection({ eventCoalescing: true }),
|
||||||
provideRouter(routes),
|
provideRouter(routes),
|
||||||
provideHttpClient()
|
provideHttpClient(),
|
||||||
|
provideAnimations(),
|
||||||
|
importProvidersFrom(ToastrModule.forRoot()),
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -3,28 +3,36 @@ 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 { 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/guard/guards/auth.guard';
|
||||||
|
import { RegisterComponent } from './pages/register/register.component';
|
||||||
|
|
||||||
export const routes: Routes = [
|
export const routes: Routes = [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
redirectTo:'dashboard',
|
redirectTo: 'auth',
|
||||||
pathMatch:'full'
|
pathMatch: 'full'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'auth',
|
path: 'auth',
|
||||||
component: AuthComponent
|
component: AuthComponent
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'register',
|
||||||
|
component: RegisterComponent
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
component: LayoutsComponent,
|
component: LayoutsComponent,
|
||||||
children:[
|
children: [
|
||||||
{
|
{
|
||||||
path: 'dashboard',
|
path: 'dashboard',
|
||||||
component: DashboardComponent,
|
component: DashboardComponent,
|
||||||
|
canActivate: [AuthGuard]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'graph',
|
path: 'graph',
|
||||||
component: GraphComponent
|
component: GraphComponent,
|
||||||
|
canActivate: [AuthGuard]
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
import { CanActivateFn } from '@angular/router';
|
||||||
|
|
||||||
|
import { authGuard } from './auth.guard';
|
||||||
|
|
||||||
|
describe('authGuard', () => {
|
||||||
|
const executeGuard: CanActivateFn = (...guardParameters) =>
|
||||||
|
TestBed.runInInjectionContext(() => authGuard(...guardParameters));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(executeGuard).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
20
agrilink_vocpro/src/app/cores/guard/guards/auth.guard.ts
Normal file
20
agrilink_vocpro/src/app/cores/guard/guards/auth.guard.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { CanActivate, Router } from '@angular/router';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class AuthGuard implements CanActivate {
|
||||||
|
|
||||||
|
constructor(private router: Router) {}
|
||||||
|
|
||||||
|
canActivate(): boolean {
|
||||||
|
const token = localStorage.getItem('accessToken');
|
||||||
|
if (token) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
this.router.navigate(['auth']);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
agrilink_vocpro/src/app/cores/interface/auth.ts
Normal file
14
agrilink_vocpro/src/app/cores/interface/auth.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
export interface LoginData {
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
rememberMe?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RegistrationData {
|
||||||
|
username: string;
|
||||||
|
pwd: string;
|
||||||
|
email: string;
|
||||||
|
google_id: string;
|
||||||
|
fullname: string;
|
||||||
|
avatar?: string;
|
||||||
|
}
|
||||||
|
|
@ -18,13 +18,14 @@ export interface ParameterSensor {
|
||||||
|
|
||||||
export interface ApiResponse {
|
export interface ApiResponse {
|
||||||
data: {
|
data: {
|
||||||
dht?: ParameterSensor[];
|
dht: Array<any>;
|
||||||
npk1?: ParameterSensor[];
|
npk1: Array<any>;
|
||||||
npk2?: ParameterSensor[];
|
npk2: Array<any>;
|
||||||
};
|
};
|
||||||
statusCode: number;
|
statusCode: number;
|
||||||
message: string;
|
message: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface DHTSensor {
|
export interface DHTSensor {
|
||||||
lightIntensity: number;
|
lightIntensity: number;
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,11 @@
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { HttpClient, HttpParams } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { Observable } from 'rxjs';
|
|
||||||
import { ApiResponse } from '../interface/sensor-data';
|
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class ApiService {
|
export class ApiService {
|
||||||
private baseUrl = 'https://jx027dj4-3333.asse.devtunnels.ms/api/sensor/';
|
protected baseUrl = 'https://jx027dj4-3333.asse.devtunnels.ms/';
|
||||||
|
|
||||||
constructor(private http: HttpClient) {}
|
constructor(protected http: HttpClient) {}
|
||||||
|
|
||||||
getSensorData(sensor: string, metric: string, startDate: string, timeRange: string): Observable<ApiResponse> {
|
|
||||||
const url = `${this.baseUrl}getData`;
|
|
||||||
const params = new HttpParams()
|
|
||||||
.set('range[start]', startDate)
|
|
||||||
.set('range[time_range]', timeRange)
|
|
||||||
.set('sensor', sensor)
|
|
||||||
.set('metric', metric);
|
|
||||||
|
|
||||||
return this.http.get<ApiResponse>(url, { params });
|
|
||||||
}
|
|
||||||
|
|
||||||
getLatestData(): Observable<ApiResponse> {
|
|
||||||
const url = `${this.baseUrl}getLatest`;
|
|
||||||
return this.http.get<ApiResponse>(url);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
16
agrilink_vocpro/src/app/cores/services/auth.service.spec.ts
Normal file
16
agrilink_vocpro/src/app/cores/services/auth.service.spec.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { AuthService } from './auth.service';
|
||||||
|
|
||||||
|
describe('AuthService', () => {
|
||||||
|
let service: AuthService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({});
|
||||||
|
service = TestBed.inject(AuthService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
87
agrilink_vocpro/src/app/cores/services/auth.service.ts
Normal file
87
agrilink_vocpro/src/app/cores/services/auth.service.ts
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { tap } from 'rxjs/operators';
|
||||||
|
import { ApiService } from './api.service';
|
||||||
|
import { LoginData } from '../interface/auth';
|
||||||
|
import {jwtDecode}from 'jwt-decode';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class AuthService extends ApiService {
|
||||||
|
private authUrl = `${this.baseUrl}auth/login`;
|
||||||
|
private logoutUrl = `${this.baseUrl}auth/logout`;
|
||||||
|
private registerUrl = `${this.baseUrl}auth/register`;
|
||||||
|
|
||||||
|
constructor(http: HttpClient) {
|
||||||
|
super(http);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
login(data: LoginData): Observable<any> {
|
||||||
|
const headers = new HttpHeaders({
|
||||||
|
Authorization: 'Basic ' + btoa(`${data.email}:${data.password}`)
|
||||||
|
});
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('remember_me', data.rememberMe ? 'true' : 'false');
|
||||||
|
|
||||||
|
return this.http.post<any>(this.authUrl, formData, { headers }).pipe(
|
||||||
|
tap(response => {
|
||||||
|
const accessToken = response.data.token;
|
||||||
|
this.saveTokens(accessToken);
|
||||||
|
|
||||||
|
const jwtToken = response.data.jwtToken;
|
||||||
|
const decodedToken: any = jwtDecode(jwtToken);
|
||||||
|
|
||||||
|
this.saveUserDataToStorage(decodedToken.user.fullname, decodedToken.user.avatar);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
saveTokens(token: string) {
|
||||||
|
localStorage.setItem('accessToken', token);
|
||||||
|
}
|
||||||
|
|
||||||
|
logout(): Observable<any> {
|
||||||
|
const token = localStorage.getItem('accessToken');
|
||||||
|
const headers = new HttpHeaders({
|
||||||
|
Authorization: `Bearer ${token}`
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.http.post<any>(this.logoutUrl, {}, { headers }).pipe(
|
||||||
|
tap(() => {
|
||||||
|
localStorage.removeItem('accessToken');
|
||||||
|
this.clearUserDataFromStorage();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
register(data: any): Observable<any> {
|
||||||
|
const headers = new HttpHeaders({});
|
||||||
|
return this.http.post<any>(this.registerUrl, data, { headers }).pipe(
|
||||||
|
// tap(response => {
|
||||||
|
// console.log('Registration response:', response);
|
||||||
|
// })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getUserFullName(): string | null {
|
||||||
|
return localStorage.getItem('userFullName');
|
||||||
|
}
|
||||||
|
|
||||||
|
getAvatar(): string | null {
|
||||||
|
return localStorage.getItem('avatar');
|
||||||
|
}
|
||||||
|
|
||||||
|
private saveUserDataToStorage(fullName: string | null, avatar: string | null) {
|
||||||
|
localStorage.setItem('userFullName', fullName || '');
|
||||||
|
localStorage.setItem('avatar', avatar || '');
|
||||||
|
}
|
||||||
|
|
||||||
|
private clearUserDataFromStorage() {
|
||||||
|
localStorage.removeItem('userFullName');
|
||||||
|
localStorage.removeItem('avatar');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { SensorService } from './sensor.service';
|
||||||
|
|
||||||
|
describe('SensorService', () => {
|
||||||
|
let service: SensorService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({});
|
||||||
|
service = TestBed.inject(SensorService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
55
agrilink_vocpro/src/app/cores/services/sensor.service.ts
Normal file
55
agrilink_vocpro/src/app/cores/services/sensor.service.ts
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { HttpParams, HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
|
||||||
|
import { catchError, Observable, throwError, tap } from 'rxjs';
|
||||||
|
import { ApiService } from './api.service';
|
||||||
|
import { ApiResponse } from '../interface/sensor-data';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class SensorService extends ApiService {
|
||||||
|
constructor(http: HttpClient) {
|
||||||
|
super(http);
|
||||||
|
}
|
||||||
|
|
||||||
|
getSensorData(sensor: string, metric: string, startEnd: string, timeRange: string): Observable<ApiResponse> {
|
||||||
|
const url = `${this.baseUrl}api/sensor/getData`;
|
||||||
|
const params = new HttpParams()
|
||||||
|
.set('range[end]', startEnd)
|
||||||
|
.set('range[time_range]', timeRange)
|
||||||
|
.set('sensor', sensor)
|
||||||
|
.set('metric', metric);
|
||||||
|
|
||||||
|
const token = localStorage.getItem('accessToken');
|
||||||
|
const headers = new HttpHeaders({
|
||||||
|
Authorization: 'Bearer ' + token,
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.http.get<ApiResponse>(url, { params, headers }).pipe(
|
||||||
|
catchError((error: HttpErrorResponse) => {
|
||||||
|
if (error.error instanceof ErrorEvent) {
|
||||||
|
console.error('An error occurred:', error.error.message);
|
||||||
|
} else {
|
||||||
|
console.error(`Backend returned code ${error.status}, body was: ${error.error}`);
|
||||||
|
}
|
||||||
|
return throwError('Something went wrong; please try again later.');
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getLatestData(): Observable<ApiResponse> {
|
||||||
|
const url = `${this.baseUrl}api/sensor/getLatest`;
|
||||||
|
const headers = new HttpHeaders({
|
||||||
|
Authorization: 'Bearer ' + localStorage.getItem('accessToken'),
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.http.get<any>(url, { headers }).pipe(
|
||||||
|
catchError(error => {
|
||||||
|
console.error('API Error:', error);
|
||||||
|
return throwError(error);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,43 +1,40 @@
|
||||||
<div class="container-fluid ps-md-0">
|
<div class="container-fluid ps-md-0">
|
||||||
<div class="row g-0">
|
<div class="row g-0">
|
||||||
<div class="d-none d-md-flex col-md-4 col-lg-6 bg-image"></div>
|
<div class="d-none d-md-flex col-md-4 col-lg-6 bg-image"></div>
|
||||||
<div class="col-md-8 col-lg-6">
|
<div class="col-md-8 col-lg-6">
|
||||||
<div class="login d-flex align-items-center py-5">
|
<div class="login d-flex align-items-center py-5">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<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>
|
||||||
|
|
||||||
<!-- Sign In Form -->
|
|
||||||
<form>
|
|
||||||
<div class="form-floating mb-3">
|
|
||||||
<input type="email" class="form-control" id="floatingInput" placeholder="name@example.com">
|
|
||||||
<label for="floatingInput">Email address</label>
|
|
||||||
</div>
|
|
||||||
<div class="form-floating mb-3">
|
|
||||||
<input type="password" class="form-control" id="floatingPassword" placeholder="Password">
|
|
||||||
<label for="floatingPassword">Password</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-check mb-3">
|
<form (ngSubmit)="onSubmit()">
|
||||||
<input class="form-check-input chechkbox" type="checkbox" value="" id="rememberPasswordCheck">
|
<div class="form-floating mb-3">
|
||||||
<label class="form-check-label" for="rememberPasswordCheck">
|
<input type="email" class="form-control" [(ngModel)]="email" name="email" id="floatingInput" placeholder="name@example.com" required>
|
||||||
Remember password
|
<label for="floatingInput">Email address</label>
|
||||||
</label>
|
</div>
|
||||||
</div>
|
<div class="form-floating mb-3">
|
||||||
|
<input type="password" class="form-control" [(ngModel)]="password" name="password" id="floatingPassword" placeholder="Password" required>
|
||||||
|
<label for="floatingPassword">Password</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="d-grid">
|
<div class="form-check mb-3">
|
||||||
<a class="btn btn-lg color-btn btn-login text-uppercase fw-bold mb-2" routerLink=''>Sign in</a>
|
<input class="form-check-input" type="checkbox" [(ngModel)]="rememberMe" name="rememberMe" id="rememberPasswordCheck">
|
||||||
<div class="text-center">
|
<label class="form-check-label" for="rememberPasswordCheck">Remember Me? </label>
|
||||||
<a class="small forgot" href="#">Forgot password?</a>
|
</div>
|
||||||
</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>
|
||||||
|
<div class="text-center">
|
||||||
|
<a class="small forgot" routerLink= '/register'>Register here</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</div>
|
||||||
</div>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,40 @@
|
||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { RouterLink } from '@angular/router';
|
import { Router, RouterModule } from '@angular/router';
|
||||||
|
import { AuthService } from '../../cores/services/auth.service';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { LoginData } from '../../cores/interface/auth';
|
||||||
|
import { ToastrService } from 'ngx-toastr';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-auth',
|
selector: 'app-auth',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [RouterLink],
|
imports: [FormsModule, RouterModule],
|
||||||
templateUrl: './auth.component.html',
|
templateUrl: './auth.component.html',
|
||||||
styleUrl: './auth.component.scss'
|
styleUrls: ['./auth.component.scss']
|
||||||
})
|
})
|
||||||
export class AuthComponent {
|
export class AuthComponent {
|
||||||
|
email: string = '';
|
||||||
|
password: string = '';
|
||||||
|
rememberMe: boolean = false;
|
||||||
|
|
||||||
|
constructor(private authService: AuthService, private router: Router, private toastr: ToastrService) {}
|
||||||
|
|
||||||
|
onSubmit() {
|
||||||
|
const loginData: LoginData = {
|
||||||
|
email: this.email,
|
||||||
|
password: this.password,
|
||||||
|
rememberMe: this.rememberMe
|
||||||
|
};
|
||||||
|
|
||||||
|
this.authService.login(loginData).subscribe(
|
||||||
|
(response) => {
|
||||||
|
this.authService.saveTokens(response.data.token);
|
||||||
|
this.router.navigate(['/dashboard']);
|
||||||
|
this.toastr.success('Login successful');
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
this.toastr.error(error.error.message);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,16 @@
|
||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { RouterOutlet } from '@angular/router';
|
import { RouterOutlet } from '@angular/router';
|
||||||
import { SidebarComponent } from './layouts/sidebar/sidebar.component';
|
import { SidebarComponent } from './layouts/sidebar/sidebar.component';
|
||||||
import { GaugeChartComponent } from './page/gauge-chart/gauge-chart.component';
|
|
||||||
import { GraphComponent } from './page/graph/graph.component';
|
import { GraphComponent } from './page/graph/graph.component';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { ApiService } from '../../cores/services/api.service';
|
import { SensorService } from '../../cores/services/sensor.service';
|
||||||
import { SensorData } from '../../cores/interface/sensor-data';
|
import { SensorData } from '../../cores/interface/sensor-data';
|
||||||
import { interval } from 'rxjs';
|
import { interval } from 'rxjs';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-dashboard',
|
selector: 'app-dashboard',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [RouterOutlet, SidebarComponent, GaugeChartComponent, CommonModule, GraphComponent],
|
imports: [RouterOutlet, SidebarComponent, CommonModule, GraphComponent],
|
||||||
templateUrl: './dashboard.component.html',
|
templateUrl: './dashboard.component.html',
|
||||||
styleUrls: ['./dashboard.component.scss']
|
styleUrls: ['./dashboard.component.scss']
|
||||||
})
|
})
|
||||||
|
|
@ -26,7 +25,7 @@ export class DashboardComponent implements OnInit {
|
||||||
npk2: { temperature: 0, moisture: 0, conductivity: 0, ph: 0, nitrogen: 0, phosphorus: 0, potassium: 0 }
|
npk2: { temperature: 0, moisture: 0, conductivity: 0, ph: 0, nitrogen: 0, phosphorus: 0, potassium: 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(private apiService: ApiService) {}
|
constructor(private apiService: SensorService) {}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.selectedButton = 'dht';
|
this.selectedButton = 'dht';
|
||||||
|
|
@ -58,7 +57,7 @@ export class DashboardComponent implements OnInit {
|
||||||
minute: '2-digit',
|
minute: '2-digit',
|
||||||
second: '2-digit'
|
second: '2-digit'
|
||||||
};
|
};
|
||||||
this.latestUpdate = now.toLocaleString('en-GB', options); // Update waktu ke format yang sesuai
|
this.latestUpdate = now.toLocaleString('en-GB', options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -105,7 +104,7 @@ export class DashboardComponent implements OnInit {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateLatestTime(); // Update waktu setelah data diambil
|
this.updateLatestTime();
|
||||||
this.isLoaded = false;
|
this.isLoaded = false;
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
|
|
|
||||||
|
|
@ -13,13 +13,15 @@
|
||||||
</li> -->
|
</li> -->
|
||||||
</ul>
|
</ul>
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<div class="dropdown pb-4">
|
<div class="dropdown pb-4">
|
||||||
<a href="#" class="d-flex align-items-center text-white text-decoration-none dropdown-toggle" id="dropdownUser1" data-bs-toggle="dropdown" aria-expanded="false">
|
<a href="#" class="d-flex align-items-center text-white text-decoration-none dropdown-toggle" id="dropdownUser1" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
<img src="https://github.com/mdo.png" alt="hugenerd" width="30" height="30" class="rounded-circle">
|
<img [src]="avatar || 'https://github.com/mdo.png'" alt="User Avatar" width="30" height="30" class="rounded-circle"> <!-- Use avatar or a default image -->
|
||||||
<span class="d-none d-sm-inline mx-1">Tiffany</span>
|
<span class="d-none d-sm-inline mx-1">{{ fullName }}</span>
|
||||||
</a>
|
</a>
|
||||||
<ul class="dropdown-menu dropdown-menu-dark text-small shadow" aria-labelledby="dropdownUser1">
|
<ul class="dropdown-menu dropdown-menu-dark text-small shadow" aria-labelledby="dropdownUser1">
|
||||||
<li><a class="dropdown-item" routerLink='/auth'>Sign out</a></li>
|
<li><a class="dropdown-item" (click)="onLogout()">Sign out</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1,13 +1,32 @@
|
||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { RouterLink } from '@angular/router';
|
import { AuthService } from '../../../../cores/services/auth.service';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { ToastrService } from 'ngx-toastr';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-sidebar',
|
selector: 'app-sidebar',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [RouterLink],
|
|
||||||
templateUrl: './sidebar.component.html',
|
templateUrl: './sidebar.component.html',
|
||||||
styleUrl: './sidebar.component.scss'
|
styleUrls: ['./sidebar.component.scss']
|
||||||
})
|
})
|
||||||
export class SidebarComponent {
|
export class SidebarComponent {
|
||||||
|
fullName: string | null = null;
|
||||||
|
avatar: string | null = null;
|
||||||
|
|
||||||
|
constructor(private authService: AuthService, private router: Router, private toast: ToastrService) {
|
||||||
|
this.fullName = this.authService.getUserFullName();
|
||||||
|
this.avatar = this.authService.getAvatar();
|
||||||
|
}
|
||||||
|
|
||||||
|
onLogout(): void {
|
||||||
|
this.authService.logout().subscribe(
|
||||||
|
() => {
|
||||||
|
this.router.navigate(['/auth']);
|
||||||
|
this.toast.success('Logout successful');
|
||||||
|
},
|
||||||
|
(error: any) => {
|
||||||
|
this.toast.error(error.error.message);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
<div *ngIf="isLoading" class="loading">
|
|
||||||
Loading...
|
|
||||||
</div>
|
|
||||||
<highcharts-chart
|
|
||||||
*ngIf="!isLoading"
|
|
||||||
class="gauge-chart"
|
|
||||||
[Highcharts]="Highcharts"
|
|
||||||
[options]="chartOptions"
|
|
||||||
>
|
|
||||||
</highcharts-chart>
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
.gauge-chart{
|
|
||||||
width: 50%;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading{
|
|
||||||
font-size: 18px;
|
|
||||||
text-align: center;
|
|
||||||
color: #888;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,99 +0,0 @@
|
||||||
import { Component, Input } from '@angular/core';
|
|
||||||
import * as Highcharts from 'highcharts';
|
|
||||||
import HighchartsMore from 'highcharts/highcharts-more';
|
|
||||||
import HC_solidGauge from 'highcharts/modules/solid-gauge';
|
|
||||||
import { HighchartsChartModule } from 'highcharts-angular';
|
|
||||||
import { CommonModule } from '@angular/common';
|
|
||||||
|
|
||||||
|
|
||||||
HighchartsMore(Highcharts);
|
|
||||||
HC_solidGauge(Highcharts);
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-gauge-chart',
|
|
||||||
standalone: true,
|
|
||||||
imports: [HighchartsChartModule, CommonModule],
|
|
||||||
templateUrl: './gauge-chart.component.html',
|
|
||||||
styleUrls: ['./gauge-chart.component.scss']
|
|
||||||
})
|
|
||||||
export class GaugeChartComponent {
|
|
||||||
@Input() gaugeTitle: string = 'Gauge';
|
|
||||||
@Input() gaugeData: number = 0;
|
|
||||||
@Input() colorStops: [number, string][] = [];
|
|
||||||
@Input() maxValue: number = 100;
|
|
||||||
@Input() satuanData: string = '';
|
|
||||||
|
|
||||||
isLoading: boolean = true;
|
|
||||||
|
|
||||||
Highcharts: typeof Highcharts = Highcharts;
|
|
||||||
|
|
||||||
chartOptions: Highcharts.Options = {};
|
|
||||||
|
|
||||||
constructor() {}
|
|
||||||
|
|
||||||
ngOnChanges() {
|
|
||||||
this.chartOptions = {
|
|
||||||
|
|
||||||
chart: {
|
|
||||||
type: 'solidgauge',
|
|
||||||
height: '100%',
|
|
||||||
backgroundColor: 'transparent'
|
|
||||||
},
|
|
||||||
credits: {
|
|
||||||
enabled: false
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
text: this.gaugeTitle,
|
|
||||||
style: {
|
|
||||||
fontSize: '15px',
|
|
||||||
fontFamily: 'Onest, sans-serif',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
pane: {
|
|
||||||
startAngle: -180,
|
|
||||||
endAngle: 180,
|
|
||||||
background: [{
|
|
||||||
innerRadius: '50%',
|
|
||||||
outerRadius: '110%',
|
|
||||||
shape: 'arc'
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
yAxis: {
|
|
||||||
min: 0,
|
|
||||||
max: this.maxValue,
|
|
||||||
stops: this.colorStops.length ? this.colorStops : [
|
|
||||||
[0.1, '#55BF3B'],
|
|
||||||
[0.5, '#DDDF0D'],
|
|
||||||
[0.9, '#DF5353']
|
|
||||||
],
|
|
||||||
lineWidth: 0,
|
|
||||||
tickWidth: 0,
|
|
||||||
tickAmount: 2,
|
|
||||||
labels: {
|
|
||||||
enabled: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
series: [{
|
|
||||||
name: this.gaugeTitle,
|
|
||||||
data: [this.gaugeData],
|
|
||||||
tooltip: {
|
|
||||||
valueSuffix: this.satuanData
|
|
||||||
},
|
|
||||||
dataLabels: {
|
|
||||||
enabled: true,
|
|
||||||
y: -10,
|
|
||||||
x: 0,
|
|
||||||
borderColor: 'transparent',
|
|
||||||
borderWidth: 0,
|
|
||||||
style: {
|
|
||||||
fontSize: '15px',
|
|
||||||
fontFamily: 'Onest, sans-serif',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}] as Highcharts.SeriesSolidgaugeOptions[]
|
|
||||||
};
|
|
||||||
|
|
||||||
this.isLoading = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
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 { ApiService } from '../../../../cores/services/api.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';
|
||||||
|
|
@ -56,7 +56,7 @@ export class GraphComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||||
labelsHourly: string[] = [];
|
labelsHourly: string[] = [];
|
||||||
private resizeListener!: () => void;
|
private resizeListener!: () => void;
|
||||||
|
|
||||||
constructor(private sensorService: ApiService) {}
|
constructor(private sensorService: SensorService) {}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.selectedSensor = 'dht';
|
this.selectedSensor = 'dht';
|
||||||
|
|
@ -189,10 +189,10 @@ export class GraphComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||||
const month = String(today.getMonth() + 1).padStart(2, '0');
|
const month = String(today.getMonth() + 1).padStart(2, '0');
|
||||||
const day = String(today.getDate()).padStart(2, '0');
|
const day = String(today.getDate()).padStart(2, '0');
|
||||||
|
|
||||||
const startDate = `${year}-${month}-${day}`;
|
const startEnd= `${year}-${month}-${day}`;
|
||||||
const timeRange = 'HOURLY';
|
const timeRange = 'HOURLY';
|
||||||
|
|
||||||
this.sensorService.getSensorData(this.selectedSensor, this.selectedParameter, startDate, timeRange).subscribe(
|
this.sensorService.getSensorData(this.selectedSensor, this.selectedParameter, startEnd, timeRange).subscribe(
|
||||||
(response: ApiResponse) => {
|
(response: ApiResponse) => {
|
||||||
if (response.statusCode === 200) {
|
if (response.statusCode === 200) {
|
||||||
const { data, labels } = this.getDataFromResponse(response, this.selectedSensor, this.selectedParameter);
|
const { data, labels } = this.getDataFromResponse(response, this.selectedSensor, this.selectedParameter);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
<div class="container-fluid ps-md-0">
|
||||||
|
<div class="row g-0">
|
||||||
|
<div class="d-none d-md-flex col-md-4 col-lg-6 bg-image"></div>
|
||||||
|
<div class="col-md-8 col-lg-6">
|
||||||
|
<div class="login d-flex align-items-center py-5">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-9 col-lg-8 mx-auto">
|
||||||
|
<h3 class="login-heading mb-4">Create an Account!</h3>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
<label for="floatingPassword">Password</label>
|
||||||
|
</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>
|
||||||
|
<div class="text-center">
|
||||||
|
<a class="small forgot" routerLink= '/auth'>Log In</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
.login {
|
||||||
|
min-height: 100vh;
|
||||||
|
font-family: "Onest", sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-image {
|
||||||
|
background-image: url('../../../assets/images/auth.png');
|
||||||
|
background-size: cover;
|
||||||
|
background-position: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-heading {
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-login {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
letter-spacing: 0.05rem;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-btn{
|
||||||
|
background-color: #16423C;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.forgot{
|
||||||
|
color: #16423C;
|
||||||
|
}
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { GaugeChartComponent } from './gauge-chart.component';
|
import { RegisterComponent } from './register.component';
|
||||||
|
|
||||||
describe('GaugeChartComponent', () => {
|
describe('RegisterComponent', () => {
|
||||||
let component: GaugeChartComponent;
|
let component: RegisterComponent;
|
||||||
let fixture: ComponentFixture<GaugeChartComponent>;
|
let fixture: ComponentFixture<RegisterComponent>;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
imports: [GaugeChartComponent]
|
imports: [RegisterComponent]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
|
|
||||||
fixture = TestBed.createComponent(GaugeChartComponent);
|
fixture = TestBed.createComponent(RegisterComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
44
agrilink_vocpro/src/app/pages/register/register.component.ts
Normal file
44
agrilink_vocpro/src/app/pages/register/register.component.ts
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { Router, RouterModule } from '@angular/router';
|
||||||
|
import { AuthService } from '../../cores/services/auth.service';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { RegistrationData } from '../../cores/interface/auth';
|
||||||
|
import { ToastrService } from 'ngx-toastr';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-register',
|
||||||
|
standalone: true,
|
||||||
|
imports: [FormsModule, RouterModule, CommonModule],
|
||||||
|
templateUrl: './register.component.html',
|
||||||
|
styleUrls: ['./register.component.scss']
|
||||||
|
})
|
||||||
|
export class RegisterComponent {
|
||||||
|
username: string = '';
|
||||||
|
password: string = '';
|
||||||
|
email: string = '';
|
||||||
|
fullname: string = '';
|
||||||
|
|
||||||
|
constructor(private authService: AuthService, private router: Router, private toast: ToastrService) {}
|
||||||
|
|
||||||
|
onSubmit() {
|
||||||
|
const registrationData: RegistrationData = {
|
||||||
|
username: this.username,
|
||||||
|
pwd: this.password,
|
||||||
|
email: this.email,
|
||||||
|
google_id: '1',
|
||||||
|
fullname: this.fullname,
|
||||||
|
avatar: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
this.authService.register(registrationData).subscribe(
|
||||||
|
(response) => {
|
||||||
|
this.toast.success('Registration successful');
|
||||||
|
this.router.navigate(['/auth']);
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
this.toast.error(error.error.message);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user