Big changes on API and Frontend
This commit is contained in:
@@ -4,11 +4,13 @@ import 'bootstrap/dist/css/bootstrap.min.css'
|
||||
import 'bootstrap/dist/js/bootstrap.bundle.min.js'
|
||||
|
||||
import Home from '../pages/Home.jsx'
|
||||
import Dashboard from '../pages/Dashboard.jsx'
|
||||
import MenuButton from './MenuButton.jsx'
|
||||
import SideMenu from './SideMenu.jsx'
|
||||
import ThemeButton from '../components/ThemeButton.jsx'
|
||||
import Header from '../components/Header.jsx'
|
||||
|
||||
import { Routes, Route } from 'react-router-dom'
|
||||
import { useState } from 'react'
|
||||
|
||||
/**
|
||||
@@ -51,13 +53,15 @@ const App = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Planeo añadir un React Router */}
|
||||
<MenuButton onClick={toggleSideMenu} />
|
||||
<SideMenu isOpen={isSideMenuOpen} onClose={toggleSideMenu} />
|
||||
<ThemeButton />
|
||||
<div className={isSideMenuOpen ? 'blur m-0 p-0' : 'm-0 p-0'} onClick={closeSideMenu}>
|
||||
<Header title='Contamin' subtitle='Midiendo la calidad del aire y las calles en Sevilla 🌿🚛' />
|
||||
<Home />
|
||||
<Routes>
|
||||
<Route path="/" element={<Home />} />
|
||||
<Route path="/dashboard/:deviceId" element={<Dashboard />} />
|
||||
</Routes>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* Dashboard.jsx
|
||||
*
|
||||
* Este archivo define el componente Dashboard, que actúa como contenedor para los componentes principales de la página.
|
||||
*
|
||||
* Importaciones:
|
||||
* - PropTypes: Librería para la validación de tipos de propiedades en componentes de React.
|
||||
*
|
||||
* Funcionalidad:
|
||||
* - Dashboard: Componente que renderiza un contenedor principal (`main`) con los componentes hijos pasados como `props.children`.
|
||||
*
|
||||
* PropTypes:
|
||||
* - Dashboard espera una propiedad `children` que es un nodo de React.
|
||||
*
|
||||
*/
|
||||
|
||||
const Dashboard = (props) => {
|
||||
return (
|
||||
<main className='container justify-content-center'>
|
||||
{props.children}
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
Dashboard.propTypes = {
|
||||
children: PropTypes.node
|
||||
}
|
||||
|
||||
export default Dashboard;
|
||||
@@ -44,7 +44,7 @@ const HistoryCharts = () => {
|
||||
if (configError) return <p>Error al cargar configuración: {configError}</p>;
|
||||
if (!config) return <p>Configuración no disponible.</p>;
|
||||
|
||||
const BASE = config.appConfig.endpoints.baseUrl;
|
||||
const BASE = config.appConfig.endpoints.BASE_URL;
|
||||
const ENDPOINT = config.appConfig.endpoints.sensors;
|
||||
|
||||
const reqConfig = {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { MapContainer, TileLayer, Circle, Popup } from 'react-leaflet';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { useConfig } from '../contexts/ConfigContext.jsx';
|
||||
|
||||
@@ -57,18 +58,19 @@ const PollutionCircles = ({ data }) => {
|
||||
});
|
||||
};
|
||||
|
||||
const PollutionMap = () => {
|
||||
const PollutionMap = ({ deviceId }) => {
|
||||
const { config, configLoading, configError } = useConfig();
|
||||
|
||||
if (configLoading) return <p>Cargando configuración...</p>;
|
||||
if (configError) return <p>Error al cargar configuración: {configError}</p>;
|
||||
if (!config) return <p>Configuración no disponible.</p>;
|
||||
|
||||
const BASE = config.appConfig.endpoints.baseUrl;
|
||||
const ENDPOINT = config.appConfig.endpoints.sensors;
|
||||
const BASE = config.appConfig.endpoints.BASE_URL;
|
||||
const ENDPOINT = config.appConfig.endpoints.GET_DEVICE_POLLUTION_MAP;
|
||||
let endp = ENDPOINT.replace('{0}', deviceId);
|
||||
|
||||
const reqConfig = {
|
||||
baseUrl: `${BASE}/${ENDPOINT}`,
|
||||
baseUrl: `${BASE}/${endp}`,
|
||||
params: {}
|
||||
}
|
||||
|
||||
@@ -93,10 +95,10 @@ const PollutionMapContent = () => {
|
||||
|
||||
const SEVILLA = config?.userConfig.city;
|
||||
|
||||
const pollutionData = data.map((sensor) => ({
|
||||
lat: sensor.lat,
|
||||
lng: sensor.lon,
|
||||
level: sensor.value
|
||||
const pollutionData = data.map((measure) => ({
|
||||
lat: measure.lat,
|
||||
lng: measure.lon,
|
||||
level: measure.carbonMonoxide
|
||||
}));
|
||||
|
||||
return (
|
||||
@@ -118,4 +120,8 @@ const mapStyles = {
|
||||
borderRadius: '20px'
|
||||
};
|
||||
|
||||
PollutionMap.propTypes = {
|
||||
deviceId: PropTypes.number.isRequired
|
||||
};
|
||||
|
||||
export default PollutionMap;
|
||||
@@ -3,6 +3,14 @@ import PropTypes from 'prop-types';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faTimes } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
import { DataProvider } from '../contexts/DataContext';
|
||||
import { useData } from '../contexts/DataContext';
|
||||
|
||||
import { useConfig } from '../contexts/ConfigContext';
|
||||
import { useTheme } from "../contexts/ThemeContext";
|
||||
|
||||
import Card from './Card';
|
||||
|
||||
/** ⚠️ EN PRUEBAS ⚠️
|
||||
* SideMenu.jsx
|
||||
*
|
||||
@@ -24,16 +32,56 @@ import { faTimes } from '@fortawesome/free-solid-svg-icons';
|
||||
* ⚠️ EN PRUEBAS ⚠️ **/
|
||||
|
||||
const SideMenu = ({ isOpen, onClose }) => {
|
||||
const { config, configLoading, configError } = useConfig();
|
||||
|
||||
if (configLoading) return <p>Cargando configuración...</p>;
|
||||
if (configError) return <p>Error al cargar configuración: {configError}</p>;
|
||||
if (!config) return <p>Configuración no disponible.</p>;
|
||||
|
||||
const BASE = config.appConfig.endpoints.BASE_URL;
|
||||
const ENDPOINT = config.appConfig.endpoints.GET_DEVICES;
|
||||
|
||||
const reqConfig = {
|
||||
baseUrl: `${BASE}/${ENDPOINT}`,
|
||||
params: {}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`side-menu ${isOpen ? 'open' : ''}`}>
|
||||
<DataProvider config={reqConfig}>
|
||||
<SideMenuContent isOpen={isOpen} onClose={onClose} />
|
||||
</DataProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const SideMenuContent = ({ isOpen, onClose }) => {
|
||||
const { data, dataLoading, dataError } = useData();
|
||||
const { theme } = useTheme();
|
||||
|
||||
if (dataLoading) return <p>Cargando datos...</p>;
|
||||
if (dataError) return <p>Error al cargar datos: {dataError}</p>;
|
||||
if (!data) return <p>Datos no disponibles.</p>;
|
||||
|
||||
return (
|
||||
<div className={`side-menu ${isOpen ? 'open' : ''} ${theme}`}>
|
||||
<button className="close-btn" onClick={onClose}>
|
||||
<FontAwesomeIcon icon={faTimes} />
|
||||
</button>
|
||||
<ul>
|
||||
<li><a href="#inicio">ɪɴɪᴄɪᴏ</a></li>
|
||||
<li><a href="#mapa">ᴍᴀᴘᴀ</a></li>
|
||||
<li><a href="#historico">ʜɪsᴛᴏʀɪᴄᴏ</a></li>
|
||||
</ul>
|
||||
<div className="d-flex flex-column gap-3 mt-5">
|
||||
{data.map(device => {
|
||||
return (
|
||||
<a href={`/dashboard/${device.deviceId}`} key={device.deviceId} style={{ textDecoration: 'none' }}>
|
||||
<Card
|
||||
title={device.deviceName}
|
||||
status={`ID: ${device.deviceId}`}
|
||||
styleMode={"override"}
|
||||
className={"col-12"}
|
||||
>
|
||||
{[]}
|
||||
</Card>
|
||||
</a>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -43,4 +91,9 @@ SideMenu.propTypes = {
|
||||
onClose: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
SideMenuContent.propTypes = {
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
onClose: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
export default SideMenu;
|
||||
@@ -1,12 +1,14 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import CardContainer from './CardContainer';
|
||||
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faCloud, faClock, faTemperature0, faWater } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
import { DataProvider } from '../contexts/DataContext';
|
||||
import { useData } from '../contexts/DataContext';
|
||||
|
||||
import { useConfig } from '../contexts/ConfigContext';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faCloud, faGauge, faTemperature0, faWater } from '@fortawesome/free-solid-svg-icons';
|
||||
import { timestampToTime } from '../util/date.js';
|
||||
|
||||
/**
|
||||
* SummaryCards.jsx
|
||||
@@ -31,54 +33,59 @@ import { faCloud, faGauge, faTemperature0, faWater } from '@fortawesome/free-sol
|
||||
*
|
||||
*/
|
||||
|
||||
const SummaryCards = () => {
|
||||
const SummaryCards = ({ deviceId }) => {
|
||||
const { config, configLoading, configError } = useConfig();
|
||||
|
||||
if (configLoading) return <p>Cargando configuración...</p>;
|
||||
if (configError) return <p>Error al cargar configuración: {configError}</p>;
|
||||
if (!config) return <p>Configuración no disponible.</p>;
|
||||
|
||||
const BASE = config.appConfig.endpoints.baseUrl;
|
||||
const ENDPOINT = config.appConfig.endpoints.sensors;
|
||||
const BASE = config.appConfig.endpoints.BASE_URL;
|
||||
const ENDPOINT = config.appConfig.endpoints.GET_DEVICE_LATEST_VALUES;
|
||||
const endp = ENDPOINT.replace('{0}', deviceId);
|
||||
|
||||
const reqConfig = {
|
||||
baseUrl: `${BASE}/${ENDPOINT}`,
|
||||
params: {
|
||||
_sort: 'timestamp',
|
||||
_order: 'desc',
|
||||
_limit: 1
|
||||
}
|
||||
baseUrl: `${BASE}/${endp}`,
|
||||
params: {}
|
||||
}
|
||||
|
||||
return (
|
||||
<DataProvider config={reqConfig}>
|
||||
<SummaryCardsContent />
|
||||
<SummaryCardsContent deviceId={deviceId} />
|
||||
</DataProvider>
|
||||
);
|
||||
}
|
||||
|
||||
const SummaryCardsContent = () => {
|
||||
const { data } = useData();
|
||||
const { data, dataLoading, dataError } = useData();
|
||||
|
||||
if (dataLoading) return <p>Cargando datos...</p>;
|
||||
if (dataError) return <p>Error al cargar datos: {dataError}</p>;
|
||||
if (!data) return <p>Datos no disponibles.</p>;
|
||||
|
||||
const CardsData = [
|
||||
{ id: 1, title: "Temperatura", content: "N/A", status: "Esperando datos...", titleIcon: <FontAwesomeIcon icon={faTemperature0} /> },
|
||||
{ id: 2, title: "Humedad", content: "N/A", status: "Esperando datos...", titleIcon: <FontAwesomeIcon icon={faWater} /> },
|
||||
{ id: 3, title: "Contaminación", content: "N/A", status: "Esperando datos...", titleIcon: <FontAwesomeIcon icon={faCloud} /> },
|
||||
{ id: 4, title: "Presión", content: "N/A", status: "Esperando datos...", titleIcon: <FontAwesomeIcon icon={faGauge} /> }
|
||||
{ id: 3, title: "Nivel de CO", content: "N/A", status: "Esperando datos...", titleIcon: <FontAwesomeIcon icon={faCloud} /> },
|
||||
{ id: 4, title: "Actualizado a las", content: "N/A", status: "Esperando datos...", titleIcon: <FontAwesomeIcon icon={faClock} /> }
|
||||
];
|
||||
|
||||
if (data) {
|
||||
data.forEach((sensor) => {
|
||||
if (sensor.sensor_type === "MQ-135") {
|
||||
CardsData[2].content = `${sensor.value} µg/m³`;
|
||||
CardsData[2].status = sensor.value > 100 ? "Alta contaminación 😷" : "Aire moderado 🌤️";
|
||||
} else if (sensor.sensor_type === "DHT-11") {
|
||||
CardsData[1].content = `${sensor.humidity}%`;
|
||||
CardsData[1].status = sensor.humidity > 70 ? "Humedad alta 🌧️" : "Nivel normal 🌤️";
|
||||
CardsData[0].content = `${sensor.temperature}°C`;
|
||||
CardsData[0].status = sensor.temperature > 30 ? "Calor intenso ☀️" : "Clima agradable 🌤️";
|
||||
}
|
||||
});
|
||||
let coData = data[1];
|
||||
let tempData = data[2];
|
||||
|
||||
let lastTime = timestampToTime(coData.airValuesTimestamp);
|
||||
let lastDate = new Date(coData.airValuesTimestamp);
|
||||
|
||||
CardsData[0].content = tempData.temperature + "°C";
|
||||
CardsData[0].status = "Temperatura actual";
|
||||
CardsData[1].content = tempData.humidity + "%";
|
||||
CardsData[1].status = "Humedad actual";
|
||||
CardsData[2].content = coData.carbonMonoxide + " ppm";
|
||||
CardsData[2].status = "Nivel de CO actual";
|
||||
CardsData[3].content = lastTime.slice(0, 5);
|
||||
CardsData[3].status = "Día " + lastDate.toLocaleDateString();
|
||||
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -87,7 +94,7 @@ const SummaryCardsContent = () => {
|
||||
}
|
||||
|
||||
SummaryCards.propTypes = {
|
||||
data: PropTypes.array
|
||||
deviceId: PropTypes.number.isRequired
|
||||
};
|
||||
|
||||
export default SummaryCards;
|
||||
Reference in New Issue
Block a user