diff --git a/Interface/.gitignore b/Interface/.gitignore index a3f2ce2..9064298 100644 --- a/Interface/.gitignore +++ b/Interface/.gitignore @@ -1,4 +1,3 @@ -/src/contexts/SampleContext.tsx /test.http # Logs diff --git a/Interface/floor-plan.svg b/Interface/floor-plan.svg new file mode 100644 index 0000000..75b2117 --- /dev/null +++ b/Interface/floor-plan.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Interface/index.html b/Interface/index.html index 6cba438..8255f51 100644 --- a/Interface/index.html +++ b/Interface/index.html @@ -6,8 +6,8 @@ - -
+ +
diff --git a/Interface/src/App.tsx b/Interface/src/App.tsx index 0ce21ce..a13a740 100644 --- a/Interface/src/App.tsx +++ b/Interface/src/App.tsx @@ -8,13 +8,14 @@ function App() { const toggleDarkTheme = () => { document.documentElement.classList.toggle("dark") - const bodyColor = document.querySelector("body"); + const body = document.querySelector("body"); setDarkTheme(!darkTheme); - if (bodyColor) { + if (body) { + console.log(body) if (darkTheme) { - bodyColor.style.backgroundColor = "#F9F9F9"; + body.style.backgroundColor = "#F9F9F9"; } else { - bodyColor.style.backgroundColor = "rgb(12 23 42)"; + body.style.backgroundColor = "rgb(12 23 42)"; } } }; @@ -25,8 +26,7 @@ function App() {
- diff --git a/Interface/src/contexts/SampleContext.tsx b/Interface/src/contexts/SampleContext.tsx new file mode 100644 index 0000000..7b531c2 --- /dev/null +++ b/Interface/src/contexts/SampleContext.tsx @@ -0,0 +1,4 @@ +export const SampleContext = { + urlData: "http://postgrest.memoires-info.com", + urlLogin: "http://login.memoires-info.com" +}; diff --git a/Interface/src/contexts/lib.tsx b/Interface/src/contexts/lib.tsx index 8e18f52..8b685cd 100644 --- a/Interface/src/contexts/lib.tsx +++ b/Interface/src/contexts/lib.tsx @@ -10,7 +10,6 @@ export function getToken(){ } export function error401(response:Response){ if (response.status === 401) { - window.location.href = "http://localhost:5173/login"; } } @@ -25,4 +24,11 @@ export interface Data { temperature: number; humidity: number; timestamp: string; + ip:number; } +export interface Esp{ + x:number; + y:number; + ip:number; + "name": "test" +} \ No newline at end of file diff --git a/Interface/src/elements/CircularElement.tsx b/Interface/src/elements/CircularElement.tsx index 1172d9d..f93230c 100644 --- a/Interface/src/elements/CircularElement.tsx +++ b/Interface/src/elements/CircularElement.tsx @@ -18,7 +18,7 @@ export default function CircularElement({data, unity, color}: { data: number | s return (
-

+

{data} {unity}

diff --git a/Interface/src/elements/DashboardElement.tsx b/Interface/src/elements/DashboardElement.tsx index 43f2f81..4f0c2bb 100644 --- a/Interface/src/elements/DashboardElement.tsx +++ b/Interface/src/elements/DashboardElement.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useState } from 'react'; import CircularElementData from "./CircularElementData.tsx"; import MonthlyAverageStore, { AverageStore } from "./AverageStore.tsx"; import CardElement from "@/elements/CardElement.tsx"; @@ -8,22 +8,18 @@ import SideBarElement from "@/elements/SideBarElement.tsx"; export default function DashboardElement() { const d = new Date(); + const currentYear = d.getFullYear(); const monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; const [monthSelected, setMonthSelected] = useState(monthNames[d.getMonth()]); - const [startDate, setStartDate] = useState(null); const [endDate, setEndDate] = useState(null); + const handleMonthClick = (month: string) => { setMonthSelected(month); }; - useEffect(() => { - setStartDate(null); - setEndDate(null) - }, [monthSelected]); - return (
@@ -35,13 +31,13 @@ export default function DashboardElement() {
) diff --git a/Interface/src/elements/LoginElement.tsx b/Interface/src/elements/LoginElement.tsx index 5db49c7..c03f7ed 100644 --- a/Interface/src/elements/LoginElement.tsx +++ b/Interface/src/elements/LoginElement.tsx @@ -21,11 +21,9 @@ export default function LoginElement() { setError(reponse.error); } if (reponse.token) { - console.log(reponse.token) localStorage.setItem('token', reponse.token); localStorage.setItem('username', username); - window.location.href = window.location.href.concat("dashboard"); - + window.location.replace("http://localhost:5173/dashboard"); } }) .catch(e => { diff --git a/Interface/src/elements/PlanElement.tsx b/Interface/src/elements/PlanElement.tsx deleted file mode 100644 index fb8eda1..0000000 --- a/Interface/src/elements/PlanElement.tsx +++ /dev/null @@ -1,11 +0,0 @@ - - -export default function PlanElement() { - - - return ( - <> - - - ) -} diff --git a/Interface/src/elements/SideBarElement.tsx b/Interface/src/elements/SideBarElement.tsx index 1c7a0eb..371ce42 100644 --- a/Interface/src/elements/SideBarElement.tsx +++ b/Interface/src/elements/SideBarElement.tsx @@ -13,7 +13,7 @@ export default function SideBarElement() {
+ className="flex items-center p-2 text-gray-900 rounded-xl dark:text-white hover:bg-gray-100 dark:hover:bg-slate-950 group" to="/dashboard"> Inbox + className="flex items-center p-2 text-gray-900 rounded-xl dark:text-white hover:bg-gray-100 dark:hover:bg-slate-950 group" + to="/plan"> + floor plan icon + Plan + className="flex items-center p-2 text-gray-900 rounded-xl dark:text-white hover:bg-gray-100 dark:hover:bg-slate-950 group" to="/plan">
- +
diff --git a/Interface/src/elements/plan/AddRoomElement.tsx b/Interface/src/elements/plan/AddRoomElement.tsx new file mode 100644 index 0000000..0175063 --- /dev/null +++ b/Interface/src/elements/plan/AddRoomElement.tsx @@ -0,0 +1,70 @@ +import {useState} from "react"; +import {SampleContext} from "@/contexts/SampleContext.tsx"; +import {getToken} from "@/contexts/lib.tsx"; + +export default function AddRoomElement() { + const apiAuthToken = getToken(); + + const [roomName, setRoomName] = useState(''); + const [newIp, setNewIp] = useState(''); + const [error, setError] = useState(""); + + const addRoom = () => { + if (roomName && newIp) { + fetch(`${SampleContext.urlData}/esp`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${apiAuthToken}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + ip: newIp, + name: roomName + }) + }) + .then(response => { + if (!response.ok) { + throw new Error('Erreur lors de la requête'); + } + console.log('Requête POST réussie'); + }) + .catch(error => { + console.error('Une erreur s\'est produite:', error); + }); + setNewIp("") + setRoomName("") + setError("") + } else setError("veuillez remplir le champ") + + } + + return ( + <> +
{ + e.preventDefault(); + addRoom(); + }}> + setRoomName(e.target.value)} + className="mt-5 tracking-wide font-semibold text-white-500 w-full px-4 py-4 rounded-lg transition-all duration-300 ease-in-out flex items-center justify-center focus:shadow-outline focus:outline-none" + type="text" placeholder="Room name"/> + + setNewIp(e.target.value)} + className="mt-5 tracking-wide font-semibold text-white-500 w-full px-4 py-4 rounded-lg transition-all duration-300 ease-in-out flex items-center justify-center focus:shadow-outline focus:outline-none" + type="text" placeholder="New IP"/> + + +
+ {error} + + ) +} diff --git a/Interface/src/elements/plan/CanvasPlan.tsx b/Interface/src/elements/plan/CanvasPlan.tsx new file mode 100644 index 0000000..0fc3c04 --- /dev/null +++ b/Interface/src/elements/plan/CanvasPlan.tsx @@ -0,0 +1,202 @@ +import {useEffect, useState} from 'react'; +import {SampleContext} from "@/contexts/SampleContext.tsx"; +import {Data, Esp, getToken} from "@/contexts/lib.tsx"; + +export default function CanvasPlan() { + const [espData, setEspData] = useState([]); + const [groupedData, setGroupedData] = useState<{ [key: string]: Data[] }>({}); + + + useEffect(() => { + fetch(`${SampleContext.urlData}/esp`, {"headers": {"Authorization": `Bearer ${getToken()}`}}) + .then(response => response.json()) + .then((dataEsp: Esp[]) => { + setEspData(dataEsp); + console.log(dataEsp) + }) + .catch(error => { + console.error('Une erreur s\'est produite:', error); + }); + }, []); + useEffect(() => { + fetch(`${SampleContext.urlData}/data`, {"headers": {"Authorization": `Bearer ${getToken()}`}}) + .then(response => response.json()) + .then((apiData: Data[]) => { + const groupedData: { [key: string]: Data[] } = {}; + espData.forEach((esp) => { + groupedData[esp.name] = apiData.filter((data) => data.ip === esp.ip); + }); + setGroupedData(groupedData); + }) + .catch(error => { + console.error('Une erreur s\'est produite:', error); + }); + }, [espData]); + + return ( +
+ + + + + + + + + + + + + + + + + + 23℃ + 24℃ + 25℃ + 24℃ + + + {espData.map(esp => { + const espGroup = groupedData[esp.name] || []; + const x = espGroup.length > 0 ? esp.x : 0; + const y = espGroup.length > 0 ? esp.y : 0; + return ( + + ); + })} + + + +
+ ); +} + diff --git a/Interface/src/elements/plan/CardPlanElement.tsx b/Interface/src/elements/plan/CardPlanElement.tsx new file mode 100644 index 0000000..b402f8a --- /dev/null +++ b/Interface/src/elements/plan/CardPlanElement.tsx @@ -0,0 +1,51 @@ +import {Data} from "@/contexts/lib.tsx"; +import CircularElement from "@/elements/CircularElement.tsx"; +import CardElement from "@/elements/CardElement.tsx"; + +export default function CardPlanElement({room, data}: { room: string, data: Data[] }) { + const calculateAverage = (values: number[]): number => { + if (values.length === 0) return 0; + const sum = values.reduce((acc, value) => acc + value, 0); + return sum / values.length; + }; + + const temperatures: number[] = []; + const humidities: number[] = []; + + data.forEach((item) => { + if (item.temperature) temperatures.push(item.temperature); + if (item.humidity) humidities.push(item.humidity); + }); + + const averageTemperature = calculateAverage(temperatures); + const averageHumidity = calculateAverage(humidities); + + return ( + <> +
+
+ {room} +
+ +
+ + } + theme="Humidity"/> + + + } + theme="Humidity"/> +
+ +
+ + ) +} diff --git a/Interface/src/elements/plan/PlanElement.tsx b/Interface/src/elements/plan/PlanElement.tsx new file mode 100644 index 0000000..c7ef7d2 --- /dev/null +++ b/Interface/src/elements/plan/PlanElement.tsx @@ -0,0 +1,71 @@ +import CardPlanElement from "@/elements/plan/CardPlanElement.tsx"; +import SideBarElement from "@/elements/SideBarElement.tsx"; +import {useEffect, useState} from "react"; +import {SampleContext} from "@/contexts/SampleContext.tsx"; +import {Data, Esp, getToken} from "@/contexts/lib.tsx"; +import CanvasPlan from "@/elements/plan/CanvasPlan.tsx"; + +export default function PlanElement() { + + const [espData, setEspData] = useState([]); + const [groupedData, setGroupedData] = useState<{ [key: string]: Data[] }>({}); + + useEffect(() => { + fetch(`${SampleContext.urlData}/esp`, {"headers": {"Authorization": `Bearer ${getToken()}`}}) + .then(response => response.json()) + .then((dataEsp: Esp[]) => { + setEspData(dataEsp); + console.log(dataEsp) + }) + .catch(error => { + console.error('Une erreur s\'est produite:', error); + }); + }, []); + + useEffect(() => { + fetch(`${SampleContext.urlData}/data`, {"headers": {"Authorization": `Bearer ${getToken()}`}}) + .then(response => response.json()) + .then((apiData: Data[]) => { + const groupedData: { [key: string]: Data[] } = {}; + espData.forEach((esp) => { + groupedData[esp.name] = apiData.filter((data) => data.ip === esp.ip); + }); + setGroupedData(groupedData); + }) + .catch(error => { + console.error('Une erreur s\'est produite:', error); + }); + }, [espData]); + + + return ( + <> +
+
+ +
+ +
+ + {Object.keys(groupedData).map((name) => ( + + + ))} +
+ + +
+ + ) +} \ No newline at end of file diff --git a/Interface/src/main.tsx b/Interface/src/main.tsx index 1946f2c..844b954 100644 --- a/Interface/src/main.tsx +++ b/Interface/src/main.tsx @@ -4,7 +4,7 @@ import App from './App.tsx'; import './index.css'; import { createBrowserRouter, RouterProvider} from 'react-router-dom'; import DashboardElement from "./elements/DashboardElement.tsx"; -import PlanElement from "@/elements/PlanElement.tsx"; +import PlanElement from "@/elements/plan/PlanElement.tsx"; import LoginElement from "@/elements/LoginElement.tsx"; const isAuthenticated = () => { diff --git a/database/db.sql b/database/db.sql index bba0a92..38bea23 100644 --- a/database/db.sql +++ b/database/db.sql @@ -82,3 +82,11 @@ begin order by date; end; $$ language plpgsql; + +-- add table to store esp32 names +create table esp( + ip varchar(15) primary key not null, + name varchar(50) unique not null +); + +grant insert, select, update, delete on esp to web_user; \ No newline at end of file