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