Compare commits
10 Commits
703c522927
...
a4ab48ab20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a4ab48ab20 | ||
|
|
98ca03b55e | ||
|
|
944a9fa5cb | ||
|
|
4377bb493d | ||
|
|
7c44188d6e | ||
|
|
a5fbb018c4 | ||
|
|
d6e7c7696c | ||
|
|
ae43ca880c | ||
|
|
cfbc25fd66 | ||
|
|
da65fdf819 |
13
README.md
13
README.md
|
|
@ -20,7 +20,8 @@ The data that will display on the graph is 24 hour trend data and there will be
|
||||||
|
|
||||||
## Technology Stack
|
## Technology Stack
|
||||||
|
|
||||||
- Angular 18 (Front-end Web Framework)
|
- Node JS version 18.20.4
|
||||||
|
- Angular 18.2.4 (Front-end Web Framework)
|
||||||
- REST API (Backend communication)
|
- REST API (Backend communication)
|
||||||
- GitLab for version control
|
- GitLab for version control
|
||||||
|
|
||||||
|
|
@ -45,21 +46,25 @@ git clone https://gitlab.com/profile-image/kedaireka/smartfarming/frontend-smart
|
||||||
```
|
```
|
||||||
cd agrilink_vocpro
|
cd agrilink_vocpro
|
||||||
```
|
```
|
||||||
3. Run the project:
|
3. Install Dependencies:
|
||||||
|
```
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
4. Run the project:
|
||||||
```
|
```
|
||||||
ng serve
|
ng serve
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Project Structure (Angular)
|
## Project Structure (Angular)
|
||||||
|
|
||||||
- `src/app/`: Contains the Angular application source code.
|
- `src/app/`: Contains the Angular application source code.
|
||||||
- `cores/`: Contains all constants, interfaces, and services needed for the project.
|
- `cores/`: Contains all constants, interfaces, and services needed for the project.
|
||||||
|
- `guards/`: Contains guards for
|
||||||
|
- `interceptors`: Contains handling error
|
||||||
- `interfaces/`: Contains TypeScript interfaces for data models and types used throughout the application.
|
- `interfaces/`: Contains TypeScript interfaces for data models and types used throughout the application.
|
||||||
- `services/`: Contains service files for managing API requests.
|
- `services/`: Contains service files for managing API requests.
|
||||||
- `pages/`: Contains Angular components and views representing different pages of the application.
|
- `pages/`: Contains Angular components and views representing different pages of the application.
|
||||||
|
|
||||||
- `angular.json`: Angular project configuration file.
|
- `angular.json`: Angular project configuration file.
|
||||||
- `package.json`: Contains project dependencies and scripts for building and running the project.
|
- `package.json`: Contains project dependencies and scripts for building and running the project.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,9 @@ export const httpErrorInterceptor: HttpInterceptorFn = (req, next) => {
|
||||||
case 404:
|
case 404:
|
||||||
errorMessage = 'Resource not found: The requested resource does not exist.';
|
errorMessage = 'Resource not found: The requested resource does not exist.';
|
||||||
break;
|
break;
|
||||||
|
case 422:
|
||||||
|
errorMessage = 'Enter a different username or email';
|
||||||
|
break;
|
||||||
case 500:
|
case 500:
|
||||||
errorMessage = 'Server error: Please try again later.';
|
errorMessage = 'Server error: Please try again later.';
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
<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-3 col-lg-6 bg-image"></div>
|
||||||
<div class="col-md-8 col-lg-6">
|
<div class="col-md-9 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>
|
||||||
|
|
||||||
<form (ngSubmit)="onSubmit()">
|
<form (ngSubmit)="onSubmit()">
|
||||||
<div class="form-floating mb-3">
|
<div class="form-floating mb-3">
|
||||||
<input type="email"
|
<input type="email"
|
||||||
|
|
@ -55,13 +54,17 @@
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<a class="small forgot" routerLink= '/register'>Register here</a>
|
<a class="small forgot" routerLink='/register'>Register here</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="text-center-copyright">
|
||||||
|
<div>©2024 Agrilink</div>
|
||||||
|
<div>Powered by <strong>Politeknik Negeri Malang</strong></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,17 @@
|
||||||
.login {
|
.login {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
font-family: "Onest", sans-serif;
|
font-family: "Onest", sans-serif;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-image {
|
.bg-image {
|
||||||
background-image: url('../../../assets/images/auth.png');
|
background-image: url('../../../assets/images/bg-auth.jpg');
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
background-position: left;
|
background-position: right;
|
||||||
|
height: 100vh;
|
||||||
|
border-radius: 0px 10px 10px 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-heading {
|
.login-heading {
|
||||||
|
|
@ -56,3 +61,17 @@
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-center {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-center-copyright {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: #636363;
|
||||||
|
padding: 15px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container{
|
||||||
|
padding-top: 50px;
|
||||||
|
}
|
||||||
|
|
@ -20,7 +20,7 @@
|
||||||
|
|
||||||
<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]="avatar || 'https://github.com/mdo.png'" alt="User Avatar" width="30" height="30" class="rounded-circle"> <!-- Use avatar or a default image -->
|
<img [src]="avatar || 'https://cdn.pixabay.com/photo/2015/10/05/22/37/blank-profile-picture-973460_1280.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">{{ fullName }}</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">
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,12 @@ export class ActualgraphComponent implements OnInit {
|
||||||
y: {
|
y: {
|
||||||
stacked: true,
|
stacked: true,
|
||||||
beginAtZero: false,
|
beginAtZero: false,
|
||||||
|
ticks: {
|
||||||
|
callback: (value: string | number, index: number, values: any) => {
|
||||||
|
return `${value} mg/L`;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,23 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="sensor-wrapper">
|
<div class="sensor-wrapper">
|
||||||
<div class="title">Sensor DHT</div>
|
<div class="title">Sensor DHT</div>
|
||||||
|
<div class="button-param">
|
||||||
|
<button
|
||||||
|
[ngClass]="{'active': activeButton === 'vicitemperature'}"
|
||||||
|
(click)="filterData('vicitemperature')">
|
||||||
|
Temperatur Udara
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
[ngClass]="{'active': activeButton === 'vicihumidity'}"
|
||||||
|
(click)="filterData('vicihumidity')">
|
||||||
|
Kelembaban Udara
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
[ngClass]="{'active': activeButton === 'viciluminosity'}"
|
||||||
|
(click)="filterData('viciluminosity')">
|
||||||
|
Intensitas Cahaya
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<ng-container *ngIf="isLoadingDHT; else dhtData">
|
<ng-container *ngIf="isLoadingDHT; else dhtData">
|
||||||
<div class="d-flex align-items-center" style="padding: 50px 0px 50px 0px">
|
<div class="d-flex align-items-center" style="padding: 50px 0px 50px 0px">
|
||||||
<i class="fa fa-spinner fa-spin"></i>
|
<i class="fa fa-spinner fa-spin"></i>
|
||||||
|
|
@ -31,7 +48,7 @@
|
||||||
|
|
||||||
<div class="sensor-wrapper">
|
<div class="sensor-wrapper">
|
||||||
<div class="title-with-dropdown">
|
<div class="title-with-dropdown">
|
||||||
<div class="title">Sensor NPK 1</div>
|
<div class="title">Sensor NPK1</div>
|
||||||
<select class="form-select" style="margin-top: 10px" [(ngModel)]="selectedNPK1" (change)="onResize()">
|
<select class="form-select" style="margin-top: 10px" [(ngModel)]="selectedNPK1" (change)="onResize()">
|
||||||
<option value="npk">NPK</option>
|
<option value="npk">NPK</option>
|
||||||
<option value="temperature">Temperatur Tanah</option>
|
<option value="temperature">Temperatur Tanah</option>
|
||||||
|
|
@ -53,7 +70,7 @@
|
||||||
|
|
||||||
<div class="sensor-wrapper">
|
<div class="sensor-wrapper">
|
||||||
<div class="title-with-dropdown">
|
<div class="title-with-dropdown">
|
||||||
<div class="title">Sensor NPK 2</div>
|
<div class="title">Sensor NPK2</div>
|
||||||
<select class="form-select" style="margin-top: 10px" [(ngModel)]="selectedNPK2" (change)=" onResize()">
|
<select class="form-select" style="margin-top: 10px" [(ngModel)]="selectedNPK2" (change)=" onResize()">
|
||||||
<option value="npk">NPK</option>
|
<option value="npk">NPK</option>
|
||||||
<option value="temperature">Temperatur Tanah</option>
|
<option value="temperature">Temperatur Tanah</option>
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@
|
||||||
.title {
|
.title {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
margin: 18px 0px 0px 0px;
|
margin: 18px 0px 18px 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
|
|
@ -90,6 +90,18 @@
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button-param {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 80%;
|
||||||
|
max-width: 300px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 344px) {
|
@media (max-width: 344px) {
|
||||||
|
|
@ -109,6 +121,17 @@
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button-param {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 80%;
|
||||||
|
max-width: 300px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading {
|
.loading {
|
||||||
|
|
@ -128,4 +151,28 @@
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button-param {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
margin: 10px 0 10px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
font-family: 'Onest', sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
padding: 5px 10px;
|
||||||
|
background-color: #E5E5E5;
|
||||||
|
color: #16423C;
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.active {
|
||||||
|
background-color: #16423C;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { Component, OnInit, ElementRef, ViewChild, AfterViewInit, OnDestroy, OnChanges, Input, SimpleChanges, ChangeDetectorRef } from '@angular/core';
|
import { Component, OnInit, ElementRef, ViewChild, AfterViewInit, OnDestroy, OnChanges, Input, SimpleChanges, ChangeDetectorRef } from '@angular/core';
|
||||||
import { Chart, registerables } from 'chart.js';
|
import { Chart, registerables, Tooltip } from 'chart.js';
|
||||||
import { SensorService } from '../../../../cores/services/sensor.service';
|
import { SensorService } from '../../../../cores/services/sensor.service';
|
||||||
import { ApiResponse, ParameterSensor } from '../../../../cores/interface/sensor-data';
|
import { ApiResponse, ParameterSensor } from '../../../../cores/interface/sensor-data';
|
||||||
import { CommonModule, formatDate } from '@angular/common';
|
import { CommonModule, formatDate } from '@angular/common';
|
||||||
|
|
@ -16,10 +16,10 @@ const parameterColors: { [key: string]: string } = {
|
||||||
vicitemperature: '#8F5A62',
|
vicitemperature: '#8F5A62',
|
||||||
vicihumidity: '#16423C',
|
vicihumidity: '#16423C',
|
||||||
viciluminosity: '#DF9B55',
|
viciluminosity: '#DF9B55',
|
||||||
soiltemperature: '#FF6347',
|
soiltemperature: '#8c2d1c',
|
||||||
soilhumidity: '#0389b5',
|
soilhumidity: '#3d7b8f',
|
||||||
soilconductivity: '#A52A2A',
|
soilconductivity: '#f2b8b8',
|
||||||
soilph: '#228B22',
|
soilph: '#459645',
|
||||||
soilnitrogen: '#fece48',
|
soilnitrogen: '#fece48',
|
||||||
soilphosphorus: '#B80F0A',
|
soilphosphorus: '#B80F0A',
|
||||||
soilpotassium: '#4c1f74',
|
soilpotassium: '#4c1f74',
|
||||||
|
|
@ -52,6 +52,7 @@ export class GraphComponent implements OnInit, AfterViewInit, OnDestroy, OnChang
|
||||||
isNoDataNPK2: boolean = false;
|
isNoDataNPK2: boolean = false;
|
||||||
|
|
||||||
allNoData: boolean = false;
|
allNoData: boolean = false;
|
||||||
|
activeButton: string = 'vicitemperature';
|
||||||
|
|
||||||
sensorParameters: { [key: string]: string[] } = {
|
sensorParameters: { [key: string]: string[] } = {
|
||||||
dht: ['vicitemperature', 'vicihumidity', 'viciluminosity'],
|
dht: ['vicitemperature', 'vicihumidity', 'viciluminosity'],
|
||||||
|
|
@ -99,6 +100,9 @@ export class GraphComponent implements OnInit, AfterViewInit, OnDestroy, OnChang
|
||||||
}
|
}
|
||||||
|
|
||||||
ngAfterViewInit(): void {
|
ngAfterViewInit(): void {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.updateCharts();
|
||||||
|
}, 0);
|
||||||
this.onResize();
|
this.onResize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -162,7 +166,7 @@ export class GraphComponent implements OnInit, AfterViewInit, OnDestroy, OnChang
|
||||||
const dEnd = endDate ? this.formatDate(endDate) : this.getDate();
|
const dEnd = endDate ? this.formatDate(endDate) : this.getDate();
|
||||||
|
|
||||||
if (timeRange === 'HOURLY') {
|
if (timeRange === 'HOURLY') {
|
||||||
this.sensorService.getSensorDataHourly('dht', 'npk', hStart, hEnd, timeRange).subscribe({
|
this.sensorService.getSensorDataHourly('dht', '', hStart, hEnd, timeRange).subscribe({
|
||||||
next: (response) => {
|
next: (response) => {
|
||||||
this.isLoadingDHT = false;
|
this.isLoadingDHT = false;
|
||||||
if (response.statusCode === 200 && response.data.dht?.length > 0) {
|
if (response.statusCode === 200 && response.data.dht?.length > 0) {
|
||||||
|
|
@ -280,7 +284,6 @@ export class GraphComponent implements OnInit, AfterViewInit, OnDestroy, OnChang
|
||||||
|
|
||||||
updateCharts(): void {
|
updateCharts(): void {
|
||||||
const interval = this.selectedInterval;
|
const interval = this.selectedInterval;
|
||||||
|
|
||||||
Object.keys(this.charts).forEach(key => {
|
Object.keys(this.charts).forEach(key => {
|
||||||
if (this.charts[key]) {
|
if (this.charts[key]) {
|
||||||
this.charts[key]?.destroy();
|
this.charts[key]?.destroy();
|
||||||
|
|
@ -298,6 +301,23 @@ export class GraphComponent implements OnInit, AfterViewInit, OnDestroy, OnChang
|
||||||
this.fetchNPK1Data(interval);
|
this.fetchNPK1Data(interval);
|
||||||
this.fetchNPK2Data(interval);
|
this.fetchNPK2Data(interval);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.charts['dht']) {
|
||||||
|
this.filterData(this.activeButton);
|
||||||
|
console.log(this.activeButton);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filterData(parameter: string): void {
|
||||||
|
this.activeButton = parameter;
|
||||||
|
const chart = this.charts['dht'];
|
||||||
|
|
||||||
|
if (chart) {
|
||||||
|
chart.data.datasets.forEach((dataset: any) => {
|
||||||
|
dataset.hidden = dataset.label !== this.parameterDisplayNames[parameter];
|
||||||
|
});
|
||||||
|
chart.update();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -327,6 +347,7 @@ export class GraphComponent implements OnInit, AfterViewInit, OnDestroy, OnChang
|
||||||
|
|
||||||
const pointRadius = data.length === 1 ? 5 : 0;
|
const pointRadius = data.length === 1 ? 5 : 0;
|
||||||
const pointHoverRadius = data.length === 1 ? 7 : 0;
|
const pointHoverRadius = data.length === 1 ? 7 : 0;
|
||||||
|
const isHidden = parameter !== this.activeButton;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
label: displayName,
|
label: displayName,
|
||||||
|
|
@ -338,6 +359,7 @@ export class GraphComponent implements OnInit, AfterViewInit, OnDestroy, OnChang
|
||||||
tension: 0.5,
|
tension: 0.5,
|
||||||
pointRadius,
|
pointRadius,
|
||||||
pointHoverRadius,
|
pointHoverRadius,
|
||||||
|
hidden : isHidden
|
||||||
};
|
};
|
||||||
}).filter(dataset => dataset !== null);
|
}).filter(dataset => dataset !== null);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -345,8 +367,6 @@ export class GraphComponent implements OnInit, AfterViewInit, OnDestroy, OnChang
|
||||||
.filter(parameter => {
|
.filter(parameter => {
|
||||||
if (selectedOption === 'npk') {
|
if (selectedOption === 'npk') {
|
||||||
return ['soilphosphorus', 'soilnitrogen', 'soilpotassium'].includes(parameter);
|
return ['soilphosphorus', 'soilnitrogen', 'soilpotassium'].includes(parameter);
|
||||||
} else if (selectedOption === 'others') {
|
|
||||||
return !['soilphosphorus', 'soilnitrogen', 'soilpotassium'].includes(parameter);
|
|
||||||
} else if (selectedOption === 'temperature') {
|
} else if (selectedOption === 'temperature') {
|
||||||
return ['soiltemperature'].includes(parameter);
|
return ['soiltemperature'].includes(parameter);
|
||||||
} else if (selectedOption === 'humidity') {
|
} else if (selectedOption === 'humidity') {
|
||||||
|
|
@ -372,7 +392,6 @@ export class GraphComponent implements OnInit, AfterViewInit, OnDestroy, OnChang
|
||||||
|
|
||||||
const pointRadius = data.length === 1 ? 5 : 0;
|
const pointRadius = data.length === 1 ? 5 : 0;
|
||||||
const pointHoverRadius = data.length === 1 ? 7 : 0;
|
const pointHoverRadius = data.length === 1 ? 7 : 0;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
label: displayName,
|
label: displayName,
|
||||||
data,
|
data,
|
||||||
|
|
@ -397,7 +416,7 @@ export class GraphComponent implements OnInit, AfterViewInit, OnDestroy, OnChang
|
||||||
this.charts[sensor]?.destroy();
|
this.charts[sensor]?.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(sensor === 'dht'){
|
||||||
this.charts[sensor] = new Chart(ctx, {
|
this.charts[sensor] = new Chart(ctx, {
|
||||||
type: 'line',
|
type: 'line',
|
||||||
data: {
|
data: {
|
||||||
|
|
@ -421,7 +440,9 @@ export class GraphComponent implements OnInit, AfterViewInit, OnDestroy, OnChang
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
legend: { display: true }
|
legend: {
|
||||||
|
display: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
scales: {
|
scales: {
|
||||||
x: {
|
x: {
|
||||||
|
|
@ -434,10 +455,87 @@ export class GraphComponent implements OnInit, AfterViewInit, OnDestroy, OnChang
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
y: { grid: { display: false }, beginAtZero: true }
|
y: {
|
||||||
|
grid: { display: false },
|
||||||
|
beginAtZero: true,
|
||||||
|
ticks: {
|
||||||
|
callback: (value: string | number, index: number, values: any) => {
|
||||||
|
if (this.activeButton === 'vicitemperature') {
|
||||||
|
return `${value} °C`;
|
||||||
|
} else if (this.activeButton === 'vicihumidity') {
|
||||||
|
return `${value} %`;
|
||||||
|
} else if (this.activeButton === 'viciluminosity') {
|
||||||
|
return `${value} lux`;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}else if(sensor === 'npk1' || sensor === 'npk2'){
|
||||||
|
this.charts[sensor] = new Chart(ctx, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: this.getLabels(response, sensor),
|
||||||
|
datasets,
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
aspectRatio: 2,
|
||||||
|
plugins: {
|
||||||
|
tooltip: {
|
||||||
|
enabled: true,
|
||||||
|
mode: 'nearest',
|
||||||
|
intersect: false,
|
||||||
|
callbacks: {
|
||||||
|
label: (tooltipItem: any) => {
|
||||||
|
const paramLabel = tooltipItem.dataset.label;
|
||||||
|
const value = tooltipItem.formattedValue;
|
||||||
|
return `${paramLabel}: ${value}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
display: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
x: {
|
||||||
|
grid: { display: false },
|
||||||
|
beginAtZero: true,
|
||||||
|
ticks: {
|
||||||
|
callback: (value: string | number, index: number, values: any) => {
|
||||||
|
const labels = this.getLabels(response, sensor);
|
||||||
|
return labels[index] || value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
grid: { display: false },
|
||||||
|
beginAtZero: true,
|
||||||
|
ticks:{
|
||||||
|
callback: (value: string | number, index: number, values: any) => {
|
||||||
|
if(selectedOption === 'npk'){
|
||||||
|
return `${value} mg/L`;
|
||||||
|
} else if(selectedOption === 'temperature'){
|
||||||
|
return `${value} °C`;
|
||||||
|
} else if(selectedOption === 'humidity'){
|
||||||
|
return `${value} %`;
|
||||||
|
} else if(selectedOption === 'conductivity'){
|
||||||
|
return `${value} μS/cm`;
|
||||||
|
} else if(selectedOption === 'ph'){
|
||||||
|
return `${value} pH`;
|
||||||
|
} return value;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -488,5 +586,5 @@ export class GraphComponent implements OnInit, AfterViewInit, OnDestroy, OnChang
|
||||||
const days = ['Sun', 'Mon', 'Tues', 'Wed', 'Thrus', 'Fri', 'Sat'];
|
const days = ['Sun', 'Mon', 'Tues', 'Wed', 'Thrus', 'Fri', 'Sat'];
|
||||||
return days[date.getDay()];
|
return days[date.getDay()];
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,10 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="text-center-copyright">
|
||||||
|
<div>©2024 Agrilink</div>
|
||||||
|
<div>Powered by <strong>Politeknik Negeri Malang</strong></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,17 @@
|
||||||
.login {
|
.login {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
font-family: "Onest", sans-serif;
|
font-family: "Onest", sans-serif;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-image {
|
.bg-image {
|
||||||
background-image: url('../../../assets/images/auth.png');
|
background-image: url('../../../assets/images/bg-auth.jpg');
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
background-position: left;
|
background-position: right;
|
||||||
|
height: 100vh;
|
||||||
|
border-radius: 0px 10px 10px 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-heading {
|
.login-heading {
|
||||||
|
|
@ -46,3 +51,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-center-copyright {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: #636363;
|
||||||
|
padding: 15px 0;
|
||||||
|
}
|
||||||
|
|
@ -40,6 +40,17 @@ export class RegisterComponent implements OnInit {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
this.toast.error('Please fill in all fields.');
|
this.toast.error('Please fill in all fields.');
|
||||||
return;
|
return;
|
||||||
|
} else if(this.password.length<8){
|
||||||
|
this.loading= false;
|
||||||
|
this.toast.error('Password must be at least 8 characters')
|
||||||
|
} else if(!this.email.includes('@')){
|
||||||
|
this.loading=false;
|
||||||
|
this.toast.error('Invalid Email');
|
||||||
|
} else {
|
||||||
|
(error: any) => {
|
||||||
|
this.loading = false;
|
||||||
|
this.toast.error(error.error.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const registrationData: RegistrationData = {
|
const registrationData: RegistrationData = {
|
||||||
|
|
|
||||||
BIN
agrilink_vocpro/src/assets/images/bg-auth.jpg
Normal file
BIN
agrilink_vocpro/src/assets/images/bg-auth.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 724 KiB |
BIN
agrilink_vocpro/src/assets/images/profile.png
Normal file
BIN
agrilink_vocpro/src/assets/images/profile.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
Loading…
Reference in New Issue
Block a user