1
0

Refactor application structure and components

- Moved components to a new layout directory for better organization.
- Updated App component to include GroupView and adjust routing.
- Removed unused components (App.jsx, Card.jsx, CardContainer.jsx, Header.jsx, MenuButton.jsx, SideMenu.jsx, ThemeButton.jsx).
- Introduced LoadingIcon component for loading states.
- Updated PollutionMap, SummaryCards, and HistoryCharts components to accept groupId as a prop.
- Modified API endpoint configurations in settings.prod.json for better clarity and consistency.
- Enhanced chart options in historyChartConfig for improved visual representation.
- Updated favicon and logo images.
This commit is contained in:
Jose
2025-05-09 22:47:13 +02:00
parent 02a2a2ce07
commit 0ce48c18e2
18 changed files with 297 additions and 268 deletions

View File

@@ -1,42 +1,45 @@
{
"userConfig": {
"city": [
37.38283,
-5.97317
]
"city": [37.38283, -5.97317]
},
"appConfig": {
"endpoints": {
"DATA_URL": "https://contaminus.miarma.net/api/raw/v1",
"LOGIC_URL": "https://contaminus.miarma.net/api/v1",
"DATA_URL": "http://localhost:8081/api/raw/v1",
"LOGIC_URL": "http://localhost:8082/api/v1",
"GET_GROUPS": "/groups",
"GET_GROUP_BY_ID": "/groups/{0}",
"GET_GROUP_DEVICES": "/groups/{0}/devices",
"GET_GROUP_BY_ID": "/groups/:groupId",
"POST_GROUPS": "/groups",
"PUT_GROUP_BY_ID": "/groups/{0}",
"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",
"GET_SENSOR_BY_ID": "/sensors/{0}",
"GET_SENSOR_VALUES": "/sensors/{0}/values",
"POST_SENSORS": "/sensors",
"PUT_SENSOR_BY_ID": "/sensors/{0}",
"GET_ACTUATORS": "/actuators",
"GET_ACTUATOR_BY_ID": "/actuators/{0}",
"POST_ACTUATORS": "/actuators",
"PUT_ACTUATOR_BY_ID": "/actuators/{0}",
"GET_GPS_VALUES": "/gps-values",
"GET_GPS_VALUE_BY_ID": "/gps-values/{0}",
"POST_GPS_VALUES": "/gps-values",
"GET_AIR_VALUES": "/air-values",
"GET_AIR_VALUE_BY_ID": "/air-values/{0}",
"POST_AIR_VALUES": "/air-values"
"PUT_GROUP_BY_ID": "/groups/:groupId",
"GET_GROUP_DEVICES": "/groups/:groupId/devices",
"GET_DEVICE_BY_ID": "/groups/:groupId/devices/:deviceId",
"POST_DEVICES": "/groups/:groupId/devices",
"PUT_DEVICE_BY_ID": "/groups/:groupId/devices/:deviceId",
"GET_DEVICE_LATEST_VALUES": "/groups/:groupId/devices/:deviceId/latest-values",
"GET_DEVICE_POLLUTION_MAP": "/groups/:groupId/devices/:deviceId/pollution-map",
"GET_DEVICE_HISTORY": "/groups/:groupId/devices/:deviceId/history",
"GET_DEVICE_SENSORS": "/groups/:groupId/devices/:deviceId/sensors",
"GET_SENSOR_BY_ID": "/groups/:groupId/devices/:deviceId/sensors/:sensorId",
"POST_SENSORS": "/groups/:groupId/devices/:deviceId/sensors",
"PUT_SENSOR_BY_ID": "/groups/:groupId/devices/:deviceId/sensors/:sensorId",
"GET_SENSOR_VALUES": "/groups/:groupId/devices/:deviceId/sensors/:sensorId/values",
"GET_ACTUATORS": "/groups/:groupId/devices/:deviceId/actuators",
"GET_ACTUATOR_BY_ID": "/groups/:groupId/devices/:deviceId/actuators/:actuator_id",
"POST_ACTUATORS": "/groups/:groupId/devices/:deviceId/actuators",
"PUT_ACTUATOR_BY_ID": "/groups/:groupId/devices/:deviceId/actuators/:actuator_id",
"GET_ACTUATOR_STATUS": "/groups/:groupId/devices/:deviceId/actuators/:actuator_id/status",
"VIEW_LATEST_VALUES": "/v_latest_values",
"VIEW_POLLUTION_MAP": "/v_pollution_map",
"VIEW_SENSOR_HISTORY": "/v_sensor_history_by_device",
"VIEW_SENSOR_VALUES": "/v_sensor_values",
"VIEW_CO_BY_DEVICE": "/v_co_by_device",
"VIEW_GPS_BY_DEVICE": "/v_gps_by_device",
"VIEW_WEATHER_BY_DEVICE": "/v_weather_by_device"
},
"historyChartConfig": {
"chartOptionsDark": {
@@ -44,54 +47,30 @@
"maintainAspectRatio": false,
"scales": {
"x": {
"grid": {
"color": "rgba(255, 255, 255, 0.1)"
},
"ticks": {
"color": "#E0E0E0"
}
"grid": { "color": "rgba(255, 255, 255, 0.1)" },
"ticks": { "color": "#E0E0E0" }
},
"y": {
"grid": {
"color": "rgba(255, 255, 255, 0.1)"
},
"ticks": {
"color": "#E0E0E0"
}
"grid": { "color": "rgba(255, 255, 255, 0.1)" },
"ticks": { "color": "#E0E0E0" }
}
},
"plugins": {
"legend": {
"display": false
}
}
"plugins": { "legend": { "display": false } }
},
"chartOptionsLight": {
"responsive": true,
"maintainAspectRatio": false,
"scales": {
"x": {
"grid": {
"color": "rgba(0, 0, 0, 0.1)"
},
"ticks": {
"color": "#333"
}
"grid": { "color": "rgba(0, 0, 0, 0.1)" },
"ticks": { "color": "#333" }
},
"y": {
"grid": {
"color": "rgba(0, 0, 0, 0.1)"
},
"ticks": {
"color": "#333"
}
"grid": { "color": "rgba(0, 0, 0, 0.1)" },
"ticks": { "color": "#333" }
}
},
"plugins": {
"legend": {
"display": false
}
}
"plugins": { "legend": { "display": false } }
}
}
}

View File

@@ -1,42 +1,45 @@
{
"userConfig": {
"city": [
37.38283,
-5.97317
]
"city": [37.38283, -5.97317]
},
"appConfig": {
"endpoints": {
"DATA_URL": "https://contaminus.miarma.net/api/raw/v1",
"LOGIC_URL": "https://contaminus.miarma.net/api/v1",
"GET_GROUPS": "/groups",
"GET_GROUP_BY_ID": "/groups/{0}",
"GET_GROUP_DEVICES": "/groups/{0}/devices",
"GET_GROUP_BY_ID": "/groups/:groupId",
"POST_GROUPS": "/groups",
"PUT_GROUP_BY_ID": "/groups/{0}",
"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",
"GET_SENSOR_BY_ID": "/sensors/{0}",
"GET_SENSOR_VALUES": "/sensors/{0}/values",
"POST_SENSORS": "/sensors",
"PUT_SENSOR_BY_ID": "/sensors/{0}",
"GET_ACTUATORS": "/actuators",
"GET_ACTUATOR_BY_ID": "/actuators/{0}",
"POST_ACTUATORS": "/actuators",
"PUT_ACTUATOR_BY_ID": "/actuators/{0}",
"GET_GPS_VALUES": "/gps-values",
"GET_GPS_VALUE_BY_ID": "/gps-values/{0}",
"POST_GPS_VALUES": "/gps-values",
"GET_AIR_VALUES": "/air-values",
"GET_AIR_VALUE_BY_ID": "/air-values/{0}",
"POST_AIR_VALUES": "/air-values"
"PUT_GROUP_BY_ID": "/groups/:groupId",
"GET_GROUP_DEVICES": "/groups/:groupId/devices",
"GET_DEVICE_BY_ID": "/groups/:groupId/devices/:deviceId",
"POST_DEVICES": "/groups/:groupId/devices",
"PUT_DEVICE_BY_ID": "/groups/:groupId/devices/:deviceId",
"GET_DEVICE_LATEST_VALUES": "/groups/:groupId/devices/:deviceId/latest-values",
"GET_DEVICE_POLLUTION_MAP": "/groups/:groupId/devices/:deviceId/pollution-map",
"GET_DEVICE_HISTORY": "/groups/:groupId/devices/:deviceId/history",
"GET_DEVICE_SENSORS": "/groups/:groupId/devices/:deviceId/sensors",
"GET_SENSOR_BY_ID": "/groups/:groupId/devices/:deviceId/sensors/:sensorId",
"POST_SENSORS": "/groups/:groupId/devices/:deviceId/sensors",
"PUT_SENSOR_BY_ID": "/groups/:groupId/devices/:deviceId/sensors/:sensorId",
"GET_SENSOR_VALUES": "/groups/:groupId/devices/:deviceId/sensors/:sensorId/values",
"GET_ACTUATORS": "/groups/:groupId/devices/:deviceId/actuators",
"GET_ACTUATOR_BY_ID": "/groups/:groupId/devices/:deviceId/actuators/:actuator_id",
"POST_ACTUATORS": "/groups/:groupId/devices/:deviceId/actuators",
"PUT_ACTUATOR_BY_ID": "/groups/:groupId/devices/:deviceId/actuators/:actuator_id",
"GET_ACTUATOR_STATUS": "/groups/:groupId/devices/:deviceId/actuators/:actuator_id/status",
"VIEW_LATEST_VALUES": "/v_latest_values",
"VIEW_POLLUTION_MAP": "/v_pollution_map",
"VIEW_SENSOR_HISTORY": "/v_sensor_history_by_device",
"VIEW_SENSOR_VALUES": "/v_sensor_values",
"VIEW_CO_BY_DEVICE": "/v_co_by_device",
"VIEW_GPS_BY_DEVICE": "/v_gps_by_device",
"VIEW_WEATHER_BY_DEVICE": "/v_weather_by_device"
},
"historyChartConfig": {
"chartOptionsDark": {
@@ -44,54 +47,30 @@
"maintainAspectRatio": false,
"scales": {
"x": {
"grid": {
"color": "rgba(255, 255, 255, 0.1)"
},
"ticks": {
"color": "#E0E0E0"
}
"grid": { "color": "rgba(255, 255, 255, 0.1)" },
"ticks": { "color": "#E0E0E0" }
},
"y": {
"grid": {
"color": "rgba(255, 255, 255, 0.1)"
},
"ticks": {
"color": "#E0E0E0"
}
"grid": { "color": "rgba(255, 255, 255, 0.1)" },
"ticks": { "color": "#E0E0E0" }
}
},
"plugins": {
"legend": {
"display": false
}
}
"plugins": { "legend": { "display": false } }
},
"chartOptionsLight": {
"responsive": true,
"maintainAspectRatio": false,
"scales": {
"x": {
"grid": {
"color": "rgba(0, 0, 0, 0.1)"
},
"ticks": {
"color": "#333"
}
"grid": { "color": "rgba(0, 0, 0, 0.1)" },
"ticks": { "color": "#333" }
},
"y": {
"grid": {
"color": "rgba(0, 0, 0, 0.1)"
},
"ticks": {
"color": "#333"
}
"grid": { "color": "rgba(0, 0, 0, 0.1)" },
"ticks": { "color": "#333" }
}
},
"plugins": {
"legend": {
"display": false
}
}
"plugins": { "legend": { "display": false } }
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 412 KiB

After

Width:  |  Height:  |  Size: 126 KiB

View File

@@ -5,10 +5,11 @@ 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 MenuButton from '@/components/layout/MenuButton.jsx'
import SideMenu from '@/components/layout/SideMenu.jsx'
import ThemeButton from '@/components/layout/ThemeButton.jsx'
import Header from '@/components/layout/Header.jsx'
import GroupView from '@/pages/GroupView.jsx'
import { Routes, Route } from 'react-router-dom'
import { useState } from 'react'
@@ -30,10 +31,11 @@ const App = () => {
<SideMenu isOpen={isSideMenuOpen} onClose={toggleSideMenu} />
<ThemeButton />
<div className={isSideMenuOpen ? 'blur m-0 p-0' : 'm-0 p-0'} onClick={closeSideMenu}>
<Header title='Contamin' subtitle='Midiendo la calidad del aire y las calles en Sevilla 🌿🚛' />
<Header subtitle='Midiendo la calidad del aire y las calles en Sevilla 🌿🚛' />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/dashboard/:deviceId" element={<Dashboard />} />
<Route path="/groups/:groupId" element={<GroupView />} />
<Route path="/groups/:groupId/devices/:deviceId" element={<Dashboard />} />
</Routes>
</div>
</>

View File

@@ -1,6 +1,6 @@
import { Line } from "react-chartjs-2";
import { Chart as ChartJS, LineElement, PointElement, LinearScale, CategoryScale, Filler } from "chart.js";
import CardContainer from "./CardContainer";
import CardContainer from "./layout/CardContainer";
import "@/css/HistoryCharts.css";
import PropTypes from "prop-types";
@@ -11,7 +11,7 @@ import { useConfig } from "@/hooks/useConfig";
ChartJS.register(LineElement, PointElement, LinearScale, CategoryScale, Filler);
const HistoryCharts = () => {
const HistoryCharts = ({ groupId, deviceId }) => {
const { config, configLoading, configError } = useConfig();
if (configLoading) return <p>Cargando configuración...</p>;
@@ -19,12 +19,15 @@ const HistoryCharts = () => {
if (!config) return <p>Configuración no disponible.</p>;
const BASE = config.appConfig.endpoints.DATA_URL;
const ENDPOINT = config.appConfig.endpoints.GET_SENSORS;
const ENDPOINT = config.appConfig.endpoints.GET_DEVICE_HISTORY;
const endp = ENDPOINT
.replace(':groupId', groupId)
.replace(':deviceId', deviceId); // si tu endpoint lo necesita
const reqConfig = {
baseUrl: `${BASE}${ENDPOINT}`,
baseUrl: `${BASE}${endp}`,
params: {}
}
};
return (
<DataProvider config={reqConfig}>
@@ -92,6 +95,11 @@ const HistoryChartsContent = () => {
);
};
HistoryCharts.propTypes = {
groupId: PropTypes.string.isRequired,
deviceId: PropTypes.string.isRequired
};
HistoryChartsContent.propTypes = {
options: PropTypes.object,
timeLabels: PropTypes.array,

View File

@@ -0,0 +1,10 @@
import { faSpinner } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
const LoadingIcon = () => {
return (
<FontAwesomeIcon icon={faSpinner} className='fa-spin fa-lg' />
);
}
export default LoadingIcon;

View File

@@ -39,7 +39,7 @@ const PollutionCircles = ({ data }) => {
});
};
const PollutionMap = ({ deviceId }) => {
const PollutionMap = ({ groupId, deviceId }) => {
const { config, configLoading, configError } = useConfig();
if (configLoading) return <p>Cargando configuración...</p>;
@@ -48,12 +48,14 @@ const PollutionMap = ({ deviceId }) => {
const BASE = config.appConfig.endpoints.LOGIC_URL;
const ENDPOINT = config.appConfig.endpoints.GET_DEVICE_POLLUTION_MAP;
let endp = ENDPOINT.replace('{0}', deviceId);
const endp = ENDPOINT
.replace(':groupId', groupId)
.replace(':deviceId', deviceId);
const reqConfig = {
baseUrl: `${BASE}${endp}`,
params: {}
}
};
return (
<DataProvider config={reqConfig}>
@@ -62,6 +64,7 @@ const PollutionMap = ({ deviceId }) => {
);
};
const PollutionMapContent = () => {
const { config, configLoading, configError } = useConfig();
const { data, dataLoading, dataError } = useDataContext();
@@ -102,6 +105,7 @@ const mapStyles = {
};
PollutionMap.propTypes = {
groupId: PropTypes.number.isRequired,
deviceId: PropTypes.number.isRequired
};

View File

@@ -1,5 +1,5 @@
import PropTypes from 'prop-types';
import CardContainer from './CardContainer';
import CardContainer from './layout/CardContainer';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faCloud, faClock, faTemperature0, faWater } from '@fortawesome/free-solid-svg-icons';
@@ -10,7 +10,7 @@ import { useDataContext } from '@/hooks/useDataContext';
import { useConfig } from '@/hooks/useConfig.js';
import { DateParser } from '@/util/dateParser';
const SummaryCards = ({ deviceId }) => {
const SummaryCards = ({ groupId, deviceId }) => {
const { config, configLoading, configError } = useConfig();
if (configLoading) return <p>Cargando configuración...</p>;
@@ -19,19 +19,21 @@ const SummaryCards = ({ deviceId }) => {
const BASE = config.appConfig.endpoints.LOGIC_URL;
const ENDPOINT = config.appConfig.endpoints.GET_DEVICE_LATEST_VALUES;
const endp = ENDPOINT.replace('{0}', deviceId);
const endp = ENDPOINT
.replace(':groupId', groupId)
.replace(':deviceId', deviceId); // solo si lo necesitas así
const reqConfig = {
baseUrl: `${BASE}${endp}`,
params: {}
}
};
return (
<DataProvider config={reqConfig}>
<SummaryCardsContent />
</DataProvider>
);
}
};
const SummaryCardsContent = () => {
const { data, dataLoading, dataError } = useDataContext();
@@ -71,6 +73,7 @@ const SummaryCardsContent = () => {
}
SummaryCards.propTypes = {
groupId: PropTypes.string.isRequired,
deviceId: PropTypes.number.isRequired
};

View File

@@ -1,19 +1,29 @@
import Card from "./Card.jsx";
import PropTypes from "prop-types";
import { Link } from "react-router-dom";
const CardContainer = ({ cards, className }) => {
const CardContainer = ({ links, cards, className }) => {
return (
<div className={`row justify-content-center g-0 ${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}>
<p className="card-text text-center">{card.content}</p>
</Card>
</Link>
) : (
<Card key={index} title={card.title} status={card.status} styleMode={card.styleMode} className={card.className} titleIcon={card.titleIcon}>
<p className="card-text text-center">{card.content}</p>
</Card>
)
))}
</div>
);
};
CardContainer.propTypes = {
links: Boolean,
cards: PropTypes.arrayOf(
PropTypes.shape({
title: PropTypes.string.isRequired,

View File

@@ -2,19 +2,18 @@ import PropTypes from 'prop-types';
import '@/css/Header.css';
import { useTheme } from "@/hooks/useTheme";
const Header = (props) => {
const Header = ({ subtitle }) => {
const { theme } = useTheme();
return (
<header className={`justify-content-center text-center mb-4 ${theme}`}>
<h1>{props.title}</h1>
<p className='subtitle'>{props.subtitle}</p>
<img src='/images/logo.png' width={500} />
<p className='subtitle'>{subtitle}</p>
</header>
);
}
Header.propTypes = {
title: PropTypes.string.isRequired,
subtitle: PropTypes.string
}

View File

@@ -9,6 +9,8 @@ import { useDataContext } from "@/hooks/useDataContext";
import { useConfig } from '@/hooks/useConfig.js';
import { useTheme } from "@/hooks/useTheme";
import { Link } from 'react-router-dom';
import Card from './Card';
const SideMenu = ({ isOpen, onClose }) => {
@@ -19,7 +21,7 @@ const SideMenu = ({ isOpen, onClose }) => {
if (!config) return <p>Configuración no disponible.</p>;
const BASE = config.appConfig.endpoints.DATA_URL;
const ENDPOINT = config.appConfig.endpoints.GET_DEVICES;
const ENDPOINT = config.appConfig.endpoints.GET_GROUPS;
const reqConfig = {
baseUrl: `${BASE}${ENDPOINT}`,
@@ -51,18 +53,18 @@ const SideMenuContent = ({ isOpen, onClose }) => {
</button>
<hr className="separation w-100"></hr>
<div className="d-flex flex-column gap-3 mt-5">
{data.map(device => {
{data.map(group => {
return (
<a href={`/dashboard/${device.deviceId}`} key={device.deviceId} style={{ textDecoration: 'none' }}>
<Link to={`/groups/${group.groupId}`} key={group.groupId} style={{ textDecoration: 'none' }}>
<Card
title={device.deviceName}
status={`ID: ${device.deviceId}`}
title={group.groupName}
status={`ID: ${group.groupId}`}
styleMode={"override"}
className={"col-12"}
>
{[]}
</Card>
</a>
</Link>
);
})}
</div>

View File

@@ -2,7 +2,7 @@ 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'
import App from './App.jsx'
import { ThemeProvider } from './context/ThemeContext.jsx'
import { ConfigProvider } from './context/ConfigContext.jsx'

View File

@@ -4,32 +4,16 @@ 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();
const { groupId, deviceId } = useParams();
return (
<main className='container justify-content-center'>
<SummaryCards deviceId={deviceId} />
<PollutionMap deviceId={deviceId}/>
<HistoryCharts deviceId={deviceId} />
<SummaryCards groupId={groupId} deviceId={deviceId} />
<PollutionMap groupId={groupId} deviceId={deviceId} />
<HistoryCharts groupId={groupId} deviceId={deviceId} />
</main>
);
}
};
export default Dashboard;

View File

@@ -0,0 +1,49 @@
import CardContainer from "@/components/layout/CardContainer";
import LoadingIcon from "@/components/LoadingIcon";
import { useParams } from "react-router-dom";
import { useConfig } from "@/hooks/useConfig";
import { useDataContext } from "@/hooks/useDataContext";
import { DataProvider } from "@/context/DataContext";
const GroupView = () => {
const { groupId } = useParams();
const { config, configLoading } = useConfig();
if (configLoading || !config) return <p className="text-center my-5"><LoadingIcon /></p>;
const replacedEndpoint = config.appConfig.endpoints.GET_GROUP_DEVICES.replace(':groupId', groupId);
const reqConfig = {
baseUrl: `${config.appConfig.endpoints.DATA_URL}${replacedEndpoint}`,
};
return (
<DataProvider config={reqConfig}>
<GroupViewContent />
</DataProvider>
);
}
const GroupViewContent = () => {
const { data, dataLoading, dataError } = useDataContext();
const { groupId } = useParams();
if (dataLoading) return <p className="text-center my-5"><LoadingIcon /></p>;
if (dataError) return <p className="text-center my-5">Error al cargar datos: {dataError}</p>;
return (
<CardContainer
links
cards={data.map(device => ({
title: device.deviceName,
status: `ID: ${device.deviceId}`,
to: `/groups/${groupId}/devices/${device.deviceId}`,
styleMode: "override",
className: "col-12 col-md-6 col-lg-4",
}))}
/>
);
}
export default GroupView;