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:
@@ -1,98 +1,77 @@
|
||||
{
|
||||
"userConfig": {
|
||||
"city": [
|
||||
37.38283,
|
||||
-5.97317
|
||||
]
|
||||
"userConfig": {
|
||||
"city": [37.38283, -5.97317]
|
||||
},
|
||||
"appConfig": {
|
||||
"endpoints": {
|
||||
"DATA_URL": "http://localhost:8081/api/raw/v1",
|
||||
"LOGIC_URL": "http://localhost:8082/api/v1",
|
||||
|
||||
"GET_GROUPS": "/groups",
|
||||
"GET_GROUP_BY_ID": "/groups/:groupId",
|
||||
"POST_GROUPS": "/groups",
|
||||
"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"
|
||||
},
|
||||
"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",
|
||||
"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"
|
||||
"historyChartConfig": {
|
||||
"chartOptionsDark": {
|
||||
"responsive": true,
|
||||
"maintainAspectRatio": false,
|
||||
"scales": {
|
||||
"x": {
|
||||
"grid": { "color": "rgba(255, 255, 255, 0.1)" },
|
||||
"ticks": { "color": "#E0E0E0" }
|
||||
},
|
||||
"y": {
|
||||
"grid": { "color": "rgba(255, 255, 255, 0.1)" },
|
||||
"ticks": { "color": "#E0E0E0" }
|
||||
}
|
||||
},
|
||||
"historyChartConfig": {
|
||||
"chartOptionsDark": {
|
||||
"responsive": true,
|
||||
"maintainAspectRatio": false,
|
||||
"scales": {
|
||||
"x": {
|
||||
"grid": {
|
||||
"color": "rgba(255, 255, 255, 0.1)"
|
||||
},
|
||||
"ticks": {
|
||||
"color": "#E0E0E0"
|
||||
}
|
||||
},
|
||||
"y": {
|
||||
"grid": {
|
||||
"color": "rgba(255, 255, 255, 0.1)"
|
||||
},
|
||||
"ticks": {
|
||||
"color": "#E0E0E0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"legend": {
|
||||
"display": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"chartOptionsLight": {
|
||||
"responsive": true,
|
||||
"maintainAspectRatio": false,
|
||||
"scales": {
|
||||
"x": {
|
||||
"grid": {
|
||||
"color": "rgba(0, 0, 0, 0.1)"
|
||||
},
|
||||
"ticks": {
|
||||
"color": "#333"
|
||||
}
|
||||
},
|
||||
"y": {
|
||||
"grid": {
|
||||
"color": "rgba(0, 0, 0, 0.1)"
|
||||
},
|
||||
"ticks": {
|
||||
"color": "#333"
|
||||
}
|
||||
}
|
||||
},
|
||||
"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" }
|
||||
},
|
||||
"y": {
|
||||
"grid": { "color": "rgba(0, 0, 0, 0.1)" },
|
||||
"ticks": { "color": "#333" }
|
||||
}
|
||||
},
|
||||
"plugins": { "legend": { "display": false } }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,98 +1,77 @@
|
||||
{
|
||||
"userConfig": {
|
||||
"city": [
|
||||
37.38283,
|
||||
-5.97317
|
||||
]
|
||||
"userConfig": {
|
||||
"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/:groupId",
|
||||
"POST_GROUPS": "/groups",
|
||||
"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"
|
||||
},
|
||||
"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",
|
||||
"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"
|
||||
"historyChartConfig": {
|
||||
"chartOptionsDark": {
|
||||
"responsive": true,
|
||||
"maintainAspectRatio": false,
|
||||
"scales": {
|
||||
"x": {
|
||||
"grid": { "color": "rgba(255, 255, 255, 0.1)" },
|
||||
"ticks": { "color": "#E0E0E0" }
|
||||
},
|
||||
"y": {
|
||||
"grid": { "color": "rgba(255, 255, 255, 0.1)" },
|
||||
"ticks": { "color": "#E0E0E0" }
|
||||
}
|
||||
},
|
||||
"historyChartConfig": {
|
||||
"chartOptionsDark": {
|
||||
"responsive": true,
|
||||
"maintainAspectRatio": false,
|
||||
"scales": {
|
||||
"x": {
|
||||
"grid": {
|
||||
"color": "rgba(255, 255, 255, 0.1)"
|
||||
},
|
||||
"ticks": {
|
||||
"color": "#E0E0E0"
|
||||
}
|
||||
},
|
||||
"y": {
|
||||
"grid": {
|
||||
"color": "rgba(255, 255, 255, 0.1)"
|
||||
},
|
||||
"ticks": {
|
||||
"color": "#E0E0E0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"legend": {
|
||||
"display": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"chartOptionsLight": {
|
||||
"responsive": true,
|
||||
"maintainAspectRatio": false,
|
||||
"scales": {
|
||||
"x": {
|
||||
"grid": {
|
||||
"color": "rgba(0, 0, 0, 0.1)"
|
||||
},
|
||||
"ticks": {
|
||||
"color": "#333"
|
||||
}
|
||||
},
|
||||
"y": {
|
||||
"grid": {
|
||||
"color": "rgba(0, 0, 0, 0.1)"
|
||||
},
|
||||
"ticks": {
|
||||
"color": "#333"
|
||||
}
|
||||
}
|
||||
},
|
||||
"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" }
|
||||
},
|
||||
"y": {
|
||||
"grid": { "color": "rgba(0, 0, 0, 0.1)" },
|
||||
"ticks": { "color": "#333" }
|
||||
}
|
||||
},
|
||||
"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 |
@@ -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>
|
||||
</>
|
||||
@@ -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,26 +11,29 @@ import { useConfig } from "@/hooks/useConfig";
|
||||
|
||||
ChartJS.register(LineElement, PointElement, LinearScale, CategoryScale, Filler);
|
||||
|
||||
const HistoryCharts = () => {
|
||||
const { config, configLoading, configError } = useConfig();
|
||||
|
||||
if (configLoading) return <p>Cargando configuración...</p>;
|
||||
if (configError) return <p>Error al cargar configuración: {configError}</p>;
|
||||
if (!config) return <p>Configuración no disponible.</p>;
|
||||
const HistoryCharts = ({ groupId, deviceId }) => {
|
||||
const { config, configLoading, configError } = useConfig();
|
||||
|
||||
const BASE = config.appConfig.endpoints.DATA_URL;
|
||||
const ENDPOINT = config.appConfig.endpoints.GET_SENSORS;
|
||||
if (configLoading) return <p>Cargando configuración...</p>;
|
||||
if (configError) return <p>Error al cargar configuración: {configError}</p>;
|
||||
if (!config) return <p>Configuración no disponible.</p>;
|
||||
|
||||
const reqConfig = {
|
||||
baseUrl: `${BASE}${ENDPOINT}`,
|
||||
params: {}
|
||||
}
|
||||
const BASE = config.appConfig.endpoints.DATA_URL;
|
||||
const ENDPOINT = config.appConfig.endpoints.GET_DEVICE_HISTORY;
|
||||
const endp = ENDPOINT
|
||||
.replace(':groupId', groupId)
|
||||
.replace(':deviceId', deviceId); // si tu endpoint lo necesita
|
||||
|
||||
return (
|
||||
<DataProvider config={reqConfig}>
|
||||
<HistoryChartsContent />
|
||||
</DataProvider>
|
||||
);
|
||||
const reqConfig = {
|
||||
baseUrl: `${BASE}${endp}`,
|
||||
params: {}
|
||||
};
|
||||
|
||||
return (
|
||||
<DataProvider config={reqConfig}>
|
||||
<HistoryChartsContent />
|
||||
</DataProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const HistoryChartsContent = () => {
|
||||
@@ -92,6 +95,11 @@ const HistoryChartsContent = () => {
|
||||
);
|
||||
};
|
||||
|
||||
HistoryCharts.propTypes = {
|
||||
groupId: PropTypes.string.isRequired,
|
||||
deviceId: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
HistoryChartsContent.propTypes = {
|
||||
options: PropTypes.object,
|
||||
timeLabels: PropTypes.array,
|
||||
|
||||
10
frontend/src/components/LoadingIcon.jsx
Normal file
10
frontend/src/components/LoadingIcon.jsx
Normal 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;
|
||||
@@ -39,29 +39,32 @@ const PollutionCircles = ({ data }) => {
|
||||
});
|
||||
};
|
||||
|
||||
const PollutionMap = ({ deviceId }) => {
|
||||
const { config, configLoading, configError } = useConfig();
|
||||
|
||||
if (configLoading) return <p>Cargando configuración...</p>;
|
||||
if (configError) return <p>Error al cargar configuración: {configError}</p>;
|
||||
if (!config) return <p>Configuración no disponible.</p>;
|
||||
const PollutionMap = ({ groupId, deviceId }) => {
|
||||
const { config, configLoading, configError } = useConfig();
|
||||
|
||||
const BASE = config.appConfig.endpoints.LOGIC_URL;
|
||||
const ENDPOINT = config.appConfig.endpoints.GET_DEVICE_POLLUTION_MAP;
|
||||
let endp = ENDPOINT.replace('{0}', deviceId);
|
||||
if (configLoading) return <p>Cargando configuración...</p>;
|
||||
if (configError) return <p>Error al cargar configuración: {configError}</p>;
|
||||
if (!config) return <p>Configuración no disponible.</p>;
|
||||
|
||||
const reqConfig = {
|
||||
baseUrl: `${BASE}${endp}`,
|
||||
params: {}
|
||||
}
|
||||
const BASE = config.appConfig.endpoints.LOGIC_URL;
|
||||
const ENDPOINT = config.appConfig.endpoints.GET_DEVICE_POLLUTION_MAP;
|
||||
const endp = ENDPOINT
|
||||
.replace(':groupId', groupId)
|
||||
.replace(':deviceId', deviceId);
|
||||
|
||||
return (
|
||||
<DataProvider config={reqConfig}>
|
||||
<PollutionMapContent />
|
||||
</DataProvider>
|
||||
);
|
||||
const reqConfig = {
|
||||
baseUrl: `${BASE}${endp}`,
|
||||
params: {}
|
||||
};
|
||||
|
||||
return (
|
||||
<DataProvider config={reqConfig}>
|
||||
<PollutionMapContent />
|
||||
</DataProvider>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
|
||||
@@ -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,
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -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'
|
||||
|
||||
@@ -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;
|
||||
|
||||
49
frontend/src/pages/GroupView.jsx
Normal file
49
frontend/src/pages/GroupView.jsx
Normal 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;
|
||||
Reference in New Issue
Block a user