From 6cc3c6525e9a8201ceba5fa2a3f3c8374c5f5f7f Mon Sep 17 00:00:00 2001 From: Jose Date: Tue, 11 Mar 2025 23:58:11 +0100 Subject: [PATCH] Big changes on API and Frontend --- .../miarma/contaminus/common/Constants.java | 13 ++-- .../miarma/contaminus/server/ApiVerticle.java | 22 ++++++ .../contaminus/server/MainVerticle.java | 2 +- frontend/package-lock.json | 70 ++++++++++++++++++- frontend/package.json | 3 +- frontend/public/config/settings.json | 3 + frontend/src/components/App.jsx | 8 ++- frontend/src/components/Dashboard.jsx | 31 -------- frontend/src/components/HistoryCharts.jsx | 2 +- frontend/src/components/PollutionMap.jsx | 22 +++--- frontend/src/components/SideMenu.jsx | 65 +++++++++++++++-- frontend/src/components/SummaryCards.jsx | 61 +++++++++------- frontend/src/css/Home.css | 67 ++++++++++++++++++ frontend/src/css/SideMenu.css | 42 +++++------ frontend/src/main.jsx | 5 +- frontend/src/pages/Dashboard.jsx | 35 ++++++++++ frontend/src/pages/Home.jsx | 66 +++++++++-------- frontend/src/util/date.js | 11 +++ frontend/vite.config.js | 3 + 19 files changed, 389 insertions(+), 142 deletions(-) delete mode 100644 frontend/src/components/Dashboard.jsx create mode 100644 frontend/src/css/Home.css create mode 100644 frontend/src/pages/Dashboard.jsx create mode 100644 frontend/src/util/date.js diff --git a/backend/src/main/java/net/miarma/contaminus/common/Constants.java b/backend/src/main/java/net/miarma/contaminus/common/Constants.java index 48e5eb1..706f3ab 100644 --- a/backend/src/main/java/net/miarma/contaminus/common/Constants.java +++ b/backend/src/main/java/net/miarma/contaminus/common/Constants.java @@ -29,7 +29,9 @@ public class Constants { public static final String PUT_DEVICE_BY_ID = API_PREFIX + "/devices/:deviceId"; public static final String GET_DEVICE_ACTUATORS = API_PREFIX + "/devices/:deviceId/actuators"; public static final String GET_DEVICE_LATEST_VALUES = API_PREFIX + "/devices/:deviceId/latest"; - + public static final String GET_DEVICE_POLLUTION_MAP = API_PREFIX + "/devices/:deviceId/pollution-map"; + public static final String GET_DEVICE_HISTORY = API_PREFIX + "/devices/:deviceId/history"; + public static final String GET_SENSORS = API_PREFIX + "/sensors"; public static final String GET_SENSOR_BY_ID = API_PREFIX + "/sensors/:sensorId"; public static final String GET_SENSOR_VALUES = API_PREFIX + "/sensors/:sensorId/values"; @@ -40,14 +42,7 @@ public class Constants { public static final String GET_ACTUATOR_BY_ID = API_PREFIX + "/actuators/:actuatorId"; public static final String POST_ACTUATORS = API_PREFIX + "/actuators"; public static final String PUT_ACTUATOR_BY_ID = API_PREFIX + "/actuators/:actuatorId"; - - public static final String GET_GPS_VALUES = API_PREFIX + "/gps-values"; - public static final String GET_GPS_VALUE_BY_ID = API_PREFIX + "/gps-values/:valueId"; - public static final String POST_GPS_VALUES = API_PREFIX + "/gps-values"; - - public static final String GET_AIR_VALUES = API_PREFIX + "/air-values"; - public static final String GET_AIR_VALUE_BY_ID = API_PREFIX + "/air-values/:valueId"; - public static final String POST_AIR_VALUES = API_PREFIX + "/air-values"; + private Constants() { throw new AssertionError("Utility class cannot be instantiated."); diff --git a/backend/src/main/java/net/miarma/contaminus/server/ApiVerticle.java b/backend/src/main/java/net/miarma/contaminus/server/ApiVerticle.java index f750f89..1a7b1a8 100644 --- a/backend/src/main/java/net/miarma/contaminus/server/ApiVerticle.java +++ b/backend/src/main/java/net/miarma/contaminus/server/ApiVerticle.java @@ -50,6 +50,8 @@ public class ApiVerticle extends AbstractVerticle { router.route(HttpMethod.GET, Constants.GET_DEVICE_LATEST_VALUES).handler(this::getDeviceLatestValuesHandler); router.route(HttpMethod.POST, Constants.POST_DEVICES).handler(this::postDeviceHandler); router.route(HttpMethod.PUT, Constants.PUT_DEVICE_BY_ID).handler(this::putDeviceByIdHandler); + router.route(HttpMethod.GET, Constants.GET_DEVICE_POLLUTION_MAP).handler(this::getPollutionMapHandler); + router.route(HttpMethod.GET, Constants.GET_DEVICE_HISTORY).handler(this::getDeviceHistoryHandler); // Sensor Routes router.route(HttpMethod.GET, Constants.GET_SENSORS).handler(this::getSensorsHandler); @@ -245,6 +247,26 @@ public class ApiVerticle extends AbstractVerticle { sendQuery(query, context); } + + private void getPollutionMapHandler(RoutingContext context) { + String deviceId = context.request().getParam("deviceId"); + String query = QueryBuilder + .select("*") + .from("v_pollution_map") + .where("deviceId = ?", deviceId) + .build(); + sendQuery(query, context); + } + + private void getDeviceHistoryHandler(RoutingContext context) { + String deviceId = context.request().getParam("deviceId"); + String query = QueryBuilder + .select("*") + .from("v_sensor_history_by_device") + .where("deviceId = ?", deviceId) + .build(); + sendQuery(query, context); + } // Sensor Handlers private void getSensorsHandler(RoutingContext context) { diff --git a/backend/src/main/java/net/miarma/contaminus/server/MainVerticle.java b/backend/src/main/java/net/miarma/contaminus/server/MainVerticle.java index e57eb7a..a351c67 100644 --- a/backend/src/main/java/net/miarma/contaminus/server/MainVerticle.java +++ b/backend/src/main/java/net/miarma/contaminus/server/MainVerticle.java @@ -14,7 +14,7 @@ public class MainVerticle extends AbstractVerticle { getVertx().deployVerticle(new DatabaseVerticle(), options); getVertx().deployVerticle(new ApiVerticle(), options); - getVertx().deployVerticle(new HttpServerVerticle()); + //getVertx().deployVerticle(new HttpServerVerticle()); } @Override diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 3ee2330..1ad79c1 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -19,7 +19,8 @@ "react": "^19.0.0", "react-chartjs-2": "^5.3.0", "react-dom": "^19.0.0", - "react-leaflet": "^5.0.0" + "react-leaflet": "^5.0.0", + "react-router-dom": "^7.3.0" }, "devDependencies": { "@eslint/js": "^9.19.0", @@ -1424,6 +1425,12 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", @@ -1917,6 +1924,15 @@ "dev": true, "license": "MIT" }, + "node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -3922,6 +3938,46 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.3.0.tgz", + "integrity": "sha512-466f2W7HIWaNXTKM5nHTqNxLrHTyXybm7R0eBlVSt0k/u55tTCDO194OIx/NrYD4TS5SXKTNekXfT37kMKUjgw==", + "license": "MIT", + "dependencies": { + "@types/cookie": "^0.6.0", + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0", + "turbo-stream": "2.4.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.3.0.tgz", + "integrity": "sha512-z7Q5FTiHGgQfEurX/FBinkOXhWREJIAB2RiU24lvcBa82PxUpwqvs/PAXb9lJyPjTs2jrl6UkLvCZVGJPeNuuQ==", + "license": "MIT", + "dependencies": { + "react-router": "7.3.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -4104,6 +4160,12 @@ "semver": "bin/semver.js" } }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "license": "MIT" + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -4399,6 +4461,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/turbo-stream": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz", + "integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==", + "license": "ISC" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index cb61e21..c437e3a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -22,7 +22,8 @@ "react": "^19.0.0", "react-chartjs-2": "^5.3.0", "react-dom": "^19.0.0", - "react-leaflet": "^5.0.0" + "react-leaflet": "^5.0.0", + "react-router-dom": "^7.3.0" }, "devDependencies": { "@eslint/js": "^9.19.0", diff --git a/frontend/public/config/settings.json b/frontend/public/config/settings.json index 60a064f..58754ca 100644 --- a/frontend/public/config/settings.json +++ b/frontend/public/config/settings.json @@ -16,6 +16,9 @@ "GET_DEVICES": "/devices", "GET_DEVICE_BY_ID": "/devices/{0}", "GET_DEVICE_SENSORS": "/devices/{0}/sensors", + "GET_DEVICE_LATEST_VALUES": "/devices/{0}/latest", + "GET_DEVICE_POLLUTION_MAP": "/devices/{0}/pollution-map", + "GET_DEVICE_HISTORY": "/devices/{0}/history", "POST_DEVICES": "/devices", "PUT_DEVICE_BY_ID": "/devices/{0}", "GET_SENSORS": "/sensors", diff --git a/frontend/src/components/App.jsx b/frontend/src/components/App.jsx index 00297e0..18ff5b5 100644 --- a/frontend/src/components/App.jsx +++ b/frontend/src/components/App.jsx @@ -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 */}
- + + } /> + } /> +
); diff --git a/frontend/src/components/Dashboard.jsx b/frontend/src/components/Dashboard.jsx deleted file mode 100644 index c9b38ac..0000000 --- a/frontend/src/components/Dashboard.jsx +++ /dev/null @@ -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 ( -
- {props.children} -
- ); -} - -Dashboard.propTypes = { - children: PropTypes.node -} - -export default Dashboard; \ No newline at end of file diff --git a/frontend/src/components/HistoryCharts.jsx b/frontend/src/components/HistoryCharts.jsx index b82c4a4..4ec0674 100644 --- a/frontend/src/components/HistoryCharts.jsx +++ b/frontend/src/components/HistoryCharts.jsx @@ -44,7 +44,7 @@ const HistoryCharts = () => { if (configError) return

Error al cargar configuración: {configError}

; if (!config) return

Configuración no disponible.

; - const BASE = config.appConfig.endpoints.baseUrl; + const BASE = config.appConfig.endpoints.BASE_URL; const ENDPOINT = config.appConfig.endpoints.sensors; const reqConfig = { diff --git a/frontend/src/components/PollutionMap.jsx b/frontend/src/components/PollutionMap.jsx index ffc961e..281ef32 100644 --- a/frontend/src/components/PollutionMap.jsx +++ b/frontend/src/components/PollutionMap.jsx @@ -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

Cargando configuración...

; if (configError) return

Error al cargar configuración: {configError}

; if (!config) return

Configuración no disponible.

; - 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; \ No newline at end of file diff --git a/frontend/src/components/SideMenu.jsx b/frontend/src/components/SideMenu.jsx index ca82867..dd3b16a 100644 --- a/frontend/src/components/SideMenu.jsx +++ b/frontend/src/components/SideMenu.jsx @@ -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

Cargando configuración...

; + if (configError) return

Error al cargar configuración: {configError}

; + if (!config) return

Configuración no disponible.

; + + const BASE = config.appConfig.endpoints.BASE_URL; + const ENDPOINT = config.appConfig.endpoints.GET_DEVICES; + + const reqConfig = { + baseUrl: `${BASE}/${ENDPOINT}`, + params: {} + } + return ( -
+ + + + ); +}; + +const SideMenuContent = ({ isOpen, onClose }) => { + const { data, dataLoading, dataError } = useData(); + const { theme } = useTheme(); + + if (dataLoading) return

Cargando datos...

; + if (dataError) return

Error al cargar datos: {dataError}

; + if (!data) return

Datos no disponibles.

; + + return ( +
- +
+ {data.map(device => { + return ( + + + {[]} + + + ); + })} +
); }; @@ -43,4 +91,9 @@ SideMenu.propTypes = { onClose: PropTypes.func.isRequired } +SideMenuContent.propTypes = { + isOpen: PropTypes.bool.isRequired, + onClose: PropTypes.func.isRequired +} + export default SideMenu; \ No newline at end of file diff --git a/frontend/src/components/SummaryCards.jsx b/frontend/src/components/SummaryCards.jsx index 2920f8f..eeb2488 100644 --- a/frontend/src/components/SummaryCards.jsx +++ b/frontend/src/components/SummaryCards.jsx @@ -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

Cargando configuración...

; if (configError) return

Error al cargar configuración: {configError}

; if (!config) return

Configuración no disponible.

; - 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 ( - + ); } const SummaryCardsContent = () => { - const { data } = useData(); + const { data, dataLoading, dataError } = useData(); + + if (dataLoading) return

Cargando datos...

; + if (dataError) return

Error al cargar datos: {dataError}

; + if (!data) return

Datos no disponibles.

; const CardsData = [ { id: 1, title: "Temperatura", content: "N/A", status: "Esperando datos...", titleIcon: }, { id: 2, title: "Humedad", content: "N/A", status: "Esperando datos...", titleIcon: }, - { id: 3, title: "Contaminación", content: "N/A", status: "Esperando datos...", titleIcon: }, - { id: 4, title: "Presión", content: "N/A", status: "Esperando datos...", titleIcon: } + { id: 3, title: "Nivel de CO", content: "N/A", status: "Esperando datos...", titleIcon: }, + { id: 4, title: "Actualizado a las", content: "N/A", status: "Esperando datos...", titleIcon: } ]; 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; \ No newline at end of file diff --git a/frontend/src/css/Home.css b/frontend/src/css/Home.css new file mode 100644 index 0000000..1a8fd92 --- /dev/null +++ b/frontend/src/css/Home.css @@ -0,0 +1,67 @@ +.home-container { + font-family: Arial, sans-serif; + text-align: center; + background-color: #f4f4f4; + padding: 20px; +} + +.hero-section { + background: #4CAF50; + color: white; + padding: 50px 20px; + border-radius: 8px; +} + +.hero-title { + font-size: 3em; + margin-bottom: 10px; +} + +.hero-description { + font-size: 1.2em; + margin-bottom: 20px; +} + +.cta-button { + background-color: #fff; + color: #4CAF50; + border: none; + padding: 15px 30px; + font-size: 1.1em; + cursor: pointer; + border-radius: 5px; + transition: background-color 0.3s ease; +} + +.cta-button:hover { + background-color: #388E3C; + color: white; +} + +.about-section { + margin-top: 50px; +} + +.features { + display: flex; + justify-content: space-around; + margin-top: 30px; +} + +.feature { + background-color: #ffffff; + padding: 20px; + border-radius: 8px; + width: 30%; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); +} + +.feature h3 { + font-size: 1.5em; + color: #333; +} + +.feature p { + font-size: 1.1em; + color: #666; +} diff --git a/frontend/src/css/SideMenu.css b/frontend/src/css/SideMenu.css index 28c7b39..6428305 100644 --- a/frontend/src/css/SideMenu.css +++ b/frontend/src/css/SideMenu.css @@ -4,14 +4,22 @@ left: -350px; width: 350px; height: 100%; - background-color: #333; - color: white; transition: left 0.3s ease; padding: 30px; box-shadow: 2px 0 5px rgba(0,0,0,0.5); z-index: 1000; } +.side-menu.light { + background-color: white; + color: black; +} + +.side-menu.dark { + background-color: #333; + color: white; +} + .side-menu.open { left: 0; } @@ -27,11 +35,18 @@ right: 20px; background: none; border: none; - color: white; font-size: 30px; cursor: pointer; } +.side-menu .close-btn.light { + color: black; +} + +.side-menu .close-btn.dark { + color: white; +} + .side-menu .close-btn .fa-times { width: 30px; height: 30px; @@ -39,25 +54,4 @@ .side-menu .close-btn:hover { color: var(--primary-color); -} - -.side-menu ul { - list-style: none; - padding: 0; -} - -.side-menu ul li { - margin: 20px 0; -} - -.side-menu ul li a:hover { - color: var(--primary-color); -} - -.side-menu ul li a { - color: white; - text-decoration: none; - font-size: 30px; - font-weight: 600; - /*text-transform: uppercase;*/ } \ No newline at end of file diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx index 7d4e405..452ec5e 100644 --- a/frontend/src/main.jsx +++ b/frontend/src/main.jsx @@ -1,5 +1,6 @@ import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' +import { BrowserRouter } from 'react-router-dom' import './css/index.css' import App from './components/App.jsx' @@ -29,7 +30,9 @@ createRoot(document.getElementById('root')).render( - + + + , diff --git a/frontend/src/pages/Dashboard.jsx b/frontend/src/pages/Dashboard.jsx new file mode 100644 index 0000000..9c56e17 --- /dev/null +++ b/frontend/src/pages/Dashboard.jsx @@ -0,0 +1,35 @@ +import PollutionMap from '../components/PollutionMap.jsx' +import HistoryCharts from '../components/HistoryCharts.jsx' +import SummaryCards from '../components/SummaryCards.jsx' + +import { useParams } from 'react-router-dom'; + +/** + * Dashboard.jsx + * + * Este archivo define el componente Dashboard, que es el panel de control de un device. + * + * Importaciones: + * - PollutionMap: Un componente que muestra un mapa de la contaminación. + * - HistoryCharts: Un componente que muestra gráficos históricos de la contaminación. + * - SummaryCards: Un componente que muestra tarjetas resumen con información relevante. + * + * Funcionalidad: + * - El componente Home utiliza una estructura de JSX para organizar y renderizar los componentes importados. + * - El componente Dashboard contiene los componentes SummaryCards, PollutionMap y HistoryCharts. + * + */ + +const Dashboard = () => { + const { deviceId } = useParams(); + + return ( +
+ + + +
+ ); +} + +export default Dashboard; diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx index 4efaad5..e2ba2cd 100644 --- a/frontend/src/pages/Home.jsx +++ b/frontend/src/pages/Home.jsx @@ -1,35 +1,41 @@ -import Dashboard from '../components/Dashboard.jsx' -import PollutionMap from '../components/PollutionMap.jsx' -import HistoryCharts from '../components/HistoryCharts.jsx' -import SummaryCards from '../components/SummaryCards.jsx' - -/** - * Home.jsx - * - * Este archivo define el componente Home, que es una página principal de la aplicación. - * - * Importaciones: - * - Dashboard: Un componente que actúa como contenedor para los componentes principales de la página. - * - PollutionMap: Un componente que muestra un mapa de la contaminación. - * - HistoryCharts: Un componente que muestra gráficos históricos de la contaminación. - * - SummaryCards: Un componente que muestra tarjetas resumen con información relevante. - * - * Funcionalidad: - * - El componente Home utiliza una estructura de JSX para organizar y renderizar los componentes importados. - * - El componente Dashboard contiene los componentes SummaryCards, PollutionMap y HistoryCharts. - * - */ +import '../css/Home.css'; const Home = () => { return ( - <> - - - - - - - ) +
+
+

ContaminUS

+

+ Proyecto universitario para monitorear la calidad del aire usando sensores IoT. +

+ +
+ +
+

Sobre el Proyecto

+

+ ContaminUS es una solución basada en tecnologías IoT para medir la calidad del aire en tiempo real. + Este proyecto busca crear una herramienta accesible para estudiantes, investigadores y comunidades + interesadas en el monitoreo ambiental. +

+
+
+

Medición en tiempo real

+

Monitorea la calidad del aire con sensores MQ-135 y DHT11, mostrando datos precisos y actualizados.

+
+
+

Aplicación web interactiva

+

Visualiza los datos de calidad del aire mediante mapas interactivos y gráficos.

+
+
+

Colaboración en la universidad

+

El proyecto está orientado a estudiantes que deseen aprender y colaborar con el análisis de datos ambientales.

+
+
+
+
+ ); } -export default Home; + +export default Home; \ No newline at end of file diff --git a/frontend/src/util/date.js b/frontend/src/util/date.js new file mode 100644 index 0000000..eec9bc7 --- /dev/null +++ b/frontend/src/util/date.js @@ -0,0 +1,11 @@ +const timestampToTime = (timestamp) => { + const date = new Date(timestamp); + return date.toLocaleTimeString(); +} + +const timestampToDate = (timestamp) => { + const date = new Date(timestamp); + return date.toLocaleDateString(); +} + +export { timestampToTime, timestampToDate }; \ No newline at end of file diff --git a/frontend/vite.config.js b/frontend/vite.config.js index 14bdcc6..7eb7a81 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -3,6 +3,9 @@ import react from '@vitejs/plugin-react' // https://vite.dev/config/ export default defineConfig({ + server: { + port: 8080, + }, plugins: [react()], build: { rollupOptions: {