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": {
|
"userConfig": {
|
||||||
"city": [
|
"city": [37.38283, -5.97317]
|
||||||
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": {
|
"historyChartConfig": {
|
||||||
"endpoints": {
|
"chartOptionsDark": {
|
||||||
"DATA_URL": "https://contaminus.miarma.net/api/raw/v1",
|
"responsive": true,
|
||||||
"LOGIC_URL": "https://contaminus.miarma.net/api/v1",
|
"maintainAspectRatio": false,
|
||||||
"GET_GROUPS": "/groups",
|
"scales": {
|
||||||
"GET_GROUP_BY_ID": "/groups/{0}",
|
"x": {
|
||||||
"GET_GROUP_DEVICES": "/groups/{0}/devices",
|
"grid": { "color": "rgba(255, 255, 255, 0.1)" },
|
||||||
"POST_GROUPS": "/groups",
|
"ticks": { "color": "#E0E0E0" }
|
||||||
"PUT_GROUP_BY_ID": "/groups/{0}",
|
},
|
||||||
"GET_DEVICES": "/devices",
|
"y": {
|
||||||
"GET_DEVICE_BY_ID": "/devices/{0}",
|
"grid": { "color": "rgba(255, 255, 255, 0.1)" },
|
||||||
"GET_DEVICE_SENSORS": "/devices/{0}/sensors",
|
"ticks": { "color": "#E0E0E0" }
|
||||||
"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": {
|
"plugins": { "legend": { "display": false } }
|
||||||
"chartOptionsDark": {
|
},
|
||||||
"responsive": true,
|
"chartOptionsLight": {
|
||||||
"maintainAspectRatio": false,
|
"responsive": true,
|
||||||
"scales": {
|
"maintainAspectRatio": false,
|
||||||
"x": {
|
"scales": {
|
||||||
"grid": {
|
"x": {
|
||||||
"color": "rgba(255, 255, 255, 0.1)"
|
"grid": { "color": "rgba(0, 0, 0, 0.1)" },
|
||||||
},
|
"ticks": { "color": "#333" }
|
||||||
"ticks": {
|
},
|
||||||
"color": "#E0E0E0"
|
"y": {
|
||||||
}
|
"grid": { "color": "rgba(0, 0, 0, 0.1)" },
|
||||||
},
|
"ticks": { "color": "#333" }
|
||||||
"y": {
|
}
|
||||||
"grid": {
|
},
|
||||||
"color": "rgba(255, 255, 255, 0.1)"
|
"plugins": { "legend": { "display": false } }
|
||||||
},
|
}
|
||||||
"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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,98 +1,77 @@
|
|||||||
{
|
{
|
||||||
"userConfig": {
|
"userConfig": {
|
||||||
"city": [
|
"city": [37.38283, -5.97317]
|
||||||
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": {
|
"historyChartConfig": {
|
||||||
"endpoints": {
|
"chartOptionsDark": {
|
||||||
"DATA_URL": "https://contaminus.miarma.net/api/raw/v1",
|
"responsive": true,
|
||||||
"LOGIC_URL": "https://contaminus.miarma.net/api/v1",
|
"maintainAspectRatio": false,
|
||||||
"GET_GROUPS": "/groups",
|
"scales": {
|
||||||
"GET_GROUP_BY_ID": "/groups/{0}",
|
"x": {
|
||||||
"GET_GROUP_DEVICES": "/groups/{0}/devices",
|
"grid": { "color": "rgba(255, 255, 255, 0.1)" },
|
||||||
"POST_GROUPS": "/groups",
|
"ticks": { "color": "#E0E0E0" }
|
||||||
"PUT_GROUP_BY_ID": "/groups/{0}",
|
},
|
||||||
"GET_DEVICES": "/devices",
|
"y": {
|
||||||
"GET_DEVICE_BY_ID": "/devices/{0}",
|
"grid": { "color": "rgba(255, 255, 255, 0.1)" },
|
||||||
"GET_DEVICE_SENSORS": "/devices/{0}/sensors",
|
"ticks": { "color": "#E0E0E0" }
|
||||||
"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": {
|
"plugins": { "legend": { "display": false } }
|
||||||
"chartOptionsDark": {
|
},
|
||||||
"responsive": true,
|
"chartOptionsLight": {
|
||||||
"maintainAspectRatio": false,
|
"responsive": true,
|
||||||
"scales": {
|
"maintainAspectRatio": false,
|
||||||
"x": {
|
"scales": {
|
||||||
"grid": {
|
"x": {
|
||||||
"color": "rgba(255, 255, 255, 0.1)"
|
"grid": { "color": "rgba(0, 0, 0, 0.1)" },
|
||||||
},
|
"ticks": { "color": "#333" }
|
||||||
"ticks": {
|
},
|
||||||
"color": "#E0E0E0"
|
"y": {
|
||||||
}
|
"grid": { "color": "rgba(0, 0, 0, 0.1)" },
|
||||||
},
|
"ticks": { "color": "#333" }
|
||||||
"y": {
|
}
|
||||||
"grid": {
|
},
|
||||||
"color": "rgba(255, 255, 255, 0.1)"
|
"plugins": { "legend": { "display": false } }
|
||||||
},
|
}
|
||||||
"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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
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 Home from '@/pages/Home.jsx'
|
||||||
import Dashboard from '@/pages/Dashboard.jsx'
|
import Dashboard from '@/pages/Dashboard.jsx'
|
||||||
import MenuButton from './MenuButton.jsx'
|
import MenuButton from '@/components/layout/MenuButton.jsx'
|
||||||
import SideMenu from './SideMenu.jsx'
|
import SideMenu from '@/components/layout/SideMenu.jsx'
|
||||||
import ThemeButton from '@/components/ThemeButton.jsx'
|
import ThemeButton from '@/components/layout/ThemeButton.jsx'
|
||||||
import Header from '@/components/Header.jsx'
|
import Header from '@/components/layout/Header.jsx'
|
||||||
|
import GroupView from '@/pages/GroupView.jsx'
|
||||||
|
|
||||||
import { Routes, Route } from 'react-router-dom'
|
import { Routes, Route } from 'react-router-dom'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
@@ -30,10 +31,11 @@ const App = () => {
|
|||||||
<SideMenu isOpen={isSideMenuOpen} onClose={toggleSideMenu} />
|
<SideMenu isOpen={isSideMenuOpen} onClose={toggleSideMenu} />
|
||||||
<ThemeButton />
|
<ThemeButton />
|
||||||
<div className={isSideMenuOpen ? 'blur m-0 p-0' : 'm-0 p-0'} onClick={closeSideMenu}>
|
<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>
|
<Routes>
|
||||||
<Route path="/" element={<Home />} />
|
<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>
|
</Routes>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Line } from "react-chartjs-2";
|
import { Line } from "react-chartjs-2";
|
||||||
import { Chart as ChartJS, LineElement, PointElement, LinearScale, CategoryScale, Filler } from "chart.js";
|
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 "@/css/HistoryCharts.css";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
@@ -11,26 +11,29 @@ import { useConfig } from "@/hooks/useConfig";
|
|||||||
|
|
||||||
ChartJS.register(LineElement, PointElement, LinearScale, CategoryScale, Filler);
|
ChartJS.register(LineElement, PointElement, LinearScale, CategoryScale, Filler);
|
||||||
|
|
||||||
const HistoryCharts = () => {
|
const HistoryCharts = ({ groupId, deviceId }) => {
|
||||||
const { config, configLoading, configError } = useConfig();
|
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 BASE = config.appConfig.endpoints.DATA_URL;
|
if (configLoading) return <p>Cargando configuración...</p>;
|
||||||
const ENDPOINT = config.appConfig.endpoints.GET_SENSORS;
|
if (configError) return <p>Error al cargar configuración: {configError}</p>;
|
||||||
|
if (!config) return <p>Configuración no disponible.</p>;
|
||||||
|
|
||||||
const reqConfig = {
|
const BASE = config.appConfig.endpoints.DATA_URL;
|
||||||
baseUrl: `${BASE}${ENDPOINT}`,
|
const ENDPOINT = config.appConfig.endpoints.GET_DEVICE_HISTORY;
|
||||||
params: {}
|
const endp = ENDPOINT
|
||||||
}
|
.replace(':groupId', groupId)
|
||||||
|
.replace(':deviceId', deviceId); // si tu endpoint lo necesita
|
||||||
|
|
||||||
return (
|
const reqConfig = {
|
||||||
<DataProvider config={reqConfig}>
|
baseUrl: `${BASE}${endp}`,
|
||||||
<HistoryChartsContent />
|
params: {}
|
||||||
</DataProvider>
|
};
|
||||||
);
|
|
||||||
|
return (
|
||||||
|
<DataProvider config={reqConfig}>
|
||||||
|
<HistoryChartsContent />
|
||||||
|
</DataProvider>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const HistoryChartsContent = () => {
|
const HistoryChartsContent = () => {
|
||||||
@@ -92,6 +95,11 @@ const HistoryChartsContent = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
HistoryCharts.propTypes = {
|
||||||
|
groupId: PropTypes.string.isRequired,
|
||||||
|
deviceId: PropTypes.string.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
HistoryChartsContent.propTypes = {
|
HistoryChartsContent.propTypes = {
|
||||||
options: PropTypes.object,
|
options: PropTypes.object,
|
||||||
timeLabels: PropTypes.array,
|
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 PollutionMap = ({ groupId, deviceId }) => {
|
||||||
const { config, configLoading, configError } = useConfig();
|
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 BASE = config.appConfig.endpoints.LOGIC_URL;
|
if (configLoading) return <p>Cargando configuración...</p>;
|
||||||
const ENDPOINT = config.appConfig.endpoints.GET_DEVICE_POLLUTION_MAP;
|
if (configError) return <p>Error al cargar configuración: {configError}</p>;
|
||||||
let endp = ENDPOINT.replace('{0}', deviceId);
|
if (!config) return <p>Configuración no disponible.</p>;
|
||||||
|
|
||||||
const reqConfig = {
|
const BASE = config.appConfig.endpoints.LOGIC_URL;
|
||||||
baseUrl: `${BASE}${endp}`,
|
const ENDPOINT = config.appConfig.endpoints.GET_DEVICE_POLLUTION_MAP;
|
||||||
params: {}
|
const endp = ENDPOINT
|
||||||
}
|
.replace(':groupId', groupId)
|
||||||
|
.replace(':deviceId', deviceId);
|
||||||
|
|
||||||
return (
|
const reqConfig = {
|
||||||
<DataProvider config={reqConfig}>
|
baseUrl: `${BASE}${endp}`,
|
||||||
<PollutionMapContent />
|
params: {}
|
||||||
</DataProvider>
|
};
|
||||||
);
|
|
||||||
|
return (
|
||||||
|
<DataProvider config={reqConfig}>
|
||||||
|
<PollutionMapContent />
|
||||||
|
</DataProvider>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const PollutionMapContent = () => {
|
const PollutionMapContent = () => {
|
||||||
const { config, configLoading, configError } = useConfig();
|
const { config, configLoading, configError } = useConfig();
|
||||||
const { data, dataLoading, dataError } = useDataContext();
|
const { data, dataLoading, dataError } = useDataContext();
|
||||||
@@ -102,6 +105,7 @@ const mapStyles = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
PollutionMap.propTypes = {
|
PollutionMap.propTypes = {
|
||||||
|
groupId: PropTypes.number.isRequired,
|
||||||
deviceId: PropTypes.number.isRequired
|
deviceId: PropTypes.number.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import CardContainer from './CardContainer';
|
import CardContainer from './layout/CardContainer';
|
||||||
|
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { faCloud, faClock, faTemperature0, faWater } from '@fortawesome/free-solid-svg-icons';
|
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 { useConfig } from '@/hooks/useConfig.js';
|
||||||
import { DateParser } from '@/util/dateParser';
|
import { DateParser } from '@/util/dateParser';
|
||||||
|
|
||||||
const SummaryCards = ({ deviceId }) => {
|
const SummaryCards = ({ groupId, deviceId }) => {
|
||||||
const { config, configLoading, configError } = useConfig();
|
const { config, configLoading, configError } = useConfig();
|
||||||
|
|
||||||
if (configLoading) return <p>Cargando configuración...</p>;
|
if (configLoading) return <p>Cargando configuración...</p>;
|
||||||
@@ -19,19 +19,21 @@ const SummaryCards = ({ deviceId }) => {
|
|||||||
|
|
||||||
const BASE = config.appConfig.endpoints.LOGIC_URL;
|
const BASE = config.appConfig.endpoints.LOGIC_URL;
|
||||||
const ENDPOINT = config.appConfig.endpoints.GET_DEVICE_LATEST_VALUES;
|
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 = {
|
const reqConfig = {
|
||||||
baseUrl: `${BASE}${endp}`,
|
baseUrl: `${BASE}${endp}`,
|
||||||
params: {}
|
params: {}
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DataProvider config={reqConfig}>
|
<DataProvider config={reqConfig}>
|
||||||
<SummaryCardsContent />
|
<SummaryCardsContent />
|
||||||
</DataProvider>
|
</DataProvider>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
const SummaryCardsContent = () => {
|
const SummaryCardsContent = () => {
|
||||||
const { data, dataLoading, dataError } = useDataContext();
|
const { data, dataLoading, dataError } = useDataContext();
|
||||||
@@ -71,6 +73,7 @@ const SummaryCardsContent = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SummaryCards.propTypes = {
|
SummaryCards.propTypes = {
|
||||||
|
groupId: PropTypes.string.isRequired,
|
||||||
deviceId: PropTypes.number.isRequired
|
deviceId: PropTypes.number.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,29 @@
|
|||||||
import Card from "./Card.jsx";
|
import Card from "./Card.jsx";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
const CardContainer = ({ cards, className }) => {
|
const CardContainer = ({ links, cards, className }) => {
|
||||||
return (
|
return (
|
||||||
<div className={`row justify-content-center g-0 ${className}`}>
|
<div className={`row justify-content-center g-0 ${className}`}>
|
||||||
{cards.map((card, index) => (
|
{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}>
|
<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>
|
<p className="card-text text-center">{card.content}</p>
|
||||||
</Card>
|
</Card>
|
||||||
|
)
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
CardContainer.propTypes = {
|
CardContainer.propTypes = {
|
||||||
|
links: Boolean,
|
||||||
cards: PropTypes.arrayOf(
|
cards: PropTypes.arrayOf(
|
||||||
PropTypes.shape({
|
PropTypes.shape({
|
||||||
title: PropTypes.string.isRequired,
|
title: PropTypes.string.isRequired,
|
||||||
@@ -2,19 +2,18 @@ import PropTypes from 'prop-types';
|
|||||||
import '@/css/Header.css';
|
import '@/css/Header.css';
|
||||||
import { useTheme } from "@/hooks/useTheme";
|
import { useTheme } from "@/hooks/useTheme";
|
||||||
|
|
||||||
const Header = (props) => {
|
const Header = ({ subtitle }) => {
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className={`justify-content-center text-center mb-4 ${theme}`}>
|
<header className={`justify-content-center text-center mb-4 ${theme}`}>
|
||||||
<h1>{props.title}</h1>
|
<img src='/images/logo.png' width={500} />
|
||||||
<p className='subtitle'>{props.subtitle}</p>
|
<p className='subtitle'>{subtitle}</p>
|
||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Header.propTypes = {
|
Header.propTypes = {
|
||||||
title: PropTypes.string.isRequired,
|
|
||||||
subtitle: PropTypes.string
|
subtitle: PropTypes.string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -9,6 +9,8 @@ import { useDataContext } from "@/hooks/useDataContext";
|
|||||||
import { useConfig } from '@/hooks/useConfig.js';
|
import { useConfig } from '@/hooks/useConfig.js';
|
||||||
import { useTheme } from "@/hooks/useTheme";
|
import { useTheme } from "@/hooks/useTheme";
|
||||||
|
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
import Card from './Card';
|
import Card from './Card';
|
||||||
|
|
||||||
const SideMenu = ({ isOpen, onClose }) => {
|
const SideMenu = ({ isOpen, onClose }) => {
|
||||||
@@ -19,7 +21,7 @@ const SideMenu = ({ isOpen, onClose }) => {
|
|||||||
if (!config) return <p>Configuración no disponible.</p>;
|
if (!config) return <p>Configuración no disponible.</p>;
|
||||||
|
|
||||||
const BASE = config.appConfig.endpoints.DATA_URL;
|
const BASE = config.appConfig.endpoints.DATA_URL;
|
||||||
const ENDPOINT = config.appConfig.endpoints.GET_DEVICES;
|
const ENDPOINT = config.appConfig.endpoints.GET_GROUPS;
|
||||||
|
|
||||||
const reqConfig = {
|
const reqConfig = {
|
||||||
baseUrl: `${BASE}${ENDPOINT}`,
|
baseUrl: `${BASE}${ENDPOINT}`,
|
||||||
@@ -51,18 +53,18 @@ const SideMenuContent = ({ isOpen, onClose }) => {
|
|||||||
</button>
|
</button>
|
||||||
<hr className="separation w-100"></hr>
|
<hr className="separation w-100"></hr>
|
||||||
<div className="d-flex flex-column gap-3 mt-5">
|
<div className="d-flex flex-column gap-3 mt-5">
|
||||||
{data.map(device => {
|
{data.map(group => {
|
||||||
return (
|
return (
|
||||||
<a href={`/dashboard/${device.deviceId}`} key={device.deviceId} style={{ textDecoration: 'none' }}>
|
<Link to={`/groups/${group.groupId}`} key={group.groupId} style={{ textDecoration: 'none' }}>
|
||||||
<Card
|
<Card
|
||||||
title={device.deviceName}
|
title={group.groupName}
|
||||||
status={`ID: ${device.deviceId}`}
|
status={`ID: ${group.groupId}`}
|
||||||
styleMode={"override"}
|
styleMode={"override"}
|
||||||
className={"col-12"}
|
className={"col-12"}
|
||||||
>
|
>
|
||||||
{[]}
|
{[]}
|
||||||
</Card>
|
</Card>
|
||||||
</a>
|
</Link>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
@@ -2,7 +2,7 @@ import { StrictMode } from 'react'
|
|||||||
import { createRoot } from 'react-dom/client'
|
import { createRoot } from 'react-dom/client'
|
||||||
import { BrowserRouter } from 'react-router-dom'
|
import { BrowserRouter } from 'react-router-dom'
|
||||||
import './css/index.css'
|
import './css/index.css'
|
||||||
import App from './components/App.jsx'
|
import App from './App.jsx'
|
||||||
|
|
||||||
import { ThemeProvider } from './context/ThemeContext.jsx'
|
import { ThemeProvider } from './context/ThemeContext.jsx'
|
||||||
import { ConfigProvider } from './context/ConfigContext.jsx'
|
import { ConfigProvider } from './context/ConfigContext.jsx'
|
||||||
|
|||||||
@@ -4,32 +4,16 @@ import SummaryCards from '@/components/SummaryCards.jsx'
|
|||||||
|
|
||||||
import { useParams } from 'react-router-dom';
|
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 Dashboard = () => {
|
||||||
const { deviceId } = useParams();
|
const { groupId, deviceId } = useParams();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className='container justify-content-center'>
|
<main className='container justify-content-center'>
|
||||||
<SummaryCards deviceId={deviceId} />
|
<SummaryCards groupId={groupId} deviceId={deviceId} />
|
||||||
<PollutionMap deviceId={deviceId}/>
|
<PollutionMap groupId={groupId} deviceId={deviceId} />
|
||||||
<HistoryCharts deviceId={deviceId} />
|
<HistoryCharts groupId={groupId} deviceId={deviceId} />
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default Dashboard;
|
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