Ajout de la page d'info d'un objet

This commit is contained in:
Mathis 2025-03-30 17:10:40 +02:00
parent 40179a548a
commit d229dc9467
26 changed files with 1372 additions and 170 deletions

View File

@ -1,17 +1,12 @@
package com.example.starter; package com.example.starter;
import io.vertx.ext.web.handler.BodyHandler;
import io.vertx.ext.web.handler.CorsHandler; import io.vertx.ext.web.handler.CorsHandler;
import io.vertx.sqlclient.Row;
import io.vertx.sqlclient.RowSet;
import io.vertx.sqlclient.Tuple;
import io.vertx.core.http.HttpMethod; import io.vertx.core.http.HttpMethod;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.core.AbstractVerticle; import io.vertx.core.AbstractVerticle;
import io.vertx.core.Promise; import io.vertx.core.Promise;
import io.vertx.ext.web.Router; import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
public class MainVerticle extends AbstractVerticle { public class MainVerticle extends AbstractVerticle {
private DatabaseService databaseService; private DatabaseService databaseService;
@ -19,17 +14,22 @@ public class MainVerticle extends AbstractVerticle {
@Override @Override
public void start(Promise<Void> startPromise) throws Exception { public void start(Promise<Void> startPromise) throws Exception {
databaseService = new DatabaseService(vertx); databaseService = new DatabaseService(vertx);
QueryObjects queryObjects = new QueryObjects(databaseService);
QueryWeatherData queryWeather = new QueryWeatherData(databaseService);
// Create a Router // Create a Router
Router router = Router.router(vertx); Router router = Router.router(vertx);
router.route().handler(BodyHandler.create());
router.route().handler(CorsHandler.create() router.route().handler(CorsHandler.create()
.addOrigin("*") // Allow all origins .addOrigin("*") // Allow all origins
.allowedMethod(HttpMethod.GET) // Allow GET requests .allowedMethod(HttpMethod.GET) // Allow GET requests
.allowedMethod(HttpMethod.POST) // Allow POST requests .allowedMethod(HttpMethod.POST) // Allow POST requests
.allowedHeader("Content-Type") // Allow Content-Type header .allowedHeader("Content-Type") // Allow Content-Type header
.allowedHeader("Authorization")); .allowedHeader("Authorization"));
router.get("/objets").handler(this::getObjects); router.get("/objets").handler(queryObjects::getObjects);
router.get("/objet").handler(this::getParticularObject); router.get("/objet").handler(queryObjects::getParticularObject);
// Create the HTTP server router.post("/modifObjet").handler(queryObjects::setInfoObjet);
router.get("/wind").handler(queryWeather::getWindInfos);
router.get("/meteo").handler(queryWeather::getMeteoInfos);
vertx.createHttpServer() vertx.createHttpServer()
.requestHandler(router) .requestHandler(router)
@ -43,65 +43,4 @@ public class MainVerticle extends AbstractVerticle {
startPromise.fail(throwable); startPromise.fail(throwable);
}); });
} }
private void getObjects(RoutingContext context) {
databaseService.pool
.query("SELECT * FROM weather_objects;")
.execute()
.onFailure(e -> {
System.err.println("Erreur de récupération de la BDD :" + e.getMessage());
context.response()
.setStatusCode(500)
.end(new JsonObject().put("error", "Erreur de récupération de la BDD").encode());
})
.onSuccess(rows -> {
context.response()
.putHeader("content-type", "application/json; charset=UTF-8")
.end(getInfosObjects(rows).encode());
});
}
private void getParticularObject(RoutingContext context) {
String id= context.request().getParam("id");
if(id==null){
context.response()
.setStatusCode(400)
.end(new JsonObject().put("error", "Paramètre 'id' manquant").encode());
return;
}
databaseService.pool
.preparedQuery("SELECT * FROM weather_objects WHERE id=?")
.execute(Tuple.of(Integer.parseInt(id)))
.onFailure(e -> {
System.err.println("Erreur de récupération de la BDD :" + e.getMessage());
context.response()
.setStatusCode(500)
.end(new JsonObject().put("Erreur","Erreur de récupération de la BDD").encode());
})
.onSuccess(rows -> {
if (rows.size() == 0) {
context.response()
.setStatusCode(404)
.end(new JsonObject().put("error", "Objet non trouvé").encode());
return;
}
context.response()
.putHeader("content-type","application/json: charset=UTF-8")
.end(getInfosObjects(rows).encode());
});
}
private JsonArray getInfosObjects(RowSet<Row> rows){
JsonArray objects = new JsonArray();
for (Row row : rows) {
JsonObject object = new JsonObject()
.put("id", row.getInteger("id"))
.put("name", row.getString("name"))
.put("description", row.getString("description"))
.put("type", row.getString("type"))
.put("location", row.getString("location"));
objects.add(object);
}
return objects;
}
} }

View File

@ -1,5 +0,0 @@
package com.example.starter;
public class ProductHandler {
}

View File

@ -0,0 +1,124 @@
package com.example.starter;
import io.vertx.sqlclient.Row;
import io.vertx.sqlclient.RowSet;
import io.vertx.sqlclient.Tuple;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import java.time.format.DateTimeFormatter;
import io.vertx.ext.web.RoutingContext;
public class QueryObjects {
private DatabaseService databaseService;
public QueryObjects(DatabaseService dtbS){
this.databaseService = dtbS;
}
public void getObjects(RoutingContext context) {
databaseService.pool
.query("SELECT * FROM weather_objects;")
.execute()
.onFailure(e -> {
System.err.println("Erreur de récupération de la BDD :" + e.getMessage());
context.response()
.setStatusCode(500)
.end(new JsonObject().put("error", "Erreur de récupération de la BDD").encode());
})
.onSuccess(rows -> {
context.response()
.putHeader("content-type", "application/json; charset=UTF-8")
.end(getInfosObjects(rows).encode());
});
}
public void getParticularObject(RoutingContext context) {
String id = context.request().getParam("id");
if (id == null) {
context.response()
.setStatusCode(400)
.end(new JsonObject().put("error", "Paramètre 'id' manquant").encode());
return;
}
databaseService.pool
.preparedQuery("SELECT * FROM weather_objects WHERE id=?")
.execute(Tuple.of(Integer.parseInt(id)))
.onFailure(e -> {
System.err.println("Erreur de récupération de la BDD :" + e.getMessage());
context.response()
.setStatusCode(500)
.end(new JsonObject().put("Erreur", "Erreur de récupération de la BDD").encode());
})
.onSuccess(rows -> {
if (rows.size() == 0) {
context.response()
.setStatusCode(404)
.end(new JsonObject().put("error", "Objet non trouvé").encode());
return;
}
context.response()
.putHeader("content-type", "application/json: charset=UTF-8")
.end(getInfosObjects(rows).encode());
});
}
private JsonArray getInfosObjects(RowSet<Row> rows) {
JsonArray objects = new JsonArray();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm:ss");
for (Row row : rows) {
JsonObject object = new JsonObject()
.put("id", row.getInteger("id"))
.put("name", row.getString("name"))
.put("description", row.getString("description"))
.put("type", row.getString("type"))
.put("location", row.getString("location"))
.put("last_update", row.getLocalDateTime("last_update").format(formatter))
.put("status", row.getString("status"));
objects.add(object);
}
return objects;
}
public void setInfoObjet(RoutingContext context) {
JsonObject body = context.body().asJsonObject();
if (body == null) {
context.response()
.setStatusCode(400)
.end(new JsonObject().put("error", "Corps de la requête manquant").encode());
return;
}
Integer id = body.getInteger("id");
String description = body.getString("description");
String type = body.getString("type");
String location = body.getString("location");
String status = body.getString("status");
databaseService.pool
.preparedQuery(
"UPDATE weather_objects SET description=?,type=?,location=?,status=?,last_update=CURRENT_TIMESTAMP WHERE id=?")
.execute(Tuple.of(description, type, location, status, id))
.onFailure(e -> {
System.err.println("Erreur de récupération de la BDD :" + e.getMessage());
context.response()
.setStatusCode(500)
.end(new JsonObject().put("Erreur", "Erreur de récupération de la BDD").encode());
})
.onSuccess(rows -> {
if (rows.rowCount() == 0) {
context.response()
.setStatusCode(404)
.end(new JsonObject().put("error", "Objet non trouvé").encode());
return;
}
context.response()
.putHeader("content-type", "application/json: charset=UTF-8")
.end(new JsonObject().put("success", "L'objet à bien été mis à jour").encode());
return;
});
}
}

View File

@ -0,0 +1,99 @@
package com.example.starter;
import io.vertx.sqlclient.Row;
import io.vertx.sqlclient.RowSet;
import io.vertx.sqlclient.Tuple;
import java.time.format.DateTimeFormatter;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.RoutingContext;
public class QueryWeatherData {
private DatabaseService databaseService;
public QueryWeatherData(DatabaseService dtbS) {
this.databaseService = dtbS;
}
public void getWindInfos(RoutingContext context) {
String id = context.request().getParam("id");
if (id == null) {
context.response()
.setStatusCode(400)
.end(new JsonObject().put("error", "Paramètre 'id' manquant").encode());
return;
}
databaseService.pool
.preparedQuery("SELECT wind_speed,wind_direction,timestamp FROM weather_data WHERE station_id=?")
.execute(Tuple.of(Integer.parseInt(id)))
.onFailure(e -> {
System.err.println("Erreur de récupération de la BDD :" + e.getMessage());
context.response()
.setStatusCode(500)
.end(new JsonObject().put("Erreur", "Erreur de récupération de la BDD").encode());
})
.onSuccess(rows -> {
if (rows.size() == 0) {
context.response()
.setStatusCode(404)
.end(new JsonObject().put("error", "Objet non trouvé").encode());
return;
}
context.response()
.putHeader("content-type", "application/json: charset=UTF-8")
.end((convertRowsToJson(rows)).encode());
});
}
public void getMeteoInfos(RoutingContext context) {
String id = context.request().getParam("id");
if (id == null) {
context.response()
.setStatusCode(400)
.end(new JsonObject().put("error", "Paramètre 'id' manquant").encode());
return;
}
databaseService.pool
.preparedQuery("SELECT temperature,humidity,pressure,timestamp FROM weather_data WHERE station_id=?")
.execute(Tuple.of(Integer.parseInt(id)))
.onFailure(e -> {
System.err.println("Erreur de récupération de la BDD :" + e.getMessage());
context.response()
.setStatusCode(500)
.end(new JsonObject().put("Erreur", "Erreur de récupération de la BDD").encode());
})
.onSuccess(rows -> {
if (rows.size() == 0) {
context.response()
.setStatusCode(404)
.end(new JsonObject().put("error", "Objet non trouvé").encode());
return;
}
context.response()
.putHeader("content-type", "application/json: charset=UTF-8")
.end((convertRowsToJson(rows)).encode());
});
}
private JsonArray convertRowsToJson(RowSet<Row> rows) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm:ss");
JsonArray jsonArray = new JsonArray();
for (Row row : rows) {
JsonObject jsonObject = new JsonObject();
for (int i = 0; i < row.size(); i++) {
String column = row.getColumnName(i);
if(column.compareTo("timestamp") == 0){
jsonObject.put("timestamp",row.getLocalDateTime("timestamp").format(formatter));
}else{
jsonObject.put(column, row.getValue(column));
}
}
jsonArray.add(jsonObject);
}
return jsonArray;
}
}

View File

@ -12,8 +12,10 @@
"axios": "^1.8.4", "axios": "^1.8.4",
"lucide-react": "^0.427.0", "lucide-react": "^0.427.0",
"react": "^18.3.1", "react": "^18.3.1",
"react-charts": "^3.0.0-beta.57",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-router-dom": "^7.4.0" "react-router-dom": "^7.4.0",
"recharts": "^2.15.1"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.9.1", "@eslint/js": "^9.9.1",
@ -262,6 +264,18 @@
"@babel/core": "^7.0.0-0" "@babel/core": "^7.0.0-0"
} }
}, },
"node_modules/@babel/runtime": {
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz",
"integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==",
"license": "MIT",
"dependencies": {
"regenerator-runtime": "^0.14.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/template": { "node_modules/@babel/template": {
"version": "7.26.9", "version": "7.26.9",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz",
@ -1302,6 +1316,69 @@
"integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/d3-array": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz",
"integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==",
"license": "MIT"
},
"node_modules/@types/d3-color": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
"integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==",
"license": "MIT"
},
"node_modules/@types/d3-ease": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz",
"integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==",
"license": "MIT"
},
"node_modules/@types/d3-interpolate": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
"integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
"license": "MIT",
"dependencies": {
"@types/d3-color": "*"
}
},
"node_modules/@types/d3-path": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz",
"integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==",
"license": "MIT"
},
"node_modules/@types/d3-scale": {
"version": "4.0.9",
"resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz",
"integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==",
"license": "MIT",
"dependencies": {
"@types/d3-time": "*"
}
},
"node_modules/@types/d3-shape": {
"version": "3.1.7",
"resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz",
"integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==",
"license": "MIT",
"dependencies": {
"@types/d3-path": "*"
}
},
"node_modules/@types/d3-time": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz",
"integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==",
"license": "MIT"
},
"node_modules/@types/d3-timer": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz",
"integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==",
"license": "MIT"
},
"node_modules/@types/estree": { "node_modules/@types/estree": {
"version": "1.0.7", "version": "1.0.7",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
@ -1314,6 +1391,44 @@
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
"dev": true "dev": true
}, },
"node_modules/@types/prop-types": {
"version": "15.7.14",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz",
"integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==",
"license": "MIT"
},
"node_modules/@types/raf": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz",
"integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==",
"license": "MIT"
},
"node_modules/@types/react": {
"version": "17.0.85",
"resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.85.tgz",
"integrity": "sha512-5oBDUsRDsrYq4DdyHaL99gE1AJCfuDhyxqF6/55fvvOIRkp1PpKuwJ+aMiGJR+GJt7YqMNclPROTHF20vY2cXA==",
"license": "MIT",
"dependencies": {
"@types/prop-types": "*",
"@types/scheduler": "^0.16",
"csstype": "^3.0.2"
}
},
"node_modules/@types/react-dom": {
"version": "17.0.26",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.26.tgz",
"integrity": "sha512-Z+2VcYXJwOqQ79HreLU/1fyQ88eXSSFh6I3JdrEHQIfYSI0kCQpTGvOrbE6jFGGYXKsHuwY9tBa/w5Uo6KzrEg==",
"license": "MIT",
"peerDependencies": {
"@types/react": "^17.0.0"
}
},
"node_modules/@types/scheduler": {
"version": "0.16.8",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz",
"integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==",
"license": "MIT"
},
"node_modules/@vitejs/plugin-react": { "node_modules/@vitejs/plugin-react": {
"version": "4.3.4", "version": "4.3.4",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.4.tgz", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.4.tgz",
@ -1657,6 +1772,15 @@
"node": ">= 6" "node": ">= 6"
} }
}, },
"node_modules/clsx": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/color-convert": { "node_modules/color-convert": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@ -1743,6 +1867,127 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"license": "MIT"
},
"node_modules/d3-array": {
"version": "2.12.1",
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz",
"integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==",
"license": "BSD-3-Clause",
"dependencies": {
"internmap": "^1.0.0"
}
},
"node_modules/d3-color": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-2.0.0.tgz",
"integrity": "sha512-SPXi0TSKPD4g9tw0NMZFnR95XVgUZiBH+uUTqQuDu1OsE2zomHU7ho0FISciaPvosimixwHFl3WHLGabv6dDgQ==",
"license": "BSD-3-Clause"
},
"node_modules/d3-delaunay": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-5.3.0.tgz",
"integrity": "sha512-amALSrOllWVLaHTnDLHwMIiz0d1bBu9gZXd1FiLfXf8sHcX9jrcj81TVZOqD4UX7MgBZZ07c8GxzEgBpJqc74w==",
"license": "ISC",
"dependencies": {
"delaunator": "4"
}
},
"node_modules/d3-ease": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
"integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-format": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-2.0.0.tgz",
"integrity": "sha512-Ab3S6XuE/Q+flY96HXT0jOXcM4EAClYFnRGY5zsjRGNy6qCYrQsMffs7cV5Q9xejb35zxW5hf/guKw34kvIKsA==",
"license": "BSD-3-Clause"
},
"node_modules/d3-interpolate": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-2.0.1.tgz",
"integrity": "sha512-c5UhwwTs/yybcmTpAVqwSFl6vrQ8JZJoT5F7xNFK9pymv5C0Ymcc9/LIJHtYIggg/yS9YHw8i8O8tgb9pupjeQ==",
"license": "BSD-3-Clause",
"dependencies": {
"d3-color": "1 - 2"
}
},
"node_modules/d3-path": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/d3-path/-/d3-path-2.0.0.tgz",
"integrity": "sha512-ZwZQxKhBnv9yHaiWd6ZU4x5BtCQ7pXszEV9CU6kRgwIQVQGLMv1oiL4M+MK/n79sYzsj+gcgpPQSctJUsLN7fA==",
"license": "BSD-3-Clause"
},
"node_modules/d3-scale": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-3.3.0.tgz",
"integrity": "sha512-1JGp44NQCt5d1g+Yy+GeOnZP7xHo0ii8zsQp6PGzd+C1/dl0KGsp9A7Mxwp+1D1o4unbTTxVdU/ZOIEBoeZPbQ==",
"license": "BSD-3-Clause",
"dependencies": {
"d3-array": "^2.3.0",
"d3-format": "1 - 2",
"d3-interpolate": "1.2.0 - 2",
"d3-time": "^2.1.1",
"d3-time-format": "2 - 3"
}
},
"node_modules/d3-scale/node_modules/d3-time-format": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-3.0.0.tgz",
"integrity": "sha512-UXJh6EKsHBTjopVqZBhFysQcoXSv/5yLONZvkQ5Kk3qbwiUYkdX17Xa1PT6U1ZWXGGfB1ey5L8dKMlFq2DO0Ag==",
"license": "BSD-3-Clause",
"dependencies": {
"d3-time": "1 - 2"
}
},
"node_modules/d3-shape": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-2.1.0.tgz",
"integrity": "sha512-PnjUqfM2PpskbSLTJvAzp2Wv4CZsnAgTfcVRTwW03QR3MkXF8Uo7B1y/lWkAsmbKwuecto++4NlsYcvYpXpTHA==",
"license": "BSD-3-Clause",
"dependencies": {
"d3-path": "1 - 2"
}
},
"node_modules/d3-time": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-2.1.1.tgz",
"integrity": "sha512-/eIQe/eR4kCQwq7yxi7z4c6qEXf2IYGcjoWB5OOQy4Tq9Uv39/947qlDcN2TLkiTzQWzvnsuYPB9TrWaNfipKQ==",
"license": "BSD-3-Clause",
"dependencies": {
"d3-array": "2"
}
},
"node_modules/d3-time-format": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
"integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
"license": "ISC",
"dependencies": {
"d3-time": "1 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-timer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
"integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/debug": { "node_modules/debug": {
"version": "4.4.0", "version": "4.4.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
@ -1760,12 +2005,24 @@
} }
} }
}, },
"node_modules/decimal.js-light": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
"integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==",
"license": "MIT"
},
"node_modules/deep-is": { "node_modules/deep-is": {
"version": "0.1.4", "version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
"dev": true "dev": true
}, },
"node_modules/delaunator": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/delaunator/-/delaunator-4.0.1.tgz",
"integrity": "sha512-WNPWi1IRKZfCt/qIDMfERkDp93+iZEmOxN2yy4Jg+Xhv8SLk2UTqqbe1sfiipn0and9QrE914/ihdx82Y/Giag==",
"license": "ISC"
},
"node_modules/delayed-stream": { "node_modules/delayed-stream": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@ -1787,6 +2044,16 @@
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
"dev": true "dev": true
}, },
"node_modules/dom-helpers": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
"integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.8.7",
"csstype": "^3.0.2"
}
},
"node_modules/dunder-proto": { "node_modules/dunder-proto": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@ -2091,12 +2358,27 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/eventemitter3": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
"license": "MIT"
},
"node_modules/fast-deep-equal": { "node_modules/fast-deep-equal": {
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"dev": true "dev": true
}, },
"node_modules/fast-equals": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.2.2.tgz",
"integrity": "sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw==",
"license": "MIT",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/fast-glob": { "node_modules/fast-glob": {
"version": "3.3.3", "version": "3.3.3",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
@ -2498,6 +2780,12 @@
"node": ">=0.8.19" "node": ">=0.8.19"
} }
}, },
"node_modules/internmap": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz",
"integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==",
"license": "ISC"
},
"node_modules/is-binary-path": { "node_modules/is-binary-path": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
@ -2708,6 +2996,12 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"license": "MIT"
},
"node_modules/lodash.merge": { "node_modules/lodash.merge": {
"version": "4.6.2", "version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@ -2885,7 +3179,6 @@
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"dev": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@ -3198,6 +3491,23 @@
"node": ">= 0.8.0" "node": ">= 0.8.0"
} }
}, },
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
"license": "MIT",
"dependencies": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
"react-is": "^16.13.1"
}
},
"node_modules/prop-types/node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"license": "MIT"
},
"node_modules/proxy-from-env": { "node_modules/proxy-from-env": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
@ -3244,6 +3554,36 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/react-charts": {
"version": "3.0.0-beta.57",
"resolved": "https://registry.npmjs.org/react-charts/-/react-charts-3.0.0-beta.57.tgz",
"integrity": "sha512-vqas7IQhsnDGcMxreGaWXvSIL3poEMoUBNltJrslz/+m0pI3QejBCszL1QrLNYQfOWXrbZADfedi/a+yWOQ7Hw==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.14.6",
"@types/d3-array": "^3.0.1",
"@types/d3-scale": "^4.0.1",
"@types/d3-shape": "^3.0.1",
"@types/raf": "^3.4.0",
"@types/react": "^17.0.14",
"@types/react-dom": "^17.0.9",
"d3-array": "^2.12.1",
"d3-delaunay": "5.3.0",
"d3-scale": "^3.3.0",
"d3-shape": "^2.1.0",
"d3-time": "^2.1.1",
"d3-time-format": "^4.1.0",
"ts-toolbelt": "^9.6.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
},
"peerDependencies": {
"react": ">=16",
"react-dom": ">=16"
}
},
"node_modules/react-dom": { "node_modules/react-dom": {
"version": "18.3.1", "version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
@ -3256,6 +3596,12 @@
"react": "^18.3.1" "react": "^18.3.1"
} }
}, },
"node_modules/react-is": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
"license": "MIT"
},
"node_modules/react-refresh": { "node_modules/react-refresh": {
"version": "0.14.2", "version": "0.14.2",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz",
@ -3305,6 +3651,37 @@
"react-dom": ">=18" "react-dom": ">=18"
} }
}, },
"node_modules/react-smooth": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz",
"integrity": "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==",
"license": "MIT",
"dependencies": {
"fast-equals": "^5.0.1",
"prop-types": "^15.8.1",
"react-transition-group": "^4.4.5"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/react-transition-group": {
"version": "4.4.5",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
"integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
"license": "BSD-3-Clause",
"dependencies": {
"@babel/runtime": "^7.5.5",
"dom-helpers": "^5.0.1",
"loose-envify": "^1.4.0",
"prop-types": "^15.6.2"
},
"peerDependencies": {
"react": ">=16.6.0",
"react-dom": ">=16.6.0"
}
},
"node_modules/read-cache": { "node_modules/read-cache": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@ -3326,6 +3703,44 @@
"node": ">=8.10.0" "node": ">=8.10.0"
} }
}, },
"node_modules/recharts": {
"version": "2.15.1",
"resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.1.tgz",
"integrity": "sha512-v8PUTUlyiDe56qUj82w/EDVuzEFXwEHp9/xOowGAZwfLjB9uAy3GllQVIYMWF6nU+qibx85WF75zD7AjqoT54Q==",
"license": "MIT",
"dependencies": {
"clsx": "^2.0.0",
"eventemitter3": "^4.0.1",
"lodash": "^4.17.21",
"react-is": "^18.3.1",
"react-smooth": "^4.0.4",
"recharts-scale": "^0.4.4",
"tiny-invariant": "^1.3.1",
"victory-vendor": "^36.6.8"
},
"engines": {
"node": ">=14"
},
"peerDependencies": {
"react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/recharts-scale": {
"version": "0.4.5",
"resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz",
"integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==",
"license": "MIT",
"dependencies": {
"decimal.js-light": "^2.4.1"
}
},
"node_modules/regenerator-runtime": {
"version": "0.14.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
"license": "MIT"
},
"node_modules/resolve": { "node_modules/resolve": {
"version": "1.22.10", "version": "1.22.10",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
@ -3710,6 +4125,12 @@
"node": ">=0.8" "node": ">=0.8"
} }
}, },
"node_modules/tiny-invariant": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
"integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
"license": "MIT"
},
"node_modules/to-regex-range": { "node_modules/to-regex-range": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@ -3728,6 +4149,12 @@
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
"dev": true "dev": true
}, },
"node_modules/ts-toolbelt": {
"version": "9.6.0",
"resolved": "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-9.6.0.tgz",
"integrity": "sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w==",
"license": "Apache-2.0"
},
"node_modules/turbo-stream": { "node_modules/turbo-stream": {
"version": "2.4.0", "version": "2.4.0",
"resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz", "resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz",
@ -3791,6 +4218,101 @@
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"dev": true "dev": true
}, },
"node_modules/victory-vendor": {
"version": "36.9.2",
"resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz",
"integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==",
"license": "MIT AND ISC",
"dependencies": {
"@types/d3-array": "^3.0.3",
"@types/d3-ease": "^3.0.0",
"@types/d3-interpolate": "^3.0.1",
"@types/d3-scale": "^4.0.2",
"@types/d3-shape": "^3.1.0",
"@types/d3-time": "^3.0.0",
"@types/d3-timer": "^3.0.0",
"d3-array": "^3.1.6",
"d3-ease": "^3.0.1",
"d3-interpolate": "^3.0.1",
"d3-scale": "^4.0.2",
"d3-shape": "^3.1.0",
"d3-time": "^3.0.0",
"d3-timer": "^3.0.1"
}
},
"node_modules/victory-vendor/node_modules/d3-array": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
"integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
"license": "ISC",
"dependencies": {
"internmap": "1 - 2"
},
"engines": {
"node": ">=12"
}
},
"node_modules/victory-vendor/node_modules/d3-interpolate": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
"integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
"license": "ISC",
"dependencies": {
"d3-color": "1 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/victory-vendor/node_modules/d3-path": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz",
"integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/victory-vendor/node_modules/d3-scale": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
"integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
"license": "ISC",
"dependencies": {
"d3-array": "2.10.0 - 3",
"d3-format": "1 - 3",
"d3-interpolate": "1.2.0 - 3",
"d3-time": "2.1.1 - 3",
"d3-time-format": "2 - 4"
},
"engines": {
"node": ">=12"
}
},
"node_modules/victory-vendor/node_modules/d3-shape": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
"integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
"license": "ISC",
"dependencies": {
"d3-path": "^3.1.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/victory-vendor/node_modules/d3-time": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
"integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
"license": "ISC",
"dependencies": {
"d3-array": "2 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/vite": { "node_modules/vite": {
"version": "5.4.15", "version": "5.4.15",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.15.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.15.tgz",

View File

@ -13,8 +13,10 @@
"axios": "^1.8.4", "axios": "^1.8.4",
"lucide-react": "^0.427.0", "lucide-react": "^0.427.0",
"react": "^18.3.1", "react": "^18.3.1",
"react-charts": "^3.0.0-beta.57",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-router-dom": "^7.4.0" "react-router-dom": "^7.4.0",
"recharts": "^2.15.1"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.9.1", "@eslint/js": "^9.9.1",

View File

@ -0,0 +1,21 @@
import React from "react";
import { ChartLine } from "lucide-react";
function BoutonGraphique({ TypeAff, setAffichage }) {
return !TypeAff ? (
<button
className="bg-blue-200 py-2 my-2 px-4 rounded-full mr-2"
onClick={() => setAffichage(true)}
>
<ChartLine className="text-red-500" size={24} />
</button>
) : (
<button
className="bg-blue-400 py-2 my-2 px-4 rounded-full mr-2"
onClick={() => setAffichage(false)}
>
<ChartLine className="text-red-500" size={24} />
</button>
);
}
export default BoutonGraphique;

View File

@ -4,22 +4,32 @@ import { LogIn, UserPlus } from "lucide-react";
function Header() { function Header() {
return ( return (
<header className="bg-white shadow-sm"> <header className="bg-white shadow-sm">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4 flex justify-between items-center">
<div className="flex justify-between items-center"> {/* Logo */}
<h1 className="text-2xl font-bold text-indigo-600">Hopital de Pau</h1> <h1 className="text-2xl font-bold text-indigo-600">VigiMétéo</h1>
{/* Navigation */}
<nav> <nav>
<ul> <ul className="flex gap-6 text-gray-600">
<li> <li>
<a href="/">Accueil</a> <a href="/" className="hover:text-indigo-600">
Accueil
</a>
</li> </li>
<li> <li>
<a href="/about">A propos</a> <a href="/about" className="hover:text-indigo-600">
À propos
</a>
</li> </li>
<li> <li>
<a href="/gestion">Gestion</a> <a href="/gestion" className="hover:text-indigo-600">
Gestion
</a>
</li> </li>
</ul> </ul>
</nav> </nav>
{/* Actions */}
<div className="flex gap-4"> <div className="flex gap-4">
<button className="flex items-center gap-2 text-gray-600 hover:text-indigo-600"> <button className="flex items-center gap-2 text-gray-600 hover:text-indigo-600">
<LogIn size={20} /> <LogIn size={20} />
@ -31,7 +41,6 @@ function Header() {
</button> </button>
</div> </div>
</div> </div>
</div>
</header> </header>
); );
} }

View File

@ -0,0 +1,48 @@
import React from "react";
import { Info } from "lucide-react";
function InfoObject({ object,defafficherModif }) {
return (
<div key={object.id} className="bg-white p-6 rounded-xl min-w-5xl">
<div className="flex align-items gap-6">
<div className="w-12 h-12 bg-indigo-100 rounded-lg flex items-center justify-center mb-4">
<Info className="text-indigo-600" size={24} />
</div>
<h1 className="text-black text-2xl font-bold mb-1 ">Informations</h1>
</div>
<div className="flex flex-col gap-4">
<div className="flex items-center gap-4 mb-1">
<p className="text-black-900 font-bold">Description :</p>
<p className="text-gray-600">{object.description}</p>
</div>
<div className="flex items-center gap-4 mb-1">
<p className="text-black-900 font-bold">Type :</p>
<p className="text-gray-600">{object.type}</p>
</div>
<div className="flex items-center gap-4 mb-1">
<p className="text-black-900 font-bold">Localisation :</p>
<p className="text-gray-600">{object.location}</p>
</div>
<div className="flex items-center gap-4 mb-1">
<p className="text-black-900 font-bold">Status :</p>
<p className="text-gray-600">{object.status}</p>
</div>
<div className="flex items-center gap-4 mb-1">
<p className="text-black-900 font-bold">
Derniere mise à jour :
</p>
<p className="text-gray-600">{object.last_update}</p>
</div>
<div className="flex items-center gap-4 mb-1">
<a className="text-blue-500 hover:cursor-pointer" onClick={(()=>defafficherModif(true))}>Modifier ces infos</a>
</div>
</div>
</div>
);
}
export default InfoObject;

View File

@ -0,0 +1,91 @@
import React, { useEffect, useState } from "react";
import {
LineChart,
Line,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
Legend,
ResponsiveContainer,
ReferenceLine,
} from "recharts";
import { Wind } from "lucide-react";
import axios from "axios";
import { API_BASE_URL } from "../config";
function MeteoGraph({ object, categorie, Logo }) {
const [rawData, setRawData] = useState([]);
const identifiant = object.id;
useEffect(() => {
axios.get(`${API_BASE_URL}/meteo?id=${identifiant}`).then((response) => {
setRawData(response.data);
});
}, [object]);
function getAvg() {
let moyenne = 0;
rawData.forEach((element) => {
if(element){
moyenne += element[categorie];
}
});
return moyenne / rawData.length;
}
console.log(getAvg());
return (
<div
key={object.id}
className="bg-white mb-6 p-6 rounded-xl min-w-5xl"
style={{ width: "100%", height: "400px" }}
>
<div className="flex align-items gap-6">
<div className="w-12 h-12 bg-indigo-100 rounded-lg flex items-center justify-center mb-4">
<Logo className="text-indigo-600" size={24} />
</div>
{categorie === "temperature" ? (
<h1 className="text-black text-2xl font-bold mb-1 ">
Historique de la température
</h1>
) : categorie === "humidity" ? (
<h1 className="text-black text-2xl font-bold mb-1 ">
Historique de l'humidité
</h1>
) : (
<h1 className="text-black text-2xl font-bold mb-1 ">
Historique de la pression
</h1>
)}
</div>
<ResponsiveContainer width="100%" height="100%">
<LineChart
width={500}
height={300}
data={rawData}
margin={{
top: 5,
right: 30,
left: 10,
bottom: 60,
}}
>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="timestamp" />
<YAxis />
<Tooltip />
<Legend />
<Line
type="monotone"
dataKey={categorie}
stroke="#8884d8"
activeDot={{ r: 8 }}
/>
<ReferenceLine y={getAvg()} label="Moy" stroke="red" />
</LineChart>
</ResponsiveContainer>
</div>
);
}
export default MeteoGraph;

View File

@ -0,0 +1,117 @@
import { Thermometer, Sun, CircleGauge, Droplet } from "lucide-react";
import React, { useEffect, useState } from "react";
import axios from "axios";
import { API_BASE_URL } from "../config";
import BoutonGraphique from "./BoutonGraphique";
function MeteoInfos({
object,
defAffTempGraph,
AffTempGraph,
defAffPressionGraph,
AffPressionGraph,
defAffHumiditeGraph,
AffHumiditeGraph,
}) {
const [rawData, setRawData] = useState([]);
const identifiant = object.id;
useEffect(() => {
axios.get(`${API_BASE_URL}/meteo?id=${identifiant}`).then((response) => {
setRawData(response.data);
});
}, [object]);
const lastData = rawData.length > 0 ? rawData[rawData.length - 1] : null;
return (
<div key={object.id} className="bg-white p-6 rounded-xl min-w-5xl">
<div className="flex align-items gap-6">
<div className="w-12 h-12 bg-indigo-100 rounded-lg flex items-center justify-center mb-4">
<Sun className="text-indigo-600" size={24} />
</div>
<h1 className="text-black text-2xl font-bold mb-1 ">Météo actuelle</h1>
</div>
{lastData ? (
<div className="flex flex-col items-center gap-4">
{lastData.temperature && (
<div className="bg-indigo-50 rounded-lg flex items-center w-full">
<div className="flex align-items gap-3 w-full justify-between">
<div className="flex align-items ml-3 gap-2">
<div className="flex items-center">
<Thermometer className="text-red-600" size={40} />
</div>
<div className="flex flex-col items-start">
<h1 className="text-red-600 text-xl font-bold ">
Température
</h1>
<h1 className="text-gray-700 text-4xl font-bold">
{lastData.temperature} <span className="text-lg">°C</span>
</h1>
</div>
</div>
<BoutonGraphique
TypeAff={AffTempGraph}
setAffichage={defAffTempGraph}
/>
</div>
</div>
)}
{lastData.pressure && (
<div className="bg-indigo-50 rounded-lg flex items-center w-full">
<div className="flex align-items gap-3 w-full justify-between">
<div className="flex align-items ml-3 gap-2">
<div className="flex items-center">
<CircleGauge className="text-red-600" size={40} />
</div>
<div className="flex flex-col items-start">
<h1 className="text-red-600 text-xl font-bold ">
Pression
</h1>
<h1 className="text-gray-700 text-4xl font-bold">
{lastData.pressure} <span className="text-lg">hPa</span>
</h1>
</div>
</div>
<BoutonGraphique
TypeAff={AffPressionGraph}
setAffichage={defAffPressionGraph}
/>
</div>
</div>
)}
{lastData.humidity && (
<div className="bg-indigo-50 rounded-lg flex items-center w-full">
<div className="flex align-items gap-3 w-full justify-between">
<div className="flex align-items ml-3 gap-2">
<div className="flex items-center">
<Droplet className="text-red-600" size={40} />
</div>
<div className="flex flex-col items-start">
<h1 className="text-red-600 text-xl font-bold ">
Humidité
</h1>
<h1 className="text-gray-700 text-4xl font-bold">
{lastData.humidity} <span className="text-lg">%</span>
</h1>
</div>
</div>
<BoutonGraphique
TypeAff={AffHumiditeGraph}
setAffichage={defAffHumiditeGraph}
/>
</div>
</div>
)}
<h1 className="text-gray-500 text-sm">
Dernier enregistrement : {lastData.timestamp}
</h1>
</div>
) : (
<p>Chargement des données...</p>
)}
</div>
);
}
export default MeteoInfos;

View File

@ -0,0 +1,80 @@
import React, { use } from "react";
import { Info } from "lucide-react";
import { useState } from "react";
import axios from "axios";
import { API_BASE_URL } from "../config";
function ModifObject({ object,defafficherModif }) {
const [description,setDescription] = useState(object.description || "");
const [type,setType] = useState(object.type || "");
const [location,setLocalisation] = useState(object.location || "");
const [status,setStatus] = useState(object.status || "");
const [response, setResponse] = useState(null);
function makeChange(){
axios
.post(`${API_BASE_URL}/modifObjet`, {
id : object.id,
description: description,
type: type,
location: location,
status: status
})
.then((response) => {
setResponse(response.data[0]);
});
defafficherModif(false);
window.location.reload();
}
function cancelChange(){
defafficherModif(false);
}
function handleDescriptionChange(event){
setDescription(event.target.value);
}
function handleTypeChange(event){
setType(event.target.value);
}
function handleLocalisationChange(event){
setLocalisation(event.target.value);
}
function handleStatusChange(event){
setStatus(event.target.value);
}
return (
<div key={object.id} className="bg-white p-6 rounded-xl min-w-5xl">
<div className="flex align-items gap-9">
<div className="w-12 h-12 bg-indigo-100 rounded-lg flex items-center justify-center mb-4">
<Info className="text-indigo-600" size={24} />
</div>
<h1 className="text-black text-2xl font-bold mb-1 ">Modifier les infos</h1>
</div>
<div className="flex flex-col gap-4">
<div className="flex items-center gap-4 mb-1">
<p className="text-black-900 font-bold">Description :</p>
<input className="text-gray-600 border" type="text" value={description} onChange={handleDescriptionChange}/>
</div>
<div className="flex items-center gap-4 mb-1">
<p className="text-black-900 font-bold">Type :</p>
<input className="text-gray-600 border" type="text" value={type} onChange={handleTypeChange}/>
</div>
<div className="flex items-center gap-4 mb-1">
<p className="text-black-900 font-bold">Localisation :</p>
<input className="text-gray-600 border" type="text" value={location} onChange={handleLocalisationChange}/>
</div>
<div className="flex items-center gap-4 mb-1">
<p className="text-black-900 font-bold">Status :</p>
<input className="text-gray-600 border" type="text" value={status} onChange={handleStatusChange}/>
</div>
<div className="flex items-center gap-4 mb-1">
<a className="text-blue-500 hover:cursor-pointer" onClick={(()=>makeChange())}>Confirmer les modifs</a>
<a className="text-blue-500 hover:cursor-pointer" onClick={(()=>cancelChange())}>Annuler les modifs</a>
</div>
</div>
</div>
);
}
export default ModifObject;

View File

@ -0,0 +1,62 @@
import React, { useEffect, useState} from "react";
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';
import { Wind } from "lucide-react";
import axios from "axios";
import { API_BASE_URL } from "../config";
function WindGraph({ object }) {
const [rawData, setRawData] = useState([]);
const identifiant = object.id;
useEffect(() => {
axios.get(`${API_BASE_URL}/wind?id=${identifiant}`).then((response) => {
setRawData(response.data);
});
}, [object]);
const CustomTooltip = ({ payload, label, active }) => {
if (active && payload && payload.length) {
const { wind_speed, timestamp,wind_direction } = payload[0].payload;
return (
<div className="custom-tooltip">
<p><strong>Date:</strong> {timestamp}</p>
<p><strong>Vitesse du vent:</strong> {wind_speed} km/h</p>
<p><strong>Direction du vent:</strong> {wind_direction}</p>
</div>
);
}
return null; // Si aucun point n'est survolé
};
return (
<div key={object.id} className="bg-white mb-6 p-6 rounded-xl min-w-5xl" style={{width: "100%", height: "400px"}}>
<div className="flex align-items gap-6">
<div className="w-12 h-12 bg-indigo-100 rounded-lg flex items-center justify-center mb-4">
<Wind className="text-indigo-600" size={24} />
</div>
<h1 className="text-black text-2xl font-bold mb-1 ">Historique du vent</h1>
</div>
<ResponsiveContainer width="100%" height="100%">
<LineChart
width={500}
height={300}
data={rawData}
margin={{
top: 5,
right: 30,
left: 10,
bottom: 60,
}}
>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="timestamp" />
<YAxis />
<Tooltip content={<CustomTooltip/>}/>
<Legend />
<Line type="monotone" dataKey="wind_speed" stroke="#8884d8" activeDot={{ r: 8 }} />
</LineChart>
</ResponsiveContainer>
</div>
);
}
export default WindGraph;

View File

@ -0,0 +1,78 @@
import { Wind } from "lucide-react";
import React, { useEffect, useState } from "react";
import axios from "axios";
import { API_BASE_URL } from "../config";
function WindInfo({ object, defAffWindGraph, AffWindGraph }) {
const [rawData, setRawData] = useState([]);
const identifiant = object.id;
useEffect(() => {
axios.get(`${API_BASE_URL}/wind?id=${identifiant}`).then((response) => {
setRawData(response.data);
});
}, [object]);
const lastData = rawData.length > 0 ? rawData[rawData.length - 1] : null;
return (
<div key={object.id} className="bg-white p-6 rounded-xl min-w-5xl">
<div className="flex align-items gap-6">
<div className="w-12 h-12 bg-indigo-100 rounded-lg flex items-center justify-center mb-4">
<Wind className="text-indigo-600" size={24} />
</div>
<h1 className="text-black text-2xl font-bold mb-1 ">Vent actuel</h1>
</div>
{lastData ? (
<div className="flex flex-col items-center gap-4">
<div className="flex flex-row gap-4">
<div className="flex flex-col items-center g-2">
<img
src={`./src/img/${lastData.wind_direction}.png`}
alt="Wind Direction"
className="h-45"
/>
<h1 className="text-gray-600 text-xl font-bold mb-1 ">
{lastData.wind_direction}
</h1>
</div>
<div className="flex flex-col items-center gap-4">
<div className="flex align-items gap-1">
<div className="flex items-center">
<Wind className="text-red-600" size={24} />
</div>
<h1 className="text-red-600 text-xl font-bold ">Valeur</h1>
</div>
<h1 className="text-gray-700 text-4xl font-bold">
{lastData.wind_speed} <span className="text-lg">km/h</span>
</h1>
</div>
</div>
{!AffWindGraph ? (
<button
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 mt-4 px-4 rounded-full"
onClick={() => defAffWindGraph(true)}
>
Afficher l'historique
</button>
) : (
<button
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 mt-4 px-4 rounded-full"
onClick={() => defAffWindGraph(false)}
>
Masquer l'historique
</button>
)}
<h1 className="text-gray-500 text-sm">
Dernier enregistrement : {lastData.timestamp}
</h1>
</div>
) : (
<p>Chargement des données...</p>
)}
</div>
);
}
export default WindInfo;

1
Front-end/src/config.js Normal file
View File

@ -0,0 +1 @@
export const API_BASE_URL = 'http://localhost:8888';

BIN
Front-end/src/img/Est.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
Front-end/src/img/Nord.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
Front-end/src/img/Ouest.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
Front-end/src/img/Sud.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@ -22,8 +22,7 @@ function Gestion() {
Bienvenue dans le module <b>Gestion</b>. Bienvenue dans le module <b>Gestion</b>.
</h2> </h2>
<p className="text-xl text-gray-600 max-w-3xl mx-auto"> <p className="text-xl text-gray-600 max-w-3xl mx-auto">
Dans ce module, vous allez pouvoir gerer les objets connectés de Ce module vous permet de gérer les objets connectés de l'hôpital de manière simple et efficace.
l'hopital.
</p> </p>
</div> </div>
@ -34,17 +33,16 @@ function Gestion() {
<RadioTower className="text-indigo-600" size={24} /> <RadioTower className="text-indigo-600" size={24} />
</div> </div>
<h3 className="text-xl font-semibold mb-2"> <h3 className="text-xl font-semibold mb-2">
Gestion des objets connectés Consulter les objets connectés météorologiques
</h3> </h3>
<p className="text-gray-600 mb-4"> <p className="text-gray-600 mb-4">
Découvrez les meilleurs endroits de votre ville, qu'il s'agisse de Accédez aux données en temps réel des objets connectés météorologiques, modifiez leurs paramètres et consultez l'historique des mesures.
restaurants, de parcs ou de lieux culturels.
</p> </p>
<a <a
href="/gestionObjets" href="/gestionObjets"
className="flex items-center text-indigo-600 hover:text-indigo-700" className="flex items-center text-indigo-600 hover:text-indigo-700"
> >
Explorer les lieux <ArrowRight size={16} className="ml-2" /> Explorer les objets <ArrowRight size={16} className="ml-2" />
</a> </a>
</div> </div>
<div className="bg-white p-6 rounded-xl shadow-sm hover:shadow-md transition-shadow"> <div className="bg-white p-6 rounded-xl shadow-sm hover:shadow-md transition-shadow">
@ -52,17 +50,16 @@ function Gestion() {
<Settings className="text-indigo-600" size={24} /> <Settings className="text-indigo-600" size={24} />
</div> </div>
<h3 className="text-xl font-semibold mb-2"> <h3 className="text-xl font-semibold mb-2">
Configurer des services Ajouter un nouvel objet connecté
</h3> </h3>
<p className="text-gray-600 mb-4"> <p className="text-gray-600 mb-4">
Découvrez les meilleurs endroits de votre ville, qu'il s'agisse de Intégrez facilement un nouvel objet connecté en renseignant ses informations et en configurant ses paramètres pour une gestion optimale.
restaurants, de parcs ou de lieux culturels.
</p> </p>
<a <a
href="#" href="#"
className="flex items-center text-indigo-600 hover:text-indigo-700" className="flex items-center text-indigo-600 hover:text-indigo-700"
> >
Explorer les lieux <ArrowRight size={16} className="ml-2" /> Ajouter un objet <ArrowRight size={16} className="ml-2" />
</a> </a>
</div> </div>
<div className="bg-white p-6 rounded-xl shadow-sm hover:shadow-md transition-shadow"> <div className="bg-white p-6 rounded-xl shadow-sm hover:shadow-md transition-shadow">
@ -70,36 +67,16 @@ function Gestion() {
<Binoculars className="text-indigo-600" size={24} /> <Binoculars className="text-indigo-600" size={24} />
</div> </div>
<h3 className="text-xl font-semibold mb-2"> <h3 className="text-xl font-semibold mb-2">
Surveillance et optimisation des ressources Optimisation et surveillance des ressources
</h3> </h3>
<p className="text-gray-600 mb-4"> <p className="text-gray-600 mb-4">
Restez informé des derniers événements, festivals et Suivez les performances des dispositifs connectés et optimisez leur utilisation pour une gestion efficace des ressources.
rassemblements communautaires dans votre région.
</p> </p>
<a <a
href="#" href="#"
className="flex items-center text-indigo-600 hover:text-indigo-700" className="flex items-center text-indigo-600 hover:text-indigo-700"
> >
Voir les événements <ArrowRight size={16} className="ml-2" /> En savoir plus <ArrowRight size={16} className="ml-2" />
</a>
</div>
<div className="bg-white p-6 rounded-xl shadow-sm hover:shadow-md transition-shadow">
<div className="w-12 h-12 bg-indigo-100 rounded-lg flex items-center justify-center mb-4">
<ChartArea className="text-indigo-600" size={24} />
</div>
<h3 className="text-xl font-semibold mb-2">
Rapports statistiques
</h3>
<p className="text-gray-600 mb-4">
Accédez en temps réel aux horaires et aux itinéraires des bus, des
trains et des autres transports publics.
</p>
<a
href="#"
className="flex items-center text-indigo-600 hover:text-indigo-700"
>
Vérifier les horaires <ArrowRight size={16} className="ml-2" />
</a> </a>
</div> </div>
</div> </div>

View File

@ -23,7 +23,6 @@ function ObjectManagement() {
useEffect(() => { useEffect(() => {
axios.get("http://localhost:8888/objets").then((response) => { axios.get("http://localhost:8888/objets").then((response) => {
setObjects(response.data); setObjects(response.data);
console.log(response.data);
}); });
}, []); }, []);
return ( return (

View File

@ -1,58 +1,96 @@
import React from "react"; import React from "react";
import { import { Thermometer, CircleGauge, Droplet } from "lucide-react";
Search,
MapPin,
Calendar,
Bus,
ArrowRight,
LogIn,
UserPlus,
RadioTower,
Binoculars,
Settings,
ChartArea,
} from "lucide-react";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import axios from "axios"; import axios from "axios";
import { useFetcher } from "react-router-dom"; import { useFetcher } from "react-router-dom";
import InfoObjet from "../../components/InfoObject";
import ModifObject from "../../components/ModifObject";
import WindGraph from "../../components/WindGraph";
import WindInfo from "../../components/WindInfo";
import MeteoInfos from "../../components/MeteoInfos";
import MeteoGraph from "../../components/MeteoGraph";
function Objet() { function Objet() {
const identifiant = new URLSearchParams(window.location.search).get("id"); const identifiant = new URLSearchParams(window.location.search).get("id");
const [searchQuery, setSearchQuery] = useState(""); const [searchQuery, setSearchQuery] = useState("");
const [activeFilter, setActiveFilter] = useState("all"); const [activeFilter, setActiveFilter] = useState("all");
const [object, setObject] = useState({}); const [object, setObject] = useState({});
const [afficherModif, defafficherModif] = useState(false);
const [AffWindGraph, defAffWindGraph] = useState(false);
const [AffTempGraph, defAffTempGraph] = useState(false);
const [AffPressionGraph, defAffPressionGraph] = useState(false);
const [AffHumiditeGraph, defAffHumideGraph] = useState(false);
useEffect(() => { useEffect(() => {
axios.get(`http://localhost:8888/objet?id=${identifiant}`).then((response) => { axios
.get(`http://localhost:8888/objet?id=${identifiant}`)
.then((response) => {
setObject(response.data[0]); setObject(response.data[0]);
console.log(response.data);
}); });
}, [identifiant]); }, [identifiant]);
return ( return (
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-50"> <div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-50">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12"> <div className=" max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<div className="text-center mb-12"> <div className="text-center mb-5">
<h2 className="text-4xl font-bold text-gray-900 mb-4"> <h2 className="text-4xl font-bold text-gray-900 mb-12">
{object.name} Tableau de bord - {object.name}
</h2> </h2>
</div> </div>
<div className="grid md:grid-cols-3 gap-8"> <div className="grid md:grid-cols-3 gap-8 mb-5">
<div {!afficherModif && (
key={object.id} <InfoObjet object={object} defafficherModif={defafficherModif} />
className="bg-white p-6 rounded-xl shadow-sm hover:shadow-md transition-shadow" )}
> {afficherModif && (
<div className="w-12 h-12 bg-indigo-100 rounded-lg flex items-center justify-center mb-4"> <ModifObject object={object} defafficherModif={defafficherModif} />
<RadioTower className="text-indigo-600" size={24} /> )}
</div>
<h3 className="text-xl font-semibold mb-2">{object.name}</h3> {object && object.id ? (
<p className="text-gray-600 mb-4">{object.description}</p> <WindInfo
<a object={object}
href={`/objet?id=${object.id}`} defAffWindGraph={defAffWindGraph}
className="flex items-center text-indigo-600 hover:text-indigo-700" AffWindGraph={AffWindGraph}
> />
Plus d'infos <ArrowRight size={16} className="ml-2" /> ) : (
</a> <p>Chargement des données...</p>
</div> )}
{object && object.id ? (
<MeteoInfos
object={object}
defAffTempGraph={defAffTempGraph}
AffTempGraph={AffTempGraph}
defAffPressionGraph={defAffPressionGraph}
AffPressionGraph={AffPressionGraph}
defAffHumiditeGraph={defAffHumideGraph}
AffHumiditeGraph={AffHumiditeGraph}
/>
) : (
<p>Chargement des données...</p>
)}
</div> </div>
{AffWindGraph &&
(object && object.id ? (
<WindGraph object={object} />
) : (
<p>Chargement des données...</p>
))}
{AffTempGraph &&
(object && object.id ? (
<MeteoGraph object={object} categorie={"temperature"} Logo={Thermometer}/>
) : (
<p>Chargement des données...</p>
))}
{AffPressionGraph &&
(object && object.id ? (
<MeteoGraph object={object} categorie={"pressure"} Logo={CircleGauge}/>
) : (
<p>Chargement des données...</p>
))}
{AffHumiditeGraph &&
(object && object.id ? (
<MeteoGraph object={object} categorie={"humidity"} Logo={Droplet} />
) : (
<p>Chargement des données...</p>
))}
</div> </div>
</div> </div>
); );