diff --git a/backend/pom.xml b/backend/pom.xml
index 4d71278..848550d 100644
--- a/backend/pom.xml
+++ b/backend/pom.xml
@@ -20,6 +20,13 @@
4.5.13
+
+
+ io.vertx
+ vertx-web-client
+ 4.5.13
+
+
io.vertx
diff --git a/backend/src/main/java/net/miarma/contaminus/common/entities/Actuator.java b/backend/src/main/java/net/miarma/contaminus/common/entities/Actuator.java
new file mode 100644
index 0000000..05bc49f
--- /dev/null
+++ b/backend/src/main/java/net/miarma/contaminus/common/entities/Actuator.java
@@ -0,0 +1,5 @@
+package net.miarma.contaminus.common.entities;
+
+public class Actuator {
+
+}
diff --git a/backend/src/main/java/net/miarma/contaminus/common/entities/COValue.java b/backend/src/main/java/net/miarma/contaminus/common/entities/COValue.java
new file mode 100644
index 0000000..7e1c521
--- /dev/null
+++ b/backend/src/main/java/net/miarma/contaminus/common/entities/COValue.java
@@ -0,0 +1,5 @@
+package net.miarma.contaminus.common.entities;
+
+public class COValue {
+
+}
diff --git a/backend/src/main/java/net/miarma/contaminus/common/entities/Device.java b/backend/src/main/java/net/miarma/contaminus/common/entities/Device.java
new file mode 100644
index 0000000..ed3fc0a
--- /dev/null
+++ b/backend/src/main/java/net/miarma/contaminus/common/entities/Device.java
@@ -0,0 +1,5 @@
+package net.miarma.contaminus.common.entities;
+
+public class Device {
+
+}
diff --git a/backend/src/main/java/net/miarma/contaminus/common/entities/GpsValue.java b/backend/src/main/java/net/miarma/contaminus/common/entities/GpsValue.java
new file mode 100644
index 0000000..ea46779
--- /dev/null
+++ b/backend/src/main/java/net/miarma/contaminus/common/entities/GpsValue.java
@@ -0,0 +1,5 @@
+package net.miarma.contaminus.common.entities;
+
+public class GpsValue {
+
+}
diff --git a/backend/src/main/java/net/miarma/contaminus/common/entities/Group.java b/backend/src/main/java/net/miarma/contaminus/common/entities/Group.java
new file mode 100644
index 0000000..9b96951
--- /dev/null
+++ b/backend/src/main/java/net/miarma/contaminus/common/entities/Group.java
@@ -0,0 +1,5 @@
+package net.miarma.contaminus.common.entities;
+
+public class Group {
+
+}
diff --git a/backend/src/main/java/net/miarma/contaminus/common/entities/Sensor.java b/backend/src/main/java/net/miarma/contaminus/common/entities/Sensor.java
new file mode 100644
index 0000000..1799081
--- /dev/null
+++ b/backend/src/main/java/net/miarma/contaminus/common/entities/Sensor.java
@@ -0,0 +1,5 @@
+package net.miarma.contaminus.common.entities;
+
+public class Sensor {
+
+}
diff --git a/backend/src/main/java/net/miarma/contaminus/common/entities/WeatherValue.java b/backend/src/main/java/net/miarma/contaminus/common/entities/WeatherValue.java
new file mode 100644
index 0000000..8286952
--- /dev/null
+++ b/backend/src/main/java/net/miarma/contaminus/common/entities/WeatherValue.java
@@ -0,0 +1,5 @@
+package net.miarma.contaminus.common.entities;
+
+public class WeatherValue {
+
+}
diff --git a/backend/src/main/java/net/miarma/contaminus/server/DatabaseVerticle.java b/backend/src/main/java/net/miarma/contaminus/server/DatabaseVerticle.java
index b5c6e89..bf56561 100644
--- a/backend/src/main/java/net/miarma/contaminus/server/DatabaseVerticle.java
+++ b/backend/src/main/java/net/miarma/contaminus/server/DatabaseVerticle.java
@@ -25,7 +25,7 @@ public class DatabaseVerticle extends AbstractVerticle {
private EventBus eventBus;
private Gson gson = new GsonBuilder()
.registerTypeAdapter(LocalDateTime.class, new LocalDateTimeSerializer())
- .create();;
+ .create();
@SuppressWarnings("unused")
diff --git a/backend/src/main/java/net/miarma/contaminus/util/RestClientUtil.java b/backend/src/main/java/net/miarma/contaminus/util/RestClientUtil.java
new file mode 100644
index 0000000..c376323
--- /dev/null
+++ b/backend/src/main/java/net/miarma/contaminus/util/RestClientUtil.java
@@ -0,0 +1,139 @@
+package net.miarma.contaminus.util;
+
+import java.util.Map;
+
+import com.google.gson.Gson;
+
+import io.vertx.core.Promise;
+import io.vertx.core.buffer.Buffer;
+import io.vertx.core.json.JsonObject;
+import io.vertx.ext.web.client.HttpRequest;
+import io.vertx.ext.web.client.WebClient;
+
+public class RestClientUtil {
+
+ public WebClient client;
+ private Gson gson;
+
+ public RestClientUtil(WebClient client) {
+ gson = new Gson();
+ this.client = client;
+ }
+
+ /**
+ * Get request utility
+ *
+ * @param Type of result enveloped in JSON response
+ * @param port Port
+ * @param host Host address
+ * @param resource URI where resource is provided
+ * @param classType Type of result enveloped in JSON response
+ * @param promise Promise to be executed on call finish
+ */
+ public void getRequest(Integer port, String host, String resource, Class classType, Promise promise) {
+ client.getAbs(host + ":" + port + "/" + resource).send(elem -> {
+ if (elem.succeeded()) {
+ promise.complete(gson.fromJson(elem.result().bodyAsString(), classType));
+ } else {
+ promise.fail(elem.cause());
+ }
+ });
+
+ }
+
+ /**
+ * Get request utility
+ *
+ * @param Type of result enveloped in JSON response
+ * @param port Port
+ * @param host Host address
+ * @param resource URI where resource is provided
+ * @param classType Type of result enveloped in JSON response
+ * @param promise Promise to be executed on call finish
+ * @param params Map with key-value entries for call parameters
+ */
+ public void getRequestWithParams(Integer port, String host, String resource, Class classType,
+ Promise promise, Map params) {
+ HttpRequest httpRequest = client.getAbs(host + ":" + port + "/" + resource);
+
+ params.forEach((key, value) -> {
+ httpRequest.addQueryParam(key, value);
+ });
+
+ httpRequest.send(elem -> {
+ if (elem.succeeded()) {
+ promise.complete(gson.fromJson(elem.result().bodyAsString(), classType));
+ } else {
+ promise.fail(elem.cause());
+ }
+ });
+
+ }
+
+ /**
+ * Post request utility
+ *
+ * @param Type of body enveloped in JSON request
+ * @param Type of result enveloped in JSON response
+ * @param port Port
+ * @param host Host address
+ * @param resource URI where resource is provided
+ * @param classType Type of result enveloped in JSON response
+ * @param promise Promise to be executed on call finish
+ */
+ public void postRequest(Integer port, String host, String resource, Object body, Class classType,
+ Promise promise) {
+ JsonObject jsonBody = new JsonObject(gson.toJson(body));
+ client.postAbs(host + ":" + port + "/" + resource).sendJsonObject(jsonBody, elem -> {
+ if (elem.succeeded()) {
+ Gson gson = new Gson();
+ promise.complete(gson.fromJson(elem.result().bodyAsString(), classType));
+ } else {
+ promise.fail(elem.cause());
+ }
+ });
+ }
+
+ /**
+ * Put request utility
+ *
+ * @param Type of body enveloped in JSON request
+ * @param Type of result enveloped in JSON response
+ * @param port Port
+ * @param host Host address
+ * @param resource URI where resource is provided
+ * @param classType Type of result enveloped in JSON response
+ * @param promise Promise to be executed on call finish
+ */
+ public void putRequest(Integer port, String host, String resource, Object body, Class classType,
+ Promise promise) {
+ JsonObject jsonBody = new JsonObject(gson.toJson(body));
+ client.putAbs(host + ":" + port + "/" + resource).sendJsonObject(jsonBody, elem -> {
+ if (elem.succeeded()) {
+ Gson gson = new Gson();
+ promise.complete(gson.fromJson(elem.result().bodyAsString(), classType));
+ } else {
+ promise.fail(elem.cause());
+ }
+ });
+ }
+
+ /**
+ * Delete request utility
+ *
+ * @param port Port
+ * @param host Host address
+ * @param resource URI where resource is provided
+ * @param promise Promise to be executed on call finish
+ */
+ public void deleteRequest(Integer port, String host, String resource, Promise promise) {
+ client.deleteAbs(host + ":" + port + "/" + resource).send(elem -> {
+ if (elem.succeeded()) {
+ promise.complete(elem.result().bodyAsString());
+ } else {
+ promise.fail(elem.cause());
+ }
+ });
+
+ }
+}
diff --git a/backend/src/main/resources/webroot/assets/index-ByqS16T9.js b/backend/src/main/resources/webroot/assets/index-75wHSipM.js
similarity index 98%
rename from backend/src/main/resources/webroot/assets/index-ByqS16T9.js
rename to backend/src/main/resources/webroot/assets/index-75wHSipM.js
index f6cc704..bf49d51 100644
--- a/backend/src/main/resources/webroot/assets/index-ByqS16T9.js
+++ b/backend/src/main/resources/webroot/assets/index-75wHSipM.js
@@ -609,4 +609,4 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
* Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
* Copyright 2024 Fonticons, Inc.
- */const v2={prefix:"fas",iconName:"cloud",icon:[640,512,[9729],"f0c2","M0 336c0 79.5 64.5 144 144 144l368 0c70.7 0 128-57.3 128-128c0-61.9-44-113.6-102.4-125.4c4.1-10.7 6.4-22.4 6.4-34.6c0-53-43-96-96-96c-19.7 0-38.1 6-53.3 16.2C367 64.2 315.3 32 256 32C167.6 32 96 103.6 96 192c0 2.7 .1 5.4 .2 8.1C40.2 219.8 0 273.2 0 336z"]},y2={prefix:"fas",iconName:"bars",icon:[448,512,["navicon"],"f0c9","M0 96C0 78.3 14.3 64 32 64l384 0c17.7 0 32 14.3 32 32s-14.3 32-32 32L32 128C14.3 128 0 113.7 0 96zM0 256c0-17.7 14.3-32 32-32l384 0c17.7 0 32 14.3 32 32s-14.3 32-32 32L32 288c-17.7 0-32-14.3-32-32zM448 416c0 17.7-14.3 32-32 32L32 448c-17.7 0-32-14.3-32-32s14.3-32 32-32l384 0c17.7 0 32 14.3 32 32z"]},b2={prefix:"fas",iconName:"temperature-empty",icon:[320,512,["temperature-0","thermometer-0","thermometer-empty"],"f2cb","M112 112c0-26.5 21.5-48 48-48s48 21.5 48 48l0 164.5c0 17.3 7.1 31.9 15.3 42.5C233.8 332.6 240 349.5 240 368c0 44.2-35.8 80-80 80s-80-35.8-80-80c0-18.5 6.2-35.4 16.7-48.9c8.2-10.6 15.3-25.2 15.3-42.5L112 112zM160 0C98.1 0 48 50.2 48 112l0 164.4c0 .1-.1 .3-.2 .6c-.2 .6-.8 1.6-1.7 2.8C27.2 304.2 16 334.8 16 368c0 79.5 64.5 144 144 144s144-64.5 144-144c0-33.2-11.2-63.8-30.1-88.1c-.9-1.2-1.5-2.2-1.7-2.8c-.1-.3-.2-.5-.2-.6L272 112C272 50.2 221.9 0 160 0zm0 416a48 48 0 1 0 0-96 48 48 0 1 0 0 96z"]},_2=b2,S2={prefix:"fas",iconName:"water",icon:[576,512,[],"f773","M269.5 69.9c11.1-7.9 25.9-7.9 37 0C329 85.4 356.5 96 384 96c26.9 0 55.4-10.8 77.4-26.1c0 0 0 0 0 0c11.9-8.5 28.1-7.8 39.2 1.7c14.4 11.9 32.5 21 50.6 25.2c17.2 4 27.9 21.2 23.9 38.4s-21.2 27.9-38.4 23.9c-24.5-5.7-44.9-16.5-58.2-25C449.5 149.7 417 160 384 160c-31.9 0-60.6-9.9-80.4-18.9c-5.8-2.7-11.1-5.3-15.6-7.7c-4.5 2.4-9.7 5.1-15.6 7.7c-19.8 9-48.5 18.9-80.4 18.9c-33 0-65.5-10.3-94.5-25.8c-13.4 8.4-33.7 19.3-58.2 25c-17.2 4-34.4-6.7-38.4-23.9s6.7-34.4 23.9-38.4C42.8 92.6 61 83.5 75.3 71.6c11.1-9.5 27.3-10.1 39.2-1.7c0 0 0 0 0 0C136.7 85.2 165.1 96 192 96c27.5 0 55-10.6 77.5-26.1zm37 288C329 373.4 356.5 384 384 384c26.9 0 55.4-10.8 77.4-26.1c0 0 0 0 0 0c11.9-8.5 28.1-7.8 39.2 1.7c14.4 11.9 32.5 21 50.6 25.2c17.2 4 27.9 21.2 23.9 38.4s-21.2 27.9-38.4 23.9c-24.5-5.7-44.9-16.5-58.2-25C449.5 437.7 417 448 384 448c-31.9 0-60.6-9.9-80.4-18.9c-5.8-2.7-11.1-5.3-15.6-7.7c-4.5 2.4-9.7 5.1-15.6 7.7c-19.8 9-48.5 18.9-80.4 18.9c-33 0-65.5-10.3-94.5-25.8c-13.4 8.4-33.7 19.3-58.2 25c-17.2 4-34.4-6.7-38.4-23.9s6.7-34.4 23.9-38.4c18.1-4.2 36.2-13.3 50.6-25.2c11.1-9.4 27.3-10.1 39.2-1.7c0 0 0 0 0 0C136.7 373.2 165.1 384 192 384c27.5 0 55-10.6 77.5-26.1c11.1-7.9 25.9-7.9 37 0zm0-144C329 229.4 356.5 240 384 240c26.9 0 55.4-10.8 77.4-26.1c0 0 0 0 0 0c11.9-8.5 28.1-7.8 39.2 1.7c14.4 11.9 32.5 21 50.6 25.2c17.2 4 27.9 21.2 23.9 38.4s-21.2 27.9-38.4 23.9c-24.5-5.7-44.9-16.5-58.2-25C449.5 293.7 417 304 384 304c-31.9 0-60.6-9.9-80.4-18.9c-5.8-2.7-11.1-5.3-15.6-7.7c-4.5 2.4-9.7 5.1-15.6 7.7c-19.8 9-48.5 18.9-80.4 18.9c-33 0-65.5-10.3-94.5-25.8c-13.4 8.4-33.7 19.3-58.2 25c-17.2 4-34.4-6.7-38.4-23.9s6.7-34.4 23.9-38.4c18.1-4.2 36.2-13.3 50.6-25.2c11.1-9.5 27.3-10.1 39.2-1.7c0 0 0 0 0 0C136.7 229.2 165.1 240 192 240c27.5 0 55-10.6 77.5-26.1c11.1-7.9 25.9-7.9 37 0z"]},E2={prefix:"fas",iconName:"clock",icon:[512,512,[128339,"clock-four"],"f017","M256 0a256 256 0 1 1 0 512A256 256 0 1 1 256 0zM232 120l0 136c0 8 4 15.5 10.7 20l96 64c11 7.4 25.9 4.4 33.3-6.7s4.4-25.9-6.7-33.3L280 243.2 280 120c0-13.3-10.7-24-24-24s-24 10.7-24 24z"]},A2={prefix:"fas",iconName:"xmark",icon:[384,512,[128473,10005,10006,10060,215,"close","multiply","remove","times"],"f00d","M342.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L192 210.7 86.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L146.7 256 41.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L192 301.3 297.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L237.3 256 342.6 150.6z"]},x2=A2,T2=l=>new Date(l).toLocaleTimeString(),Ov=({deviceId:l})=>{const{config:u,configLoading:o,configError:c}=Fr();if(o)return G.jsx("p",{children:"Cargando configuración..."});if(c)return G.jsxs("p",{children:["Error al cargar configuración: ",c]});if(!u)return G.jsx("p",{children:"Configuración no disponible."});const f=u.appConfig.endpoints.BASE_URL,y=u.appConfig.endpoints.GET_DEVICE_LATEST_VALUES.replace("{0}",l),b={baseUrl:`${f}/${y}`,params:{}};return G.jsx(Mu,{config:b,children:G.jsx(O2,{deviceId:l})})},O2=()=>{const{data:l,dataLoading:u,dataError:o}=_c();if(u)return G.jsx("p",{children:"Cargando datos..."});if(o)return G.jsxs("p",{children:["Error al cargar datos: ",o]});if(!l)return G.jsx("p",{children:"Datos no disponibles."});const c=[{id:1,title:"Temperatura",content:"N/A",status:"Esperando datos...",titleIcon:G.jsx(Kl,{icon:_2})},{id:2,title:"Humedad",content:"N/A",status:"Esperando datos...",titleIcon:G.jsx(Kl,{icon:S2})},{id:3,title:"Nivel de CO",content:"N/A",status:"Esperando datos...",titleIcon:G.jsx(Kl,{icon:v2})},{id:4,title:"Actualizado a las",content:"N/A",status:"Esperando datos...",titleIcon:G.jsx(Kl,{icon:E2})}];if(l){let f=l[1],m=l[2],y=T2(f.airValuesTimestamp),b=new Date(f.airValuesTimestamp);c[0].content=m.temperature+"°C",c[0].status="Temperatura actual",c[1].content=m.humidity+"%",c[1].status="Humedad actual",c[2].content=f.carbonMonoxide+" ppm",c[2].status="Nivel de CO actual",c[3].content=y.slice(0,5),c[3].status="Día "+b.toLocaleDateString()}return G.jsx(oh,{cards:c})};Ov.propTypes={deviceId:et.number.isRequired};const C2=()=>{const{deviceId:l}=O0();return G.jsxs("main",{className:"container justify-content-center",children:[G.jsx(Ov,{deviceId:l}),G.jsx(Hg,{deviceId:l}),G.jsx(n1,{deviceId:l})]})};function Cv({onClick:l}){return G.jsx("button",{className:"menuBtn",onClick:l,children:G.jsx(Kl,{icon:y2})})}Cv.propTypes={onClick:et.func.isRequired};const wv=({isOpen:l,onClose:u})=>{const{config:o,configLoading:c,configError:f}=Fr();if(c)return G.jsx("p",{children:"Cargando configuración..."});if(f)return G.jsxs("p",{children:["Error al cargar configuración: ",f]});if(!o)return G.jsx("p",{children:"Configuración no disponible."});const m=o.appConfig.endpoints.BASE_URL,y=o.appConfig.endpoints.GET_DEVICES,b={baseUrl:`${m}/${y}`,params:{}};return G.jsx(Mu,{config:b,children:G.jsx(Dv,{isOpen:l,onClose:u})})},Dv=({isOpen:l,onClose:u})=>{const{data:o,dataLoading:c,dataError:f}=_c(),{theme:m}=Nu();return c?G.jsx("p",{children:"Cargando datos..."}):f?G.jsxs("p",{children:["Error al cargar datos: ",f]}):o?G.jsxs("div",{className:`side-menu ${l?"open":""} ${m}`,children:[G.jsx("button",{className:"close-btn",onClick:u,children:G.jsx(Kl,{icon:x2})}),G.jsx("div",{className:"d-flex flex-column gap-3 mt-5",children:o.map(y=>G.jsx("a",{href:`/dashboard/${y.deviceId}`,style:{textDecoration:"none"},children:G.jsx(Sc,{title:y.deviceName,status:`ID: ${y.deviceId}`,styleMode:"override",className:"col-12",children:[]})},y.deviceId))})]}):G.jsx("p",{children:"Datos no disponibles."})};wv.propTypes={isOpen:et.bool.isRequired,onClose:et.func.isRequired};Dv.propTypes={isOpen:et.bool.isRequired,onClose:et.func.isRequired};function w2(){const{theme:l,toggleTheme:u}=Nu();return G.jsx("button",{className:"theme-toggle",onClick:u,children:l==="dark"?"☀️":"🌙"})}const Mv=l=>{const{theme:u}=Nu();return G.jsxs("header",{className:`justify-content-center text-center mb-4 ${u}`,children:[G.jsx("h1",{children:l.title}),G.jsx("p",{className:"subtitle",children:l.subtitle})]})};Mv.propTypes={title:et.string.isRequired,subtitle:et.string};const D2=()=>{const[l,u]=w.useState(!1),o=()=>{u(!l)},c=()=>{u(!1)};return G.jsxs(G.Fragment,{children:[G.jsx(Cv,{onClick:o}),G.jsx(wv,{isOpen:l,onClose:o}),G.jsx(w2,{}),G.jsxs("div",{className:l?"blur m-0 p-0":"m-0 p-0",onClick:c,children:[G.jsx(Mv,{title:"Contamin",subtitle:"Midiendo la calidad del aire y las calles en Sevilla 🌿🚛"}),G.jsxs(Y0,{children:[G.jsx(Nd,{path:"/",element:G.jsx(Ab,{})}),G.jsx(Nd,{path:"/dashboard/:deviceId",element:G.jsx(C2,{})})]})]})]})};Fy.createRoot(document.getElementById("root")).render(G.jsx(w.StrictMode,{children:G.jsx(Xg,{children:G.jsx(jg,{children:G.jsx(cb,{children:G.jsx(D2,{})})})})}));
+ */const v2={prefix:"fas",iconName:"cloud",icon:[640,512,[9729],"f0c2","M0 336c0 79.5 64.5 144 144 144l368 0c70.7 0 128-57.3 128-128c0-61.9-44-113.6-102.4-125.4c4.1-10.7 6.4-22.4 6.4-34.6c0-53-43-96-96-96c-19.7 0-38.1 6-53.3 16.2C367 64.2 315.3 32 256 32C167.6 32 96 103.6 96 192c0 2.7 .1 5.4 .2 8.1C40.2 219.8 0 273.2 0 336z"]},y2={prefix:"fas",iconName:"bars",icon:[448,512,["navicon"],"f0c9","M0 96C0 78.3 14.3 64 32 64l384 0c17.7 0 32 14.3 32 32s-14.3 32-32 32L32 128C14.3 128 0 113.7 0 96zM0 256c0-17.7 14.3-32 32-32l384 0c17.7 0 32 14.3 32 32s-14.3 32-32 32L32 288c-17.7 0-32-14.3-32-32zM448 416c0 17.7-14.3 32-32 32L32 448c-17.7 0-32-14.3-32-32s14.3-32 32-32l384 0c17.7 0 32 14.3 32 32z"]},b2={prefix:"fas",iconName:"temperature-empty",icon:[320,512,["temperature-0","thermometer-0","thermometer-empty"],"f2cb","M112 112c0-26.5 21.5-48 48-48s48 21.5 48 48l0 164.5c0 17.3 7.1 31.9 15.3 42.5C233.8 332.6 240 349.5 240 368c0 44.2-35.8 80-80 80s-80-35.8-80-80c0-18.5 6.2-35.4 16.7-48.9c8.2-10.6 15.3-25.2 15.3-42.5L112 112zM160 0C98.1 0 48 50.2 48 112l0 164.4c0 .1-.1 .3-.2 .6c-.2 .6-.8 1.6-1.7 2.8C27.2 304.2 16 334.8 16 368c0 79.5 64.5 144 144 144s144-64.5 144-144c0-33.2-11.2-63.8-30.1-88.1c-.9-1.2-1.5-2.2-1.7-2.8c-.1-.3-.2-.5-.2-.6L272 112C272 50.2 221.9 0 160 0zm0 416a48 48 0 1 0 0-96 48 48 0 1 0 0 96z"]},_2=b2,S2={prefix:"fas",iconName:"water",icon:[576,512,[],"f773","M269.5 69.9c11.1-7.9 25.9-7.9 37 0C329 85.4 356.5 96 384 96c26.9 0 55.4-10.8 77.4-26.1c0 0 0 0 0 0c11.9-8.5 28.1-7.8 39.2 1.7c14.4 11.9 32.5 21 50.6 25.2c17.2 4 27.9 21.2 23.9 38.4s-21.2 27.9-38.4 23.9c-24.5-5.7-44.9-16.5-58.2-25C449.5 149.7 417 160 384 160c-31.9 0-60.6-9.9-80.4-18.9c-5.8-2.7-11.1-5.3-15.6-7.7c-4.5 2.4-9.7 5.1-15.6 7.7c-19.8 9-48.5 18.9-80.4 18.9c-33 0-65.5-10.3-94.5-25.8c-13.4 8.4-33.7 19.3-58.2 25c-17.2 4-34.4-6.7-38.4-23.9s6.7-34.4 23.9-38.4C42.8 92.6 61 83.5 75.3 71.6c11.1-9.5 27.3-10.1 39.2-1.7c0 0 0 0 0 0C136.7 85.2 165.1 96 192 96c27.5 0 55-10.6 77.5-26.1zm37 288C329 373.4 356.5 384 384 384c26.9 0 55.4-10.8 77.4-26.1c0 0 0 0 0 0c11.9-8.5 28.1-7.8 39.2 1.7c14.4 11.9 32.5 21 50.6 25.2c17.2 4 27.9 21.2 23.9 38.4s-21.2 27.9-38.4 23.9c-24.5-5.7-44.9-16.5-58.2-25C449.5 437.7 417 448 384 448c-31.9 0-60.6-9.9-80.4-18.9c-5.8-2.7-11.1-5.3-15.6-7.7c-4.5 2.4-9.7 5.1-15.6 7.7c-19.8 9-48.5 18.9-80.4 18.9c-33 0-65.5-10.3-94.5-25.8c-13.4 8.4-33.7 19.3-58.2 25c-17.2 4-34.4-6.7-38.4-23.9s6.7-34.4 23.9-38.4c18.1-4.2 36.2-13.3 50.6-25.2c11.1-9.4 27.3-10.1 39.2-1.7c0 0 0 0 0 0C136.7 373.2 165.1 384 192 384c27.5 0 55-10.6 77.5-26.1c11.1-7.9 25.9-7.9 37 0zm0-144C329 229.4 356.5 240 384 240c26.9 0 55.4-10.8 77.4-26.1c0 0 0 0 0 0c11.9-8.5 28.1-7.8 39.2 1.7c14.4 11.9 32.5 21 50.6 25.2c17.2 4 27.9 21.2 23.9 38.4s-21.2 27.9-38.4 23.9c-24.5-5.7-44.9-16.5-58.2-25C449.5 293.7 417 304 384 304c-31.9 0-60.6-9.9-80.4-18.9c-5.8-2.7-11.1-5.3-15.6-7.7c-4.5 2.4-9.7 5.1-15.6 7.7c-19.8 9-48.5 18.9-80.4 18.9c-33 0-65.5-10.3-94.5-25.8c-13.4 8.4-33.7 19.3-58.2 25c-17.2 4-34.4-6.7-38.4-23.9s6.7-34.4 23.9-38.4c18.1-4.2 36.2-13.3 50.6-25.2c11.1-9.5 27.3-10.1 39.2-1.7c0 0 0 0 0 0C136.7 229.2 165.1 240 192 240c27.5 0 55-10.6 77.5-26.1c11.1-7.9 25.9-7.9 37 0z"]},E2={prefix:"fas",iconName:"clock",icon:[512,512,[128339,"clock-four"],"f017","M256 0a256 256 0 1 1 0 512A256 256 0 1 1 256 0zM232 120l0 136c0 8 4 15.5 10.7 20l96 64c11 7.4 25.9 4.4 33.3-6.7s4.4-25.9-6.7-33.3L280 243.2 280 120c0-13.3-10.7-24-24-24s-24 10.7-24 24z"]},A2={prefix:"fas",iconName:"xmark",icon:[384,512,[128473,10005,10006,10060,215,"close","multiply","remove","times"],"f00d","M342.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L192 210.7 86.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L146.7 256 41.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L192 301.3 297.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L237.3 256 342.6 150.6z"]},x2=A2,T2=l=>new Date(l).toLocaleTimeString(),O2=l=>l.length===8?l.slice(0,5):`0${l.slice(0,3)}`,Ov=({deviceId:l})=>{const{config:u,configLoading:o,configError:c}=Fr();if(o)return G.jsx("p",{children:"Cargando configuración..."});if(c)return G.jsxs("p",{children:["Error al cargar configuración: ",c]});if(!u)return G.jsx("p",{children:"Configuración no disponible."});const f=u.appConfig.endpoints.BASE_URL,y=u.appConfig.endpoints.GET_DEVICE_LATEST_VALUES.replace("{0}",l),b={baseUrl:`${f}/${y}`,params:{}};return G.jsx(Mu,{config:b,children:G.jsx(C2,{deviceId:l})})},C2=()=>{const{data:l,dataLoading:u,dataError:o}=_c();if(u)return G.jsx("p",{children:"Cargando datos..."});if(o)return G.jsxs("p",{children:["Error al cargar datos: ",o]});if(!l)return G.jsx("p",{children:"Datos no disponibles."});const c=[{id:1,title:"Temperatura",content:"N/A",status:"Esperando datos...",titleIcon:G.jsx(Kl,{icon:_2})},{id:2,title:"Humedad",content:"N/A",status:"Esperando datos...",titleIcon:G.jsx(Kl,{icon:S2})},{id:3,title:"Nivel de CO",content:"N/A",status:"Esperando datos...",titleIcon:G.jsx(Kl,{icon:v2})},{id:4,title:"Actualizado a las",content:"N/A",status:"Esperando datos...",titleIcon:G.jsx(Kl,{icon:E2})}];if(l){let f=l[1],m=l[2],y=T2(f.airValuesTimestamp),b=new Date(f.airValuesTimestamp);c[0].content=m.temperature+"°C",c[0].status="Temperatura actual",c[1].content=m.humidity+"%",c[1].status="Humedad actual",c[2].content=f.carbonMonoxide+" ppm",c[2].status="Nivel de CO actual",c[3].content=O2(y),c[3].status="Día "+b.toLocaleDateString()}return G.jsx(oh,{cards:c})};Ov.propTypes={deviceId:et.number.isRequired};const w2=()=>{const{deviceId:l}=O0();return G.jsxs("main",{className:"container justify-content-center",children:[G.jsx(Ov,{deviceId:l}),G.jsx(Hg,{deviceId:l}),G.jsx(n1,{deviceId:l})]})};function Cv({onClick:l}){return G.jsx("button",{className:"menuBtn",onClick:l,children:G.jsx(Kl,{icon:y2})})}Cv.propTypes={onClick:et.func.isRequired};const wv=({isOpen:l,onClose:u})=>{const{config:o,configLoading:c,configError:f}=Fr();if(c)return G.jsx("p",{children:"Cargando configuración..."});if(f)return G.jsxs("p",{children:["Error al cargar configuración: ",f]});if(!o)return G.jsx("p",{children:"Configuración no disponible."});const m=o.appConfig.endpoints.BASE_URL,y=o.appConfig.endpoints.GET_DEVICES,b={baseUrl:`${m}/${y}`,params:{}};return G.jsx(Mu,{config:b,children:G.jsx(Dv,{isOpen:l,onClose:u})})},Dv=({isOpen:l,onClose:u})=>{const{data:o,dataLoading:c,dataError:f}=_c(),{theme:m}=Nu();return c?G.jsx("p",{children:"Cargando datos..."}):f?G.jsxs("p",{children:["Error al cargar datos: ",f]}):o?G.jsxs("div",{className:`side-menu ${l?"open":""} ${m}`,children:[G.jsx("button",{className:"close-btn",onClick:u,children:G.jsx(Kl,{icon:x2})}),G.jsx("div",{className:"d-flex flex-column gap-3 mt-5",children:o.map(y=>G.jsx("a",{href:`/dashboard/${y.deviceId}`,style:{textDecoration:"none"},children:G.jsx(Sc,{title:y.deviceName,status:`ID: ${y.deviceId}`,styleMode:"override",className:"col-12",children:[]})},y.deviceId))})]}):G.jsx("p",{children:"Datos no disponibles."})};wv.propTypes={isOpen:et.bool.isRequired,onClose:et.func.isRequired};Dv.propTypes={isOpen:et.bool.isRequired,onClose:et.func.isRequired};function D2(){const{theme:l,toggleTheme:u}=Nu();return G.jsx("button",{className:"theme-toggle",onClick:u,children:l==="dark"?"☀️":"🌙"})}const Mv=l=>{const{theme:u}=Nu();return G.jsxs("header",{className:`justify-content-center text-center mb-4 ${u}`,children:[G.jsx("h1",{children:l.title}),G.jsx("p",{className:"subtitle",children:l.subtitle})]})};Mv.propTypes={title:et.string.isRequired,subtitle:et.string};const M2=()=>{const[l,u]=w.useState(!1),o=()=>{u(!l)},c=()=>{u(!1)};return G.jsxs(G.Fragment,{children:[G.jsx(Cv,{onClick:o}),G.jsx(wv,{isOpen:l,onClose:o}),G.jsx(D2,{}),G.jsxs("div",{className:l?"blur m-0 p-0":"m-0 p-0",onClick:c,children:[G.jsx(Mv,{title:"Contamin",subtitle:"Midiendo la calidad del aire y las calles en Sevilla 🌿🚛"}),G.jsxs(Y0,{children:[G.jsx(Nd,{path:"/",element:G.jsx(Ab,{})}),G.jsx(Nd,{path:"/dashboard/:deviceId",element:G.jsx(w2,{})})]})]})]})};Fy.createRoot(document.getElementById("root")).render(G.jsx(w.StrictMode,{children:G.jsx(Xg,{children:G.jsx(jg,{children:G.jsx(cb,{children:G.jsx(M2,{})})})})}));
diff --git a/backend/src/main/resources/webroot/index.html b/backend/src/main/resources/webroot/index.html
index 5e21cc0..3a67346 100644
--- a/backend/src/main/resources/webroot/index.html
+++ b/backend/src/main/resources/webroot/index.html
@@ -15,7 +15,7 @@
ContaminUS
-
+
diff --git a/frontend/src/components/SummaryCards.jsx b/frontend/src/components/SummaryCards.jsx
index eeb2488..0fe781f 100644
--- a/frontend/src/components/SummaryCards.jsx
+++ b/frontend/src/components/SummaryCards.jsx
@@ -8,7 +8,7 @@ import { DataProvider } from '../contexts/DataContext';
import { useData } from '../contexts/DataContext';
import { useConfig } from '../contexts/ConfigContext';
-import { timestampToTime } from '../util/date.js';
+import { timestampToTime, formatTime } from '../util/date.js';
/**
* SummaryCards.jsx
@@ -83,7 +83,7 @@ const SummaryCardsContent = () => {
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].content = formatTime(lastTime);
CardsData[3].status = "Día " + lastDate.toLocaleDateString();
}
diff --git a/frontend/src/util/date.js b/frontend/src/util/date.js
index eec9bc7..2fc42d9 100644
--- a/frontend/src/util/date.js
+++ b/frontend/src/util/date.js
@@ -8,4 +8,12 @@ const timestampToDate = (timestamp) => {
return date.toLocaleDateString();
}
-export { timestampToTime, timestampToDate };
\ No newline at end of file
+const formatTime = (date) => {
+ if (date.length === 8) {
+ return date.slice(0,5);
+ } else {
+ return `0${date.slice(0,3)}`;
+ }
+}
+
+export { timestampToTime, timestampToDate, formatTime };
\ No newline at end of file