Merge remote-tracking branch 'origin/main' into Charles
@ -49,7 +49,7 @@
|
||||
<dependency>
|
||||
<groupId>io.agroal</groupId>
|
||||
<artifactId>agroal-pool</artifactId>
|
||||
<version>1.16</version> <!-- Utilisez la version la plus récente -->
|
||||
<version>1.16</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.vertx</groupId>
|
||||
|
||||
@ -1,15 +1,14 @@
|
||||
package com.example.starter;
|
||||
|
||||
import io.vertx.ext.web.handler.BodyHandler;
|
||||
import io.vertx.ext.web.handler.BodyHandler;
|
||||
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.json.JsonArray;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import io.vertx.core.AbstractVerticle;
|
||||
import io.vertx.core.Promise;
|
||||
|
||||
import io.vertx.ext.web.Router;
|
||||
import io.vertx.ext.web.RoutingContext;
|
||||
import at.favre.lib.crypto.bcrypt.BCrypt;
|
||||
@ -45,6 +44,12 @@ public class MainVerticle extends AbstractVerticle {
|
||||
router.route().handler(BodyHandler.create());
|
||||
|
||||
// Gestion des CORS
|
||||
QueryObjects queryObjects = new QueryObjects(databaseService);
|
||||
QueryWeatherData queryWeather = new QueryWeatherData(databaseService);
|
||||
SetObjects setObjects = new SetObjects(databaseService);
|
||||
// Create a Router
|
||||
Router router = Router.router(vertx);
|
||||
router.route().handler(BodyHandler.create());
|
||||
router.route().handler(CorsHandler.create()
|
||||
.addOrigin("*")
|
||||
.allowedMethod(HttpMethod.GET)
|
||||
@ -59,6 +64,12 @@ public class MainVerticle extends AbstractVerticle {
|
||||
router.post("/login").handler(this::handleLogin); // Route pour la connexion
|
||||
// Protéger toutes les routes commençant par "/api/"
|
||||
router.route("/api/*").handler(JWTAuthHandler.create(jwtAuth));
|
||||
router.get("/objets").handler(queryObjects::getObjects);
|
||||
router.get("/objet").handler(queryObjects::getParticularObject);
|
||||
router.post("/modifObjet").handler(setObjects::setInfoObjet);
|
||||
router.get("/wind").handler(queryWeather::getWindInfos);
|
||||
router.get("/meteo").handler(queryWeather::getMeteoInfos);
|
||||
router.post("/addObject").handler(setObjects::newObject);
|
||||
|
||||
// Création du serveur HTTP
|
||||
vertx.createHttpServer()
|
||||
@ -258,4 +269,6 @@ public class MainVerticle extends AbstractVerticle {
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
package com.example.starter;
|
||||
|
||||
public class ProductHandler {
|
||||
|
||||
}
|
||||
86
Back-end/src/main/java/com/example/starter/QueryObjects.java
Normal file
@ -0,0 +1,86 @@
|
||||
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"))
|
||||
.put("batterie",row.getInteger("batterie"))
|
||||
.put("type_batterie",row.getString("type_batterie"));
|
||||
objects.add(object);
|
||||
}
|
||||
return objects;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
89
Back-end/src/main/java/com/example/starter/SetObjects.java
Normal file
@ -0,0 +1,89 @@
|
||||
package com.example.starter;
|
||||
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import io.vertx.ext.web.RoutingContext;
|
||||
import io.vertx.sqlclient.Tuple;
|
||||
|
||||
public class SetObjects {
|
||||
private DatabaseService databaseService;
|
||||
|
||||
public SetObjects(DatabaseService ddbs) {
|
||||
this.databaseService = ddbs;
|
||||
}
|
||||
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
||||
public void newObject(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;
|
||||
}
|
||||
String name = body.getString("nom");
|
||||
String description = body.getString("description");
|
||||
String type = body.getString("type");
|
||||
String location = body.getString("location");
|
||||
String status = body.getString("status");
|
||||
databaseService.pool
|
||||
.preparedQuery("INSERT INTO weather_objects (name,description,type,location,status) VALUES (?,?,?,?,?)")
|
||||
.execute(Tuple.of(name,description,type,location,status))
|
||||
.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 -> {
|
||||
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é ajouté").encode());
|
||||
return;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
576
Front-end/package-lock.json
generated
@ -12,8 +12,11 @@
|
||||
"axios": "^1.8.4",
|
||||
"lucide-react": "^0.427.0",
|
||||
"react": "^18.3.1",
|
||||
"react-charts": "^3.0.0-beta.57",
|
||||
"react-circle-progress-bar": "^0.1.4",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-router-dom": "^7.4.0"
|
||||
"react-router-dom": "^7.4.0",
|
||||
"recharts": "^2.15.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.9.1",
|
||||
@ -262,6 +265,18 @@
|
||||
"@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": {
|
||||
"version": "7.26.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz",
|
||||
@ -1302,6 +1317,69 @@
|
||||
"integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==",
|
||||
"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": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
|
||||
@ -1314,6 +1392,44 @@
|
||||
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
|
||||
"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": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.4.tgz",
|
||||
@ -1656,6 +1772,15 @@
|
||||
"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": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
@ -1742,6 +1867,127 @@
|
||||
"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": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
|
||||
@ -1759,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": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
|
||||
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
|
||||
"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": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
@ -1786,6 +2044,16 @@
|
||||
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
|
||||
"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": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
@ -2090,12 +2358,27 @@
|
||||
"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": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
||||
"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": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
|
||||
@ -2497,6 +2780,12 @@
|
||||
"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": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
||||
@ -2707,6 +2996,12 @@
|
||||
"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": {
|
||||
"version": "4.6.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||
@ -2884,7 +3179,6 @@
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@ -3197,6 +3491,23 @@
|
||||
"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": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
@ -3243,6 +3554,85 @@
|
||||
"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-circle-progress-bar": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/react-circle-progress-bar/-/react-circle-progress-bar-0.1.4.tgz",
|
||||
"integrity": "sha512-2a47TDthNyUHJf8p1hv0wcTwIWnJBbEUfj/7dZcO+7BYd1W1sRC2t5x+SEWX9/1QT7hhf4t8ppcLaG0XrdwwgQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6"
|
||||
}
|
||||
},
|
||||
"node_modules/react-circle-progress-bar/node_modules/react": {
|
||||
"version": "16.14.0",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz",
|
||||
"integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-circle-progress-bar/node_modules/react-dom": {
|
||||
"version": "16.14.0",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz",
|
||||
"integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"scheduler": "^0.19.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-circle-progress-bar/node_modules/scheduler": {
|
||||
"version": "0.19.1",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz",
|
||||
"integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
||||
@ -3255,6 +3645,12 @@
|
||||
"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": {
|
||||
"version": "0.14.2",
|
||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz",
|
||||
@ -3304,6 +3700,37 @@
|
||||
"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": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||
@ -3325,6 +3752,44 @@
|
||||
"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": {
|
||||
"version": "1.22.10",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
||||
@ -3709,6 +4174,12 @@
|
||||
"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": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
@ -3727,6 +4198,12 @@
|
||||
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
|
||||
"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": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz",
|
||||
@ -3790,6 +4267,101 @@
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||
"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": {
|
||||
"version": "5.4.15",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.15.tgz",
|
||||
|
||||
@ -13,8 +13,11 @@
|
||||
"axios": "^1.8.4",
|
||||
"lucide-react": "^0.427.0",
|
||||
"react": "^18.3.1",
|
||||
"react-charts": "^3.0.0-beta.57",
|
||||
"react-circle-progress-bar": "^0.1.4",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-router-dom": "^7.4.0"
|
||||
"react-router-dom": "^7.4.0",
|
||||
"recharts": "^2.15.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.9.1",
|
||||
|
||||
@ -6,6 +6,7 @@ import Gestion from "./pages/Gestion/Gestion.jsx";
|
||||
import Header from "./components/Header.jsx";
|
||||
import ObjectManagement from "./pages/Gestion/ObjectManagement.jsx";
|
||||
import Objet from "./pages/Gestion/Objet.jsx";
|
||||
import AddObject from "./pages/Gestion/AddObject.jsx";
|
||||
import Signup from './pages/Signup.jsx';
|
||||
import Login from './pages/Login.jsx';
|
||||
|
||||
@ -23,6 +24,7 @@ function App() {
|
||||
<Route path="/objet" element={<Objet />} />
|
||||
<Route path="/signup" element={<Signup />} />
|
||||
<Route path="/login" element={<Login />} />
|
||||
<Route path="/ajouterObjet" element={<AddObject />} />
|
||||
</Routes>
|
||||
</div>
|
||||
</Router>
|
||||
|
||||
22
Front-end/src/components/AlertInactive.jsx
Normal file
@ -0,0 +1,22 @@
|
||||
import React, {useState} from "react";
|
||||
import { TriangleAlert,X } from "lucide-react";
|
||||
|
||||
function AlertInactive({affAlert,setAffAlert}) {
|
||||
return (
|
||||
(affAlert&&(
|
||||
<div className="flex flex-col md:flex-row bg-slate-600 w-full md:w-1/2 lg:w-1/3 fixed top-20 right-4 rounded-lg p-4 md:p-5 items-center gap-4 md:gap-6 shadow-lg opacity-90">
|
||||
<button onClick={()=>setAffAlert(false)}className="absolute top-2 right-2 text-white hover:text-gray-300">
|
||||
<X/>
|
||||
</button>
|
||||
|
||||
<TriangleAlert className="text-red-700 w-12 h-12 md:w-16 md:h-16" />
|
||||
|
||||
<p className="text-sm md:text-base text-white text-center md:text-left">
|
||||
Cet objet peut être inactif dû à son manque de données. Vous pouvez le
|
||||
rendre inactif en appuyant <a>ici</a>.
|
||||
</p>
|
||||
</div>
|
||||
)));
|
||||
}
|
||||
|
||||
export default AlertInactive;
|
||||
27
Front-end/src/components/BatterieInfo.jsx
Normal file
@ -0,0 +1,27 @@
|
||||
import React from "react";
|
||||
import { Battery } from "lucide-react";
|
||||
import Progress from "react-circle-progress-bar";
|
||||
|
||||
function BatterieInfo({ object }) {
|
||||
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">
|
||||
<Battery className="text-indigo-600" size={24} />
|
||||
</div>
|
||||
<h1 className="text-black text-2xl font-bold mb-1 ">
|
||||
Etat de la batterie
|
||||
</h1>
|
||||
</div>
|
||||
<div className="flex flex-col items-center">
|
||||
<Progress progress={object.batterie} />
|
||||
<h1 className="font-bold">
|
||||
Type de batterie :{" "}
|
||||
<span className="capitalize font-normal">{object.type_batterie}</span>
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default BatterieInfo;
|
||||
27
Front-end/src/components/BoutonGraphique.jsx
Normal file
@ -0,0 +1,27 @@
|
||||
import React, {useRef} from "react";
|
||||
import { ChartLine } from "lucide-react";
|
||||
function BoutonGraphique({ TypeAff, setAffichage,graphCible}) {
|
||||
const handleClick = (newAffichage) =>{
|
||||
setAffichage(newAffichage);
|
||||
if(graphCible.current){
|
||||
graphCible.current.scrollIntoView({ behavior: "smooth" });
|
||||
}
|
||||
};
|
||||
return !TypeAff ? (
|
||||
<button
|
||||
className="bg-blue-200 py-2 my-2 px-4 rounded-full mr-2"
|
||||
onClick={() => handleClick(true)}
|
||||
>
|
||||
<ChartLine className="text-indigo-600" size={24} />
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
className="bg-blue-400 py-2 my-2 px-4 rounded-full mr-2"
|
||||
onClick={() => handleClick(false)}
|
||||
>
|
||||
<ChartLine className="text-indigo-600" size={24} />
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
export default BoutonGraphique;
|
||||
46
Front-end/src/components/InfoObject.jsx
Normal file
@ -0,0 +1,46 @@
|
||||
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-1">
|
||||
<Info className="text-indigo-600" size={24} />
|
||||
</div>
|
||||
<h1 className="text-black text-2xl font-bold mb-1 ">Informations</h1>
|
||||
</div>
|
||||
<div className="mb-5">
|
||||
<p className="text-black-900 font-bold">Description :</p>
|
||||
<p className="text-gray-600 capitalize">{object.description}</p>
|
||||
</div>
|
||||
|
||||
<div className="mb-5">
|
||||
<p className="text-black-900 font-bold">Type :</p>
|
||||
<p className="text-gray-600 capitalize">{object.type}</p>
|
||||
</div>
|
||||
|
||||
<div className="mb-5">
|
||||
<p className="text-black-900 font-bold">Localisation :</p>
|
||||
<p className="text-gray-600">{object.location}</p>
|
||||
</div>
|
||||
|
||||
<div className="mb-5">
|
||||
<p className="text-black-900 font-bold">Status :</p>
|
||||
<p className="text-gray-600 capitalize">{object.status}</p>
|
||||
</div>
|
||||
|
||||
<div className="mb-5">
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
export default InfoObject;
|
||||
90
Front-end/src/components/MeteoGraph.jsx
Normal file
@ -0,0 +1,90 @@
|
||||
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;
|
||||
}
|
||||
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="Moyenne" stroke="red" />
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default MeteoGraph;
|
||||
130
Front-end/src/components/MeteoInfos.jsx
Normal file
@ -0,0 +1,130 @@
|
||||
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";
|
||||
import AlertInactive from "./AlertInactive";
|
||||
|
||||
function MeteoInfos({
|
||||
object,
|
||||
defAffTempGraph,
|
||||
AffTempGraph,
|
||||
defAffPressionGraph,
|
||||
AffPressionGraph,
|
||||
defAffHumiditeGraph,
|
||||
AffHumiditeGraph,
|
||||
graphCible
|
||||
}) {
|
||||
const [rawData, setRawData] = useState([]);
|
||||
const [AffAlert,setAffAlert] = useState(false);
|
||||
const identifiant = object.id;
|
||||
|
||||
useEffect(() => {
|
||||
axios.get(`${API_BASE_URL}/meteo?id=${identifiant}`).then((response) => {
|
||||
setRawData(response.data);
|
||||
if(rawData.length <5){
|
||||
setAffAlert(true);
|
||||
}
|
||||
});
|
||||
}, [object]);
|
||||
|
||||
const lastData = rawData.length > 0 ? rawData[rawData.length - 1] : null;
|
||||
console.log(rawData.length);
|
||||
return (
|
||||
<div key={object.id} className="bg-white p-6 rounded-xl min-w-5xl">
|
||||
{(AffAlert&&(object.status==="active")) && (
|
||||
<AlertInactive affAlert={AffAlert} setAffAlert={setAffAlert} />
|
||||
)}
|
||||
<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-indigo-600" size={40} />
|
||||
</div>
|
||||
<div className="flex flex-col items-start">
|
||||
<h1 className="text-indigo-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}
|
||||
graphCible={graphCible}
|
||||
|
||||
/>
|
||||
</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-indigo-600" size={40} />
|
||||
</div>
|
||||
<div className="flex flex-col items-start">
|
||||
<h1 className="text-indigo-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}
|
||||
graphCible={graphCible}
|
||||
/>
|
||||
</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-indigo-600" size={40} />
|
||||
</div>
|
||||
<div className="flex flex-col items-start">
|
||||
<h1 className="text-indigo-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;
|
||||
158
Front-end/src/components/ModifObject.jsx
Normal file
@ -0,0 +1,158 @@
|
||||
import React, { useState } from "react";
|
||||
import { Info } from "lucide-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 || "inactive");
|
||||
const [isActive, setActive] = useState(object.status === "active");
|
||||
|
||||
function handleSubmit(event) {
|
||||
event.preventDefault(); // Empêche le rechargement de la page
|
||||
axios
|
||||
.post(`${API_BASE_URL}/modifObjet`, {
|
||||
id: object.id,
|
||||
description,
|
||||
type,
|
||||
location,
|
||||
status,
|
||||
})
|
||||
.then((response) => {
|
||||
console.log("Modification réussie :", response.data);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Erreur lors de la modification :", error);
|
||||
});
|
||||
defafficherModif(false);
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
function handleCancel() {
|
||||
defafficherModif(false);
|
||||
}
|
||||
|
||||
function handleStatusChange() {
|
||||
setActive((prevIsActive) => {
|
||||
const newIsActive = !prevIsActive;
|
||||
setStatus(newIsActive ? "active" : "inactive");
|
||||
return newIsActive;
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} 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="mb-5">
|
||||
<label
|
||||
htmlFor="description"
|
||||
className="block mb-2 text-sm font-medium text-gray-900"
|
||||
>
|
||||
Description
|
||||
</label>
|
||||
<input
|
||||
id="description"
|
||||
className="text-gray-600 border rounded-lg p-2 w-full"
|
||||
type="text"
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mb-5">
|
||||
<label
|
||||
htmlFor="type"
|
||||
className="block mb-2 text-sm font-medium text-gray-900"
|
||||
>
|
||||
Type :
|
||||
</label>
|
||||
<input
|
||||
id="type"
|
||||
className="text-gray-600 border rounded-lg p-2 w-full"
|
||||
type="text"
|
||||
value={type}
|
||||
onChange={(e) => setType(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mb-5">
|
||||
<label
|
||||
htmlFor="location"
|
||||
className="block mb-2 text-sm font-medium text-gray-900"
|
||||
>
|
||||
Localisation :
|
||||
</label>
|
||||
<input
|
||||
id="location"
|
||||
className="text-gray-600 border rounded-lg p-2 w-full"
|
||||
type="text"
|
||||
value={location}
|
||||
onChange={(e) => setLocalisation(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mb-5">
|
||||
<label className="block mb-2 text-sm font-medium text-gray-900">
|
||||
Status :
|
||||
</label>
|
||||
<div className="inline-flex items-center gap-2">
|
||||
<label
|
||||
htmlFor="switch-component-on"
|
||||
className="text-slate-600 text-sm cursor-pointer"
|
||||
>
|
||||
Inactive
|
||||
</label>
|
||||
<div className="relative inline-block w-11 h-5">
|
||||
<input
|
||||
id="switch-component-on"
|
||||
type="checkbox"
|
||||
checked={isActive}
|
||||
onChange={handleStatusChange}
|
||||
className="peer appearance-none w-11 h-5 bg-slate-100 rounded-full checked:bg-slate-800 cursor-pointer transition-colors duration-300"
|
||||
/>
|
||||
<label
|
||||
htmlFor="switch-component-on"
|
||||
className="absolute top-0 left-0 w-5 h-5 bg-white rounded-full border border-slate-300 shadow-sm transition-transform duration-300 peer-checked:translate-x-6 peer-checked:border-slate-800 cursor-pointer"
|
||||
></label>
|
||||
</div>
|
||||
<label
|
||||
htmlFor="switch-component-on"
|
||||
className="text-slate-600 text-sm cursor-pointer"
|
||||
>
|
||||
Active
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-5 flex flex-col">
|
||||
<button
|
||||
type="submit"
|
||||
className="text-blue-500 hover:cursor-pointer hover:underline"
|
||||
>
|
||||
Confirmer les modifications
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="text-red-500 hover:cursor-pointer hover:underline"
|
||||
onClick={handleCancel}
|
||||
>
|
||||
Annuler les modifications
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
export default ModifObject;
|
||||
62
Front-end/src/components/WindGraph.jsx
Normal 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;
|
||||
67
Front-end/src/components/WindInfo.jsx
Normal file
@ -0,0 +1,67 @@
|
||||
import { Wind } from "lucide-react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
import axios from "axios";
|
||||
import { API_BASE_URL } from "../config";
|
||||
import BoutonGraphique from "./BoutonGraphique";
|
||||
|
||||
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-1">
|
||||
<Wind className="text-indigo-600" size={24} />
|
||||
</div>
|
||||
<h1 className="text-black text-2xl font-bold">Vent actuel</h1>
|
||||
</div>
|
||||
{lastData ? (
|
||||
<div className="flex flex-col items-center gap-1">
|
||||
<img
|
||||
src={`./src/img/${lastData.wind_direction}.png`}
|
||||
alt="Wind Direction"
|
||||
className="h-25"
|
||||
/>
|
||||
<h1 className="text-gray-600 text-xl font-bold mb-1 ">
|
||||
{lastData.wind_direction}
|
||||
</h1>
|
||||
<div className="bg-indigo-50 rounded-lg flex flex-col items-center mb-1 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">
|
||||
<Wind className="text-indigo-600" size={40} />
|
||||
</div>
|
||||
<div className="flex flex-col items-start">
|
||||
<h1 className="text-indigo-600 text-xl font-bold ">Valeur</h1>
|
||||
<h1 className="text-gray-700 text-4xl font-bold">
|
||||
{lastData.wind_speed} <span className="text-lg">Km/h</span>
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
<BoutonGraphique
|
||||
TypeAff={AffWindGraph}
|
||||
setAffichage={defAffWindGraph}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<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
@ -0,0 +1 @@
|
||||
export const API_BASE_URL = 'http://localhost:8888';
|
||||
BIN
Front-end/src/img/Est.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
Front-end/src/img/Nord-Est.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
Front-end/src/img/Nord-Ouest.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
Front-end/src/img/Nord.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
Front-end/src/img/NotreMission.png
Normal file
|
After Width: | Height: | Size: 2.3 MiB |
BIN
Front-end/src/img/Ouest.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
Front-end/src/img/Sud-Est.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
Front-end/src/img/Sud-Ouest.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
Front-end/src/img/Sud.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
Front-end/src/img/fr-alert.webp
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
Front-end/src/img/gestioniot.png
Normal file
|
After Width: | Height: | Size: 142 KiB |
BIN
Front-end/src/img/iotmeteo.jpg
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
Front-end/src/img/precisionfiable.jpg
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
Front-end/src/img/surveillancemeteo.webp
Normal file
|
After Width: | Height: | Size: 81 KiB |
BIN
Front-end/src/img/surveillancetempsreel.jpg
Normal file
|
After Width: | Height: | Size: 76 KiB |
@ -1,9 +1,166 @@
|
||||
import React from 'react';
|
||||
import React from "react";
|
||||
|
||||
function About() {
|
||||
return (
|
||||
<div>
|
||||
<h1>A propos</h1>
|
||||
<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="mb-5">
|
||||
{/* Grille principale */}
|
||||
<div className="grid md:grid-cols-2 gap-10 lg:gap-20 mb-5">
|
||||
{/* Section Notre mission */}
|
||||
<div className="order-1 md:order-1">
|
||||
<h1 className="text-2xl font-bold text-gray-900 mb-6">
|
||||
Notre mission
|
||||
</h1>
|
||||
<p className="text-gray-700 leading-relaxed">
|
||||
Notre mission est de fournir une solution complète et innovante
|
||||
pour la surveillance climatique et environnementale du
|
||||
territoire français. En combinant des prévisions météorologiques
|
||||
de haute qualité avec une gestion efficace des objets connectés,
|
||||
nous visons à offrir une plateforme centralisée permettant de
|
||||
surveiller en temps réel les conditions météorologiques locales,
|
||||
tout en facilitant l'analyse des données collectées par des
|
||||
objets connectés déployés à travers le pays.
|
||||
</p>
|
||||
</div>
|
||||
<img
|
||||
className="rounded-lg h-64 w-full object-cover order-2 md:order-2"
|
||||
src="./src/img/NotreMission.png"
|
||||
alt="Notre mission"
|
||||
/>
|
||||
|
||||
{/* Section Qui sommes-nous */}
|
||||
<img
|
||||
className="rounded-lg h-64 w-full object-cover order-4 md:order-3"
|
||||
src="./src/img/iotmeteo.jpg"
|
||||
alt="IoT et météo"
|
||||
/>
|
||||
<div className="order-3 md:order-4">
|
||||
<h1 className="text-2xl font-bold text-gray-900 mb-6">
|
||||
Qui sommes-nous ?
|
||||
</h1>
|
||||
<p className="text-gray-700 leading-relaxed">
|
||||
Nous sommes une équipe de passionnés de technologie,
|
||||
d’innovation et d’environnement. Nous croyons fermement que la
|
||||
combinaison de la donnée météorologique en temps réel et de
|
||||
l’Internet des Objets (IoT) peut avoir un impact majeur sur la
|
||||
gestion des territoires. Que ce soit pour les collectivités
|
||||
locales, les entreprises ou les acteurs publics, notre
|
||||
plateforme offre les outils nécessaires pour une gestion
|
||||
proactive et réactive de l’environnement.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Section Notre Vision */}
|
||||
<div className="order-5 md:order-5">
|
||||
<h1 className="text-2xl font-bold text-gray-900 mb-6">
|
||||
Notre Vision
|
||||
</h1>
|
||||
<p className="text-gray-700 leading-relaxed">
|
||||
Dans un monde où les conditions climatiques évoluent rapidement,
|
||||
il est essentiel de pouvoir anticiper et réagir efficacement
|
||||
face aux phénomènes météorologiques. Grâce à nos objets
|
||||
connectés et à notre interface intuitive, nous permettons aux
|
||||
utilisateurs de suivre les conditions en temps réel et d’agir en
|
||||
conséquence. De la gestion des risques climatiques à la
|
||||
planification urbaine, notre plateforme aide les décideurs à
|
||||
prendre des décisions éclairées basées sur des données fiables
|
||||
et locales.
|
||||
</p>
|
||||
</div>
|
||||
<img
|
||||
className="rounded-lg h-64 w-full object-cover order-6 md:order-6"
|
||||
src="./src/img/surveillancemeteo.webp"
|
||||
alt="Surveillance météo"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Section Objectifs */}
|
||||
<div className="text-center col-span-2 order-7">
|
||||
<h1 className="text-2xl font-bold text-gray-900 mb-10 mt-20">
|
||||
Les Objectifs de Notre Plateforme
|
||||
</h1>
|
||||
<div className="grid sm:grid-cols-2 lg:grid-cols-4 gap-10">
|
||||
{/* Objectif 1 */}
|
||||
<div className="relative group w-full h-80 mb-7">
|
||||
<img
|
||||
src="./src/img/surveillancetempsreel.jpg"
|
||||
alt="Surveillance en temps réel"
|
||||
className="w-full h-full object-cover rounded-xl"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-blue-600 opacity-0 group-hover:opacity-100 transition-opacity duration-500 rounded-xl flex items-center justify-center">
|
||||
<p className="text-white text-lg font-bold px-4">
|
||||
Grâce à nos objets connectés, nous collectons des données
|
||||
météorologiques locales, permettant une surveillance
|
||||
continue des conditions climatiques sur tout le territoire
|
||||
français.
|
||||
</p>
|
||||
</div>
|
||||
<h1 className="text-xl font-bold mt-4 ">
|
||||
Surveillance en temps réel
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
{/* Objectif 2 */}
|
||||
<div className="relative group w-full h-80 mb-7">
|
||||
<img
|
||||
src="./src/img/precisionfiable.jpg"
|
||||
alt="Précision fiable"
|
||||
className="w-full h-full object-cover rounded-xl"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-blue-600 opacity-0 group-hover:opacity-100 transition-opacity duration-500 rounded-xl flex items-center justify-center">
|
||||
<p className="text-white text-lg font-bold px-4">
|
||||
En utilisant les meilleures technologies de prévision
|
||||
météorologique, nous vous fournissons des prévisions
|
||||
précises, qu’il s’agisse de la température, de la vitesse du
|
||||
vent ou de la qualité de l’air.
|
||||
</p>
|
||||
</div>
|
||||
<h1 className="text-xl font-bold mt-4 mb-6">Prédiction fiable</h1>
|
||||
</div>
|
||||
|
||||
{/* Objectif 3 */}
|
||||
<div className="relative group w-full h-80 mb-7 border-2 rounded-xl">
|
||||
<img
|
||||
src="./src/img/gestioniot.png"
|
||||
alt="Gestion IoT"
|
||||
className="w-full h-full object-cover rounded-xl"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-blue-600 opacity-0 group-hover:opacity-100 transition-opacity duration-500 rounded-xl flex items-center justify-center">
|
||||
<p className="text-white text-lg font-bold px-4">
|
||||
Nous permettons aux utilisateurs de gérer facilement leurs
|
||||
objets connectés (stations météo, capteurs, etc.) à travers
|
||||
une interface simple, tout en offrant un suivi en temps réel
|
||||
de leur statut et de leurs données.
|
||||
</p>
|
||||
</div>
|
||||
<h1 className="text-xl font-bold mt-4 mb-6">
|
||||
Gestion des objets connectés
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
{/* Objectif 4 */}
|
||||
<div className="relative group w-full h-80 mb-7">
|
||||
<img
|
||||
src="./src/img/fr-alert.webp"
|
||||
alt="Réponse rapide"
|
||||
className="w-full h-full object-cover rounded-xl"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-blue-600 opacity-0 group-hover:opacity-100 transition-opacity duration-500 rounded-xl flex items-center justify-center">
|
||||
<p className="text-white text-lg font-bold px-4">
|
||||
Notre plateforme vous envoie des alertes instantanées
|
||||
concernant les phénomènes météorologiques extrêmes, vous
|
||||
permettant de prendre des décisions rapides et adaptées.
|
||||
</p>
|
||||
</div>
|
||||
<h1 className="text-xl font-bold mt-4 mb-6">
|
||||
Réponse rapide aux alertes climatiques
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
220
Front-end/src/pages/Gestion/AddObject.jsx
Normal file
@ -0,0 +1,220 @@
|
||||
import React, { useState } from "react";
|
||||
import { BadgePlus } from "lucide-react";
|
||||
import axios from "axios";
|
||||
import { API_BASE_URL } from "../../config";
|
||||
|
||||
function AddObject() {
|
||||
const [description, setDescription] = useState("");
|
||||
const [type, setType] = useState("");
|
||||
const [location, setLocalisation] = useState("");
|
||||
const [status, setStatus] = useState("active");
|
||||
const [nom, setNom] = useState("");
|
||||
const [Response, setResponse] = useState(null);
|
||||
const [isActive, setActive] = useState(true);
|
||||
const [verif, setVerif] = useState(false);
|
||||
const [enregistre, setEnregistre] = useState(false);
|
||||
const [messRequete, setMessRequete] = useState("");
|
||||
function handleSubmit(event) {
|
||||
event.preventDefault();
|
||||
|
||||
if (verif) {
|
||||
console.log("Envoi requete");
|
||||
axios
|
||||
.post(`${API_BASE_URL}/addObject`, {
|
||||
nom,
|
||||
description,
|
||||
type,
|
||||
location,
|
||||
status,
|
||||
})
|
||||
.then((response) => {
|
||||
setMessRequete("Votre objet à bien été enregistré !");
|
||||
setEnregistre(true);
|
||||
console.log("Ajout de l'objet réussit :", response.data);
|
||||
})
|
||||
.catch((error) => {
|
||||
setMessRequete("Il y a eu une erreur dans l'ajout de votre objet !");
|
||||
console.error("Erreur lors de l'ajout de l'objet :", error);
|
||||
});
|
||||
setVerif(false);
|
||||
resetForm();
|
||||
} else {
|
||||
setVerif(true);
|
||||
}
|
||||
}
|
||||
function resetForm() {
|
||||
setNom("");
|
||||
setStatus("");
|
||||
setDescription("");
|
||||
setType("");
|
||||
setLocalisation("");
|
||||
setActive(true);
|
||||
}
|
||||
function handleCancel() {
|
||||
if (verif) {
|
||||
setVerif(false);
|
||||
} else {
|
||||
resetForm();
|
||||
}
|
||||
}
|
||||
|
||||
function handleStatusChange() {
|
||||
setActive((prevIsActive) => {
|
||||
const newIsActive = !prevIsActive;
|
||||
setStatus(newIsActive ? "active" : "inactive");
|
||||
return newIsActive;
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<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="text-center mb-5">
|
||||
<h2 className="text-4xl font-bold text-gray-900 mb-12">
|
||||
Nouvel objet
|
||||
</h2>
|
||||
</div>
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
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">
|
||||
<BadgePlus className="text-indigo-600" size={24} />
|
||||
</div>
|
||||
<h1 className="text-black text-2xl font-bold mb-1">
|
||||
{!verif
|
||||
? "Entrez les données de votre nouvel objet"
|
||||
: "Êtes-vous sûr de ces données ?"}
|
||||
</h1>
|
||||
</div>
|
||||
<div className="mb-5">
|
||||
<label
|
||||
htmlFor="nom"
|
||||
className="block mb-2 text-sm font-medium text-gray-900"
|
||||
>
|
||||
Nom :
|
||||
</label>
|
||||
<input
|
||||
id="nom"
|
||||
className="text-gray-600 border rounded-lg p-2 w-full"
|
||||
type="text"
|
||||
value={nom}
|
||||
onChange={(e) => setNom(e.target.value)}
|
||||
required
|
||||
disabled={verif}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-5">
|
||||
<label
|
||||
htmlFor="description"
|
||||
className="block mb-2 text-sm font-medium text-gray-900"
|
||||
>
|
||||
Description :
|
||||
</label>
|
||||
<input
|
||||
id="description"
|
||||
className="text-gray-600 border rounded-lg p-2 w-full"
|
||||
type="text"
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
required
|
||||
disabled={verif}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mb-5">
|
||||
<label
|
||||
htmlFor="type"
|
||||
className="block mb-2 text-sm font-medium text-gray-900"
|
||||
>
|
||||
Type :
|
||||
</label>
|
||||
<input
|
||||
id="type"
|
||||
className="text-gray-600 border rounded-lg p-2 w-full"
|
||||
type="text"
|
||||
value={type}
|
||||
onChange={(e) => setType(e.target.value)}
|
||||
required
|
||||
disabled={verif}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mb-5">
|
||||
<label
|
||||
htmlFor="location"
|
||||
className="block mb-2 text-sm font-medium text-gray-900"
|
||||
>
|
||||
Localisation :
|
||||
</label>
|
||||
<input
|
||||
id="location"
|
||||
className="text-gray-600 border rounded-lg p-2 w-full"
|
||||
type="text"
|
||||
value={location}
|
||||
onChange={(e) => setLocalisation(e.target.value)}
|
||||
required
|
||||
disabled={verif}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mb-5">
|
||||
<label className="block mb-2 text-sm font-medium text-gray-900">
|
||||
Status :
|
||||
</label>
|
||||
<div className="inline-flex items-center gap-2">
|
||||
<label
|
||||
htmlFor="switch-component-on"
|
||||
className="text-slate-600 text-sm cursor-pointer"
|
||||
>
|
||||
Inactive
|
||||
</label>
|
||||
<div className="relative inline-block w-11 h-5">
|
||||
<input
|
||||
id="switch-component-on"
|
||||
type="checkbox"
|
||||
checked={isActive}
|
||||
onChange={handleStatusChange}
|
||||
className="peer appearance-none w-11 h-5 bg-slate-100 rounded-full checked:bg-slate-800 cursor-pointer transition-colors duration-300"
|
||||
disabled={verif}
|
||||
/>
|
||||
<label
|
||||
htmlFor="switch-component-on"
|
||||
className="absolute top-0 left-0 w-5 h-5 bg-white rounded-full border border-slate-300 shadow-sm transition-transform duration-300 peer-checked:translate-x-6 peer-checked:border-slate-800 cursor-pointer"
|
||||
></label>
|
||||
</div>
|
||||
<label
|
||||
htmlFor="switch-component-on"
|
||||
className="text-slate-600 text-sm cursor-pointer"
|
||||
>
|
||||
Active
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col mb-5 ">
|
||||
<button
|
||||
type={"submit"}
|
||||
className="text-blue-500 hover:cursor-pointer hover:underline mb-2"
|
||||
>
|
||||
{!verif ? "Confirmer les informations" : "Oui je suis sûr !"}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="text-red-500 hover:cursor-pointer hover:underline"
|
||||
onClick={handleCancel}
|
||||
>
|
||||
{!verif ? "Supprimer les informations" : "Non je veux changer !"}
|
||||
</button>
|
||||
</div>
|
||||
<p className={(enregistre)?("text-green-700"):("text-red-700")}>
|
||||
{messRequete}
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default AddObject;
|
||||
@ -10,9 +10,8 @@ import {
|
||||
RadioTower,
|
||||
Binoculars,
|
||||
Settings,
|
||||
ChartArea,
|
||||
BadgePlus,
|
||||
} from "lucide-react";
|
||||
|
||||
function Gestion() {
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-50">
|
||||
@ -22,84 +21,43 @@ function Gestion() {
|
||||
Bienvenue dans le module <b>Gestion</b>.
|
||||
</h2>
|
||||
<p className="text-xl text-gray-600 max-w-3xl mx-auto">
|
||||
Dans ce module, vous allez pouvoir gerer les objets connectés de
|
||||
l'hopital.
|
||||
Ce module vous permet de gérer les capteur et stations connectés de France de manière simple et efficace.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Features Grid */}
|
||||
<div className="grid md:grid-cols-3 gap-8">
|
||||
<div className="grid md:grid-cols-2 gap-8">
|
||||
<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">
|
||||
<RadioTower className="text-indigo-600" size={24} />
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold mb-2">
|
||||
Gestion des objets connectés
|
||||
Consulter les objets connectés météorologiques
|
||||
</h3>
|
||||
<p className="text-gray-600 mb-4">
|
||||
Découvrez les meilleurs endroits de votre ville, qu'il s'agisse de
|
||||
restaurants, de parcs ou de lieux culturels.
|
||||
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.
|
||||
</p>
|
||||
<a
|
||||
href="/gestionObjets"
|
||||
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>
|
||||
</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">
|
||||
<Settings className="text-indigo-600" size={24} />
|
||||
<BadgePlus className="text-indigo-600" size={24} />
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold mb-2">
|
||||
Configurer des services
|
||||
Ajouter un nouvel objet connecté
|
||||
</h3>
|
||||
<p className="text-gray-600 mb-4">
|
||||
Découvrez les meilleurs endroits de votre ville, qu'il s'agisse de
|
||||
restaurants, de parcs ou de lieux culturels.
|
||||
Intégrez facilement un nouvel objet connecté en renseignant ses informations et en configurant ses paramètres pour une gestion optimale.
|
||||
</p>
|
||||
<a
|
||||
href="#"
|
||||
href="/ajouterObjet"
|
||||
className="flex items-center text-indigo-600 hover:text-indigo-700"
|
||||
>
|
||||
Explorer les lieux <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">
|
||||
<Binoculars className="text-indigo-600" size={24} />
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold mb-2">
|
||||
Surveillance et optimisation des ressources
|
||||
</h3>
|
||||
<p className="text-gray-600 mb-4">
|
||||
Restez informé des derniers événements, festivals et
|
||||
rassemblements communautaires dans votre région.
|
||||
</p>
|
||||
<a
|
||||
href="#"
|
||||
className="flex items-center text-indigo-600 hover:text-indigo-700"
|
||||
>
|
||||
Voir les événements <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" />
|
||||
Ajouter un objet <ArrowRight size={16} className="ml-2" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,29 +1,31 @@
|
||||
import React from "react";
|
||||
import {
|
||||
Search,
|
||||
MapPin,
|
||||
Calendar,
|
||||
Bus,
|
||||
ArrowRight,
|
||||
LogIn,
|
||||
UserPlus,
|
||||
RadioTower,
|
||||
Binoculars,
|
||||
Settings,
|
||||
ChartArea,
|
||||
} from "lucide-react";
|
||||
import { Search, ArrowRight, RadioTower,Plus } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import axios from "axios";
|
||||
import { useFetcher } from "react-router-dom";
|
||||
|
||||
function ObjectManagement() {
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [activeFilter, setActiveFilter] = useState("all");
|
||||
const [activeFilter, setActiveFilter] = useState("");
|
||||
const [objects, setObjects] = useState([]);
|
||||
const [nbAffObject,setnbAffObject] = useState(6);
|
||||
const filteredDATA = objects.filter((node) => {
|
||||
const matchesSearchQuery =
|
||||
searchQuery === "" ||
|
||||
node.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
node.description.toLowerCase().includes(searchQuery.toLowerCase());
|
||||
|
||||
const matchesTag =
|
||||
activeFilter === "" ||
|
||||
node.name.toLowerCase().includes(activeFilter.toLowerCase()) ||
|
||||
node.description.includes(activeFilter.toLowerCase()) ||
|
||||
(activeFilter === "Active" && node.status.toLowerCase() === "active") ||
|
||||
(activeFilter === "Inactive" && node.status.toLowerCase() === "inactive");
|
||||
return matchesSearchQuery && matchesTag;
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
axios.get("http://localhost:8888/objets").then((response) => {
|
||||
setObjects(response.data);
|
||||
console.log(response.data);
|
||||
});
|
||||
}, []);
|
||||
return (
|
||||
@ -50,9 +52,9 @@ function ObjectManagement() {
|
||||
</div>
|
||||
<div className="flex gap-4 mt-4 justify-center">
|
||||
<button
|
||||
onClick={() => setActiveFilter("all")}
|
||||
onClick={() => setActiveFilter("")}
|
||||
className={`px-4 py-2 rounded-lg ${
|
||||
activeFilter === "all"
|
||||
activeFilter === ""
|
||||
? "bg-indigo-600 text-white"
|
||||
: "bg-white text-gray-600"
|
||||
}`}
|
||||
@ -60,19 +62,9 @@ function ObjectManagement() {
|
||||
Tout
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveFilter("locations")}
|
||||
onClick={() => setActiveFilter("Station")}
|
||||
className={`px-4 py-2 rounded-lg ${
|
||||
activeFilter === "locations"
|
||||
? "bg-indigo-600 text-white"
|
||||
: "bg-white text-gray-600"
|
||||
}`}
|
||||
>
|
||||
Villes
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveFilter("events")}
|
||||
className={`px-4 py-2 rounded-lg ${
|
||||
activeFilter === "events"
|
||||
activeFilter === "Station"
|
||||
? "bg-indigo-600 text-white"
|
||||
: "bg-white text-gray-600"
|
||||
}`}
|
||||
@ -80,29 +72,66 @@ function ObjectManagement() {
|
||||
Station météo
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveFilter("transport")}
|
||||
onClick={() => setActiveFilter("Capteur")}
|
||||
className={`px-4 py-2 rounded-lg ${
|
||||
activeFilter === "transport"
|
||||
activeFilter === "Capteur"
|
||||
? "bg-indigo-600 text-white"
|
||||
: "bg-white text-gray-600"
|
||||
}`}
|
||||
>
|
||||
Capteur
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveFilter("Active")}
|
||||
className={`px-4 py-2 rounded-lg ${
|
||||
activeFilter === "Active"
|
||||
? "bg-indigo-600 text-white"
|
||||
: "bg-white text-gray-600"
|
||||
}`}
|
||||
>
|
||||
Actif
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveFilter("Inactive")}
|
||||
className={`px-4 py-2 rounded-lg ${
|
||||
activeFilter === "Inactive"
|
||||
? "bg-indigo-600 text-white"
|
||||
: "bg-white text-gray-600"
|
||||
}`}
|
||||
>
|
||||
Inactif
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid md:grid-cols-3 gap-8">
|
||||
{objects.map(object =>(
|
||||
<div key={object.id} className="bg-white p-6 rounded-xl shadow-sm hover:shadow-md transition-shadow">
|
||||
{filteredDATA.length === 0 ? (
|
||||
<p>Aucun objet trouvé</p>
|
||||
) : (
|
||||
filteredDATA.slice(0,nbAffObject).map((object) => (
|
||||
<div
|
||||
key={object.id}
|
||||
className="bg-white p-6 rounded-xl shadow-sm hover:shadow-md transition-shadow "
|
||||
>
|
||||
{object.status === "active" ? (
|
||||
<div className="relative w-full">
|
||||
<span className="absolute right-0 flex size-3">
|
||||
<span className="absolute inline-flex h-full w-full rounded-full animate-ping bg-green-400 opacity-75"></span>
|
||||
<span className="relative inline-flex size-3 rounded-full bg-green-500"></span>
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="relative w-full">
|
||||
<span className="absolute right-0 flex size-3">
|
||||
<span className="relative inline-flex size-3 rounded-full bg-red-600"></span>
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="w-12 h-12 bg-indigo-100 rounded-lg flex items-center justify-center mb-4">
|
||||
<RadioTower className="text-indigo-600" size={24} />
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold mb-2">
|
||||
{object.name}
|
||||
</h3>
|
||||
<p className="text-gray-600 mb-4">
|
||||
{object.description}
|
||||
</p>
|
||||
<h3 className="text-xl font-semibold mb-2">{object.name}</h3>
|
||||
<p className="text-gray-600 mb-4">{object.description}</p>
|
||||
<a
|
||||
href={`/objet?id=${object.id}`}
|
||||
className="flex items-center text-indigo-600 hover:text-indigo-700"
|
||||
@ -110,8 +139,15 @@ function ObjectManagement() {
|
||||
Plus d'infos <ArrowRight size={16} className="ml-2" />
|
||||
</a>
|
||||
</div>
|
||||
))}
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
{(nbAffObject<filteredDATA.length)&&(
|
||||
<div className="flex items-center flex-col mt-6">
|
||||
<button onClick={()=>{setnbAffObject((prev)=>prev+6 )}}><Plus size={40}/></button>
|
||||
<label>Voir plus</label>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -1,58 +1,117 @@
|
||||
import React from "react";
|
||||
import {
|
||||
Search,
|
||||
MapPin,
|
||||
Calendar,
|
||||
Bus,
|
||||
ArrowRight,
|
||||
LogIn,
|
||||
UserPlus,
|
||||
RadioTower,
|
||||
Binoculars,
|
||||
Settings,
|
||||
ChartArea,
|
||||
} from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import axios from "axios";
|
||||
import { useFetcher } from "react-router-dom";
|
||||
import { Thermometer, CircleGauge, Droplet } from "lucide-react";
|
||||
|
||||
import { useEffect, useState, useRef} from "react";
|
||||
import axios from "axios";
|
||||
import { API_BASE_URL } from "../../config";
|
||||
|
||||
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";
|
||||
import BatterieInfo from "../../components/BatterieInfo";
|
||||
function Objet() {
|
||||
const identifiant = new URLSearchParams(window.location.search).get("id");
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [activeFilter, setActiveFilter] = useState("all");
|
||||
const [object, setObject] = useState({});
|
||||
const [graphStates, setGraphStates] = useState({
|
||||
wind:false,
|
||||
temperature:false,
|
||||
pressure:false,
|
||||
humidity:false,
|
||||
})
|
||||
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);
|
||||
const tempGraphRef = useRef(null);
|
||||
const pressureGraphRef = useRef(null);
|
||||
const humidityGraphRef = useRef(null);
|
||||
const windGraphRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
axios.get(`http://localhost:8888/objet?id=${identifiant}`).then((response) => {
|
||||
axios.get(`${API_BASE_URL}/objet?id=${identifiant}`).then((response) => {
|
||||
setObject(response.data[0]);
|
||||
console.log(response.data);
|
||||
});
|
||||
}, [identifiant]);
|
||||
return (
|
||||
<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="text-center mb-12">
|
||||
<h2 className="text-4xl font-bold text-gray-900 mb-4">
|
||||
{object.name}
|
||||
<div className="text-center mb-5">
|
||||
<h2 className="text-4xl font-bold text-gray-900 mb-12">
|
||||
Tableau de bord - {object.name}
|
||||
</h2>
|
||||
</div>
|
||||
<div className="grid md:grid-cols-3 gap-8">
|
||||
<div
|
||||
key={object.id}
|
||||
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">
|
||||
<RadioTower className="text-indigo-600" size={24} />
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold mb-2">{object.name}</h3>
|
||||
<p className="text-gray-600 mb-4">{object.description}</p>
|
||||
<a
|
||||
href={`/objet?id=${object.id}`}
|
||||
className="flex items-center text-indigo-600 hover:text-indigo-700"
|
||||
>
|
||||
Plus d'infos <ArrowRight size={16} className="ml-2" />
|
||||
</a>
|
||||
</div>
|
||||
<div className="grid md:grid-cols-1 lg:grid-cols-3 gap-8 mb-5">
|
||||
{!afficherModif ? (
|
||||
<InfoObjet object={object} defafficherModif={defafficherModif} />
|
||||
) : (
|
||||
<ModifObject object={object} defafficherModif={defafficherModif} />
|
||||
)}
|
||||
|
||||
{object && object.id ? (
|
||||
<WindInfo
|
||||
object={object}
|
||||
defAffWindGraph={defAffWindGraph}
|
||||
AffWindGraph={AffWindGraph}
|
||||
/>
|
||||
) : (
|
||||
<p>Chargement des données...</p>
|
||||
)}
|
||||
{object && object.id ? (
|
||||
<MeteoInfos
|
||||
object={object}
|
||||
defAffTempGraph={defAffTempGraph}
|
||||
AffTempGraph={AffTempGraph}
|
||||
defAffPressionGraph={defAffPressionGraph}
|
||||
AffPressionGraph={AffPressionGraph}
|
||||
defAffHumiditeGraph={defAffHumideGraph}
|
||||
AffHumiditeGraph={AffHumiditeGraph}
|
||||
tempGraphRef={tempGraphRef}
|
||||
pressureGraphRef={pressureGraphRef}
|
||||
humidityGraphRef={humidityGraphRef}
|
||||
/>
|
||||
) : (
|
||||
<p>Chargement des données...</p>
|
||||
)}
|
||||
<BatterieInfo object={object} />
|
||||
</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>
|
||||
);
|
||||
|
||||
@ -1,18 +1,117 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
function Home() {
|
||||
const token = localStorage.getItem("token");
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [activeFilter, setActiveFilter] = useState('all');
|
||||
const [name, setName] = useState([]);
|
||||
return (
|
||||
|
||||
<div className='max-w-[1296px] m-auto flex flex-col md:flex-row items-center justify-between p-[50px_0_60px] md:p-[104px_0]'>
|
||||
<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="text-center mb-12">
|
||||
<h2 className="text-4xl font-bold text-gray-900 mb-4">
|
||||
Bienvenue dans ta ville intelligente.</h2>
|
||||
{token ? (
|
||||
<>Home
|
||||
<img src='public/images/snow.jpg' />
|
||||
</>):(
|
||||
<h2>Non connecté</h2>
|
||||
)}
|
||||
<p className="text-xl text-gray-600 max-w-3xl mx-auto">
|
||||
Découvrez tout ce que votre ville a à offrir - des événements locaux aux horaires de transport, le tout en un seul endroit.
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
|
||||
<div className="max-w-3xl mx-auto mb-12">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-4 top-1/2 transform -translate-y-1/2 text-gray-400" size={24} />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search for locations, events, or transport..."
|
||||
className="w-full pl-12 pr-4 py-4 rounded-xl border border-gray-200 focus:outline-none focus:ring-2 focus:ring-indigo-500"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex gap-4 mt-4 justify-center">
|
||||
<button
|
||||
onClick={() => setActiveFilter('all')}
|
||||
className={`px-4 py-2 rounded-lg ${
|
||||
activeFilter === 'all' ? 'bg-indigo-600 text-white' : 'bg-white text-gray-600'
|
||||
}`}
|
||||
>
|
||||
All
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveFilter('locations')}
|
||||
className={`px-4 py-2 rounded-lg ${
|
||||
activeFilter === 'locations' ? 'bg-indigo-600 text-white' : 'bg-white text-gray-600'
|
||||
}`}
|
||||
>
|
||||
Locations
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveFilter('events')}
|
||||
className={`px-4 py-2 rounded-lg ${
|
||||
activeFilter === 'events' ? 'bg-indigo-600 text-white' : 'bg-white text-gray-600'
|
||||
}`}
|
||||
>
|
||||
Events
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveFilter('transport')}
|
||||
className={`px-4 py-2 rounded-lg ${
|
||||
activeFilter === 'transport' ? 'bg-indigo-600 text-white' : 'bg-white text-gray-600'
|
||||
}`}
|
||||
>
|
||||
Transport
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Features Grid */}
|
||||
<div className="grid md:grid-cols-3 gap-8">
|
||||
<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">
|
||||
<MapPin className="text-indigo-600" size={24} />
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold mb-2">Points d'intérêt</h3>
|
||||
<p className="text-gray-600 mb-4">
|
||||
Découvrez les meilleurs endroits de votre ville, qu'il s'agisse de restaurants, de parcs ou de lieux culturels.
|
||||
</p>
|
||||
<a href="#" className="flex items-center text-indigo-600 hover:text-indigo-700">
|
||||
Explorer les lieux <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">
|
||||
<Calendar className="text-indigo-600" size={24} />
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold mb-2">Evenements locaux</h3>
|
||||
<p className="text-gray-600 mb-4">
|
||||
Restez informé des derniers événements, festivals et rassemblements communautaires dans votre région.
|
||||
</p>
|
||||
<a href="#" className="flex items-center text-indigo-600 hover:text-indigo-700">
|
||||
Voir les événements <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">
|
||||
<Bus className="text-indigo-600" size={24} />
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold mb-2">Transports publics</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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Home;
|
||||