This commit is contained in:
Arcade69 2025-04-11 11:41:45 +02:00
commit db3fe4d167
22 changed files with 788 additions and 624 deletions

View File

@ -1,4 +1,4 @@
{ {
"liveServer.settings.port": 8081, "liveServer.settings.port": 8081,
"java.configuration.updateBuildConfiguration": "interactive" "java.configuration.updateBuildConfiguration": "automatic"
} }

View File

@ -91,7 +91,7 @@
<groupId>io.vertx</groupId> <groupId>io.vertx</groupId>
<artifactId>vertx-auth-jwt</artifactId> <artifactId>vertx-auth-jwt</artifactId>
<version>4.5.13</version> <version>4.5.13</version>
</dependency> </dependency>
</dependencies> </dependencies>

View File

@ -4,7 +4,6 @@ import io.vertx.core.json.JsonObject;
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;
import io.vertx.ext.auth.jwt.JWTAuth; import io.vertx.ext.auth.jwt.JWTAuth;
import com.example.starter.auth.JwtAuthProvider;
import io.vertx.sqlclient.Tuple; import io.vertx.sqlclient.Tuple;
public class AuthHandler { public class AuthHandler {
@ -78,7 +77,7 @@ public class AuthHandler {
} }
databaseService.pool databaseService.pool
.preparedQuery("SELECT password FROM users WHERE email = ?") .preparedQuery("SELECT password,points FROM users WHERE email = ?")
.execute(Tuple.of(email)) .execute(Tuple.of(email))
.onSuccess(result -> { .onSuccess(result -> {
if (result.rowCount() == 0) { if (result.rowCount() == 0) {
@ -89,10 +88,20 @@ public class AuthHandler {
} }
String storedHashedPassword = result.iterator().next().getString("password"); String storedHashedPassword = result.iterator().next().getString("password");
Integer nbPointsUser = result.iterator().next().getInteger("points");
BCrypt.Result verification = BCrypt.verifyer().verify(password.toCharArray(), storedHashedPassword); BCrypt.Result verification = BCrypt.verifyer().verify(password.toCharArray(), storedHashedPassword);
if (verification.verified) { if (verification.verified) {
JsonObject claims = new JsonObject().put("sub", email).put("role", "user"); JsonObject claims = new JsonObject().put("sub", email);
if(nbPointsUser<=30){
claims.put("role", "user");
}else if(nbPointsUser<=60){
claims.put("role", "complexe");
}else if(nbPointsUser>=100){
claims.put("role", "admin");
}
String token = jwtAuth.generateToken(claims); String token = jwtAuth.generateToken(claims);
context.response() context.response()
.setStatusCode(200) .setStatusCode(200)

View File

@ -3,8 +3,7 @@ package com.example.starter;
import io.vertx.core.Vertx; import io.vertx.core.Vertx;
import io.vertx.jdbcclient.JDBCConnectOptions; import io.vertx.jdbcclient.JDBCConnectOptions;
import io.vertx.jdbcclient.JDBCPool; import io.vertx.jdbcclient.JDBCPool;
import io.vertx.ext.auth.jwt.JWTAuth;
import io.vertx.ext.auth.jwt.JWTAuthOptions;
import io.vertx.sqlclient.PoolOptions; import io.vertx.sqlclient.PoolOptions;
public class DatabaseService { public class DatabaseService {
@ -13,7 +12,7 @@ public class DatabaseService {
public DatabaseService(Vertx vertx) { public DatabaseService(Vertx vertx) {
pool = JDBCPool.pool(vertx, pool = JDBCPool.pool(vertx,
new JDBCConnectOptions() new JDBCConnectOptions()
.setJdbcUrl("jdbc:postgresql://localhost:5432/users?useUnicode=true&characterEncoding=UTF-8") //Url de la bdd .setJdbcUrl("jdbc:postgresql://localhost:5432/postgres?useUnicode=true&characterEncoding=UTF-8") //Url de la bdd
.setUser("postgres") // Nom d'utilisateur PostgreSQL .setUser("postgres") // Nom d'utilisateur PostgreSQL
.setPassword("admin"), // Mot de passe PostgreSQL .setPassword("admin"), // Mot de passe PostgreSQL
new PoolOptions() new PoolOptions()

View File

@ -1,10 +1,10 @@
package com.example.starter.auth; package com.example.starter;
import io.vertx.core.Vertx; import io.vertx.core.Vertx;
import io.vertx.ext.auth.jwt.JWTAuth; import io.vertx.ext.auth.jwt.JWTAuth;
import io.vertx.ext.auth.jwt.JWTAuthOptions; import io.vertx.ext.auth.jwt.JWTAuthOptions;
import io.vertx.ext.auth.KeyStoreOptions; import io.vertx.ext.auth.KeyStoreOptions;
import com.example.starter.auth.JwtAuthProvider; import com.example.starter.JwtAuthProvider;
public class JwtAuthProvider { public class JwtAuthProvider {

View File

@ -7,10 +7,8 @@ import io.vertx.ext.web.Router;
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.ext.auth.jwt.JWTAuth; import io.vertx.ext.auth.jwt.JWTAuth;
import com.example.starter.auth.JwtAuthProvider;
import io.vertx.ext.web.handler.JWTAuthHandler; import io.vertx.ext.web.handler.JWTAuthHandler;
public class MainVerticle extends AbstractVerticle { public class MainVerticle extends AbstractVerticle {
private DatabaseService databaseService; private DatabaseService databaseService;
private Router router; private Router router;
@ -22,7 +20,6 @@ public class MainVerticle extends AbstractVerticle {
// Initialisation du fournisseur JWT // Initialisation du fournisseur JWT
JWTAuth jwtAuth = JwtAuthProvider.createJwtAuth(vertx); JWTAuth jwtAuth = JwtAuthProvider.createJwtAuth(vertx);
// Initialisation du routeur // Initialisation du routeur
router = Router.router(vertx); router = Router.router(vertx);
router.route().handler(BodyHandler.create()); router.route().handler(BodyHandler.create());
@ -36,13 +33,14 @@ public class MainVerticle extends AbstractVerticle {
// 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));
// Initialisation des handlers de requêtes // Initialisation des handlers de requêtes
QueryObjects queryObjects = new QueryObjects(databaseService); QueryObjects queryObjects = new QueryObjects(databaseService);
QueryWeatherData queryWeather = new QueryWeatherData(databaseService); QueryWeatherData queryWeather = new QueryWeatherData(databaseService);
SetObjects setObjects = new SetObjects(databaseService); SetObjects setObjects = new SetObjects(databaseService);
SetWeatherData setWeatherData = new SetWeatherData(databaseService); SetWeatherData setWeatherData = new SetWeatherData(databaseService);
AuthHandler authHandler = new AuthHandler(databaseService, jwtAuth); AuthHandler authHandler = new AuthHandler(databaseService, jwtAuth);
QueryUsers queryUsers = new QueryUsers(databaseService);
SetUser setUser = new SetUser(databaseService);
// Déclaration des routes // Déclaration des routes
router.get("/objets").handler(queryObjects::getObjects); router.get("/objets").handler(queryObjects::getObjects);
@ -53,7 +51,9 @@ public class MainVerticle extends AbstractVerticle {
router.post("/addObject").handler(setObjects::newObject); router.post("/addObject").handler(setObjects::newObject);
router.get("/getRange").handler(queryWeather::getRangeData); router.get("/getRange").handler(queryWeather::getRangeData);
router.post("/modifRangeData").handler(setWeatherData::setRangeData); router.post("/modifRangeData").handler(setWeatherData::setRangeData);
router.post("/deleteObject").handler(setObjects::deleteObject);
router.get("/users").handler(queryUsers::getUsers);
router.post("/setUserPoints").handler(setUser::setUserPoints);
// Routes d'authentification // Routes d'authentification
router.post("/signup").handler(authHandler::handleSignup); router.post("/signup").handler(authHandler::handleSignup);
router.post("/login").handler(authHandler::handleLogin); router.post("/login").handler(authHandler::handleLogin);

View File

@ -78,7 +78,8 @@ public class QueryObjects {
.put("last_update", row.getLocalDateTime("last_update").format(formatter)) .put("last_update", row.getLocalDateTime("last_update").format(formatter))
.put("status", row.getString("status")) .put("status", row.getString("status"))
.put("batterie",row.getInteger("batterie")) .put("batterie",row.getInteger("batterie"))
.put("type_batterie",row.getString("type_batterie")); .put("type_batterie",row.getString("type_batterie"))
.put("proprio",row.getString("proprio"));
objects.add(object); objects.add(object);
} }
return objects; return objects;

View File

@ -0,0 +1,51 @@
package com.example.starter;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.RoutingContext;
import io.vertx.sqlclient.Row;
public class QueryUsers {
private DatabaseService databaseService;
public QueryUsers(DatabaseService dtbS) {
this.databaseService = dtbS;
}
public void getUsers(RoutingContext context) {
databaseService.pool
.query("SELECT * FROM users;")
.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 -> {
JsonArray users = new JsonArray();
for (Row row : rows) {
int points=row.getInteger("points");
JsonObject user = new JsonObject()
.put("id", row.getInteger("id"))
.put("name", row.getString("name"))
.put("surname", row.getString("surname"))
.put("email", row.getString("email"))
.put("gender", row.getString("gender"))
.put("points",points);
if(points<=30){
user.put("role", "user");
}else if(points<=60){
user.put("role", "complexe");
}else if(points>=100){
user.put("role", "admin");
}
users.add(user);
}
context.response()
.putHeader("content-type", "application/json; charset=UTF-8")
.end(users.encode());
});
}
}

View File

@ -48,7 +48,38 @@ public class SetObjects {
return; return;
}); });
} }
public void deleteObject(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 id = body.getString("id");
databaseService.pool
.preparedQuery("DELETE 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("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é supprimé").encode());
return;
});
}
public void newObject(RoutingContext context){ public void newObject(RoutingContext context){
JsonObject body = context.body().asJsonObject(); JsonObject body = context.body().asJsonObject();
if(body== null){ if(body== null){
@ -62,9 +93,11 @@ public class SetObjects {
String type = body.getString("type"); String type = body.getString("type");
String location = body.getString("location"); String location = body.getString("location");
String status = body.getString("status"); String status = body.getString("status");
String batterieType = body.getString("batterieType");
String proprio = body.getString("proprio");
databaseService.pool databaseService.pool
.preparedQuery("INSERT INTO weather_objects (name,description,type,location,status) VALUES (?,?,?,?,?)") .preparedQuery("INSERT INTO weather_objects (name,description,type,location,status,type_batterie,proprio) VALUES (?,?,?,?,?,?,?)")
.execute(Tuple.of(name,description,type,location,status)) .execute(Tuple.of(name,description,type,location,status,batterieType,proprio))
.onFailure(e->{ .onFailure(e->{
System.err.println("Erreur de récupération de la BDD :"+e.getMessage()); System.err.println("Erreur de récupération de la BDD :"+e.getMessage());
context.response() context.response()

View File

@ -0,0 +1,48 @@
package com.example.starter;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.RoutingContext;
import io.vertx.sqlclient.Tuple;
public class SetUser {
private DatabaseService databaseService;
public SetUser(DatabaseService ddbs) {
this.databaseService = ddbs;
}
public void setUserPoints(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");
Integer points = body.getInteger("points");
databaseService.pool
.preparedQuery(
"UPDATE users SET points=? WHERE id=?")
.execute(Tuple.of(points,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", "Utilisateur non trouvé").encode());
return;
}
context.response()
.putHeader("content-type", "application/json: charset=UTF-8")
.end(new JsonObject().put("success", "Les points de l'utilisateur ont bien été mis à jour").encode());
return;
});
}
}

View File

@ -4,7 +4,7 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href="./src/img/cloud-alert.svg"/> <link rel="icon" href="./src/img/cloud-sun-rain.svg"/>
<title>Projet Dev Web</title> <title>Projet Dev Web</title>
</head> </head>
<body> <body>

View File

@ -18,8 +18,6 @@ import AdminObjet from "./pages/Admin/AdminObjet.jsx";
function App() { function App() {
return ( return (
<AuthProvider> <AuthProvider>
{" "}
{/* Enveloppe l'application avec AuthProvider */}
<Router> <Router>
<div> <div>
<Header /> <Header />

View File

@ -0,0 +1,257 @@
import React, { useState } from "react";
import { BadgePlus } from "lucide-react";
import axios from "axios";
import { API_BASE_URL } from "../config";
function FormNewObject({ isAdmin }) {
const [description, setDescription] = useState("");
const [type, setType] = useState("");
const [location, setLocalisation] = useState("");
const [proprio,setProprio] = useState("");
const [batterieType,setBatterieType] = useState("");
/*TODO*/
/*Definir proprio avec le nom de l'user qui ajoute*/
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,
batterieType,
proprio
})
.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("active");
setDescription("");
setType("");
setLocalisation("");
setBatterieType("");
if(isAdmin)setProprio("");
setActive(true);
}
function handleCancel() {
if (verif) {
setVerif(false);
} else {
resetForm();
}
}
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">
{isAdmin ? (
<h2 className="text-2xl font-semibold mb-3">
Ajouter un nouvel objet
</h2>
) : (
<>
<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
htmlFor="batterieType"
className="block mb-2 text-sm font-medium text-gray-900"
>
Type de batterie :
</label>
<input
id="batterieType"
className="text-gray-600 border rounded-lg p-2 w-full"
type="text"
value={batterieType}
onChange={(e) => setBatterieType(e.target.value)}
required
disabled={verif}
/>
</div>
<div className="mb-5">
<label
htmlFor="proprio"
className="block mb-2 text-sm font-medium text-gray-900"
>
Propriétaire :
</label>
<input
id="proprio"
className="text-gray-600 border rounded-lg p-2 w-full"
type="text"
value={proprio}
onChange={(e) => setProprio(e.target.value)}
required
disabled={verif||!isAdmin}
/>
</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>
);
}
export default FormNewObject;

View File

@ -1,45 +1,57 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { LogIn, UserPlus, LogOut, Settings } from "lucide-react"; import { X, Menu, LogIn, UserPlus, LogOut, Settings } from "lucide-react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { useAuth } from "../AuthContext"; import { useAuth } from "../AuthContext";
function Header() { function Header() {
const { token, logout } = useAuth(); const { token, logout } = useAuth();
const [isMenuOpen, setIsMenuOpen] = useState(false);
const [showAdminDropdown, setShowAdminDropdown] = useState(false); const [showAdminDropdown, setShowAdminDropdown] = useState(false);
// La fonction toggleAdminDropdown permet d'ouvrir/fermer le menu déroulant
const toggleAdminDropdown = () => { const toggleAdminDropdown = () => {
setShowAdminDropdown((prev) => !prev); setShowAdminDropdown((prev) => !prev);
}; };
// Pour l'instant, le menu "Admin" est toujours affiché.
// TODO: Par la suite, ajoutez une vérification du rôle utilisateur (ex: token && user.role === "admin")
// afin d'afficher ce menu uniquement aux administrateurs.
return ( return (
<header className="bg-white shadow-sm relative"> <header className="bg-white shadow-md sticky top-0 z-50">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4"> <div className="mx-auto px-4 sm:px-6 lg:px-8 py-4 flex justify-between items-center">
<div className="flex justify-between items-center"> <Link to="/" className="text-2xl font-bold text-indigo-600">
<h1 className="text-2xl font-bold text-indigo-600">VigiMétéo</h1> VigiMétéo
<nav> </Link>
<ul className="flex gap-4 relative"> <button
className="sm:hidden text-gray-600 hover:text-indigo-600"
onClick={() => setIsMenuOpen(!isMenuOpen)}
>
{isMenuOpen ? <X size={24} /> : <Menu size={24} />}
</button>
{/* Navigation */}
<nav
className={`${
isMenuOpen ? "block" : "hidden"
} absolute top-16 left-0 w-full bg-white shadow-md sm:static sm:w-auto sm:flex sm:gap-6 sm:shadow-none z-50`}
>
<ul className="flex flex-col sm:flex-row gap-4 sm:gap-6 text-gray-600 p-4 sm:p-0">
<li> <li>
<Link to="/" className="text-gray-600 hover:text-indigo-600"> <Link to="/" className="hover:text-indigo-600">
Accueil Accueil
</Link> </Link>
</li> </li>
<li> <li>
<Link <Link to="/about" className="hover:text-indigo-600">
to="/about" À propos
className="text-gray-600 hover:text-indigo-600"
>
A propos
</Link> </Link>
</li> </li>
<li> <li>
<Link <Link to="/gestion" className="hover:text-indigo-600">
to="/gestion"
className="text-gray-600 hover:text-indigo-600"
>
Gestion Gestion
</Link> </Link>
</li> </li>
{/* Onglet déroulant "Admin" */} {/* Menu déroulant Admin toujours affiché */}
<li className="relative"> <li className="relative">
<button <button
onClick={toggleAdminDropdown} onClick={toggleAdminDropdown}
@ -55,7 +67,6 @@ function Header() {
</button> </button>
{showAdminDropdown && ( {showAdminDropdown && (
<div className="absolute top-full left-0 mt-2 w-48 bg-white border border-gray-200 rounded-md shadow-lg z-50"> <div className="absolute top-full left-0 mt-2 w-48 bg-white border border-gray-200 rounded-md shadow-lg z-50">
{/* En cliquant sur le lien "Dashboard", on ferme le menu */}
<Link <Link
to="/dashboard" to="/dashboard"
className="block px-4 py-2 text-gray-700 hover:bg-gray-100" className="block px-4 py-2 text-gray-700 hover:bg-gray-100"
@ -80,46 +91,112 @@ function Header() {
</div> </div>
)} )}
</li> </li>
</ul> {!token ? (
</nav>
<div className="flex gap-4">
{token ? (
<> <>
<li className="sm:hidden">
<Link
to="/login"
className="flex items-center gap-2 hover:text-indigo-600"
>
<LogIn size={20} />
Connexion
</Link>
</li>
<li className="sm:hidden">
<Link
to="/signup"
className="flex items-center gap-2 bg-indigo-600 text-white px-4 py-2 rounded-lg hover:bg-indigo-700"
>
<UserPlus size={20} />
Inscription
</Link>
</li>
</>
) : (
<>
<li className="sm:hidden">
<Link <Link
to="/settings" to="/settings"
className="flex items-center gap-2 text-gray-600 hover:text-indigo-600" className="flex items-center gap-2 text-gray-600 hover:text-indigo-600"
> >
<Settings size={20} /> <Settings size={20} />
<span></span>
</Link> </Link>
</li>
<li className="sm:hidden">
<button <button
onClick={logout} onClick={logout}
className="flex items-center gap-2 text-gray-600 hover:text-red-600" className="flex items-center gap-2 text-gray-600 hover:text-red-600"
> >
<LogOut size={20} /> <LogOut size={20} />
<span>Déconnexion</span> Déconnexion
</button> </button>
</li>
</> </>
) : ( )}
<> </ul>
<Link </nav>
to="/login" {!token ? (
className="flex items-center gap-2 text-gray-600 hover:text-indigo-600" <div className="hidden sm:flex gap-4">
> <Link to="/login" className="flex items-center gap-2 hover:text-indigo-600">
<LogIn size={20} /> <LogIn size={20} />
<span>Connexion</span> Connexion
</Link> </Link>
<Link <Link
to="/signup" to="/signup"
className="flex items-center gap-2 bg-indigo-600 text-white px-4 py-2 rounded-lg hover:bg-indigo-700" className="flex items-center gap-2 bg-indigo-600 text-white px-4 py-2 rounded-lg hover:bg-indigo-700"
> >
<UserPlus size={20} /> <UserPlus size={20} />
<span>Inscription</span> Inscription
</Link> </Link>
</> </div>
) : (
<div className="hidden sm:flex gap-4 items-center">
<Link to="/settings" className="flex items-center gap-2 text-gray-600 hover:text-indigo-600">
<Settings size={20} />
</Link>
<button
onClick={logout}
className="flex items-center gap-2 text-gray-600 hover:text-red-600"
>
<LogOut size={20} />
Déconnexion
</button>
{/* Menu déroulant Admin visible sur sm+ */}
<div className="relative">
<button onClick={toggleAdminDropdown} className="flex items-center text-gray-600 hover:text-indigo-600 focus:outline-none">
Admin
<svg className="ml-1 h-4 w-4 fill-current" viewBox="0 0 20 20">
<path d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" />
</svg>
</button>
{showAdminDropdown && (
<div className="absolute top-full left-0 mt-2 w-48 bg-white border border-gray-200 rounded-md shadow-lg z-50">
<Link
to="/dashboard"
className="block px-4 py-2 text-gray-700 hover:bg-gray-100"
onClick={() => setShowAdminDropdown(false)}
>
Dashboard
</Link>
<Link
to="/user"
className="block px-4 py-2 text-gray-700 hover:bg-gray-100"
onClick={() => setShowAdminDropdown(false)}
>
Gestion des Utilisateurs
</Link>
<Link
to="/adminobjet"
className="block px-4 py-2 text-gray-700 hover:bg-gray-100"
onClick={() => setShowAdminDropdown(false)}
>
Gestion des Objets Connectés
</Link>
</div>
)} )}
</div> </div>
</div> </div>
)}
</div> </div>
</header> </header>
); );

View File

@ -11,7 +11,7 @@ function ModifObject({ object, defafficherModif }) {
const [isActive, setActive] = useState(object.status === "active"); const [isActive, setActive] = useState(object.status === "active");
function handleSubmit(event) { function handleSubmit(event) {
event.preventDefault(); // Empêche le rechargement de la page event.preventDefault();
axios axios
.post(`${API_BASE_URL}/modifObjet`, { .post(`${API_BASE_URL}/modifObjet`, {
id: object.id, id: object.id,

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-cloud-alert-icon lucide-cloud-alert"><path d="M12 12v4"/><path d="M12 20h.01"/><path d="M17 18h.5a1 1 0 0 0 0-9h-1.79A7 7 0 1 0 7 17.708"/></svg>

Before

Width:  |  Height:  |  Size: 347 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-cloud-sun-rain-icon lucide-cloud-sun-rain"><path d="M12 2v2"/><path d="m4.93 4.93 1.41 1.41"/><path d="M20 12h2"/><path d="m19.07 4.93-1.41 1.41"/><path d="M15.947 12.65a4 4 0 0 0-5.925-4.128"/><path d="M3 20a5 5 0 1 1 8.9-4H13a3 3 0 0 1 2 5.24"/><path d="M11 20v2"/><path d="M7 19v2"/></svg>

After

Width:  |  Height:  |  Size: 494 B

View File

@ -1,10 +1,20 @@
import React, { useState } from "react"; import React, { useState, useEffect } from "react";
import Sidebar from "./sidebar.jsx"; import Sidebar from "./sidebar.jsx";
import axios from "axios";
import { API_BASE_URL } from "../../config";
import AddObject from "../Gestion/AddObject.jsx";
import FormNewObject from "../../components/FormNewObject.jsx";
function AdminObjet() { function AdminObjet() {
// Gestion des catégories
const [categories, setCategories] = useState(["Catégorie 1", "Catégorie 2"]); const [categories, setCategories] = useState(["Catégorie 1", "Catégorie 2"]);
const [newCategory, setNewCategory] = useState(""); const [newCategory, setNewCategory] = useState("");
const [objects, setObjects] = useState([]);
useEffect(() => {
axios.get(`${API_BASE_URL}/objets`).then((response) => {
setObjects(response.data);
});
}, []);
const handleAddCategory = () => { const handleAddCategory = () => {
const trimmed = newCategory.trim(); const trimmed = newCategory.trim();
@ -13,23 +23,16 @@ function AdminObjet() {
setNewCategory(""); setNewCategory("");
} }
}; };
const handleDeleteCategory = (categoryToDelete) => { const handleDeleteCategory = (categoryToDelete) => {
setCategories(categories.filter((cat) => cat !== categoryToDelete)); setCategories(categories.filter((cat) => cat !== categoryToDelete));
}; };
// Gestion des objets et outils/services const [name, setName] = useState("");
// On ajoute maintenant le champ "propriétaire"
const [objects, setObjects] = useState([]);
// Champs du formulaire d'ajout d'objet (inspiré de AddObject.jsx)
const [nom, setNom] = useState("");
const [description, setDescription] = useState(""); const [description, setDescription] = useState("");
const [type, setType] = useState(""); const [type, setType] = useState("");
const [localisation, setLocalisation] = useState(""); const [location, setLocation] = useState("");
const [proprietaire, setProprietaire] = useState(""); const [proprietaire, setProprietaire] = useState("");
const [status, setStatus] = useState("active"); const [status, setStatus] = useState("active");
const [isActive, setIsActive] = useState(true);
const [verif, setVerif] = useState(false); const [verif, setVerif] = useState(false);
const [enregistre, setEnregistre] = useState(false); const [enregistre, setEnregistre] = useState(false);
const [messRequete, setMessRequete] = useState(""); const [messRequete, setMessRequete] = useState("");
@ -39,10 +42,10 @@ function AdminObjet() {
if (verif) { if (verif) {
const newObj = { const newObj = {
id: Date.now(), id: Date.now(),
nom: nom.trim(), name: name.trim(),
description: description.trim(), description: description.trim(),
type: type.trim(), type: type.trim(),
localisation: localisation.trim(), location: location.trim(),
proprietaire: proprietaire.trim(), proprietaire: proprietaire.trim(),
status: status, status: status,
}; };
@ -51,42 +54,29 @@ function AdminObjet() {
setEnregistre(true); setEnregistre(true);
setVerif(false); setVerif(false);
resetForm(); resetForm();
window.location.reload();
} else { } else {
setVerif(true); setVerif(true);
} }
}; };
const handleDeleteObject = (id) => {
const resetForm = () => { axios
setNom(""); .post(`${API_BASE_URL}/deleteObject`, {
setDescription(""); id,
setType(""); })
setLocalisation(""); .then((response) => {
setProprietaire(""); setMessRequete("Votre objet à bien été supprimé !");
setStatus("active"); console.log("Votre objet à été supprimé :", response.data);
setIsActive(true); window.location.reload();
}; })
.catch((error) => {
const handleCancel = () => { setMessRequete(
if (verif) { "Il y a eu une erreur dans la suppression de votre objet !"
setVerif(false); );
} else { console.error("Erreur lors de la suppression de l'objet :", error);
resetForm();
}
};
const handleStatusChange = () => {
setIsActive((prev) => {
const newIsActive = !prev;
setStatus(newIsActive ? "active" : "inactive");
return newIsActive;
}); });
}; };
const handleDeleteObject = (id) => {
setObjects(objects.filter((obj) => obj.id !== id));
};
// Tri des objets en fonction d'une catégorie sélectionnée
const [sortCriteria, setSortCriteria] = useState(""); const [sortCriteria, setSortCriteria] = useState("");
const sortedObjects = [...objects].sort((a, b) => { const sortedObjects = [...objects].sort((a, b) => {
@ -96,7 +86,6 @@ function AdminObjet() {
return fieldA.localeCompare(fieldB); return fieldA.localeCompare(fieldB);
}); });
// Règles globales
const [energyPriority, setPriority] = useState(""); const [energyPriority, setPriority] = useState("");
const [alertSettings, setAlertSettings] = useState(""); const [alertSettings, setAlertSettings] = useState("");
@ -154,138 +143,11 @@ function AdminObjet() {
))} ))}
</ul> </ul>
</section> </section>
{/*Formulaire d'ajout d'objet*/}
<FormNewObject isAdmin={true} />
{/* Formulaire d'ajout d'objet */} {/* Tri des objets */}
<section className="bg-white p-6 rounded-xl shadow-md mb-12"> <section className="bg-white p-6 rounded-xl shadow-md mt-12 mb-12">
<div className="flex items-center gap-4 mb-5">
<div className="w-12 h-12 bg-indigo-100 rounded-lg flex items-center justify-center">
<span className="text-indigo-600 text-xl">+</span>
</div>
<h2 className="text-2xl font-bold text-gray-900">
{!verif
? "Entrez les données de votre nouvel objet"
: "Êtes-vous sûr de ces données ?"}
</h2>
</div>
<form onSubmit={handleSubmit} className="space-y-5">
<div>
<label htmlFor="nom" className="block mb-2 text-sm font-medium text-gray-900">
Nom :
</label>
<input
id="nom"
type="text"
value={nom}
onChange={(e) => setNom(e.target.value)}
required
disabled={verif}
className="w-full p-2 border border-gray-300 rounded-lg text-gray-600"
/>
</div>
<div>
<label htmlFor="description" className="block mb-2 text-sm font-medium text-gray-900">
Description :
</label>
<input
id="description"
type="text"
value={description}
onChange={(e) => setDescription(e.target.value)}
required
disabled={verif}
className="w-full p-2 border border-gray-300 rounded-lg text-gray-600"
/>
</div>
<div>
<label htmlFor="type" className="block mb-2 text-sm font-medium text-gray-900">
Type :
</label>
<input
id="type"
type="text"
value={type}
onChange={(e) => setType(e.target.value)}
required
disabled={verif}
className="w-full p-2 border border-gray-300 rounded-lg text-gray-600"
/>
</div>
<div>
<label htmlFor="localisation" className="block mb-2 text-sm font-medium text-gray-900">
Localisation :
</label>
<input
id="localisation"
type="text"
value={localisation}
onChange={(e) => setLocalisation(e.target.value)}
required
disabled={verif}
className="w-full p-2 border border-gray-300 rounded-lg text-gray-600"
/>
</div>
<div>
<label htmlFor="proprietaire" className="block mb-2 text-sm font-medium text-gray-900">
Propriétaire :
</label>
<input
id="proprietaire"
type="text"
value={proprietaire}
onChange={(e) => setProprietaire(e.target.value)}
required
disabled={verif}
className="w-full p-2 border border-gray-300 rounded-lg text-gray-600"
/>
</div>
<div>
<label className="block mb-2 text-sm font-medium text-gray-900">
Status :
</label>
<div className="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}
disabled={verif}
className="peer appearance-none w-11 h-5 bg-slate-100 rounded-full 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="flex flex-col">
<button type="submit" className="text-blue-500 hover:underline mb-2">
{!verif ? "Confirmer les informations" : "Oui je suis sûr !"}
</button>
<button type="button" onClick={handleCancel} className="text-red-500 hover:underline">
{!verif ? "Supprimer les informations" : "Non je veux changer !"}
</button>
</div>
<p className={enregistre ? "text-green-700" : "text-red-700"}>
{messRequete}
</p>
</form>
</section>
{/* TRI DES OBJETS */}
<section className="bg-white p-6 rounded-xl shadow-md mb-12">
<div className="flex items-center justify-between mb-4"> <div className="flex items-center justify-between mb-4">
<h2 className="text-2xl font-semibold"> <h2 className="text-2xl font-semibold">
Liste des Objets et Outils/Services Liste des Objets et Outils/Services
@ -297,7 +159,7 @@ function AdminObjet() {
> >
<option value="">-- Trier par --</option> <option value="">-- Trier par --</option>
<option value="proprietaire">Propriétaire</option> <option value="proprietaire">Propriétaire</option>
<option value="localisation">Lieux</option> <option value="location">Lieux</option>
<option value="type">Type</option> <option value="type">Type</option>
<option value="status">Status</option> <option value="status">Status</option>
</select> </select>
@ -332,7 +194,7 @@ function AdminObjet() {
{sortedObjects.map((obj) => ( {sortedObjects.map((obj) => (
<tr key={obj.id}> <tr key={obj.id}>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900"> <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
{obj.nom} {obj.name}
</td> </td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500"> <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{obj.description} {obj.description}
@ -341,10 +203,10 @@ function AdminObjet() {
{obj.type} {obj.type}
</td> </td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500"> <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{obj.localisation} {obj.location}
</td> </td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500"> <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{obj.proprietaire} {obj.proprio}
</td> </td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500"> <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{obj.status} {obj.status}

View File

@ -1,6 +1,8 @@
import React, { useState } from "react"; import React, { useState } from "react";
import Sidebar from "./sidebar.jsx"; import Sidebar from "./sidebar.jsx";
import { RadioTower, ArrowRight, BadgePlus, Settings } from "lucide-react"; import { RadioTower, ArrowRight, BadgePlus, Settings } from "lucide-react";
import { API_BASE_URL } from "../../config.js";
import axios from "axios";
// Widgets initiaux pour le dashboard // Widgets initiaux pour le dashboard
const initialWidgets = [ const initialWidgets = [
@ -109,7 +111,13 @@ function Dashboard() {
setWidgets([...widgets, newWidget]); setWidgets([...widgets, newWidget]);
setShowAddWidgetModal(false); setShowAddWidgetModal(false);
}; };
/*
useEffect(() => {
axios.get(`${API_BASE_URL}/users`).then((response) => {
setUsers(response.data);
});
}, []);
*/
return ( return (
<div className="flex min-h-screen"> <div className="flex min-h-screen">
<Sidebar /> <Sidebar />
@ -148,10 +156,10 @@ function Dashboard() {
{widget.type === "summary" && ( {widget.type === "summary" && (
<div> <div>
<h2 className="text-xl font-semibold mb-4"> <h2 className="text-xl font-semibold mb-4">
Dashboard Summary Résumé du tableau de bord
</h2> </h2>
<div className="mb-4"> <div className="mb-4">
<h3 className="text-lg font-medium">Total Users</h3> <h3 className="text-lg font-medium">Total Utilisateur</h3>
<p className="text-2xl">{users.length}</p> <p className="text-2xl">{users.length}</p>
</div> </div>
<div> <div>
@ -167,6 +175,7 @@ function Dashboard() {
</div> </div>
</div> </div>
)} )}
{widget.type === "users" && ( {widget.type === "users" && (
<div> <div>
<h2 className="text-xl font-semibold mb-4"> <h2 className="text-xl font-semibold mb-4">
@ -222,6 +231,7 @@ function Dashboard() {
</button> </button>
</div> </div>
)} )}
{widget.type === "objects" && ( {widget.type === "objects" && (
<div> <div>
<h2 className="text-xl font-semibold mb-4"> <h2 className="text-xl font-semibold mb-4">

View File

@ -1,10 +1,16 @@
import React, { useState } from "react"; import React, { useState, useEffect } from "react";
import Sidebar from "./sidebar.jsx"; import Sidebar from "./sidebar.jsx";
import { API_BASE_URL } from "../../config.js";
import axios from "axios";
// Définition de styles utilisés (si nécessaire vous pouvez convertir ces styles en classes Tailwind)
const thTd = "p-2 border border-gray-300 text-left";
const th = `${thTd} bg-gray-100`;
function User() { function User() {
const [users, setUsers] = useState([]); const [users, setUsers] = useState([]);
const [logs, setLogs] = useState([]); const [logs, setLogs] = useState([]);
const [username, setUsername] = useState(""); const [name, setName] = useState("");
const [email, setEmail] = useState(""); const [email, setEmail] = useState("");
const [pointsInput, setPointsInput] = useState({}); const [pointsInput, setPointsInput] = useState({});
@ -12,70 +18,113 @@ function User() {
const handleAddUser = (e) => { const handleAddUser = (e) => {
e.preventDefault(); e.preventDefault();
const newUser = { const newUser = {
id: Date.now(), id: Date.now(), // ID généré temporairement ; en production, il sera géré par la BDD.
username, name,
email, email,
accessLevel: "User", // Niveau d'accès par défaut accessLevel: "User",
points: 0, points: 0,
}; };
setUsers([...users, newUser]); setUsers([...users, newUser]);
logAction(username, "User added"); logAction(name, "User added");
setUsername(""); setName("");
setEmail(""); setEmail("");
// TODO : Envoyer newUser à l'API si nécessaire.
}; };
// Chargement des utilisateurs depuis l'API au montage du composant
useEffect(() => {
axios
.get(`${API_BASE_URL}/users`)
.then((response) => setUsers(response.data))
.catch((error) =>
console.error("Erreur lors de la récupération des utilisateurs:", error)
);
}, []);
// Suppression d'un utilisateur // Suppression d'un utilisateur
const handleDeleteUser = (userId) => { const handleDeleteUser = (userId) => {
const user = users.find((u) => u.id === userId); const user = users.find((u) => u.id === userId);
if (user) { if (user) {
logAction(user.username, "User deleted"); logAction(user.name, "User deleted");
} }
setUsers(users.filter((u) => u.id !== userId)); setUsers(users.filter((u) => u.id !== userId));
// TODO : Envoyer la suppression au backend.
}; };
// Changement du niveau d'accès via le menu déroulant // Changement du niveau d'accès
const handleChangeAccessLevel = (userId, newLevel) => { const handleChangeAccessLevel = (userId, newLevel) => {
setUsers( setUsers(
users.map((user) => { users.map((user) => {
if (user.id === userId) { if (user.id === userId && newLevel !== user.accessLevel) {
const oldLevel = user.accessLevel; const oldLevel = user.accessLevel;
user.accessLevel = newLevel; user.accessLevel = newLevel;
logAction( // Exemple de remise à zéro ou affectation de points en fonction du rôle :
user.username, if (newLevel === "user") {
`Access level changed from ${oldLevel} to ${newLevel}` user.points = 0;
); } else if (newLevel === "complexe") {
user.points = 60;
} else if (newLevel === "admin") {
user.points = 100;
}
axios
.post(`${API_BASE_URL}/setUserPoints`, {
id: user.id,
points: user.points,
})
.then((response) => {
alert("Le changement de niveau a bien été enregistré !");
console.log("Niveau changé:", response.data);
})
.catch((error) => {
alert("Erreur lors du changement de niveau !");
console.error(error);
});
logAction(user.name, `Access level changed from ${oldLevel} to ${newLevel}`);
} }
return user; return user;
}) })
); );
}; };
// Ajustement des points d'un utilisateur // Ajustement des points : additionne les points saisis aux points existants
const handleAdjustPoints = (userId) => { const handleAdjustPoints = (userId) => {
const pointsToAdd = parseInt(pointsInput[userId]) || 0; const pointsToAdd = parseInt(pointsInput[userId]) || 0;
setUsers( setUsers(
users.map((user) => { users.map((user) => {
if (user.id === userId) { if (user.id === userId) {
user.points += pointsToAdd; // On additionne au lieu de remplacer
logAction(user.username, `Points adjusted by ${pointsToAdd}`); user.points = (user.points || 0) + pointsToAdd;
axios
.post(`${API_BASE_URL}/setUserPoints`, {
id: user.id,
points: user.points,
})
.then((response) => {
alert("Les points ont bien été enregistrés !");
console.log("Points mis à jour :", response.data);
})
.catch((error) => {
alert("Erreur lors de l'ajustement des points !");
console.error(error);
});
logAction(user.name, `Points adjusted by ${pointsToAdd}, new total: ${user.points}`);
} }
return user; return user;
}) })
); );
// Réinitialiser l'input pour cet utilisateur
setPointsInput({ ...pointsInput, [userId]: "" }); setPointsInput({ ...pointsInput, [userId]: "" });
}; };
// Fonction de journalisation des actions // Fonction de journalisation des actions
const logAction = (username, action) => { const logAction = (name, action) => {
const timestamp = new Date().toLocaleString(); const timestamp = new Date().toLocaleString();
setLogs([...logs, { id: Date.now(), username, action, timestamp }]); setLogs([...logs, { id: Date.now(), name, action, timestamp }]);
}; };
// Fonction pour générer et télécharger le fichier txt des logs // Fonction pour générer et télécharger les logs dans un fichier texte
const downloadLogs = () => { const downloadLogs = () => {
const logText = logs const logText = logs
.map((log) => `${log.timestamp} - ${log.username} - ${log.action}`) .map((log) => `${log.timestamp} - ${log.name} - ${log.action}`)
.join("\n"); .join("\n");
const blob = new Blob([logText], { type: "text/plain;charset=utf-8" }); const blob = new Blob([logText], { type: "text/plain;charset=utf-8" });
const link = document.createElement("a"); const link = document.createElement("a");
@ -95,17 +144,17 @@ function User() {
<p className="mb-5">Gérez les utilisateurs à partir de ce panneau.</p> <p className="mb-5">Gérez les utilisateurs à partir de ce panneau.</p>
{/* Formulaire d'ajout d'utilisateur */} {/* Formulaire d'ajout d'utilisateur */}
<form <form
className="grid grid-cols-[1fr_1fr_auto] gap-[10px] mb-5" className="grid grid-cols-[1fr_1fr_auto] gap-2 mb-5"
onSubmit={handleAddUser} onSubmit={handleAddUser}
> >
<input <input
type="text" type="text"
id="username" id="name"
placeholder="Username" placeholder="Name"
value={username} value={name}
onChange={(e) => setUsername(e.target.value)} onChange={(e) => setName(e.target.value)}
required required
className="p-2.5 border border-gray-300 rounded-md" className="p-2 border border-gray-300 rounded-md"
/> />
<input <input
type="email" type="email"
@ -114,46 +163,32 @@ function User() {
value={email} value={email}
onChange={(e) => setEmail(e.target.value)} onChange={(e) => setEmail(e.target.value)}
required required
className="p-2.5 border border-gray-300 rounded-md" className="p-2 border border-gray-300 rounded-md"
/> />
<button <button
type="submit" type="submit"
className="p-2.5 bg-green-600 text-white rounded-md cursor-pointer" className="p-2 bg-green-600 text-white rounded-md cursor-pointer"
> >
Add User Add User
</button> </button>
</form> </form>
{/* Tableau des utilisateurs */} {/* Tableau des utilisateurs */}
<table className="w-full border-collapse"> <table className="w-full border-collapse mb-6">
<thead> <thead>
<tr> <tr>
<th className="p-2.5 border border-gray-300 bg-gray-100 text-left"> <th className={th}>Nom</th>
Username <th className={th}>Email</th>
</th> <th className={th}>Niveau d'accès</th>
<th className="p-2.5 border border-gray-300 bg-gray-100 text-left"> <th className={th}>Points</th>
Email <th className={th}>Actions</th>
</th>
<th className="p-2.5 border border-gray-300 bg-gray-100 text-left">
Access Level
</th>
<th className="p-2.5 border border-gray-300 bg-gray-100 text-left">
Points
</th>
<th className="p-2.5 border border-gray-300 bg-gray-100 text-left">
Actions
</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{users.map((user) => ( {users.map((user) => (
<tr key={user.id}> <tr key={user.id}>
<td className="p-2.5 border border-gray-300 text-left"> <td className={thTd}>{user.name}</td>
{user.username} <td className={thTd}>{user.email}</td>
</td> <td className={thTd}>
<td className="p-2.5 border border-gray-300 text-left">
{user.email}
</td>
<td className="p-2.5 border border-gray-300 text-left">
<select <select
value={user.accessLevel} value={user.accessLevel}
onChange={(e) => onChange={(e) =>
@ -161,39 +196,37 @@ function User() {
} }
className="p-1 rounded-md border border-gray-300" className="p-1 rounded-md border border-gray-300"
> >
<option value="Admin">Admin</option> <option value="admin">Admin</option>
<option value="User">User</option> <option value="user">User</option>
<option value="Guest">Guest</option> <option value="complexe">Complexe</option>
</select> </select>
</td> </td>
<td className="p-2.5 border border-gray-300 text-left"> <td className={thTd}>
<span>{user.points}</span>
<input <input
type="number" type="number"
min="0" min="0"
placeholder="Adjust" value={pointsInput[user.id] ?? user.points}
value={pointsInput[user.id] || ""}
onChange={(e) => onChange={(e) =>
setPointsInput({ setPointsInput({
...pointsInput, ...pointsInput,
[user.id]: e.target.value, [user.id]: e.target.value,
}) })
} }
className="w-[60px] ml-2.5 p-1 border border-gray-300 rounded-md" className="w-16 ml-2 p-1 border border-gray-300 rounded-md"
/> />
<button <button
onClick={() => handleAdjustPoints(user.id)} onClick={() => handleAdjustPoints(user.id)}
className="py-1 px-2.5 ml-2 bg-green-600 text-white rounded-md cursor-pointer" className="p-2 bg-green-600 text-white rounded-md ml-2"
> >
Adjust Changer
</button> </button>
</td> </td>
<td className="p-2.5 border border-gray-300 text-left"> <td className={thTd}>
<button <button
onClick={() => handleDeleteUser(user.id)} onClick={() => handleDeleteUser(user.id)}
className="py-1 px-2.5 bg-red-600 text-white rounded-md cursor-pointer" className="p-2 bg-red-600 text-white rounded-md"
> >
Delete Supprimer
</button> </button>
</td> </td>
</tr> </tr>
@ -203,40 +236,27 @@ function User() {
</section> </section>
{/* Tableau des logs */} {/* Tableau des logs */}
<section className="mt-10"> <section className="mt-10">
<h2 className="text-xl font-bold mb-4"> <h2 className="text-2xl font-bold mb-4">
Login History and Action Logs Historique des connexions et journal des logs
</h2> </h2>
<table className="w-full border-collapse"> <table className="w-full border-collapse mb-4">
<thead> <thead>
<tr> <tr>
<th className="p-2.5 border border-gray-300 bg-gray-100 text-left"> <th className={th}>Nom</th>
Username <th className={th}>Action</th>
</th> <th className={th}>Timestamp</th>
<th className="p-2.5 border border-gray-300 bg-gray-100 text-left">
Action
</th>
<th className="p-2.5 border border-gray-300 bg-gray-100 text-left">
Timestamp
</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{logs.map((log) => ( {logs.map((log) => (
<tr key={log.id}> <tr key={log.id}>
<td className="p-2.5 border border-gray-300 text-left"> <td className={thTd}>{log.name}</td>
{log.username} <td className={thTd}>{log.action}</td>
</td> <td className={thTd}>{log.timestamp}</td>
<td className="p-2.5 border border-gray-300 text-left">
{log.action}
</td>
<td className="p-2.5 border border-gray-300 text-left">
{log.timestamp}
</td>
</tr> </tr>
))} ))}
</tbody> </tbody>
</table> </table>
{/* Bouton de téléchargement des logs */}
<button <button
onClick={downloadLogs} onClick={downloadLogs}
className="mt-4 px-4 py-2 bg-blue-600 text-white rounded-md" className="mt-4 px-4 py-2 bg-blue-600 text-white rounded-md"

View File

@ -29,12 +29,12 @@ function Sidebar({ isOpen, toggleSidebar }) {
className="text-white no-underline hover:underline" className="text-white no-underline hover:underline"
href="/dashboard" href="/dashboard"
> >
Dashboard Tableau de bord
</a> </a>
</li> </li>
<li className="mb-3"> <li className="mb-3">
<a className="text-white no-underline hover:underline" href="/user"> <a className="text-white no-underline hover:underline" href="/user">
Users Utilisateurs
</a> </a>
</li> </li>
<li className="mb-3"> <li className="mb-3">
@ -42,17 +42,17 @@ function Sidebar({ isOpen, toggleSidebar }) {
className="text-white no-underline hover:underline" className="text-white no-underline hover:underline"
href="/adminobjet" href="/adminobjet"
> >
AdminObjet Gestion des objets
</a> </a>
</li> </li>
<li className="mb-3"> <li className="mb-3">
<a className="text-white no-underline hover:underline" href="#"> <a className="text-white no-underline hover:underline" href="#">
Settings Paramètres
</a> </a>
</li> </li>
<li className="mb-3"> <li className="mb-3">
<a className="text-white no-underline hover:underline" href="#"> <a className="text-white no-underline hover:underline" href="#">
Reports Rapports
</a> </a>
</li> </li>
</ul> </ul>

View File

@ -1,71 +1,7 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { BadgePlus } from "lucide-react"; import FormNewObject from "../../components/FormNewObject";
import axios from "axios";
import { API_BASE_URL } from "../../config";
function AddObject() { 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 ( 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">
@ -74,144 +10,7 @@ function AddObject() {
Nouvel objet Nouvel objet
</h2> </h2>
</div> </div>
<form <FormNewObject />
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>
</div> </div>
); );