Merge pull request #6 from Charles40130/ruben

Ruben
This commit is contained in:
Arcade69 2025-04-12 13:10:53 +02:00 committed by GitHub
commit 4845ff37c2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 533 additions and 187 deletions

View File

@ -6,9 +6,18 @@ import { useAuth } from "../AuthContext";
function Header() { function Header() {
const { token, user, logout } = useAuth(); const { token, user, logout } = useAuth();
const [isMenuOpen, setIsMenuOpen] = useState(false); const [isMenuOpen, setIsMenuOpen] = useState(false);
const [showAdminDropdown, setShowAdminDropdown] = useState(false);
// La fonction toggleAdminDropdown permet d'ouvrir/fermer le menu déroulant
const toggleAdminDropdown = () => {
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-md "> <header className="bg-white shadow-md sticky top-0 z-50">
<div className="mx-auto px-4 sm:px-6 lg:px-8 py-4 flex justify-between items-center"> <div className="mx-auto px-4 sm:px-6 lg:px-8 py-4 flex justify-between items-center">
<Link to="/" className="text-2xl font-bold text-indigo-600"> <Link to="/" className="text-2xl font-bold text-indigo-600">
VigiMétéo VigiMétéo
@ -20,6 +29,7 @@ function Header() {
{isMenuOpen ? <X size={24} /> : <Menu size={24} />} {isMenuOpen ? <X size={24} /> : <Menu size={24} />}
</button> </button>
{/* Navigation */}
<nav <nav
className={`${ className={`${
isMenuOpen ? "block" : "hidden" isMenuOpen ? "block" : "hidden"
@ -44,6 +54,55 @@ function Header() {
À propos À propos
</Link> </Link>
</li> </li>
<li>
<Link
to="/gestion"
onClick={() => setIsMenuOpen(false)}
className="text-gray-600 hover:text-indigo-600"
>
Gestion
</Link>
</li>
{/* Menu déroulant Admin toujours affiché */}
<li 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>
)}
</li>
{!token ? ( {!token ? (
<> <>
<li className="sm:hidden"> <li className="sm:hidden">
@ -110,12 +169,11 @@ function Header() {
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> </li>
</> </>
)} )}
<li></li>
</ul> </ul>
</nav> </nav>
{!token ? ( {!token ? (
@ -152,8 +210,42 @@ function Header() {
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>
{/* 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>

View File

@ -100,7 +100,7 @@ function AdminObjet() {
return ( return (
<div className="flex h-screen"> <div className="flex h-screen">
<Sidebar /> <Sidebar />
<div className="flex-1 bg-gradient-to-br from-blue-50 to-indigo-50 p-8 overflow-auto"> <div className="flex-1 bg-gradient-to-br from-blue-50 to-indigo-50 p-8 overflow-auto scrollbar-hide">
<div className="max-w-7xl mx-auto"> <div className="max-w-7xl mx-auto">
<h1 className="text-4xl font-bold text-gray-900 text-center mb-12"> <h1 className="text-4xl font-bold text-gray-900 text-center mb-12">
Administration des Objets et Outils/Services Administration des Objets et Outils/Services
@ -163,9 +163,9 @@ function AdminObjet() {
<option value="status">Status</option> <option value="status">Status</option>
</select> </select>
</div> </div>
{/* Conteneur pour le scroll horizontal */}
<div className="overflow-x-auto"> <div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200"> <table className="w-full min-w-[640px] divide-y divide-gray-200">
<thead> <thead>
<tr> <tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
@ -234,8 +234,7 @@ function AdminObjet() {
</table> </table>
</div> </div>
</section> </section>
{/* RÈGLES GLOBALES */}
{/* Règles globales */}
<section className="bg-white p-6 rounded-xl shadow-md mt-12"> <section className="bg-white p-6 rounded-xl shadow-md mt-12">
<h2 className="text-2xl font-semibold mb-4">Règles Globales</h2> <h2 className="text-2xl font-semibold mb-4">Règles Globales</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">

View File

@ -1,10 +1,47 @@
import React, { useState, useEffect } 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 { API_BASE_URL } from "../../config.js"; import { API_BASE_URL } from "../../config.js";
import axios from "axios"; import axios from "axios";
// Widgets initiaux pour le dashboard
const initialWidgets = [
{ id: 1, type: "summary" },
{ id: 2, type: "users" },
{ id: 3, type: "objects" },
];
function Dashboard() { function Dashboard() {
const [users, setUsers] = useState([]); // États simulés (données fictives pour l'exemple)
const [users, setUsers] = useState([
{
id: 1,
username: "Alice",
email: "alice@example.com",
accessLevel: "Admin",
},
{ id: 2, username: "Bob", email: "bob@example.com", accessLevel: "User" },
{
id: 3,
username: "Charlie",
email: "charlie@example.com",
accessLevel: "Guest",
},
{
id: 4,
username: "David",
email: "david@example.com",
accessLevel: "User",
},
{ id: 5, username: "Eva", email: "eva@example.com", accessLevel: "User" },
{
id: 6,
username: "Frank",
email: "frank@example.com",
accessLevel: "Admin",
},
]);
const [logs, setLogs] = useState([ const [logs, setLogs] = useState([
{ {
id: 1, id: 1,
@ -20,71 +57,293 @@ function Dashboard() {
}, },
]); ]);
// État pour simuler les objets présents dans la section AdminObjet.jsx
const [adminObjects, setAdminObjects] = useState([
{
id: 101,
nom: "Objet A",
description: "Description A",
type: "Type A",
localisation: "Localisation A",
proprietaire: "Propriétaire A",
status: "active",
},
{
id: 102,
nom: "Objet B",
description: "Description B",
type: "Type B",
localisation: "Localisation B",
proprietaire: "Propriétaire B",
status: "inactive",
},
{
id: 103,
nom: "Objet C",
description: "Description C",
type: "Type C",
localisation: "Localisation C",
proprietaire: "Propriétaire C",
status: "active",
},
]);
// Gestion du mode de gestion et des widgets
const [manageMode, setManageMode] = useState(false);
const [widgets, setWidgets] = useState(initialWidgets);
// Contrôle d'affichage du modal de sélection de widget
const [showAddWidgetModal, setShowAddWidgetModal] = useState(false);
// Suppression d'un widget
const handleDeleteWidget = (id) => {
setWidgets(widgets.filter((widget) => widget.id !== id));
};
// Ouvre le modal pour ajouter un widget
const openAddWidgetModal = () => {
setShowAddWidgetModal(true);
};
// Ajoute le widget sélectionné en fonction du type choisi
const handleWidgetSelection = (widgetType) => {
const newWidget = { id: Date.now(), type: widgetType };
setWidgets([...widgets, newWidget]);
setShowAddWidgetModal(false);
};
/*
useEffect(() => { useEffect(() => {
axios.get(`${API_BASE_URL}/users`).then((response) => { axios.get(`${API_BASE_URL}/users`).then((response) => {
setUsers(response.data); setUsers(response.data);
}); });
}, []); }, []);
*/
return ( return (
<div className="flex h-screen"> <div className="flex min-h-screen">
<Sidebar /> <Sidebar />
<main className="flex-grow p-5"> <main className="flex-1 bg-gradient-to-br from-blue-50 to-indigo-50 p-8 overflow-auto">
<h1 className="text-2xl font-bold mb-6">Dashboard</h1> {/* En-tête : titre + bouton pour activer/désactiver le mode gestion */}
<div className="flex items-center justify-between mb-6">
<div className="flex gap-5 mb-6"> <h1 className="text-3xl font-bold">Dashboard</h1>
<div className="bg-gray-100 p-6 flex-1 rounded-lg text-center"> <button
<h2 className="text-xl font-semibold">Total Users</h2> onClick={() => setManageMode(!manageMode)}
<p className="text-2xl mt-2">{users.length}</p> className="flex items-center px-4 py-2 bg-gray-200 hover:bg-gray-300 rounded-md"
</div> >
<div className="bg-gray-100 p-6 flex-1 rounded-lg text-center"> <Settings className="mr-2" size={20} />
<h2 className="text-xl font-semibold">Dernier Log</h2> {manageMode ? "Terminer la gestion" : "Gérer les widgets"}
{logs.length > 0 ? ( </button>
<p className="mt-2">
{logs[logs.length - 1].username} - {logs[logs.length - 1].action}
</p>
) : (
<p className="mt-2">Aucun log</p>
)}
</div>
</div> </div>
{/* Grille des widgets */}
<section> <section>
<h2 className="text-xl font-semibold mb-4">Aperçu des Utilisateurs</h2> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<div className="overflow-x-auto"> {widgets.map((widget) => (
<table className="min-w-full border border-gray-300"> <div
<thead> key={widget.id}
<tr className="bg-gray-100"> className="relative bg-white p-6 rounded-xl shadow hover:shadow-md"
<th className="p-3 border text-left">Username</th> >
<th className="p-3 border text-left">Email</th> {/* Bouton de suppression, visible en mode gestion */}
<th className="p-3 border text-left">Niveau d'accès</th> {manageMode && (
</tr> <button
</thead> onClick={() => handleDeleteWidget(widget.id)}
<tbody> className="absolute top-2 right-2 bg-red-600 text-white rounded-full w-6 h-6 flex items-center justify-center"
{users.slice(0, 5).map((user) => ( >
<tr key={user.id} className="hover:bg-gray-50">
<td className="p-3 border text-left">{user.name}</td> </button>
<td className="p-3 border text-left">{user.email}</td>
<td className="p-3 border text-left">{user.role}</td>
</tr>
))}
{users.length === 0 && (
<tr>
<td colSpan="3" className="p-3 border text-center">
Aucun utilisateur disponible
</td>
</tr>
)} )}
</tbody>
</table> {/* Contenu du widget selon son type */}
{widget.type === "summary" && (
<div>
<h2 className="text-xl font-semibold mb-4">
Résumé du tableau de bord
</h2>
<div className="mb-4">
<h3 className="text-lg font-medium">Total Utilisateur</h3>
<p className="text-2xl">{users.length}</p>
</div>
<div>
<h3 className="text-lg font-medium">Dernier Log</h3>
{logs.length > 0 ? (
<p>
{logs[logs.length - 1].username} -{" "}
{logs[logs.length - 1].action}
</p>
) : (
<p>Aucun log</p>
)}
</div>
</div>
)}
{widget.type === "users" && (
<div>
<h2 className="text-xl font-semibold mb-4">
Gestion des Utilisateurs
</h2>
<div className="overflow-x-auto">
<table className="min-w-[320px] w-full border border-gray-200">
<thead>
<tr>
<th className="px-2 py-1 border border-gray-200 bg-gray-100 text-left">
Username
</th>
<th className="px-2 py-1 border border-gray-200 bg-gray-100 text-left">
Email
</th>
<th className="px-2 py-1 border border-gray-200 bg-gray-100 text-left">
Access
</th>
</tr>
</thead>
<tbody>
{users.slice(0, 5).map((user) => (
<tr key={user.id}>
<td className="px-2 py-1 border border-gray-200">
{user.username}
</td>
<td className="px-2 py-1 border border-gray-200">
{user.email}
</td>
<td className="px-2 py-1 border border-gray-200">
{user.accessLevel}
</td>
</tr>
))}
{users.length === 0 && (
<tr>
<td
colSpan="3"
className="px-2 py-1 border border-gray-200 text-center"
>
Aucun utilisateur disponible
</td>
</tr>
)}
</tbody>
</table>
</div>
<button
className="mt-4 px-4 py-2 bg-blue-600 text-white rounded-md"
onClick={() => (window.location.href = "/user")}
>
Voir plus
</button>
</div>
)}
{widget.type === "objects" && (
<div>
<h2 className="text-xl font-semibold mb-4">
Gestion des Objets Connectés
</h2>
<div className="mb-4">
<a
href="/gestionObjets"
className="flex items-center text-indigo-600 hover:text-indigo-700"
>
<RadioTower size={24} className="mr-2" />
Consulter les objets connectés
<ArrowRight size={16} className="ml-2" />
</a>
</div>
<div>
<a
href="/adminobjet"
className="flex items-center text-indigo-600 hover:text-indigo-700"
>
<BadgePlus size={24} className="mr-2" />
Ajouter un nouvel objet
<ArrowRight size={16} className="ml-2" />
</a>
</div>
</div>
)}
{widget.type === "adminobjet" && (
<div>
<h2 className="text-xl font-semibold mb-4">
Liste des Objets et Outils/Services
</h2>
{/* Afficher seulement les 2 premiers objets */}
<ul className="mb-4 space-y-2">
{adminObjects.slice(0, 2).map((obj) => (
<li
key={obj.id}
className="border border-gray-200 p-2 rounded"
>
<p className="font-medium">{obj.nom}</p>
<p className="text-sm text-gray-500">{obj.type}</p>
<p className="text-sm text-gray-500">{obj.status}</p>
</li>
))}
</ul>
<button
onClick={() => (window.location.href = "/adminobjet")}
className="mt-4 px-4 py-2 bg-blue-600 text-white rounded-md"
>
Voir plus
</button>
</div>
)}
</div>
))}
{/* Case pour ajouter un widget */}
<div
onClick={openAddWidgetModal}
className="flex items-center justify-center border-2 border-dashed border-gray-300 rounded-xl p-6 hover:bg-gray-50 cursor-pointer"
>
<button className="flex items-center">
<span className="text-3xl font-bold mr-2">+</span>
<span>Ajouter un widget</span>
</button>
</div>
</div> </div>
<button
className="mt-4 px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
onClick={() => (window.location.href = "/user")}
>
Voir plus
</button>
</section> </section>
{/* Modal de sélection du type de widget */}
{showAddWidgetModal && (
<div className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 z-50">
<div className="bg-white rounded-lg p-6 w-80">
<h3 className="text-xl font-semibold mb-4">
Choisir un type de widget
</h3>
<div className="flex flex-col gap-4">
<button
onClick={() => handleWidgetSelection("summary")}
className="px-4 py-2 bg-gray-200 hover:bg-gray-300 rounded-md text-left"
>
Dashboard Summary
</button>
<button
onClick={() => handleWidgetSelection("users")}
className="px-4 py-2 bg-gray-200 hover:bg-gray-300 rounded-md text-left"
>
Gestion des Utilisateurs
</button>
<button
onClick={() => handleWidgetSelection("objects")}
className="px-4 py-2 bg-gray-200 hover:bg-gray-300 rounded-md text-left"
>
Gestion des Objets Connectés
</button>
<button
onClick={() => handleWidgetSelection("adminobjet")}
className="px-4 py-2 bg-gray-200 hover:bg-gray-300 rounded-md text-left"
>
Liste des Objets et Outils/Services
</button>
</div>
<button
onClick={() => setShowAddWidgetModal(false)}
className="mt-4 px-4 py-2 bg-red-500 text-white rounded-md w-full"
>
Annuler
</button>
</div>
</div>
)}
</main> </main>
</div> </div>
); );

View File

@ -3,35 +3,45 @@ import Sidebar from "./sidebar.jsx";
import { API_BASE_URL } from "../../config.js"; import { API_BASE_URL } from "../../config.js";
import axios from "axios"; 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 thTd = "p-2 border border-gray-300 text-left";
const th = `${thTd} bg-gray-100`; 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 [name, setname] = useState(""); const [name, setName] = useState("");
const [email, setEmail] = useState(""); const [email, setEmail] = useState("");
const [pointsInput, setPointsInput] = useState({}); const [pointsInput, setPointsInput] = useState({});
// Ajout d'un utilisateur
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.
name, name,
email, email,
accessLevel: "User", accessLevel: "User",
points: 0, points: 0,
}; };
setUsers([...users, newUser]); setUsers([...users, newUser]);
logAction(name, "Utilisateur ajouté"); logAction(name, "User added");
setname(""); setName("");
setEmail(""); setEmail("");
// TODO : Envoyer newUser à l'API si nécessaire.
}; };
// Chargement des utilisateurs depuis l'API au montage du composant
useEffect(() => { useEffect(() => {
axios.get(`${API_BASE_URL}/users`).then((response) => { axios
setUsers(response.data); .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
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) {
@ -55,19 +65,20 @@ function User() {
setUsers(users.filter((u) => u.id !== userId)); setUsers(users.filter((u) => u.id !== userId));
}; };
// 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 && newLevel!=user.role) { if (user.id === userId && newLevel !== user.accessLevel) {
const oldLevel = user.role; const oldLevel = user.accessLevel;
user.role = newLevel; user.accessLevel = newLevel;
/*ToDO*/ // Exemple de remise à zéro ou affectation de points en fonction du rôle :
if (user.role === "user") { if (newLevel === "user") {
user.points = 0;
} else if (newLevel === "complexe") {
user.points = 60; user.points = 60;
} else if (user.role === "complexe") { } else if (newLevel === "admin") {
user.points = 100; user.points = 100;
} else if (user.role === "admin") {
user.points = 200;
} }
axios axios
.post(`${API_BASE_URL}/setUserPoints`, { .post(`${API_BASE_URL}/setUserPoints`, {
@ -76,42 +87,42 @@ function User() {
}) })
.then((response) => { .then((response) => {
alert("Le changement de niveau a bien été enregistré !"); alert("Le changement de niveau a bien été enregistré !");
console.log("Changement de niveau réussit :", response.data); console.log("Niveau changé:", response.data);
}) })
.catch((error) => { .catch((error) => {
alert("Il y a eu une erreur dans le changement de niveau !"); alert("Erreur lors du changement de niveau !");
console.error("Erreur lors du changement de niveau :", error); console.error(error);
}); });
logAction( logAction(user.name, `Access level changed from ${oldLevel} to ${newLevel}`);
user.name,
`Niveau d'accés changé de ${oldLevel} à ${newLevel}`
);
} }
return user; return user;
}) })
); );
}; };
// 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; user.points = pointsToAdd;
// On additionne au lieu de remplacer
user.points = (user.points || 0) + pointsToAdd;
axios axios
.post(`${API_BASE_URL}/setUserPoints`, { .post(`${API_BASE_URL}/setUserPoints`, {
id: user.id, id: user.id,
points: user.points, points: user.points,
}) })
.then((response) => { .then((response) => {
alert("Les points ont bien été enregistré !"); alert("Les points ont bien été enregistrés !");
console.log("Ajout des points réussit :", response.data); console.log("Points mis à jour :", response.data);
}) })
.catch((error) => { .catch((error) => {
alert("Il y a eu une erreur dans l'ajout des points!"); alert("Erreur lors de l'ajustement des points !");
console.error("Erreur lors de l'ajout des points :", error); console.error(error);
}); });
logAction(user.name, `Points ajustés par ${pointsToAdd}`); logAction(user.name, `Points adjusted by ${pointsToAdd}, new total: ${user.points}`);
} }
return user; return user;
}) })
@ -119,107 +130,121 @@ function User() {
setPointsInput({ ...pointsInput, [userId]: "" }); setPointsInput({ ...pointsInput, [userId]: "" });
}; };
// Fonction de journalisation des actions
const logAction = (name, action) => { const logAction = (name, action) => {
/*TODO*/
/*Ajouter le suivi dans un journal de log*/
const timestamp = new Date().toLocaleString(); const timestamp = new Date().toLocaleString();
setLogs([...logs, { id: Date.now(), name, action, timestamp }]); setLogs([...logs, { id: Date.now(), name, action, timestamp }]);
}; };
// Fonction pour générer et télécharger les logs dans un fichier texte
const downloadLogs = () => {
const logText = logs
.map((log) => `${log.timestamp} - ${log.name} - ${log.action}`)
.join("\n");
const blob = new Blob([logText], { type: "text/plain;charset=utf-8" });
const link = document.createElement("a");
link.href = URL.createObjectURL(blob);
link.download = "logs.txt";
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
};
return ( return (
<div className="flex min-h-screen"> <div className="flex h-screen">
<Sidebar /> <Sidebar />
<main className="flex-grow p-5"> <main className="flex-grow p-5">
<section className="mt-5"> <section className="mt-5">
<h1 className="text-2xl font-bold text-gray-900 mb-5"> <h1 className="text-2xl font-bold mb-4">Gestion des utilisateurs</h1>
User Management <p className="mb-5">Gérez les utilisateurs à partir de ce panneau.</p>
</h1>
<p>Manage users from this panel.</p>
{/* Formulaire d'ajout d'utilisateur */} {/* Formulaire d'ajout d'utilisateur */}
<form <form
className="gap-3 mb-5 grid grid-cols-[1fr_1fr_auto]" className="grid grid-cols-[1fr_1fr_auto] gap-2 mb-5"
onSubmit={handleAddUser} onSubmit={handleAddUser}
> >
<input <input
className="p-3 border rounded-md"
type="text" type="text"
id="name" id="name"
placeholder="name" placeholder="Name"
value={name} value={name}
onChange={(e) => setname(e.target.value)} onChange={(e) => setName(e.target.value)}
required required
className="p-2 border border-gray-300 rounded-md"
/> />
<input <input
className="p-3 border rounded-md"
type="email" type="email"
id="email" id="email"
placeholder="Email" placeholder="Email"
value={email} value={email}
onChange={(e) => setEmail(e.target.value)} onChange={(e) => setEmail(e.target.value)}
required required
className="p-2 border border-gray-300 rounded-md"
/> />
<button <button
className="p-3 bg-green-600 text-white border-none rounded-md "
type="submit" type="submit"
className="p-2 bg-green-600 text-white rounded-md cursor-pointer"
> >
Ajouter utilisateur Add User
</button> </button>
</form> </form>
{/* Tableau des utilisateurs */} {/* Tableau des utilisateurs */}
<table className="w-full"> <table className="w-full border-collapse mb-6">
<thead> <thead>
<tr> <tr>
<th className={`${th}`}>Nom</th> <th className={th}>Nom</th>
<th className={`${th}`}>Email</th> <th className={th}>Email</th>
<th className={`${th}`}>Niveau d'accès</th> <th className={th}>Niveau d'accès</th>
<th className={`${th}`}>Points</th> <th className={th}>Points</th>
<th className={`${th}`}>Actions</th> <th className={th}>Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{users.map((user) => ( {users.map((user) => (
<tr key={user.id}> <tr key={user.id}>
<td className={`${thTd}`}>{user.name}</td> <td className={thTd}>{user.name}</td>
<td className={`${thTd}`}>{user.email}</td> <td className={thTd}>{user.email}</td>
<td className={`${thTd}`}> <td className={thTd}>
<select <select
value={user.role} value={user.accessLevel}
onChange={(e) => onChange={(e) =>
handleChangeAccessLevel(user.id, e.target.value) handleChangeAccessLevel(user.id, e.target.value)
} }
className="p-2 rounded-md" 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="complexe">Complexe</option> <option value="complexe">Complexe</option>
</select> </select>
</td> </td>
<td className={`${thTd}`}> <td className={thTd}>
<input <input
className="border ml-4 w-16" className="border ml-4 w-16"
type="number" type="number"
min="0" min="0"
value={pointsInput[user.id] || user.points} value={pointsInput[user.id] ?? user.points}
onChange={(e) => onChange={(e) =>
setPointsInput({ setPointsInput({
...pointsInput, ...pointsInput,
[user.id]: e.target.value, [user.id]: e.target.value,
}) })
} }
className="w-16 ml-2 p-1 border border-gray-300 rounded-md"
/> />
<button <button
className="p-2 bg-green-600 text-white border-none rounded-md"
onClick={() => handleAdjustPoints(user.id)} onClick={() => handleAdjustPoints(user.id)}
className="p-2 bg-green-600 text-white rounded-md ml-2"
> >
Changer Changer
Changer
</button> </button>
</td> </td>
<td className={`${thTd}`}> <td className={thTd}>
<button <button
className="p-2 bg-red-600 text-white border-none rounded-md"
onClick={() => handleDeleteUser(user.id)} onClick={() => handleDeleteUser(user.id)}
className="p-2 bg-red-600 text-white rounded-md"
> >
Supprimer Supprimer
Supprimer
</button> </button>
</td> </td>
</tr> </tr>
@ -228,28 +253,34 @@ function User() {
</table> </table>
</section> </section>
{/* Tableau des logs */} {/* Tableau des logs */}
<section className="user-logs mt-10"> <section className="mt-10">
<h2 className="text-2xl font-bold text-gray-900 mb-5"> <h2 className="text-2xl font-bold mb-4">
Historique des connexions et journal des logs{" "} Historique des connexions et journal des logs
</h2> </h2>
<table className="w-full"> <table className="w-full border-collapse mb-4">
<thead> <thead>
<tr> <tr>
<th className={`${th}`}>Nom</th> <th className={th}>Nom</th>
<th className={`${th}`}>Action</th> <th className={th}>Action</th>
<th className={`${th}`}>Timestamp</th> <th className={th}>Timestamp</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{logs.map((log) => ( {logs.map((log) => (
<tr key={log.id}> <tr key={log.id}>
<td className={`${thTd}`}>{log.name}</td> <td className={thTd}>{log.name}</td>
<td className={`${thTd}`}>{log.action}</td> <td className={thTd}>{log.action}</td>
<td className={`${thTd}`}>{log.timestamp}</td> <td className={thTd}>{log.timestamp}</td>
</tr> </tr>
))} ))}
</tbody> </tbody>
</table> </table>
<button
onClick={downloadLogs}
className="mt-4 px-4 py-2 bg-blue-600 text-white rounded-md"
>
Télécharger les logs
</button>
</section> </section>
</main> </main>
</div> </div>

View File

@ -1,9 +1,27 @@
import React from "react"; import React from "react";
import { Menu, X } from "lucide-react";
function Sidebar() { function Sidebar({ isOpen, toggleSidebar }) {
return ( return (
<aside className="w-[250px] bg-gray-800 text-white p-5 h-screen box-border"> <aside
<h2 className="text-xl font-bold mb-4">Admin Panel</h2> className={`
bg-gray-800 text-white p-5 h-screen w-64 fixed top-0 left-0 z-40
transform transition-transform duration-200 ease-in-out
${isOpen ? "translate-x-0" : "-translate-x-full"}
md:static md:translate-x-0
`}
>
{/* En-tête visible sur mobile avec bouton de fermeture */}
<div className="flex justify-between items-center md:hidden mb-4">
<h2 className="text-xl font-bold">Admin Panel</h2>
<button onClick={toggleSidebar} className="focus:outline-none">
<X size={24} />
</button>
</div>
{/* En-tête pour les écrans md+ */}
<div className="hidden md:block mb-4">
<h2 className="text-xl font-bold">Admin Panel</h2>
</div>
<nav> <nav>
<ul className="list-none p-0"> <ul className="list-none p-0">
<li className="mb-3"> <li className="mb-3">
@ -43,13 +61,4 @@ function Sidebar() {
); );
} }
function App() { export default Sidebar;
return (
<div className="flex h-screen m-0 p-0">
<Sidebar />
<main className="flex-1 ">{/* Ajoutez ici le contenu principal */}</main>
</div>
);
}
export default App;

View File

@ -1,44 +0,0 @@
import React, { useState } from "react";
import { Calendar, Settings, LayoutDashboard } from "lucide-react";
function AdminDashboard() {
const [activeTab, setActiveTab] = useState('dashboard');
return (
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
{/* SideMenu */}
<div className="w-64 bg-white rounded-lg shadow-sm p-4">
<h2 className="text-lg font-semibold text-gray-800 mb-4">
Administration
</h2>
<nav className="flex flex-col space-y-2">
<button
onClick={() => setActiveTab('events')}
className={`w-full flex items-center gap-2 px-4 py-2 rounded-lg ${
activeTab === 'events'
? 'bg-red-50 text-red-600'
: 'text-gray-600 hover:bg-gray-50'
}`}
>
<Calendar size={20} />
Events
</button>
<button onClick="" className="">
<LayoutDashboard size="15" />
Layout
</button>
<button onClick="">
<Settings size="15" />
Settings
</button>
</nav>
<h2>Test</h2>
</div>
</div>
);
}
export default AdminDashboard;