Implemented (partially) Voronoi algorithm for zone-dividing in Seville map. Also refactored some things in frontend. Modified hardware firmware for conditional compilation for both SENSOR and ACTUATOR type boards.
This commit is contained in:
BIN
frontend/public/fonts/LEDBOARD.ttf
Normal file
BIN
frontend/public/fonts/LEDBOARD.ttf
Normal file
Binary file not shown.
BIN
frontend/public/fonts/OpenSans.ttf
Normal file
BIN
frontend/public/fonts/OpenSans.ttf
Normal file
Binary file not shown.
BIN
frontend/public/fonts/ProductSansBold.ttf
Normal file
BIN
frontend/public/fonts/ProductSansBold.ttf
Normal file
Binary file not shown.
BIN
frontend/public/fonts/ProductSansBoldItalic.ttf
Normal file
BIN
frontend/public/fonts/ProductSansBoldItalic.ttf
Normal file
Binary file not shown.
BIN
frontend/public/fonts/ProductSansItalic.ttf
Normal file
BIN
frontend/public/fonts/ProductSansItalic.ttf
Normal file
Binary file not shown.
BIN
frontend/public/fonts/ProductSansRegular.ttf
Normal file
BIN
frontend/public/fonts/ProductSansRegular.ttf
Normal file
Binary file not shown.
@@ -7,6 +7,8 @@ import { useTheme } from "@/hooks/useTheme";
|
||||
import { DataProvider } from "@/context/DataContext.jsx";
|
||||
import { useDataContext } from "@/hooks/useDataContext";
|
||||
import { useConfig } from "@/hooks/useConfig";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faInfoCircle } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
ChartJS.register(LineElement, PointElement, LinearScale, CategoryScale, Filler);
|
||||
|
||||
@@ -54,16 +56,31 @@ const HistoryChartsContent = () => {
|
||||
carbonMonoxide: []
|
||||
};
|
||||
|
||||
const threeDaysAgo = new Date();
|
||||
threeDaysAgo.setDate(threeDaysAgo.getDate() - 3);
|
||||
const isToday = (timestamp) => {
|
||||
const date = new Date(timestamp * 1000);
|
||||
return (
|
||||
date.getUTCFullYear() >= threeDaysAgo.getUTCFullYear() &&
|
||||
date.getUTCMonth() >= threeDaysAgo.getUTCMonth() &&
|
||||
date.getUTCDate() >= threeDaysAgo.getUTCDate()
|
||||
);
|
||||
};
|
||||
|
||||
data?.forEach(sensor => {
|
||||
if (sensor.value != null && grouped[sensor.valueType]) {
|
||||
if (
|
||||
sensor.value != null &&
|
||||
grouped[sensor.valueType] &&
|
||||
isToday(sensor.timestamp)
|
||||
) {
|
||||
grouped[sensor.valueType].push({
|
||||
timestamp: sensor.timestamp * 1000,
|
||||
value: sensor.value
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const sortAndExtract = (entries) => {
|
||||
const sorted = entries.sort((a, b) => a.timestamp - b.timestamp);
|
||||
|
||||
@@ -75,45 +92,50 @@ const HistoryChartsContent = () => {
|
||||
})
|
||||
);
|
||||
|
||||
const values = sorted.map(e => e.value);
|
||||
return { labels, values };
|
||||
const values = sorted.map(e => e.value);
|
||||
return { labels, values };
|
||||
};
|
||||
|
||||
|
||||
const temp = sortAndExtract(grouped.temperature);
|
||||
const hum = sortAndExtract(grouped.humidity);
|
||||
const press = sortAndExtract(grouped.pressure);
|
||||
const co = sortAndExtract(grouped.carbonMonoxide);
|
||||
const temp = sortAndExtract(grouped.temperature);
|
||||
const hum = sortAndExtract(grouped.humidity);
|
||||
const press = sortAndExtract(grouped.pressure);
|
||||
const co = sortAndExtract(grouped.carbonMonoxide);
|
||||
|
||||
const timeLabels = temp.labels.length ? temp.labels : hum.labels.length ? hum.labels : co.labels.length ? co.labels : ["Sin datos"];
|
||||
const timeLabels = temp.labels.length ? temp.labels : hum.labels.length ? hum.labels : co.labels.length ? co.labels : ["Sin datos"];
|
||||
|
||||
const historyData = [
|
||||
{ title: "🌡️ Temperatura", data: temp.values, borderColor: "#00FF85", backgroundColor: "rgba(0, 255, 133, 0.2)" },
|
||||
{ title: "💦 Humedad", data: hum.values, borderColor: "#00D4FF", backgroundColor: "rgba(0, 212, 255, 0.2)" },
|
||||
{ title: "⏲ Presión", data: press.values, borderColor: "#B12424", backgroundColor: "rgba(255, 0, 0, 0.2)" },
|
||||
{ title: "☁️ Contaminación", data: co.values, borderColor: "#FFA500", backgroundColor: "rgba(255, 165, 0, 0.2)" }
|
||||
];
|
||||
const historyData = [
|
||||
{ title: "🌡️ Temperatura", data: temp.values, borderColor: "#00FF85", backgroundColor: "rgba(0, 255, 133, 0.2)" },
|
||||
{ title: "💦 Humedad", data: hum.values, borderColor: "#00D4FF", backgroundColor: "rgba(0, 212, 255, 0.2)" },
|
||||
{ title: "⏲ Presión", data: press.values, borderColor: "#B12424", backgroundColor: "rgba(255, 0, 0, 0.2)" },
|
||||
{ title: "☁️ Contaminación", data: co.values, borderColor: "#FFA500", backgroundColor: "rgba(255, 165, 0, 0.2)" }
|
||||
];
|
||||
|
||||
return (
|
||||
<CardContainer
|
||||
cards={historyData.map(({ title, data, borderColor, backgroundColor }) => ({
|
||||
title,
|
||||
content: (
|
||||
<Line style={{ minHeight: "250px" }}
|
||||
data={{
|
||||
labels: timeLabels,
|
||||
datasets: [{ data, borderColor, backgroundColor, fill: true, tension: 0.4 }]
|
||||
}}
|
||||
options={options}
|
||||
/>
|
||||
),
|
||||
styleMode: "override",
|
||||
className: "col-lg-6 col-xxs-12 d-flex flex-column align-items-center p-3 card-container",
|
||||
style: { minHeight: "250px" }
|
||||
}))}
|
||||
className=""
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<CardContainer
|
||||
cards={historyData.map(({ title, data, borderColor, backgroundColor }) => ({
|
||||
title,
|
||||
content: (
|
||||
<Line style={{ minHeight: "250px" }}
|
||||
data={{
|
||||
labels: timeLabels,
|
||||
datasets: [{ data, borderColor, backgroundColor, fill: true, tension: 0.4 }]
|
||||
}}
|
||||
options={options}
|
||||
/>
|
||||
),
|
||||
styleMode: "override",
|
||||
className: "col-lg-6 col-xxs-12 d-flex flex-column align-items-center",
|
||||
style: { minHeight: "250px" }
|
||||
}))}
|
||||
/>
|
||||
<span className="m-0 p-0 d-flex align-items-center justify-content-center">
|
||||
<FontAwesomeIcon icon={faInfoCircle} className="me-2" />
|
||||
<p className="m-0 p-0">El historial muestra datos de los últimos 3 días</p>
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
HistoryCharts.propTypes = {
|
||||
|
||||
@@ -72,9 +72,7 @@ const PollutionMapContent = () => {
|
||||
if (!data) return <p>Datos no disponibles.</p>;
|
||||
|
||||
return (
|
||||
<div className="p-3">
|
||||
<div id="map" className='rounded-4' style={{ height: "60vh" }}></div>
|
||||
</div>
|
||||
<div id="map" className='rounded-4' style={{ height: "60vh" }}></div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -39,12 +39,52 @@ const SummaryCardsContent = () => {
|
||||
if (!data) return <p>Datos no disponibles.</p>;
|
||||
|
||||
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: "Presión", content: "N/A", status: "Esperando datos...", titleIcon: '⏲ ' },
|
||||
{ id: 4, title: "Nivel de CO", content: "N/A", status: "Esperando datos...", titleIcon: '☁ ' }
|
||||
{
|
||||
id: 1,
|
||||
title: "Temperatura",
|
||||
content: "N/A",
|
||||
status: "Esperando datos...",
|
||||
titleIcon: '🌡 ',
|
||||
className: "col-12 col-md-6 col-lg-3",
|
||||
link: false,
|
||||
text: true
|
||||
},
|
||||
|
||||
{
|
||||
id: 2,
|
||||
title: "Humedad",
|
||||
content: "N/A",
|
||||
status: "Esperando datos...",
|
||||
titleIcon: '💦 ',
|
||||
className: "col-12 col-md-6 col-lg-3",
|
||||
link: false,
|
||||
text: true
|
||||
},
|
||||
|
||||
{
|
||||
id: 3,
|
||||
title: "Presión",
|
||||
content: "N/A",
|
||||
status: "Esperando datos...",
|
||||
titleIcon: '⏲ ',
|
||||
className: "col-12 col-md-6 col-lg-3",
|
||||
link: false,
|
||||
text: true
|
||||
},
|
||||
|
||||
{
|
||||
id: 4,
|
||||
title: "Nivel de CO",
|
||||
content: "N/A",
|
||||
status: "Esperando datos...",
|
||||
titleIcon: '☁ ',
|
||||
className: "col-12 col-md-6 col-lg-3",
|
||||
link: false,
|
||||
text: true
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
if (data) {
|
||||
let coData = data[2];
|
||||
let tempData = data[1];
|
||||
@@ -61,7 +101,7 @@ const SummaryCardsContent = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<CardContainer text cards={CardsData} />
|
||||
<CardContainer cards={CardsData} />
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,22 @@
|
||||
import PropTypes from "prop-types";
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import "@/css/Card.css";
|
||||
import { useTheme } from "@/hooks/useTheme";
|
||||
|
||||
const Card = ({ title, status, children, styleMode, className, titleIcon, style }) => {
|
||||
const Card = ({
|
||||
title,
|
||||
status,
|
||||
children,
|
||||
styleMode,
|
||||
className,
|
||||
titleIcon,
|
||||
style,
|
||||
link,
|
||||
to,
|
||||
text,
|
||||
marquee
|
||||
}) => {
|
||||
const cardRef = useRef(null);
|
||||
const [shortTitle, setShortTitle] = useState(title);
|
||||
const { theme } = useTheme();
|
||||
@@ -25,37 +38,59 @@ const Card = ({ title, status, children, styleMode, className, titleIcon, style
|
||||
return () => window.removeEventListener("resize", checkSize);
|
||||
}, [title]);
|
||||
|
||||
return (
|
||||
const cardContent = (
|
||||
<div
|
||||
ref={cardRef}
|
||||
className={styleMode === "override" ? `${className}` :
|
||||
`col-xl-3 col-sm-6 d-flex flex-column align-items-center p-3 card-container ${className}`}
|
||||
|
||||
className={`card p-3 w-100 ${theme} ${className ?? ""}`}
|
||||
style={styleMode === "override" ? style : {}}
|
||||
>
|
||||
<div className={`card p-3 w-100 ${theme}`} style={styleMode === "override" ? style : {}}>
|
||||
<h3 className="text-center">
|
||||
{titleIcon}
|
||||
{shortTitle}
|
||||
</h3>
|
||||
<div className="card-content">{children}</div>
|
||||
{status ? <span className="status text-center mt-2">{status}</span> : null}
|
||||
<h3 className="text-center">
|
||||
{titleIcon}
|
||||
{shortTitle}
|
||||
</h3>
|
||||
|
||||
<div className="card-content">
|
||||
{marquee ? (
|
||||
<marquee>
|
||||
<p className="card-text text-center">{children}</p>
|
||||
</marquee>
|
||||
) : text ? (
|
||||
<p className="card-text text-center">{children}</p>
|
||||
) : (
|
||||
<div className="my-2">{children}</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
|
||||
{status && <span className="status text-center mt-2">{status}</span>}
|
||||
</div>
|
||||
);
|
||||
|
||||
return link && to
|
||||
? <Link to={to} style={{ textDecoration: "none" }}>{cardContent}</Link>
|
||||
: cardContent;
|
||||
};
|
||||
|
||||
Card.propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
status: PropTypes.string.isRequired,
|
||||
children: PropTypes.node.isRequired,
|
||||
styleMode: PropTypes.oneOf(["override", ""]),
|
||||
className: PropTypes.string,
|
||||
styleMode: PropTypes.oneOf(["override", ""]),
|
||||
className: PropTypes.string,
|
||||
titleIcon: PropTypes.node,
|
||||
style: PropTypes.object,
|
||||
link: PropTypes.bool,
|
||||
to: PropTypes.string,
|
||||
text: PropTypes.bool,
|
||||
};
|
||||
|
||||
Card.defaultProps = {
|
||||
styleMode: "",
|
||||
className: "",
|
||||
style: {},
|
||||
link: false,
|
||||
to: "",
|
||||
text: false,
|
||||
};
|
||||
|
||||
export default Card;
|
||||
|
||||
@@ -1,41 +1,33 @@
|
||||
import Card from "./Card.jsx";
|
||||
import PropTypes from "prop-types";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
const CardContainer = ({ links, cards, className, text }) => {
|
||||
const CardContainer = ({ cards, className }) => {
|
||||
return (
|
||||
<div className={`row justify-content-center g-0 ${className}`}>
|
||||
<div className={`row justify-content-center g-3 ${className}`}>
|
||||
{cards.map((card, index) => (
|
||||
links ? (
|
||||
<Link to={card.to} key={index} style={{ textDecoration: 'none' }}>
|
||||
<Card title={card.title} status={card.status} styleMode={card.styleMode} className={card.className} titleIcon={card.titleIcon} style={card.style}>
|
||||
{text
|
||||
? <p className="card-text text-center">{card.content}</p>
|
||||
: <div className="my-2">{card.content}</div>
|
||||
}
|
||||
</Card>
|
||||
</Link>
|
||||
) : (
|
||||
<Card key={index} title={card.title} status={card.status} styleMode={card.styleMode} className={card.className} titleIcon={card.titleIcon} style={card.style}>
|
||||
{text
|
||||
? <p className="card-text text-center">{card.content}</p>
|
||||
: <div className="my-2">{card.content}</div>
|
||||
}
|
||||
<div key={index} className={card.className ?? "col-12 col-md-6 col-lg-3"}>
|
||||
<Card {...card}>
|
||||
{card.content}
|
||||
</Card>
|
||||
)
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
CardContainer.propTypes = {
|
||||
links: Boolean,
|
||||
text: Boolean,
|
||||
cards: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
title: PropTypes.string.isRequired,
|
||||
content: PropTypes.string.isRequired,
|
||||
status: PropTypes.string.isRequired,
|
||||
className: PropTypes.string,
|
||||
styleMode: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
titleIcon: PropTypes.node,
|
||||
link: PropTypes.bool,
|
||||
to: PropTypes.string,
|
||||
text: PropTypes.bool,
|
||||
})
|
||||
).isRequired,
|
||||
className: PropTypes.string,
|
||||
|
||||
@@ -7,17 +7,13 @@ const Header = ({ subtitle }) => {
|
||||
const { theme } = useTheme();
|
||||
|
||||
return (
|
||||
<header className={`row justify-content-center text-center mb-4 ${theme}`}>
|
||||
<header className={`animated-header row justify-content-center text-center mb-4 ${theme}`}>
|
||||
<div className='col-xl-4 col-lg-6 col-8'>
|
||||
<Link to="/" className="text-decoration-none">
|
||||
<img src={`/images/logo-${theme}.png`} className='img-fluid' />
|
||||
</Link>
|
||||
</div>
|
||||
<p className='col-12 text-center my-3'>{subtitle}</p>
|
||||
{/*<nav className='d-flex justify-content-center gap-4 my-3'>
|
||||
<Link to="/" className="nav-link">Inicio</Link>
|
||||
<Link to="/groups" className="nav-link">Grupos</Link>
|
||||
</nav> */}
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -46,6 +46,14 @@
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.card.led marquee > p.card-text {
|
||||
font-family: "LEDBOARD" !important;
|
||||
color: rgb(38, 60, 229) !important;
|
||||
font-size: 2.5em !important;
|
||||
text-transform: uppercase !important;
|
||||
letter-spacing: 1px !important;
|
||||
}
|
||||
|
||||
p.card-text {
|
||||
font-size: 2.2em;
|
||||
font-weight: 600;
|
||||
|
||||
@@ -34,6 +34,21 @@ header > .subtitle {
|
||||
animation: fadeIn 2s ease-in-out;
|
||||
}
|
||||
|
||||
.animated-header {
|
||||
animation: pulseHeader 6s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes pulseHeader {
|
||||
0%, 100% {
|
||||
opacity: 0.96;
|
||||
transform: translateY(3px);
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
transform: translateY(-3px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
0% { opacity: 0; }
|
||||
100% { opacity: 1; }
|
||||
|
||||
@@ -12,42 +12,52 @@
|
||||
--card-gradient-secondary: #353535;
|
||||
}
|
||||
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Poppins';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/poppins/v22/pxiEyp8kv8JHgFVrJJnecmNE.woff2) format('woff2');
|
||||
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
font-family: "Open Sans";
|
||||
src: url('/fonts/OpenSans.ttf');
|
||||
}
|
||||
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Poppins';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/poppins/v22/pxiEyp8kv8JHgFVrJJfecg.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
font-family: "Product Sans";
|
||||
src: url('/fonts/ProductSansRegular.ttf');
|
||||
}
|
||||
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Poppins';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/poppins/v22/pxiByp8kv8JHgFVrLEj6Z1JlFc-K.woff2) format('woff2');
|
||||
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
font-family: "Product Sans Italic";
|
||||
src: url('/fonts/ProductSansItalic.ttf');
|
||||
}
|
||||
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Poppins';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/poppins/v22/pxiByp8kv8JHgFVrLEj6Z1xlFQ.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
font-family: "Product Sans Italic Bold";
|
||||
src: url('/fonts/ProductSansBoldItalic.ttf');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Product Sans Bold";
|
||||
src: url('/fonts/ProductSansBold.ttf');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "LEDBOARD";
|
||||
src: url('/fonts/LEDBOARD.ttf');
|
||||
}
|
||||
|
||||
/* Tipografía global */
|
||||
div,
|
||||
label,
|
||||
input,
|
||||
p,
|
||||
span,
|
||||
a,
|
||||
button {
|
||||
font-family: "Open Sans", sans-serif;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font-family: "Product Sans", sans-serif;
|
||||
}
|
||||
@@ -8,7 +8,7 @@ const Dashboard = () => {
|
||||
const { groupId, deviceId } = useParams();
|
||||
|
||||
return (
|
||||
<main className='container justify-content-center'>
|
||||
<main className='container justify-content-center gap-3 d-flex flex-column'>
|
||||
<SummaryCards groupId={groupId} deviceId={deviceId} />
|
||||
<PollutionMap groupId={groupId} deviceId={deviceId} />
|
||||
<HistoryCharts groupId={groupId} deviceId={deviceId} />
|
||||
|
||||
@@ -9,8 +9,10 @@ import { useEffect, useState } from "react";
|
||||
import { DataProvider } from "@/context/DataContext";
|
||||
|
||||
import { MapContainer, TileLayer, Marker } from 'react-leaflet';
|
||||
import L from 'leaflet';
|
||||
import L, { map } from 'leaflet';
|
||||
import 'leaflet/dist/leaflet.css';
|
||||
import PropTypes from 'prop-types';
|
||||
import { text } from "@fortawesome/fontawesome-svg-core";
|
||||
|
||||
// Icono de marcador por defecto (porque Leaflet no lo carga bien en algunos setups)
|
||||
const markerIcon = new L.Icon({
|
||||
@@ -19,6 +21,7 @@ const markerIcon = new L.Icon({
|
||||
iconAnchor: [12, 41],
|
||||
});
|
||||
|
||||
|
||||
const MiniMap = ({ lat, lon }) => (
|
||||
<MapContainer
|
||||
center={[lat, lon]}
|
||||
@@ -34,6 +37,11 @@ const MiniMap = ({ lat, lon }) => (
|
||||
</MapContainer>
|
||||
);
|
||||
|
||||
MiniMap.propTypes = {
|
||||
lat: PropTypes.number.isRequired,
|
||||
lon: PropTypes.number.isRequired,
|
||||
};
|
||||
|
||||
const GroupView = () => {
|
||||
const { groupId } = useParams();
|
||||
const { config, configLoading } = useConfig();
|
||||
@@ -91,21 +99,20 @@ const GroupViewContent = () => {
|
||||
|
||||
return (
|
||||
<CardContainer
|
||||
links
|
||||
cards={data.map(device => {
|
||||
const latest = latestData[device.deviceId];
|
||||
const gpsSensor = latest?.data[0];
|
||||
const mapPreview = gpsSensor?.lat && gpsSensor?.lon
|
||||
? <MiniMap lat={gpsSensor.lat} lon={gpsSensor.lon} />
|
||||
: "Sin posición";
|
||||
const mapPreview = <MiniMap lat={gpsSensor?.lat} lon={gpsSensor?.lon} />;
|
||||
|
||||
return {
|
||||
title: device.deviceName,
|
||||
status: `ID: ${device.deviceId}`,
|
||||
content: mapPreview,
|
||||
link: gpsSensor != undefined,
|
||||
text: gpsSensor == undefined,
|
||||
marquee: gpsSensor == undefined,
|
||||
content: gpsSensor == undefined ? "SOLO VEHICULOS ELECTRICOS" : mapPreview,
|
||||
to: `/groups/${groupId}/devices/${device.deviceId}`,
|
||||
styleMode: "override",
|
||||
className: "col-12 col-md-6 col-lg-4"
|
||||
className: `col-12 col-md-6 col-lg-4 ${gpsSensor == undefined ? "led" : ""}`,
|
||||
};
|
||||
})}
|
||||
|
||||
|
||||
@@ -60,17 +60,16 @@ const GroupsContent = ({ config }) => {
|
||||
|
||||
return (
|
||||
<CardContainer
|
||||
links
|
||||
text
|
||||
cards={data.map(group => {
|
||||
const groupDevices = devices[group.groupId]?.data;
|
||||
const deviceCount = groupDevices?.length;
|
||||
|
||||
return {
|
||||
title: group.groupName,
|
||||
link: true,
|
||||
text: true,
|
||||
status: `ID: ${group.groupId}`,
|
||||
to: `/groups/${group.groupId}`,
|
||||
styleMode: "override",
|
||||
content: deviceCount != null
|
||||
? (deviceCount === 1 ? "1 dispositivo" : `${deviceCount} dispositivos`)
|
||||
: "Cargando dispositivos...",
|
||||
|
||||
Reference in New Issue
Block a user