Merge remote-tracking branch 'origin/main' into Charles
@ -49,7 +49,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.agroal</groupId>
|
<groupId>io.agroal</groupId>
|
||||||
<artifactId>agroal-pool</artifactId>
|
<artifactId>agroal-pool</artifactId>
|
||||||
<version>1.16</version> <!-- Utilisez la version la plus récente -->
|
<version>1.16</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.vertx</groupId>
|
<groupId>io.vertx</groupId>
|
||||||
|
|||||||
@ -1,15 +1,14 @@
|
|||||||
package com.example.starter;
|
package com.example.starter;
|
||||||
|
|
||||||
|
import io.vertx.ext.web.handler.BodyHandler;
|
||||||
import io.vertx.ext.web.handler.BodyHandler;
|
import io.vertx.ext.web.handler.BodyHandler;
|
||||||
import io.vertx.ext.web.handler.CorsHandler;
|
import io.vertx.ext.web.handler.CorsHandler;
|
||||||
import io.vertx.sqlclient.Row;
|
|
||||||
import io.vertx.sqlclient.RowSet;
|
|
||||||
import io.vertx.sqlclient.Tuple;
|
|
||||||
import io.vertx.core.http.HttpMethod;
|
import io.vertx.core.http.HttpMethod;
|
||||||
import io.vertx.core.json.JsonArray;
|
import io.vertx.core.json.JsonArray;
|
||||||
import io.vertx.core.json.JsonObject;
|
import io.vertx.core.json.JsonObject;
|
||||||
import io.vertx.core.AbstractVerticle;
|
import io.vertx.core.AbstractVerticle;
|
||||||
import io.vertx.core.Promise;
|
import io.vertx.core.Promise;
|
||||||
|
|
||||||
import io.vertx.ext.web.Router;
|
import io.vertx.ext.web.Router;
|
||||||
import io.vertx.ext.web.RoutingContext;
|
import io.vertx.ext.web.RoutingContext;
|
||||||
import at.favre.lib.crypto.bcrypt.BCrypt;
|
import at.favre.lib.crypto.bcrypt.BCrypt;
|
||||||
@ -45,6 +44,12 @@ public class MainVerticle extends AbstractVerticle {
|
|||||||
router.route().handler(BodyHandler.create());
|
router.route().handler(BodyHandler.create());
|
||||||
|
|
||||||
// Gestion des CORS
|
// 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()
|
router.route().handler(CorsHandler.create()
|
||||||
.addOrigin("*")
|
.addOrigin("*")
|
||||||
.allowedMethod(HttpMethod.GET)
|
.allowedMethod(HttpMethod.GET)
|
||||||
@ -59,6 +64,12 @@ public class MainVerticle extends AbstractVerticle {
|
|||||||
router.post("/login").handler(this::handleLogin); // Route pour la connexion
|
router.post("/login").handler(this::handleLogin); // Route pour la connexion
|
||||||
// Protéger toutes les routes commençant par "/api/"
|
// Protéger toutes les routes commençant par "/api/"
|
||||||
router.route("/api/*").handler(JWTAuthHandler.create(jwtAuth));
|
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
|
// Création du serveur HTTP
|
||||||
vertx.createHttpServer()
|
vertx.createHttpServer()
|
||||||
@ -259,3 +270,5 @@ 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",
|
"axios": "^1.8.4",
|
||||||
"lucide-react": "^0.427.0",
|
"lucide-react": "^0.427.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
|
"react-charts": "^3.0.0-beta.57",
|
||||||
|
"react-circle-progress-bar": "^0.1.4",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-router-dom": "^7.4.0"
|
"react-router-dom": "^7.4.0",
|
||||||
|
"recharts": "^2.15.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.9.1",
|
"@eslint/js": "^9.9.1",
|
||||||
@ -262,6 +265,18 @@
|
|||||||
"@babel/core": "^7.0.0-0"
|
"@babel/core": "^7.0.0-0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@babel/runtime": {
|
||||||
|
"version": "7.27.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz",
|
||||||
|
"integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"regenerator-runtime": "^0.14.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@babel/template": {
|
"node_modules/@babel/template": {
|
||||||
"version": "7.26.9",
|
"version": "7.26.9",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz",
|
||||||
@ -1302,6 +1317,69 @@
|
|||||||
"integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==",
|
"integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/d3-array": {
|
||||||
|
"version": "3.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz",
|
||||||
|
"integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-color": {
|
||||||
|
"version": "3.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
|
||||||
|
"integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-ease": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-interpolate": {
|
||||||
|
"version": "3.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
|
||||||
|
"integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/d3-color": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-path": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-scale": {
|
||||||
|
"version": "4.0.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz",
|
||||||
|
"integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/d3-time": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-shape": {
|
||||||
|
"version": "3.1.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz",
|
||||||
|
"integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/d3-path": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-time": {
|
||||||
|
"version": "3.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz",
|
||||||
|
"integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-timer": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/estree": {
|
"node_modules/@types/estree": {
|
||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
|
||||||
@ -1314,6 +1392,44 @@
|
|||||||
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
|
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/prop-types": {
|
||||||
|
"version": "15.7.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz",
|
||||||
|
"integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/raf": {
|
||||||
|
"version": "3.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz",
|
||||||
|
"integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/react": {
|
||||||
|
"version": "17.0.85",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.85.tgz",
|
||||||
|
"integrity": "sha512-5oBDUsRDsrYq4DdyHaL99gE1AJCfuDhyxqF6/55fvvOIRkp1PpKuwJ+aMiGJR+GJt7YqMNclPROTHF20vY2cXA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/prop-types": "*",
|
||||||
|
"@types/scheduler": "^0.16",
|
||||||
|
"csstype": "^3.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/react-dom": {
|
||||||
|
"version": "17.0.26",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.26.tgz",
|
||||||
|
"integrity": "sha512-Z+2VcYXJwOqQ79HreLU/1fyQ88eXSSFh6I3JdrEHQIfYSI0kCQpTGvOrbE6jFGGYXKsHuwY9tBa/w5Uo6KzrEg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "^17.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/scheduler": {
|
||||||
|
"version": "0.16.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz",
|
||||||
|
"integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@vitejs/plugin-react": {
|
"node_modules/@vitejs/plugin-react": {
|
||||||
"version": "4.3.4",
|
"version": "4.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.4.tgz",
|
||||||
@ -1656,6 +1772,15 @@
|
|||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/clsx": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/color-convert": {
|
"node_modules/color-convert": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||||
@ -1742,6 +1867,127 @@
|
|||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/csstype": {
|
||||||
|
"version": "3.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||||
|
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/d3-array": {
|
||||||
|
"version": "2.12.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz",
|
||||||
|
"integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"internmap": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-color": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-SPXi0TSKPD4g9tw0NMZFnR95XVgUZiBH+uUTqQuDu1OsE2zomHU7ho0FISciaPvosimixwHFl3WHLGabv6dDgQ==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/d3-delaunay": {
|
||||||
|
"version": "5.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-5.3.0.tgz",
|
||||||
|
"integrity": "sha512-amALSrOllWVLaHTnDLHwMIiz0d1bBu9gZXd1FiLfXf8sHcX9jrcj81TVZOqD4UX7MgBZZ07c8GxzEgBpJqc74w==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"delaunator": "4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-ease": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-format": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-Ab3S6XuE/Q+flY96HXT0jOXcM4EAClYFnRGY5zsjRGNy6qCYrQsMffs7cV5Q9xejb35zxW5hf/guKw34kvIKsA==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/d3-interpolate": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-c5UhwwTs/yybcmTpAVqwSFl6vrQ8JZJoT5F7xNFK9pymv5C0Ymcc9/LIJHtYIggg/yS9YHw8i8O8tgb9pupjeQ==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-color": "1 - 2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-path": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-path/-/d3-path-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-ZwZQxKhBnv9yHaiWd6ZU4x5BtCQ7pXszEV9CU6kRgwIQVQGLMv1oiL4M+MK/n79sYzsj+gcgpPQSctJUsLN7fA==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/d3-scale": {
|
||||||
|
"version": "3.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-3.3.0.tgz",
|
||||||
|
"integrity": "sha512-1JGp44NQCt5d1g+Yy+GeOnZP7xHo0ii8zsQp6PGzd+C1/dl0KGsp9A7Mxwp+1D1o4unbTTxVdU/ZOIEBoeZPbQ==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-array": "^2.3.0",
|
||||||
|
"d3-format": "1 - 2",
|
||||||
|
"d3-interpolate": "1.2.0 - 2",
|
||||||
|
"d3-time": "^2.1.1",
|
||||||
|
"d3-time-format": "2 - 3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-scale/node_modules/d3-time-format": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-UXJh6EKsHBTjopVqZBhFysQcoXSv/5yLONZvkQ5Kk3qbwiUYkdX17Xa1PT6U1ZWXGGfB1ey5L8dKMlFq2DO0Ag==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-time": "1 - 2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-shape": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-PnjUqfM2PpskbSLTJvAzp2Wv4CZsnAgTfcVRTwW03QR3MkXF8Uo7B1y/lWkAsmbKwuecto++4NlsYcvYpXpTHA==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-path": "1 - 2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-time": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-/eIQe/eR4kCQwq7yxi7z4c6qEXf2IYGcjoWB5OOQy4Tq9Uv39/947qlDcN2TLkiTzQWzvnsuYPB9TrWaNfipKQ==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-array": "2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-time-format": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-time": "1 - 3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-timer": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
"version": "4.4.0",
|
"version": "4.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
|
||||||
@ -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": {
|
"node_modules/deep-is": {
|
||||||
"version": "0.1.4",
|
"version": "0.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
|
||||||
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
|
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/delaunator": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/delaunator/-/delaunator-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-WNPWi1IRKZfCt/qIDMfERkDp93+iZEmOxN2yy4Jg+Xhv8SLk2UTqqbe1sfiipn0and9QrE914/ihdx82Y/Giag==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/delayed-stream": {
|
"node_modules/delayed-stream": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
@ -1786,6 +2044,16 @@
|
|||||||
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
|
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/dom-helpers": {
|
||||||
|
"version": "5.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
|
||||||
|
"integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.8.7",
|
||||||
|
"csstype": "^3.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/dunder-proto": {
|
"node_modules/dunder-proto": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||||
@ -2090,12 +2358,27 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/eventemitter3": {
|
||||||
|
"version": "4.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
|
||||||
|
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/fast-deep-equal": {
|
"node_modules/fast-deep-equal": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/fast-equals": {
|
||||||
|
"version": "5.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.2.2.tgz",
|
||||||
|
"integrity": "sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fast-glob": {
|
"node_modules/fast-glob": {
|
||||||
"version": "3.3.3",
|
"version": "3.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
|
||||||
@ -2497,6 +2780,12 @@
|
|||||||
"node": ">=0.8.19"
|
"node": ">=0.8.19"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/internmap": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/is-binary-path": {
|
"node_modules/is-binary-path": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
||||||
@ -2707,6 +2996,12 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash": {
|
||||||
|
"version": "4.17.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||||
|
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/lodash.merge": {
|
"node_modules/lodash.merge": {
|
||||||
"version": "4.6.2",
|
"version": "4.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||||
@ -2884,7 +3179,6 @@
|
|||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@ -3197,6 +3491,23 @@
|
|||||||
"node": ">= 0.8.0"
|
"node": ">= 0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/prop-types": {
|
||||||
|
"version": "15.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||||
|
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"loose-envify": "^1.4.0",
|
||||||
|
"object-assign": "^4.1.1",
|
||||||
|
"react-is": "^16.13.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/prop-types/node_modules/react-is": {
|
||||||
|
"version": "16.13.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
|
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/proxy-from-env": {
|
"node_modules/proxy-from-env": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||||
@ -3243,6 +3554,85 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-charts": {
|
||||||
|
"version": "3.0.0-beta.57",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-charts/-/react-charts-3.0.0-beta.57.tgz",
|
||||||
|
"integrity": "sha512-vqas7IQhsnDGcMxreGaWXvSIL3poEMoUBNltJrslz/+m0pI3QejBCszL1QrLNYQfOWXrbZADfedi/a+yWOQ7Hw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.14.6",
|
||||||
|
"@types/d3-array": "^3.0.1",
|
||||||
|
"@types/d3-scale": "^4.0.1",
|
||||||
|
"@types/d3-shape": "^3.0.1",
|
||||||
|
"@types/raf": "^3.4.0",
|
||||||
|
"@types/react": "^17.0.14",
|
||||||
|
"@types/react-dom": "^17.0.9",
|
||||||
|
"d3-array": "^2.12.1",
|
||||||
|
"d3-delaunay": "5.3.0",
|
||||||
|
"d3-scale": "^3.3.0",
|
||||||
|
"d3-shape": "^2.1.0",
|
||||||
|
"d3-time": "^2.1.1",
|
||||||
|
"d3-time-format": "^4.1.0",
|
||||||
|
"ts-toolbelt": "^9.6.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/tannerlinsley"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16",
|
||||||
|
"react-dom": ">=16"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-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": {
|
"node_modules/react-dom": {
|
||||||
"version": "18.3.1",
|
"version": "18.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
||||||
@ -3255,6 +3645,12 @@
|
|||||||
"react": "^18.3.1"
|
"react": "^18.3.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-is": {
|
||||||
|
"version": "18.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
|
||||||
|
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/react-refresh": {
|
"node_modules/react-refresh": {
|
||||||
"version": "0.14.2",
|
"version": "0.14.2",
|
||||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz",
|
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz",
|
||||||
@ -3304,6 +3700,37 @@
|
|||||||
"react-dom": ">=18"
|
"react-dom": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-smooth": {
|
||||||
|
"version": "4.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz",
|
||||||
|
"integrity": "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"fast-equals": "^5.0.1",
|
||||||
|
"prop-types": "^15.8.1",
|
||||||
|
"react-transition-group": "^4.4.5"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-transition-group": {
|
||||||
|
"version": "4.4.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
|
||||||
|
"integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.5.5",
|
||||||
|
"dom-helpers": "^5.0.1",
|
||||||
|
"loose-envify": "^1.4.0",
|
||||||
|
"prop-types": "^15.6.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.6.0",
|
||||||
|
"react-dom": ">=16.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/read-cache": {
|
"node_modules/read-cache": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||||
@ -3325,6 +3752,44 @@
|
|||||||
"node": ">=8.10.0"
|
"node": ">=8.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/recharts": {
|
||||||
|
"version": "2.15.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.1.tgz",
|
||||||
|
"integrity": "sha512-v8PUTUlyiDe56qUj82w/EDVuzEFXwEHp9/xOowGAZwfLjB9uAy3GllQVIYMWF6nU+qibx85WF75zD7AjqoT54Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"clsx": "^2.0.0",
|
||||||
|
"eventemitter3": "^4.0.1",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"react-is": "^18.3.1",
|
||||||
|
"react-smooth": "^4.0.4",
|
||||||
|
"recharts-scale": "^0.4.4",
|
||||||
|
"tiny-invariant": "^1.3.1",
|
||||||
|
"victory-vendor": "^36.6.8"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/recharts-scale": {
|
||||||
|
"version": "0.4.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz",
|
||||||
|
"integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"decimal.js-light": "^2.4.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/regenerator-runtime": {
|
||||||
|
"version": "0.14.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
|
||||||
|
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/resolve": {
|
"node_modules/resolve": {
|
||||||
"version": "1.22.10",
|
"version": "1.22.10",
|
||||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
||||||
@ -3709,6 +4174,12 @@
|
|||||||
"node": ">=0.8"
|
"node": ">=0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tiny-invariant": {
|
||||||
|
"version": "1.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
|
||||||
|
"integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/to-regex-range": {
|
"node_modules/to-regex-range": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||||
@ -3727,6 +4198,12 @@
|
|||||||
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
|
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/ts-toolbelt": {
|
||||||
|
"version": "9.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-9.6.0.tgz",
|
||||||
|
"integrity": "sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w==",
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
"node_modules/turbo-stream": {
|
"node_modules/turbo-stream": {
|
||||||
"version": "2.4.0",
|
"version": "2.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz",
|
||||||
@ -3790,6 +4267,101 @@
|
|||||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/victory-vendor": {
|
||||||
|
"version": "36.9.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz",
|
||||||
|
"integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==",
|
||||||
|
"license": "MIT AND ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/d3-array": "^3.0.3",
|
||||||
|
"@types/d3-ease": "^3.0.0",
|
||||||
|
"@types/d3-interpolate": "^3.0.1",
|
||||||
|
"@types/d3-scale": "^4.0.2",
|
||||||
|
"@types/d3-shape": "^3.1.0",
|
||||||
|
"@types/d3-time": "^3.0.0",
|
||||||
|
"@types/d3-timer": "^3.0.0",
|
||||||
|
"d3-array": "^3.1.6",
|
||||||
|
"d3-ease": "^3.0.1",
|
||||||
|
"d3-interpolate": "^3.0.1",
|
||||||
|
"d3-scale": "^4.0.2",
|
||||||
|
"d3-shape": "^3.1.0",
|
||||||
|
"d3-time": "^3.0.0",
|
||||||
|
"d3-timer": "^3.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/victory-vendor/node_modules/d3-array": {
|
||||||
|
"version": "3.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
|
||||||
|
"integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"internmap": "1 - 2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/victory-vendor/node_modules/d3-interpolate": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-color": "1 - 3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/victory-vendor/node_modules/d3-path": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/victory-vendor/node_modules/d3-scale": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-array": "2.10.0 - 3",
|
||||||
|
"d3-format": "1 - 3",
|
||||||
|
"d3-interpolate": "1.2.0 - 3",
|
||||||
|
"d3-time": "2.1.1 - 3",
|
||||||
|
"d3-time-format": "2 - 4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/victory-vendor/node_modules/d3-shape": {
|
||||||
|
"version": "3.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
|
||||||
|
"integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-path": "^3.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/victory-vendor/node_modules/d3-time": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-array": "2 - 3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "5.4.15",
|
"version": "5.4.15",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.15.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.15.tgz",
|
||||||
|
|||||||
@ -13,8 +13,11 @@
|
|||||||
"axios": "^1.8.4",
|
"axios": "^1.8.4",
|
||||||
"lucide-react": "^0.427.0",
|
"lucide-react": "^0.427.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
|
"react-charts": "^3.0.0-beta.57",
|
||||||
|
"react-circle-progress-bar": "^0.1.4",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-router-dom": "^7.4.0"
|
"react-router-dom": "^7.4.0",
|
||||||
|
"recharts": "^2.15.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.9.1",
|
"@eslint/js": "^9.9.1",
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import Gestion from "./pages/Gestion/Gestion.jsx";
|
|||||||
import Header from "./components/Header.jsx";
|
import Header from "./components/Header.jsx";
|
||||||
import ObjectManagement from "./pages/Gestion/ObjectManagement.jsx";
|
import ObjectManagement from "./pages/Gestion/ObjectManagement.jsx";
|
||||||
import Objet from "./pages/Gestion/Objet.jsx";
|
import Objet from "./pages/Gestion/Objet.jsx";
|
||||||
|
import AddObject from "./pages/Gestion/AddObject.jsx";
|
||||||
import Signup from './pages/Signup.jsx';
|
import Signup from './pages/Signup.jsx';
|
||||||
import Login from './pages/Login.jsx';
|
import Login from './pages/Login.jsx';
|
||||||
|
|
||||||
@ -23,6 +24,7 @@ function App() {
|
|||||||
<Route path="/objet" element={<Objet />} />
|
<Route path="/objet" element={<Objet />} />
|
||||||
<Route path="/signup" element={<Signup />} />
|
<Route path="/signup" element={<Signup />} />
|
||||||
<Route path="/login" element={<Login />} />
|
<Route path="/login" element={<Login />} />
|
||||||
|
<Route path="/ajouterObjet" element={<AddObject />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</div>
|
</div>
|
||||||
</Router>
|
</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,11 +1,168 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
|
|
||||||
function About() {
|
function About() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-50">
|
||||||
<h1>A propos</h1>
|
<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>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default About;
|
export default About;
|
||||||
|
|||||||
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,
|
RadioTower,
|
||||||
Binoculars,
|
Binoculars,
|
||||||
Settings,
|
Settings,
|
||||||
ChartArea,
|
BadgePlus,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
|
||||||
function Gestion() {
|
function Gestion() {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-50">
|
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-50">
|
||||||
@ -22,84 +21,43 @@ function Gestion() {
|
|||||||
Bienvenue dans le module <b>Gestion</b>.
|
Bienvenue dans le module <b>Gestion</b>.
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-xl text-gray-600 max-w-3xl mx-auto">
|
<p className="text-xl text-gray-600 max-w-3xl mx-auto">
|
||||||
Dans ce module, vous allez pouvoir gerer les objets connectés de
|
Ce module vous permet de gérer les capteur et stations connectés de France de manière simple et efficace.
|
||||||
l'hopital.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Features Grid */}
|
<div className="grid md:grid-cols-2 gap-8">
|
||||||
<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="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">
|
<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} />
|
<RadioTower className="text-indigo-600" size={24} />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-xl font-semibold mb-2">
|
<h3 className="text-xl font-semibold mb-2">
|
||||||
Gestion des objets connectés
|
Consulter les objets connectés météorologiques
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-gray-600 mb-4">
|
<p className="text-gray-600 mb-4">
|
||||||
Découvrez les meilleurs endroits de votre ville, qu'il s'agisse de
|
Accédez aux données en temps réel des objets connectés météorologiques, modifiez leurs paramètres et consultez l'historique des mesures.
|
||||||
restaurants, de parcs ou de lieux culturels.
|
|
||||||
</p>
|
</p>
|
||||||
<a
|
<a
|
||||||
href="/gestionObjets"
|
href="/gestionObjets"
|
||||||
className="flex items-center text-indigo-600 hover:text-indigo-700"
|
className="flex items-center text-indigo-600 hover:text-indigo-700"
|
||||||
>
|
>
|
||||||
Explorer les lieux <ArrowRight size={16} className="ml-2" />
|
Explorer les objets <ArrowRight size={16} className="ml-2" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-white p-6 rounded-xl shadow-sm hover:shadow-md transition-shadow">
|
<div className="bg-white p-6 rounded-xl shadow-sm hover:shadow-md transition-shadow">
|
||||||
<div className="w-12 h-12 bg-indigo-100 rounded-lg flex items-center justify-center mb-4">
|
<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>
|
</div>
|
||||||
<h3 className="text-xl font-semibold mb-2">
|
<h3 className="text-xl font-semibold mb-2">
|
||||||
Configurer des services
|
Ajouter un nouvel objet connecté
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-gray-600 mb-4">
|
<p className="text-gray-600 mb-4">
|
||||||
Découvrez les meilleurs endroits de votre ville, qu'il s'agisse de
|
Intégrez facilement un nouvel objet connecté en renseignant ses informations et en configurant ses paramètres pour une gestion optimale.
|
||||||
restaurants, de parcs ou de lieux culturels.
|
|
||||||
</p>
|
</p>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="/ajouterObjet"
|
||||||
className="flex items-center text-indigo-600 hover:text-indigo-700"
|
className="flex items-center text-indigo-600 hover:text-indigo-700"
|
||||||
>
|
>
|
||||||
Explorer les lieux <ArrowRight size={16} className="ml-2" />
|
Ajouter un objet <ArrowRight size={16} className="ml-2" />
|
||||||
</a>
|
|
||||||
</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" />
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,29 +1,31 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import {
|
import { Search, ArrowRight, RadioTower,Plus } from "lucide-react";
|
||||||
Search,
|
|
||||||
MapPin,
|
|
||||||
Calendar,
|
|
||||||
Bus,
|
|
||||||
ArrowRight,
|
|
||||||
LogIn,
|
|
||||||
UserPlus,
|
|
||||||
RadioTower,
|
|
||||||
Binoculars,
|
|
||||||
Settings,
|
|
||||||
ChartArea,
|
|
||||||
} from "lucide-react";
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { useFetcher } from "react-router-dom";
|
|
||||||
|
|
||||||
function ObjectManagement() {
|
function ObjectManagement() {
|
||||||
const [searchQuery, setSearchQuery] = useState("");
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
const [activeFilter, setActiveFilter] = useState("all");
|
const [activeFilter, setActiveFilter] = useState("");
|
||||||
const [objects, setObjects] = 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(() => {
|
useEffect(() => {
|
||||||
axios.get("http://localhost:8888/objets").then((response) => {
|
axios.get("http://localhost:8888/objets").then((response) => {
|
||||||
setObjects(response.data);
|
setObjects(response.data);
|
||||||
console.log(response.data);
|
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
return (
|
return (
|
||||||
@ -50,9 +52,9 @@ function ObjectManagement() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex gap-4 mt-4 justify-center">
|
<div className="flex gap-4 mt-4 justify-center">
|
||||||
<button
|
<button
|
||||||
onClick={() => setActiveFilter("all")}
|
onClick={() => setActiveFilter("")}
|
||||||
className={`px-4 py-2 rounded-lg ${
|
className={`px-4 py-2 rounded-lg ${
|
||||||
activeFilter === "all"
|
activeFilter === ""
|
||||||
? "bg-indigo-600 text-white"
|
? "bg-indigo-600 text-white"
|
||||||
: "bg-white text-gray-600"
|
: "bg-white text-gray-600"
|
||||||
}`}
|
}`}
|
||||||
@ -60,19 +62,9 @@ function ObjectManagement() {
|
|||||||
Tout
|
Tout
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setActiveFilter("locations")}
|
onClick={() => setActiveFilter("Station")}
|
||||||
className={`px-4 py-2 rounded-lg ${
|
className={`px-4 py-2 rounded-lg ${
|
||||||
activeFilter === "locations"
|
activeFilter === "Station"
|
||||||
? "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"
|
|
||||||
? "bg-indigo-600 text-white"
|
? "bg-indigo-600 text-white"
|
||||||
: "bg-white text-gray-600"
|
: "bg-white text-gray-600"
|
||||||
}`}
|
}`}
|
||||||
@ -80,38 +72,82 @@ function ObjectManagement() {
|
|||||||
Station météo
|
Station météo
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setActiveFilter("transport")}
|
onClick={() => setActiveFilter("Capteur")}
|
||||||
className={`px-4 py-2 rounded-lg ${
|
className={`px-4 py-2 rounded-lg ${
|
||||||
activeFilter === "transport"
|
activeFilter === "Capteur"
|
||||||
? "bg-indigo-600 text-white"
|
? "bg-indigo-600 text-white"
|
||||||
: "bg-white text-gray-600"
|
: "bg-white text-gray-600"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
Capteur
|
Capteur
|
||||||
</button>
|
</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>
|
</div>
|
||||||
<div className="grid md:grid-cols-3 gap-8">
|
<div className="grid md:grid-cols-3 gap-8">
|
||||||
{objects.map(object =>(
|
{filteredDATA.length === 0 ? (
|
||||||
<div key={object.id} className="bg-white p-6 rounded-xl shadow-sm hover:shadow-md transition-shadow">
|
<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">
|
<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} />
|
<RadioTower className="text-indigo-600" size={24} />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-xl font-semibold mb-2">
|
<h3 className="text-xl font-semibold mb-2">{object.name}</h3>
|
||||||
{object.name}
|
<p className="text-gray-600 mb-4">{object.description}</p>
|
||||||
</h3>
|
|
||||||
<p className="text-gray-600 mb-4">
|
|
||||||
{object.description}
|
|
||||||
</p>
|
|
||||||
<a
|
<a
|
||||||
href={`/objet?id=${object.id}`}
|
href={`/objet?id=${object.id}`}
|
||||||
className="flex items-center text-indigo-600 hover:text-indigo-700"
|
className="flex items-center text-indigo-600 hover:text-indigo-700"
|
||||||
>
|
>
|
||||||
Plus d'infos <ArrowRight size={16} className="ml-2" />
|
Plus d'infos <ArrowRight size={16} className="ml-2" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))
|
||||||
|
)}
|
||||||
</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>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,58 +1,117 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import {
|
import { Thermometer, CircleGauge, Droplet } from "lucide-react";
|
||||||
Search,
|
|
||||||
MapPin,
|
|
||||||
Calendar,
|
|
||||||
Bus,
|
|
||||||
ArrowRight,
|
|
||||||
LogIn,
|
|
||||||
UserPlus,
|
|
||||||
RadioTower,
|
|
||||||
Binoculars,
|
|
||||||
Settings,
|
|
||||||
ChartArea,
|
|
||||||
} from "lucide-react";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import axios from "axios";
|
|
||||||
import { useFetcher } from "react-router-dom";
|
|
||||||
|
|
||||||
|
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() {
|
function Objet() {
|
||||||
const identifiant = new URLSearchParams(window.location.search).get("id");
|
const identifiant = new URLSearchParams(window.location.search).get("id");
|
||||||
const [searchQuery, setSearchQuery] = useState("");
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
const [activeFilter, setActiveFilter] = useState("all");
|
const [activeFilter, setActiveFilter] = useState("all");
|
||||||
const [object, setObject] = useState({});
|
const [object, setObject] = useState({});
|
||||||
|
const [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(() => {
|
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]);
|
setObject(response.data[0]);
|
||||||
console.log(response.data);
|
|
||||||
});
|
});
|
||||||
}, [identifiant]);
|
}, [identifiant]);
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-50">
|
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-50">
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
<div className=" max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
||||||
<div className="text-center mb-12">
|
<div className="text-center mb-5">
|
||||||
<h2 className="text-4xl font-bold text-gray-900 mb-4">
|
<h2 className="text-4xl font-bold text-gray-900 mb-12">
|
||||||
{object.name}
|
Tableau de bord - {object.name}
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid md:grid-cols-3 gap-8">
|
<div className="grid md:grid-cols-1 lg:grid-cols-3 gap-8 mb-5">
|
||||||
<div
|
{!afficherModif ? (
|
||||||
key={object.id}
|
<InfoObjet object={object} defafficherModif={defafficherModif} />
|
||||||
className="bg-white p-6 rounded-xl shadow-sm hover:shadow-md transition-shadow"
|
) : (
|
||||||
>
|
<ModifObject object={object} defafficherModif={defafficherModif} />
|
||||||
<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>
|
{object && object.id ? (
|
||||||
<h3 className="text-xl font-semibold mb-2">{object.name}</h3>
|
<WindInfo
|
||||||
<p className="text-gray-600 mb-4">{object.description}</p>
|
object={object}
|
||||||
<a
|
defAffWindGraph={defAffWindGraph}
|
||||||
href={`/objet?id=${object.id}`}
|
AffWindGraph={AffWindGraph}
|
||||||
className="flex items-center text-indigo-600 hover:text-indigo-700"
|
/>
|
||||||
>
|
) : (
|
||||||
Plus d'infos <ArrowRight size={16} className="ml-2" />
|
<p>Chargement des données...</p>
|
||||||
</a>
|
)}
|
||||||
</div>
|
{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>
|
</div>
|
||||||
|
{AffWindGraph &&
|
||||||
|
(object && object.id ? (
|
||||||
|
<WindGraph object={object} />
|
||||||
|
) : (
|
||||||
|
<p>Chargement des données...</p>
|
||||||
|
))}
|
||||||
|
{AffTempGraph &&
|
||||||
|
(object && object.id ? (
|
||||||
|
<MeteoGraph
|
||||||
|
object={object}
|
||||||
|
categorie={"temperature"}
|
||||||
|
Logo={Thermometer}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<p>Chargement des données...</p>
|
||||||
|
))}
|
||||||
|
{AffPressionGraph &&
|
||||||
|
(object && object.id ? (
|
||||||
|
<MeteoGraph
|
||||||
|
object={object}
|
||||||
|
categorie={"pressure"}
|
||||||
|
Logo={CircleGauge}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<p>Chargement des données...</p>
|
||||||
|
))}
|
||||||
|
{AffHumiditeGraph &&
|
||||||
|
(object && object.id ? (
|
||||||
|
<MeteoGraph object={object} categorie={"humidity"} Logo={Droplet} />
|
||||||
|
) : (
|
||||||
|
<p>Chargement des données...</p>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,18 +1,117 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
function Home() {
|
function Home() {
|
||||||
const token = localStorage.getItem("token");
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
return (
|
const [activeFilter, setActiveFilter] = useState('all');
|
||||||
|
const [name, setName] = useState([]);
|
||||||
<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]'>
|
return (
|
||||||
{token ? (
|
<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
|
<>Home
|
||||||
<img src='public/images/snow.jpg' />
|
<img src='public/images/snow.jpg' />
|
||||||
</>):(
|
</>):(
|
||||||
<h2>Non connecté</h2>
|
<h2>Non connecté</h2>
|
||||||
)}
|
)}
|
||||||
</div>
|
<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;
|
export default Home;
|
||||||