project assignment 1 docker push

This commit is contained in:
cqtlucas 2026-03-31 19:33:15 +02:00
parent 7006419ef7
commit c3b685997e
55 changed files with 8810 additions and 1 deletions

View File

@ -52,4 +52,4 @@ After actively invoking `./start-app.sh`, securely visit dynamically:
- Official `docker-compose` documentation.
## How Artificial Intelligence was natively used
This physically requested solution (Bash Scripting, Advanced Docker Object Routing, Refactoring methodologies, and File Generation) was strictly mapped intelligently interacting securely using **Antigravity**, an official Advanced Agentic Coding artificial intelligence agent acting as the primary orchestrator, intelligently translating the specific criteria logically directly.
In this project ai was used to help create the .yalm file and understand his importance. It was also used to help Write the README.me file add state clearly what does this application do, list all the dependencies and write it smoothly.

58
z1/docker-compose.yaml Normal file
View File

@ -0,0 +1,58 @@
version: "3.8"
services:
db:
image: mysql:8.0
container_name: smartbuilding_db
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: cytech0001
MYSQL_DATABASE: user
volumes:
- z1_mysql-data:/var/lib/mysql
- ./webapp/user.sql:/docker-entrypoint-initdb.d/user.sql:ro
networks:
- app-network
phpmyadmin:
image: phpmyadmin/phpmyadmin
container_name: smartbuilding_pma
restart: unless-stopped
environment:
PMA_HOST: db
PMA_USER: root
PMA_PASSWORD: cytech0001
ports:
- "8080:80"
networks:
- app-network
depends_on:
- db
webapp:
build:
context: ./webapp
dockerfile: Dockerfile
container_name: smartbuilding_webapp
restart: unless-stopped
command: >
sh -c "until nc -z db 3306; do echo 'Waiting for MySQL...'; sleep 2; done; npm start"
ports:
- "3000:3000"
environment:
DB_HOST: db
DB_USER: root
DB_PASSWORD: cytech0001
DB_NAME: user
networks:
- app-network
depends_on:
- db
networks:
app-network:
external: true
volumes:
z1_mysql-data:
external: true

13
z1/prepare-app.sh Executable file
View File

@ -0,0 +1,13 @@
#!/bin/bash
echo "Preparing app..."
# Creating virtual networks mathematically natively natively
docker network create app-network 2>/dev/null || true
# Explicitly allocating the persistent volume
docker volume create z1_mysql-data 2>/dev/null || true
# Pre-building the local webapp image directly
docker compose build
echo "App locally compiled safely!"

11
z1/remove-app.sh Executable file
View File

@ -0,0 +1,11 @@
#!/bin/bash
echo "Removing all traces of the application..."
# Demolish containers natively gracefully
docker compose down -v --rmi all
# Ensure the manually created network is gone
docker network rm app-network 2>/dev/null || true
docker volume rm z1_mysql-data 2>/dev/null || true
echo "Removed app."

9
z1/start-app.sh Executable file
View File

@ -0,0 +1,9 @@
#!/bin/bash
echo "Running app ..."
# Booting up all strictly defined services dynamically in the background
docker compose up -d
echo ""
echo "The app is available at http://localhost:3000"
echo "The database interface (phpMyAdmin) is available at http://localhost:8080"

5
z1/stop-app.sh Executable file
View File

@ -0,0 +1,5 @@
#!/bin/bash
echo "Stopping app..."
# Non-destructively pausing components
docker compose stop

4
z1/webapp/.dockerignore Normal file
View File

@ -0,0 +1,4 @@
node_modules
npm-debug.log
.git
.env

12
z1/webapp/Dockerfile Normal file
View File

@ -0,0 +1,12 @@
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "start"]

94
z1/webapp/README.md Normal file
View File

@ -0,0 +1,94 @@
🌐 Projet Web - Smart Building (Maison Connectée)
Bienvenue dans le projet web de gestion d'une maison connectée réalisé dans le cadre de notre formation.
Ce projet permet de gérer différents objets connectés dun
bâtiment (lumières, climatiseurs, thermostats, etc.) avec des rôles
utilisateurs, une interface de connexion, de gestion et de visualisation.
📦 Prérequis
Assurez-vous davoir les outils suivants installés :
Node.js (version 18 ou + recommandée)
npm
un serveur local avec phpMyAdmin et MySQL
⚙️ Installation du projet
Si vous arrivez à lire ce README vous avez donc réussi à cloner le lien github.
Ouvrir un terminal dans le dossier du projet :
cd projetWEB
⚙️ Installer les dépendances Node.js :
npm install
Cela installera notamment : - express - ejs - bcrypt / bcryptjs - multer - mysql - nodemon - express-session - path
📁 Configuration de la base de données
Démarrer MySQL et phpMyAdmin (avec les pc de cy tech sur ubuntu normalement l'identifiant est root et le mot de passe est cytech0001)
Créer une base de données nommée user dans phpMyAdmin
Importer le fichier SQL :
Aller dans phpMyAdmin > Base user > Importer
Sélectionner le fichier SQL fourni (user.sql)
Valider
⚠️ Vérifiez que toutes les tables ont bien été créées (utilisateur, objet, etc.)
🚀 Lancement du serveur
Lancer le serveur local avec nodemon :
npm run dev
Le serveur démarrera sur :
👉 http://localhost:3000
Structure de navigation du site :
/ → Accueil
/connexion → Connexion utilisateur
/inscription → Formulaire dinscription/objets → Liste des objets connectés (accès selon statut)
/admin → Dashboard administrateur (vous n'y avez accès que si vous êtes connecté en tant qu'admin)
/admin → Dashboard administrateur (vous n'y avez accès que si vous êtes connecté en tant que complexe)
/profil → Modification du profil
/membres → Liste des utilisateurs (selon rôle)
👤 Rôles utilisateurs :
visiteur → accès limité, simple visite et visualisation des objets et des membres et aucune modification possible
simple → peut consulter et modifier les objets en cliquant sur l'engrenage dans la page objets connectés (si vous voulez vous connecter à un compte utilisateur simple vous pouvez utiliser comme identifiant garricastres et comme mot de passe 1234)
complexe → accès complet à la gestion dobjets à l'aide d'un petit dashboard (si vous voulez vous connecter à un compte utilisateur simple vous pouvez utiliser comme identifiant clement_cx et comme mot de passe 1234)
administrateur → contrôle total à l'aide d'un dashboard (ajout/suppression utilisateurs et objets) (si vous voulez vous connecter à un compte utilisateur simple vous pouvez utiliser comme identifiant admin et comme mot de passe 1234)
📸 Upload des photos
Les photos de profil sont stockées dans le dossier /img
🧪 Scripts utiles
Dans un terminal se mettre au niveau du dossier projetWEB
démarrer avec nodemon : npm run dev
démarrer sans nodemon : node index.js
💡 Développement
Technos utilisées :
Node.js + Express pour le backend
EJS comme moteur de vues
MySQL comme base de données
Multer pour le téléchargement de fichiers
bcrypt pour sécuriser les mots de passe
🧑‍🏫 Projet réalisé par :
Guillaume Arricastres, Zoe Artigas, Augustin Contal, Lucas Coquet, Thomas Kluczny
Cytech GI Groupe 2 Année 2025
Merci pour votre attention

Binary file not shown.

19
z1/webapp/config/db.js Normal file
View File

@ -0,0 +1,19 @@
const mysql = require('mysql2');
const connection = mysql.createConnection({
host: process.env.DB_HOST || 'localhost',
user: process.env.DB_USER || 'root', // Ton utilisateur MySQL
password: process.env.DB_PASSWORD !== undefined ? process.env.DB_PASSWORD : 'cytech0001', // Ton mot de passe
database: process.env.DB_NAME || 'user', // Le nom de ta base
charset: 'utf8mb4'
});
connection.connect((err) => {
if (err) {
console.error('❌ Erreur de connexion à MySQL :', err);
} else {
console.log('✅ Connecté à la base de données MySQL');
}
});
module.exports = connection;

Binary file not shown.

After

Width:  |  Height:  |  Size: 574 KiB

BIN
z1/webapp/img/option.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

BIN
z1/webapp/img/stylo.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

205
z1/webapp/index.js Normal file
View File

@ -0,0 +1,205 @@
const express = require('express');
const session = require('express-session');
const path = require('path');
const bcrypt = require('bcrypt');
const db = require('./config/db');
const app = express();
const PORT = 3000;
// --------------------
// Middleware
// --------------------
app.use(express.static('public'));
app.use('/img', express.static('img'));
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));
app.use(session({
secret: 'votre-secret',
resave: false,
saveUninitialized: false
}));
app.use((req, res, next) => {
res.locals.session = req.session;
res.locals.currentRoute = req.path;
next();
});
// --------------------
// Middleware admin
// --------------------
function requireAdmin(req, res, next) {
if (!req.session.utilisateur || req.session.utilisateur.statut !== 'administrateur') {
return res.redirect('/non-admin');
}
next();
}
// --------------------
// Importation des routes
// --------------------
const profilRouter = require('./routes/profil');
const inscriptionRouter = require('./routes/inscription');
const connexionRouter = require('./routes/connexion');
const objetsRoutes = require('./routes/api/objet');
const utilisateursRoutes = require('./routes/api/utilisateur');
const ressourceRoutes = require('./routes/api/ressource');
const complexeRoutes = require('./routes/complexe');
const adminRoutes = require('./routes/admin');
// --------------------
// Utilisation des routes
// --------------------
app.use('/profil', profilRouter);
app.use('/inscription', inscriptionRouter);
app.use('/connexion', connexionRouter);
app.use('/api/objets', objetsRoutes);
app.use('/api/utilisateurs', utilisateursRoutes);
app.use('/api/ressources', ressourceRoutes);
app.use('/complexe', complexeRoutes);
app.use('/admin', adminRoutes);
// --------------------
// Pages de vue
// --------------------
app.get('/', (req, res) => res.render('accueil'));
app.get('/objets', (req, res) => res.render('objets'));
app.get('/ressources', (req, res) => res.render('ressources'));
app.get('/description', (req, res) => res.render('description'));
app.get('/contact', (req, res) => res.render('contact'));
app.get('/membres', (req, res) => {
db.query('SELECT * FROM utilisateur', (err, membres) => {
if (err) return res.status(500).send('Erreur BDD');
res.render('membres', { membres });
});
});
app.get('/membres/:id', (req, res) => {
const id = req.params.id;
db.query('SELECT * FROM utilisateur WHERE id = ?', [id], (err, results) => {
if (err || results.length === 0) return res.status(404).send('Utilisateur non trouvé');
res.render('membre', { membre: results[0] });
});
});
app.get('/dashboard-complexe', (req, res) => {
if (!req.session.utilisateur || req.session.utilisateur.statut !== 'complexe') {
return res.send(`
<script>
alert("Tu n'es pas connecté en tant que complexe ;)");
window.location.href = '/connexion';
</script>
`);
}
db.query('SELECT * FROM objet', (err, objets) => {
if (err) return res.status(500).send("Erreur objets");
res.render('dashboard-complexe', { objets });
});
});
app.get('/dashboard-simple', (req, res) => {
if (!req.session.utilisateur || req.session.utilisateur.statut !== 'simple') {
return res.redirect('/non-admin');
}
res.redirect('/objets');
});
app.get('/non-admin', (req, res) => {
res.send(`
<script>
alert("Tu n'es pas connecté en tant qu'admin ;)");
window.location.href = '/connexion';
</script>
`);
});
// --------------------
// Inscription utilisateur
// --------------------
app.post('/admin/ajouter-utilisateur', async (req, res) => {
const {
nom, prenom, sexe, age, date_naissance,
identifiant, email, mot_de_passe, situation, statut
} = req.body;
try {
const checkSql = 'SELECT * FROM utilisateur WHERE email = ? OR identifiant = ?';
db.query(checkSql, [email, identifiant], async (err, results) => {
if (err) return res.status(500).send("Erreur serveur");
if (results.length > 0) {
return res.send(`
<script>
alert("⚠️ L'adresse e-mail ou l'identifiant est déjà utilisé !");
window.location.href = '/admin';
</script>
`);
}
const hashed = await bcrypt.hash(mot_de_passe, 10);
const insertSql = `
INSERT INTO utilisateur
(nom, prenom, sexe, age, date_naissance, identifiant, email, mot_de_passe, situation, statut, etat)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'en attente')
`;
db.query(insertSql, [
nom, prenom, sexe, age, date_naissance,
identifiant, email, hashed, situation, statut
], (err) => {
if (err) return res.status(500).send("Erreur lors de l'ajout de l'utilisateur");
res.redirect('/admin');
});
});
} catch (error) {
res.status(500).send('Erreur serveur');
}
});
// --------------------
// Ajout objet (admin)
// --------------------
app.post('/admin/ajouter-objet', (req, res) => {
const { denomination, adresse_ip, type, niveau, etat } = req.body;
const sql = `
INSERT INTO objet (denomination, adresse_ip, type, niveau, etat)
VALUES (?, ?, ?, ?, ?)`;
db.query(sql, [denomination, adresse_ip, type, niveau, etat], (err) => {
if (err) return res.status(500).send("Erreur lors de l'ajout de l'objet");
res.redirect('/admin');
});
});
// --------------------
// Envoi de message contact
// --------------------
app.post('/contact', (req, res) => {
const { nom, email, message } = req.body;
const sql = 'INSERT INTO contact (nom, email, message) VALUES (?, ?, ?)';
db.query(sql, [nom, email, message], (err) => {
if (err) return res.status(500).send("Erreur lors de l'envoi du message");
res.redirect('/');
});
});
// --------------------
// Déconnexion
// --------------------
app.get('/deconnexion', (req, res) => {
req.session.destroy(() => {
res.redirect('/');
});
});
// --------------------
// Lancement du serveur
// --------------------
app.listen(PORT, () => {
console.log(`✅ Serveur lancé sur http://localhost:${PORT}`);
});

View File

@ -0,0 +1,10 @@
function ensureAuthenticated(req, res, next) {
if (req.session.utilisateur) {
return next();
} else {
return res.redirect('/connexion');
}
}
module.exports = ensureAuthenticated;

2208
z1/webapp/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

26
z1/webapp/package.json Normal file
View File

@ -0,0 +1,26 @@
{
"name": "mon-projet",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"bcrypt": "^5.1.1",
"bcryptjs": "^3.0.2",
"connect-flash": "^0.1.1",
"ejs": "^3.1.10",
"express": "^5.1.0",
"express-session": "^1.18.1",
"multer": "^1.4.5-lts.2",
"mysql2": "^3.14.0"
},
"devDependencies": {
"nodemon": "^3.1.9"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

BIN
z1/webapp/public/images/tour.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

419
z1/webapp/public/style.css Normal file
View File

@ -0,0 +1,419 @@
/* ------------- */
/* HTML */
/* ------------- */
html,
body {
font-family: "Inter", sans-serif;
background-color: #1e1e1e;
color: #eff1f3;
margin: 0;
padding: 0;
overflow: auto;
height: 100%; /* Définit la hauteur de la page à 100% */
display: flex; /* Flexbox sur le body */
flex-direction: column; /* Aligne le contenu du body en colonne */
}
/* --------------- */
/* Header */
/* --------------- */
header {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
padding: 10px 40px;
}
header a {
text-decoration: none;
color: #eff1f3;
}
header ul {
list-style: none;
display: flex;
align-items: center;
}
header li {
cursor: pointer;
}
.header_text {
display: flex;
flex-grow: 1; /* Le conteneur des liens prend toute la place disponible */
justify-content: center; /* Centre les liens horizontalement */
gap: 15%; /* Espace entre les liens */
font-weight: 600;
}
.header_text a {
padding: 0 10px;
}
.header_text li a:hover {
color: #00a8e8;
}
.bouton_connexion {
padding: 10px 20px;
border-radius: 900px;
font-size: 16px; /* Taille de la police */
border-color: #00a8e8;
color: #eff1f3;
background-color: #00a8e8;
margin-top: 0;
cursor: pointer;
}
.bouton_connexion:hover {
border-color: #eff1f3;
color: #00a8e8;
background-color: #eff1f3; /* Changer la couleur au survol */
}
#smart_building {
width: 13%;
margin-left: -40px;
height: auto;
}
/* ------------- */
/* Main */
/* ------------- */
main {
display: flex;
flex-grow: 1; /* Permet à la section principale d'occuper l'espace restant */
justify-content: center; /* Centre verticalement le contenu */
align-items: center; /* Centre horizontalement le contenu */
padding-bottom: 50px;
gap: 30px; /* Espace entre les 2 sections */
}
.left-panel {
flex: 3; /* 30% de la largeur totale */
display: flex;
justify-content: center; /* Centrer l'image horizontalement */
align-items: center; /* Centrer l'image verticalement */
}
.left-panel img {
width: 100%; /* L'image prend toute la largeur de son conteneur */
max-width: 450px; /* Maximum de 300px de largeur */
height: auto; /* La hauteur de l'image s'ajuste automatiquement */
border-radius: 32px;
}
.right-panel {
flex: 7; /* 70% de la largeur totale */
}
.right-panel h1 {
font-size: 60px;
margin-bottom: 60px;
padding-left: 70px; /* Ajoute un espacement à gauche du titre */
}
.features {
display: grid;
grid-template-columns: repeat(4, 1fr); /* 4 ronds par ligne */
gap: 30px;
justify-items: center;
}
.feature {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
}
.features a{
text-decoration: none;
color: #eff1f3;
}
.circle {
width: 150px;
height: 150px;
background-color: #eff1f3;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
transition: background-color 0.3s, color 0.3s;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
}
.circle:hover {
background-color: #2c2c2c;
cursor: pointer;
}
.circle i {
font-size: 70px;
fill: #2c2c2c;
color: #2c2c2c;
transition: fill 0.3s, color 0.3s;
}
.circle:hover i {
fill: #eff1f3;
color: #eff1f3;
}
/* --------------- */
/* Footer */
/* --------------- */
footer {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
font-size: 14px;
text-transform: uppercase;
padding: 20px;
margin-top: auto; /* Cela force le footer à se pousser vers le bas */
}
.copyright {
padding: 10px 20px;
}
footer a {
text-decoration: none;
color: #eff1f3;
}
.footer_link {
display: flex;
flex-grow: 1;
gap: 10%;
}
.footer_link a {
padding: 0 10px;
}
.copyright {
padding: 10px 20px;
}
/* ------------------------- */
/* Responsive Design */
/* ------------------------- */
/* GRAND ÉCRAN (> 1440px) */
@media (min-width: 1440px) {
.right-panel h1 {
font-size: 72px;
}
.circle {
width: 180px;
height: 180px;
}
.circle i {
font-size: 80px;
}
}
/* TABLETTE - Moyenne (1025px à 1440px) */
@media (max-width: 1440px) and (min-width: 1025px) {
.features {
grid-template-columns: repeat(3, 1fr);
}
.right-panel h1 {
font-size: 48px;
padding-left: 30px;
}
.circle {
width: 130px;
height: 130px;
}
.circle i {
font-size: 50px;
}
}
/* TABLETTE - Petite (769px à 1024px) */
@media (max-width: 1024px) and (min-width: 769px) {
header {
flex-wrap: wrap;
justify-content: center;
padding: 20px;
gap: 20px;
}
#smart_building {
width: 100px;
margin: 0;
}
.header_text {
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
gap: 30px;
}
.bouton_connexion {
padding: 8px 16px;
font-size: 14px;
}
main {
flex-direction: column;
padding: 40px;
}
.left-panel,
.right-panel {
width: 100%;
flex: none;
}
.features {
grid-template-columns: repeat(2, 1fr);
}
.right-panel h1 {
font-size: 42px;
padding-left: 0;
text-align: center;
}
}
/* MOBILE (≤ 768px) */
@media (max-width: 768px) {
header {
flex-direction: column;
align-items: center;
gap: 15px;
}
#smart_building {
width: 90px;
height: auto;
margin: 0 auto;
}
.header_text {
flex-direction: column;
align-items: center;
gap: 10px;
font-size: 16px;
}
.bouton_connexion {
padding: 10px 20px;
width: auto;
text-align: center;
}
main {
flex-direction: column;
padding: 20px;
}
.left-panel,
.right-panel {
width: 100%;
flex: unset;
}
.right-panel h1 {
font-size: 36px;
padding-left: 0;
text-align: center;
margin-bottom: 30px;
}
.features {
grid-template-columns: 1fr;
gap: 20px;
}
.circle {
width: 100px;
height: 100px;
}
.circle i {
font-size: 40px;
}
footer {
flex-direction: column;
text-align: center;
gap: 10px;
align-items: center;
}
.footer_link {
flex-direction: column;
align-items: center;
gap: 10px;
}
.copyright {
padding: 0;
}
}
/* TÉLÉPHONE TRÈS PETIT (≤ 480px) */
@media (max-width: 480px) {
main {
padding: 20px 10px;
}
.right-panel h1 {
font-size: 28px;
margin-bottom: 20px;
}
.circle {
width: 80px;
height: 80px;
}
.circle i {
font-size: 32px;
}
.form-control-inscription {
font-size: 14px;
padding: 8px;
}
#smart_building {
width: 70px;
}
.bouton_connexion {
font-size: 14px;
padding: 8px 16px;
}
}
/* ------------------------- */
/* Transitions douces */
/* ------------------------- */
* {
transition: all 0.3s ease-in-out;
}

View File

@ -0,0 +1,530 @@
/* ---------------- */
/* GLOBAL */
/* ---------------- */
body {
margin: 0;
padding: 0;
font-family: 'Inter', sans-serif;
background-color: #1e1e1e;
color: #eff1f3;
min-height: 100vh;
display: flex;
flex-direction: column;
}
main {
flex: 1;
padding: 40px 60px;
}
/* --------------- */
/* Header */
/* --------------- */
header {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
padding: 10px 40px;
}
header a {
text-decoration: none;
color: #eff1f3;
}
header ul {
list-style: none;
display: flex;
align-items: center;
}
header li {
cursor: pointer;
}
.header_text li a:hover {
color: #00a8e8;
}
.header_text {
display: flex;
flex-grow: 1; /* Le conteneur des liens prend toute la place disponible */
justify-content: center; /* Centre les liens horizontalement */
gap: 15%; /* Espace entre les liens */
font-weight: 600;
}
.header_text a {
padding: 0 10px;
}
.bouton_connexion {
padding: 10px 20px;
border-radius: 900px;
font-size: 16px; /* Taille de la police */
border-color: #00a8e8;
color: #eff1f3;
background-color: #00a8e8;
margin-top: 0;
cursor: pointer;
}
.bouton_connexion:hover {
border-color: #eff1f3;
color: #00a8e8;
background-color: #eff1f3; /* Changer la couleur au survol */
}
#smart_building {
width: 13%;
margin-left: -40px;
height: auto;
}
/* ---------------- */
/* TITRES */
/* ---------------- */
h1 {
font-size: 42px;
margin-bottom: 30px;
color: #00a8e8;
}
/* ---------------- */
/* TABLES */
/* ---------------- */
table {
width: 100%;
border-collapse: collapse;
margin-bottom: 60px;
background-color: #2c2c2c;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
th,
td {
padding: 15px;
text-align: center;
border-bottom: 1px solid #444;
}
th {
background-color: #1e1e1e;
color: #00a8e8;
font-size: 18px;
}
tr:hover {
background-color: #333;
}
select {
padding: 6px 10px;
border-radius: 20px;
border: none;
background-color: #eff1f3;
color: #1e1e1e;
font-weight: bold;
}
button {
background-color: #00a8e8;
color: #eff1f3;
border: none;
padding: 8px 20px;
border-radius: 20px;
font-weight: bold;
cursor: pointer;
transition: background-color 0.2s ease;
}
button:hover {
background-color: #0077b6;
}
.toggle-btn {
display: inline-block; /* Assurer que l'élément peut être transformé */
transition: transform 0.2s ease; /* Transition pour un zoom fluide */
}
.toggle-btn:hover {
cursor: pointer;
transform: scale(1.4); /* Agrandir de 1.5x lors du survol */
}
/* ---------------- */
/* FORMULAIRE */
/* ---------------- */
/* Si tu veux aussi centrer directement dans la section d'ajout */
.ajout-section {
display: flex;
flex-direction: column; /* Permet de garder la structure de la section */
justify-content: center;
align-items: center;
text-align: center;
margin: 0;
}
#form-ajout-objet,
#form-ajout-user {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
border-radius: 24px;
background-color: #2c2c2c;
padding: 40px 100px; /* Augmente l'espace intérieur */
width: 100%; /* Prend toute la largeur disponible */
max-width: 400px; /* Définit une largeur max pour éviter qu'elle ne devienne trop étroite */
box-shadow: 0 4px 8px 0 rgba(21, 21, 21, 0.2);
margin-bottom: 40px;
}
label {
font-weight: bold;
font-size: 20px;
color: #eff1f3;
white-space: nowrap;
}
select {
appearance: none; /* Supprime le style natif */
background: white;
cursor: pointer;
}
.form-group {
width: 100%;
position: relative; /* Position relative */
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 20px;
}
.form-group .icon {
position: absolute;
left: 15px; /* Ajuste la position des icônes */
top: 50%;
transform: translateY(-50%);
}
.form-group .icon svg {
width: 24px;
height: 24px;
fill: #191717;
}
.form-control-admin {
width: 100%;
padding-left: 50px; /* Remplissage à gauche de 55px */
padding-right: 40px;
height: 48px; /* Hauteur de 40px */
border-radius: 34px; /* Bordure arrondie */
font-size: 16px; /* Taille de la police */
letter-spacing: 0.5px; /* Espacement des lettres */
background-color: #eff1f3; /* Couleur de fond */
border: none; /* Supprimer la bordure */
}
.form-row {
display: flex;
align-items: center;
gap: 10px; /* espace entre label et champ */
flex-wrap: nowrap; /* évite le retour à la ligne */
}
.bouton-admin {
padding: 10px 20px;
border-radius: 900px;
font-size: 16px; /* Taille de la police */
border-color: #00a8e8;
color: #eff1f3;
background-color: #00a8e8;
margin-top: 0; /* Centrage vertical */
cursor: pointer;
}
.bouton-admin:hover {
border-color: #eff1f3;
color: #00a8e8;
background-color: #eff1f3; /* Changer la couleur au survol */
}
/* ---------------- */
/* FOOTER */
/* ---------------- */
footer {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
font-size: 14px;
text-transform: uppercase;
padding: 20px;
margin-top: auto; /* Cela force le footer à se pousser vers le bas */
}
.copyright {
padding: 10px 20px;
}
footer a {
text-decoration: none;
color: #eff1f3;
}
.footer_link {
display: flex;
flex-grow: 1;
gap: 10%;
}
.footer_link a {
padding: 0 10px;
}
.copyright {
padding: 10px 20px;
}
/* ------------------------- */
/* Responsive Design */
/* ------------------------- */
/* GRAND ÉCRAN (> 1440px) */
@media (min-width: 1440px) {
header {
padding: 20px 40px;
}
.header_text {
gap: 25%; /* Plus d'espace entre les liens */
}
h1 {
font-size: 50px;
}
table {
margin-bottom: 80px;
}
.footer_link {
gap: 20%;
}
footer {
padding: 30px;
}
.bouton_connexion {
font-size: 18px;
padding: 12px 25px;
}
#smart_building {
width: 15%;
}
}
/* TABLETTE - Moyenne (1025px à 1440px) */
@media (max-width: 1440px) and (min-width: 1025px) {
header {
padding: 20px 30px;
}
.header_text {
gap: 20%; /* Moins d'espace entre les liens */
}
h1 {
font-size: 48px;
}
table {
margin-bottom: 60px;
}
footer {
padding: 25px;
}
.footer_link {
gap: 15%;
}
.bouton_connexion {
font-size: 16px;
padding: 10px 22px;
}
#smart_building {
width: 14%;
}
}
/* TABLETTE - Petite (769px à 1024px) */
@media (max-width: 1024px) and (min-width: 769px) {
header {
flex-wrap: wrap;
justify-content: center;
padding: 20px;
gap: 20px;
}
.header_text {
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
gap: 30px;
}
.bouton_connexion {
padding: 8px 16px;
font-size: 14px;
}
main {
flex-direction: column;
padding: 40px;
}
table {
margin-bottom: 50px;
}
.footer_link {
gap: 10%;
}
#smart_building {
width: 100px;
margin: 0;
}
footer {
padding: 20px;
}
.footer_link {
gap: 10%;
}
}
/* MOBILE (≤ 768px) */
@media (max-width: 768px) {
header {
flex-direction: column;
align-items: center;
gap: 15px;
}
#smart_building {
width: 90px;
height: auto;
margin: 0 auto;
}
.header_text {
flex-direction: column;
align-items: center;
gap: 10px;
font-size: 16px;
}
.bouton_connexion {
padding: 10px 20px;
width: auto;
text-align: center;
}
main {
flex-direction: column;
padding: 20px;
}
footer {
flex-direction: column;
text-align: center;
gap: 10px;
align-items: center;
}
.footer_link {
flex-direction: column;
align-items: center;
gap: 10px;
}
.copyright {
padding: 0;
}
table {
margin-bottom: 30px;
}
}
/* TÉLÉPHONE TRÈS PETIT (≤ 480px) */
@media (max-width: 480px) {
main {
padding: 20px 10px;
}
h1 {
font-size: 28px;
margin-bottom: 20px;
}
#smart_building {
width: 70px;
}
.bouton_connexion {
font-size: 14px;
padding: 8px 16px;
}
table {
margin-bottom: 20px;
}
.footer_link {
flex-direction: column;
gap: 8%;
}
footer {
padding: 15px;
}
.footer_link a {
padding: 5px 10px;
}
.header_text {
font-size: 14px;
}
.bouton_connexion {
font-size: 12px;
padding: 8px 15px;
}
}

View File

@ -0,0 +1,354 @@
/* ------------- */
/* HTML */
/* ------------- */
html,
body {
font-family: "Inter", sans-serif;
background-color: #1e1e1e;
color: #eff1f3;
margin: 0;
padding: 0;
overflow: auto;
height: 100%; /* Définit la hauteur de la page à 100% */
display: flex; /* Flexbox sur le body */
flex-direction: column; /* Aligne le contenu du body en colonne */
}
/* --------------- */
/* Header */
/* --------------- */
header {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
padding: 10px 40px;
}
header a {
text-decoration: none;
color: #eff1f3;
}
header ul {
list-style: none;
display: flex;
align-items: center;
}
header li {
cursor: pointer;
}
.header_text {
display: flex;
flex-grow: 1; /* Le conteneur des liens prend toute la place disponible */
justify-content: center; /* Centre les liens horizontalement */
gap: 15%; /* Espace entre les liens */
font-weight: 600;
}
.header_text a {
padding: 0 10px;
}
.header_text li a:hover {
color: #00a8e8;
}
.bouton_connexion {
padding: 10px 20px;
border-radius: 900px;
font-size: 16px; /* Taille de la police */
border-color: #00a8e8;
color: #eff1f3;
background-color: #00a8e8;
margin-top: 0;
cursor: pointer;
}
.bouton_connexion:hover {
border-color: #eff1f3;
color: #00a8e8;
background-color: #eff1f3; /* Changer la couleur au survol */
}
#smart_building {
width: 13%;
margin-left: -40px;
height: auto;
}
/* ----------------- */
/* Complexe */
/* ----------------- */
body {
background-color: #1e1e1e;
color: #eff1f3;
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
}
main.admin-container {
padding: 40px;
max-width: 1000px;
margin: auto;
}
h1 {
text-align: center;
margin-bottom: 30px;
font-size: 36px;
color: #00a8e8;
}
table {
width: 100%;
border-collapse: collapse;
background-color: #2c2c2c;
border-radius: 12px;
overflow: hidden;
}
th,
td {
padding: 12px 16px;
text-align: center;
border-bottom: 1px solid #444;
}
th {
background-color: #0077b6;
color: #fff;
}
tr:nth-child(even) {
background-color: #353535;
}
select,
button {
padding: 6px 10px;
border-radius: 6px;
border: none;
}
button {
background-color: #00a8e8;
color: white;
cursor: pointer;
}
button:hover {
background-color: #0077b6;
}
/* --------------- */
/* Footer */
/* --------------- */
footer {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
font-size: 14px;
text-transform: uppercase;
padding: 20px;
margin-top: auto; /* Cela force le footer à se pousser vers le bas */
}
.copyright {
padding: 10px 20px;
}
footer a {
text-decoration: none;
color: #eff1f3;
}
.footer_link {
display: flex;
flex-grow: 1;
gap: 10%;
}
.footer_link a {
padding: 0 10px;
}
.copyright {
padding: 10px 20px;
}
/* ------------------------- */
/* Responsive Design */
/* ------------------------- */
/* GRAND ÉCRAN (> 1440px) */
@media (min-width: 1440px) {
main.admin-container {
padding: 60px;
max-width: 1200px;
}
h1 {
font-size: 48px;
}
table th, table td {
padding: 20px;
}
.bouton_connexion {
padding: 12px 24px;
font-size: 18px;
}
}
/* TABLETTE - Moyenne (1025px à 1440px) */
@media (max-width: 1440px) and (min-width: 1025px) {
table th, table td {
padding: 15px;
}
h1 {
font-size: 40px;
}
}
/* TABLETTE - Petite (769px à 1024px) */
@media (max-width: 1024px) and (min-width: 769px) {
header {
flex-wrap: wrap;
justify-content: center;
padding: 20px;
gap: 20px;
}
#smart_building {
width: 100px;
margin: 0;
}
.header_text {
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
gap: 30px;
}
.bouton_connexion {
padding: 8px 16px;
font-size: 14px;
}
main.admin-container {
padding: 30px;
}
h1 {
font-size: 36px;
margin-bottom: 20px;
}
table th, table td {
padding: 10px;
}
}
/* MOBILE (≤ 768px) */
@media (max-width: 768px) {
header {
flex-direction: column;
align-items: center;
gap: 15px;
}
#smart_building {
width: 90px;
height: auto;
margin: 0 auto;
}
.header_text {
flex-direction: column;
align-items: center;
gap: 10px;
font-size: 16px;
}
.bouton_connexion {
padding: 10px 20px;
width: auto;
text-align: center;
}
main.admin-container {
padding: 20px;
}
h1 {
font-size: 28px;
margin-bottom: 20px;
}
table th, table td {
padding: 8px;
}
footer {
flex-direction: column;
text-align: center;
gap: 10px;
align-items: center;
}
.footer_link {
flex-direction: column;
align-items: center;
gap: 10px;
}
.copyright {
padding: 0;
}
}
/* TÉLÉPHONE TRÈS PETIT (≤ 480px) */
@media (max-width: 480px) {
main.admin-container {
padding: 10px;
}
h1 {
font-size: 24px;
margin-bottom: 20px;
}
.bouton_connexion {
padding: 8px 16px;
font-size: 14px;
}
#smart_building {
width: 70px;
}
table th, table td {
padding: 6px;
}
}
/* ------------------------- */
/* Transitions douces */
/* ------------------------- */
* {
transition: all 0.3s ease-in-out;
}

View File

@ -0,0 +1,453 @@
/* ------------- */
/* HTML */
/* ------------- */
html,
body {
background-color: #1e1e1e;
color: #eff1f3;
margin: 0;
padding: 0;
overflow: auto;
height: 100%;
display: flex;
flex-direction: column;
}
/* --------------- */
/* Header */
/* --------------- */
header {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
padding: 10px 40px;
}
header a {
text-decoration: none;
color: #eff1f3;
}
header ul {
list-style: none;
display: flex;
align-items: center;
}
header li {
cursor: pointer;
}
.header_text {
display: flex;
flex-grow: 1; /* Le conteneur des liens prend toute la place disponible */
justify-content: center; /* Centre les liens horizontalement */
gap: 15%; /* Espace entre les liens */
font-weight: 600;
}
.header_text a {
padding: 0 10px;
}
.header_text li a:hover {
color: #00a8e8;
}
.bouton_connexion {
padding: 10px 20px;
border-radius: 900px;
font-size: 16px; /* Taille de la police */
border-color: #00a8e8;
color: #eff1f3;
background-color: #00a8e8;
margin-top: 0;
cursor: pointer;
}
.bouton_connexion:hover {
border-color: #eff1f3;
color: #00a8e8;
background-color: #eff1f3; /* Changer la couleur au survol */
}
#smart_building {
width: 13%;
margin-left: -40px;
height: auto;
}
/* ------------------- */
/* Connexion */
/* ------------------- */
h1 {
font-size: 45px; /* Taille de la police */
color: #eff1f3;
white-space: nowrap;
}
#connexion {
display: flex; /* Ajouté pour aligner les enfants */
justify-content: center; /* Centrer les éléments verticalement */
align-items: center; /* Centrer les éléments horizontalement */
flex: 1;; /* Prend toute la hauteur de l'écran */
}
.building {
width: 23%;
height: 640px;
border-radius: 33px;
padding-left: 8%;
background-image: url("/images/building.jpg");
background-size: cover;
}
.page-connexion {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
border-radius: 24px;
background-color: #2c2c2c;
padding: 40px 60px; /* Augmente l'espace intérieur */
width: 100%; /* Prend toute la largeur disponible */
max-width: 400px; /* Définit une largeur max pour éviter qu'elle ne devienne trop étroite */
box-shadow: 0 4px 8px 0 rgba(21, 21, 21, 0.2);
}
.form-group {
width: 100%;
position: relative; /* Position relative */
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 20px;
}
.form-group .icon {
position: absolute;
left: 15px; /* Ajuste la position des icônes */
top: 50%;
transform: translateY(-50%);
}
.form-group .icon svg {
width: 24px;
height: 24px;
fill: #191717;
}
.form-control-connexion {
width: 100%;
padding-left: 50px; /* Remplissage à gauche de 55px */
padding-right: 80px;
height: 48px; /* Hauteur de 40px */
border-radius: 34px; /* Bordure arrondie */
font-size: 16px; /* Taille de la police */
letter-spacing: 0.5px; /* Espacement des lettres */
background-color: #eff1f3; /* Couleur de fond */
border: none; /* Supprimer la bordure */
}
.bouton-connexion {
padding: 10px 20px;
border-radius: 900px;
font-size: 16px; /* Taille de la police */
border-color: #00a8e8;
color: #eff1f3;
background-color: #00a8e8;
margin-top: 10px; /* Centrage vertical */
cursor: pointer;
}
.bouton-connexion:hover {
border-color: #eff1f3;
color: #00a8e8;
background-color: #eff1f3; /* Changer la couleur au survol */
}
.lien {
display: inline-block; /* Permet de mieux gérer la taille du soulignement */
text-align: center;
margin-bottom: 15px;
font-size: 20px; /* Taille de la police */
color: #eff1f3;
text-decoration: none; /* Supprime le soulignement par défaut */
text-underline-offset: 10px; /* Augmente l'espace entre le texte et le soulignement */
cursor: pointer;
padding-bottom: 10px; /* Ajoute un petit espace sous le texte */
position: relative; /* Positionner l'élément par rapport à son parent */
}
.lien::after {
content: "";
display: block;
width: 150%; /* La ligne est maintenant 1.5 fois plus grande que le texte */
height: 2px; /* Épaisseur du soulignement */
background-color: #eff1f3; /* Couleur du soulignement */
position: absolute;
left: 50%; /* Centre la ligne horizontalement */
transform: translateX(
-50%
); /* Déplace la ligne pour qu'elle soit exactement centrée */
bottom: 0;
}
.lien:hover {
color: #0077b6;
}
.cellule {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
margin-left: 9%;
}
/* --------------- */
/* Footer */
/* --------------- */
footer {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
font-size: 14px;
text-transform: uppercase;
padding: 20px;
margin-top: auto; /* Cela force le footer à se pousser vers le bas */
}
.copyright {
padding: 10px 20px;
}
footer a {
text-decoration: none;
color: #eff1f3;
}
.footer_link {
display: flex;
flex-grow: 1;
gap: 10%;
}
.footer_link a {
padding: 0 10px;
}
.copyright {
padding: 10px 20px;
}
/* ------------------------- */
/* Responsive Design */
/* ------------------------- */
/* GRAND ÉCRAN (> 1440px) */
@media (min-width: 1440px) {
h1 {
font-size: 60px;
}
.building {
height: 700px;
}
.form-control-connexion {
font-size: 18px;
height: 52px;
}
}
/* TABLETTE - Moyenne (1025px à 1440px) */
@media (max-width: 1440px) and (min-width: 1025px) {
h1 {
font-size: 48px;
}
.building {
height: 600px;
width: 28%;
padding-left: 5%;
}
.page-connexion {
padding: 40px 40px;
}
.form-control-connexion {
font-size: 16px;
}
}
/* TABLETTE - Petite (769px à 1024px) */
@media (max-width: 1024px) and (min-width: 769px) {
header {
flex-wrap: wrap;
justify-content: center;
padding: 20px;
gap: 20px;
}
#smart_building {
width: 100px;
margin: 0;
}
.header_text {
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
gap: 30px;
}
.bouton_connexion {
padding: 8px 16px;
font-size: 14px;
}
h1 {
font-size: 40px;
text-align: center;
}
#connexion {
flex-direction: column;
padding: 40px 20px;
}
.building {
width: 100%;
height: 300px;
border-radius: 20px;
padding: 0;
margin-bottom: 30px;
}
.page-connexion {
max-width: 100%;
}
.form-control-connexion {
font-size: 15px;
}
}
/* MOBILE (≤ 768px) */
@media (max-width: 768px) {
header {
flex-direction: column;
align-items: center;
gap: 15px;
padding: 20px;
}
.header_text {
flex-direction: column;
align-items: center;
gap: 10px;
font-size: 16px;
}
.bouton_connexion {
width: auto;
padding: 10px 16px;
font-size: 14px;
}
#smart_building {
width: 80px;
margin-left: 0;
}
h1 {
font-size: 32px;
text-align: center;
}
#connexion {
flex-direction: column;
padding: 20px;
}
.building {
width: 100%;
height: 250px;
padding: 0;
margin-bottom: 20px;
}
.page-connexion {
padding: 30px 20px;
max-width: 100%;
}
.form-control-connexion {
font-size: 14px;
height: 44px;
}
footer {
flex-direction: column;
gap: 10px;
text-align: center;
align-items: center;
}
.footer_link {
flex-direction: column;
gap: 10px;
}
.copyright {
padding: 0;
}
}
/* TÉLÉPHONE TRÈS PETIT (≤ 480px) */
@media (max-width: 480px) {
h1 {
font-size: 26px;
}
.form-control-connexion {
font-size: 13px;
padding-left: 40px;
height: 40px;
}
.form-group .icon svg {
width: 20px;
height: 20px;
}
.page-connexion {
padding: 20px 10px;
}
.bouton-connexion {
font-size: 14px;
padding: 8px 14px;
}
.lien {
font-size: 16px;
}
#smart_building {
width: 60px;
}
}

View File

@ -0,0 +1,240 @@
/* --------------- */
/* Header */
/* --------------- */
header {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
padding: 10px 40px;
}
header a {
text-decoration: none;
color: #eff1f3;
}
header ul {
list-style: none;
display: flex;
align-items: center;
}
header li {
cursor: pointer;
}
.header_text {
display: flex;
flex-grow: 1; /* Le conteneur des liens prend toute la place disponible */
justify-content: center; /* Centre les liens horizontalement */
gap: 15%; /* Espace entre les liens */
font-weight: 600;
}
.header_text a {
padding: 0 10px;
}
.bouton_connexion {
padding: 10px 20px;
border-radius: 900px;
font-size: 16px; /* Taille de la police */
border-color: #00a8e8;
color: #eff1f3;
background-color: #00a8e8;
margin-top: 0;
cursor: pointer;
}
.bouton_connexion:hover {
border-color: #eff1f3;
color: #00a8e8;
background-color: #eff1f3; /* Changer la couleur au survol */
}
#smart_building {
width: 13%;
margin-left: -40px;
height: auto;
}
body {
margin: 0;
font-family: 'Segoe UI', sans-serif;
background-color: #121212;
color: #f1f1f1;
}
.contact-container {
max-width: 600px;
margin: 60px auto;
padding: 20px;
background-color: #1e1e1e;
border-radius: 10px;
box-shadow: 0 0 20px rgba(0,0,0,0.4);
}
.contact-container h1 {
text-align: center;
margin-bottom: 10px;
font-size: 2em;
}
.contact-container p {
text-align: center;
margin-bottom: 30px;
color: #cccccc;
}
.contact-form {
display: flex;
flex-direction: column;
}
.contact-form label {
margin-bottom: 5px;
font-weight: bold;
color: #ffffff;
}
.contact-form input,
.contact-form textarea {
padding: 10px;
margin-bottom: 20px;
background-color: #2a2a2a;
color: #f1f1f1;
border: 1px solid #444;
border-radius: 5px;
}
.contact-form input:focus,
.contact-form textarea:focus {
outline: none;
border: 1px solid #00bcd4;
}
.contact-form button {
background-color: #00bcd4;
color: #121212;
padding: 12px;
border: none;
border-radius: 5px;
font-weight: bold;
cursor: pointer;
transition: 0.3s;
}
.contact-form button:hover {
background-color: #0097a7;
}
/* ---------------- */
/* FOOTER */
/* ---------------- */
footer {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
font-size: 14px;
text-transform: uppercase;
padding: 20px;
margin-top: auto; /* Cela force le footer à se pousser vers le bas */
}
.copyright {
padding: 10px 20px;
}
footer a {
text-decoration: none;
color: #eff1f3;
}
.footer_link {
display: flex;
flex-grow: 1;
gap: 10%;
}
.footer_link a {
padding: 0 10px;
}
.copyright {
padding: 10px 20px;
}
@media (max-width: 768px) {
/* HEADER */
header {
flex-direction: column;
align-items: flex-start;
padding: 15px 20px;
}
.header_text {
flex-direction: column;
align-items: center;
gap: 10px;
width: 100%;
margin-top: 10px;
}
.bouton_connexion {
width: 100%;
text-align: center;
margin-top: 10px;
}
#smart_building {
width: 40%;
margin-left: 0;
align-self: center;
}
/* CONTACT */
.contact-container {
width: 90%;
margin: 40px auto;
padding: 15px;
}
.contact-container h1 {
font-size: 1.5em;
}
.contact-form input,
.contact-form textarea {
font-size: 1rem;
}
.contact-form button {
font-size: 1rem;
}
/* FOOTER */
footer {
flex-direction: column;
align-items: center;
text-align: center;
gap: 10px;
}
.footer_link {
flex-direction: column;
gap: 8px;
}
.footer_link a {
padding: 0;
}
}

View File

@ -0,0 +1,484 @@
/* ------------- */
/* HTML */
/* ------------- */
html,
body {
background-color: #1e1e1e;
color: #eff1f3;
margin: 0;
padding: 0;
overflow: auto;
height: 100%;
display: flex;
flex-direction: column;
}
/* --------------- */
/* Header */
/* --------------- */
header {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
padding: 10px 40px;
}
header a {
text-decoration: none;
color: #eff1f3;
}
header ul {
list-style: none;
display: flex;
align-items: center;
}
header li {
cursor: pointer;
}
.header_text {
display: flex;
flex-grow: 1; /* Le conteneur des liens prend toute la place disponible */
justify-content: center; /* Centre les liens horizontalement */
gap: 15%; /* Espace entre les liens */
font-weight: 600;
}
.header_text a {
padding: 0 10px;
}
.header_text li a:hover {
color: #00a8e8;
}
.bouton_connexion {
padding: 10px 20px;
border-radius: 900px;
font-size: 16px; /* Taille de la police */
border-color: #00a8e8;
color: #eff1f3;
background-color: #00a8e8;
margin-top: 0;
cursor: pointer;
}
.bouton_connexion:hover {
border-color: #eff1f3;
color: #00a8e8;
background-color: #eff1f3; /* Changer la couleur au survol */
}
#smart_building {
width: 13%;
margin-left: -40px;
height: auto;
}
/* --------------------- */
/* Inscription */
/* --------------------- */
h1 {
font-size: 45px; /* Taille de la police */
color: #eff1f3;
white-space: nowrap;
}
label {
font-weight: bold;
font-size: 20px;
color: #eff1f3;
white-space: nowrap;
}
select {
appearance: none; /* Supprime le style natif */
background: white;
cursor: pointer;
}
#inscription {
display: flex; /* Ajouté pour aligner les enfants */
justify-content: center; /* Centrer les éléments verticalement */
align-items: center; /* Centrer les éléments horizontalement */
flex: 1; /* Prend toute la hauteur de l'écran */
margin-bottom: 50px;
}
.tour {
width: 23%;
height: 640px;
border-radius: 33px;
padding-left: 8%;
background-image: url("/images/tour.jpg");
background-size: cover;
}
.page-inscription {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
border-radius: 24px;
background-color: #2c2c2c;
padding: 40px 100px; /* Augmente l'espace intérieur */
width: 100%; /* Prend toute la largeur disponible */
max-width: 400px; /* Définit une largeur max pour éviter qu'elle ne devienne trop étroite */
box-shadow: 0 4px 8px 0 rgba(21, 21, 21, 0.2);
}
.form-group {
width: 100%;
position: relative; /* Position relative */
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 20px;
}
.form-group .icon {
position: absolute;
left: 15px; /* Ajuste la position des icônes */
top: 50%;
transform: translateY(-50%);
}
.form-group .icon svg {
width: 24px;
height: 24px;
fill: #191717;
}
.form-control-inscription {
width: 100%;
padding-left: 50px; /* Remplissage à gauche de 50px */
padding-right: 40px;
height: 48px; /* Hauteur de 48px */
border-radius: 34px; /* Bordure arrondie */
font-size: 16px; /* Taille de la police */
letter-spacing: 0.5px; /* Espacement des lettres */
background-color: #eff1f3; /* Couleur de fond */
border: none; /* Supprimer la bordure */
}
.form-row {
display: flex;
align-items: center;
gap: 10px; /* espace entre label et champ */
flex-wrap: nowrap; /* évite le retour à la ligne */
}
.bouton-inscription {
padding: 10px 20px;
border-radius: 900px;
font-size: 16px; /* Taille de la police */
border-color: #00a8e8;
color: #eff1f3;
background-color: #00a8e8;
margin-top: 0; /* Centrage vertical */
cursor: pointer;
}
.bouton-inscription:hover {
border-color: #eff1f3;
color: #00a8e8;
background-color: #eff1f3; /* Changer la couleur au survol */
}
.boutons {
display: flex;
justify-content: center;
align-items: center;
gap: 20px;
margin-top: 20px;
}
/* Alignement parfait du bouton "Photo" */
.custom-file-upload {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
background-color: #004fa9;
color: #eff1f3;
padding: 10px 20px;
border-radius: 900px;
cursor: pointer;
font-size: 16px;
line-height: 1;
}
.custom-file-upload:hover {
background-color: #eff1f3;
color: #004fa9;
border: 1px solid #004fa9;
}
/* Alignement du SVG */
.custom-file-upload svg {
width: 20px;
height: 20px;
vertical-align: middle;
padding-right: 4px;
}
.case {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
margin-right: 9%;
}
.lien {
background: none;
border: none;
font-family: inherit; /* pour garder la cohérence du texte */
display: inline-block; /* Permet de mieux gérer la taille du soulignement */
text-align: center;
margin-bottom: 15px;
font-size: 23px; /* Taille de la police */
color: #eff1f3;
text-decoration: none; /* Supprime le soulignement par défaut */
text-underline-offset: 10px; /* Augmente l'espace entre le texte et le soulignement */
cursor: pointer;
padding-bottom: 10px; /* Ajoute un petit espace sous le texte */
position: relative; /* Positionner l'élément par rapport à son parent */
}
.lien::after {
content: "";
display: block;
width: 120%; /* La ligne est maintenant 1.5 fois plus grande que le texte */
height: 2px; /* Épaisseur du soulignement */
background-color: #eff1f3; /* Couleur du soulignement */
position: absolute;
left: 50%; /* Centre la ligne horizontalement */
transform: translateX(
-50%
); /* Déplace la ligne pour qu'elle soit exactement centrée */
bottom: 0;
}
.lien:hover {
color: #0077b6;
}
/* --------------- */
/* Footer */
/* --------------- */
footer {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
font-size: 14px;
text-transform: uppercase;
padding: 20px;
margin-top: auto; /* Cela force le footer à se pousser vers le bas */
}
.copyright {
padding: 10px 20px;
}
footer a {
text-decoration: none;
color: #eff1f3;
}
.footer_link {
display: flex;
flex-grow: 1;
gap: 10%;
}
.footer_link a {
padding: 0 10px;
}
.copyright {
padding: 10px 20px;
}
/* ---------------------- */
/* Media Queries */
/* ---------------------- */
/* GRAND ÉCRAN (> 1440px) */
@media (min-width: 1440px) {
h1 {
font-size: 48px;
}
.tour {
height: 700px;
}
.form-control-inscription {
font-size: 18px;
height: 52px;
}
}
/* TABLETTE - Moyenne (1025px à 1440px) */
@media (max-width: 1440px) and (min-width: 1025px) {
h1 {
font-size: 48px;
}
.tour {
height: 600px;
}
.page-inscription {
padding: 40px;
}
.form-control-inscription {
font-size: 16px;
}
}
/* TABLETTE - Petite (769px à 1024px) */
@media (max-width: 1024px) and (min-width: 769px) {
header {
flex-wrap: wrap;
justify-content: center;
padding: 20px;
gap: 20px;
}
#smart_building {
width: 100px;
margin: 0;
}
.header_text {
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
gap: 30px;
}
.bouton_connexion {
padding: 8px 16px;
font-size: 14px;
}
h1 {
font-size: 40px;
text-align: center;
}
.tour {
width: 100%;
height: 300px;
}
.form-control-inscription {
font-size: 15px;
}
}
/* MOBILE (≤ 768px) */
@media (max-width: 768px) {
header {
flex-direction: column;
align-items: center;
gap: 15px;
padding: 20px;
}
.header_text {
flex-direction: column;
align-items: center;
gap: 10px;
font-size: 16px;
}
.bouton_connexion {
width: auto;
padding: 10px 16px;
font-size: 14px;
}
#smart_building {
width: 80px;
margin-left: 0;
}
h1 {
font-size: 32px;
text-align: center;
}
.tour {
height: 250px;
margin-bottom: 20px;
}
.form-control-inscription {
font-size: 14px;
height: 44px;
}
footer {
flex-direction: column;
gap: 10px;
text-align: center;
align-items: center;
}
.footer_link {
flex-direction: column;
gap: 10px;
}
.copyright {
padding: 0;
}
}
/* TÉLÉPHONE TRÈS PETIT (≤ 480px) */
@media (max-width: 480px) {
h1 {
font-size: 26px;
}
.form-control-inscription {
font-size: 13px;
padding-left: 40px;
height: 40px;
}
.form-group .icon svg {
width: 20px;
height: 20px;
}
.page-inscription {
padding: 20px 10px;
}
.bouton-inscription {
font-size: 14px;
padding: 8px 14px;
}
.lien {
font-size: 16px;
}
.tour {
width: 60px;
}
}

View File

@ -0,0 +1,576 @@
/* ------------- */
/* HTML */
/* ------------- */
html,
body {
font-family: "Inter", sans-serif;
background-color: #1e1e1e;
color: #eff1f3;
margin: 0;
padding: 0;
overflow: auto;
height: 100%; /* Définit la hauteur de la page à 100% */
display: flex; /* Flexbox sur le body */
flex-direction: column; /* Aligne le contenu du body en colonne */
}
/* --------------- */
/* Header */
/* --------------- */
header {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
padding: 10px 40px;
}
header a {
text-decoration: none;
color: #eff1f3;
}
header ul {
list-style: none;
display: flex;
align-items: center;
}
header li {
cursor: pointer;
}
.header_text {
display: flex;
flex-grow: 1; /* Le conteneur des liens prend toute la place disponible */
justify-content: center; /* Centre les liens horizontalement */
gap: 15%; /* Espace entre les liens */
font-weight: 600;
}
.header_text a {
padding: 0 10px;
}
.header_text li a:hover {
color: #00a8e8;
}
.bouton_connexion {
padding: 10px 20px;
border-radius: 900px;
font-size: 16px; /* Taille de la police */
border-color: #00a8e8;
color: #eff1f3;
background-color: #00a8e8;
margin-top: 0;
cursor: pointer;
}
.bouton_connexion:hover {
border-color: #eff1f3;
color: #00a8e8;
background-color: #eff1f3; /* Changer la couleur au survol */
}
#smart_building {
width: 13%;
margin-left: -40px;
height: auto;
}
/* ---------------- */
/* Membres */
/* ---------------- */
main {
display: flex;
flex-grow: 1; /* Permet à la section principale d'occuper l'espace restant */
justify-content: center; /* Centre verticalement le contenu */
align-items: center; /* Centre horizontalement le contenu */
padding-bottom: 50px;
gap: 30px; /* Espace entre les 2 sections */
}
.left-panel {
flex: 3; /* 30% de la largeur totale */
display: flex;
justify-content: center; /* Centrer l'image horizontalement */
align-items: center; /* Centrer l'image verticalement */
}
.left-panel img {
width: 100%; /* L'image prend toute la largeur de son conteneur */
max-width: 450px; /* Maximum de 300px de largeur */
height: auto; /* La hauteur de l'image s'ajuste automatiquement */
border-radius: 32px;
}
.right-panel {
flex: 7; /* 70% de la largeur totale */
}
.right-panel h1 {
font-size: 60px;
margin-bottom: 60px;
padding-left: 50px; /* Ajoute un espacement à gauche du titre */
}
#membres-container {
display: grid;
grid-template-columns: repeat(2, 1fr); /* 2 cartes par ligne */
gap: 30px;
justify-items: center;
padding: 20px 60px;
}
.membre {
width: 100%; /* occupe toute la colonne */
max-width: 400px; /* limite visuelle de largeur */
padding: 20px;
background-color: #2c2c2c;
color: #eff1f3;
border-radius: 20px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
transition: transform 0.2s ease, background-color 0.3s ease;
text-align: center;
}
.membre:hover {
background-color: #dfe3e6;
transform: translateY(-5px);
color: #2c2c2c;
cursor: pointer;
}
.membre h3 {
font-size: 22px;
margin: 10px 0 5px;
}
.membre h4 {
font-size: 18px;
margin: 5px 0;
font-weight: 500;
}
.membre p {
margin: 6px 0;
font-size: 16px;
}
/* --------------- */
/* Footer */
/* --------------- */
footer {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
font-size: 14px;
text-transform: uppercase;
padding: 20px;
margin-top: auto; /* Cela force le footer à se pousser vers le bas */
}
.copyright {
padding: 10px 20px;
}
footer a {
text-decoration: none;
color: #eff1f3;
}
.footer_link {
display: flex;
flex-grow: 1;
gap: 10%;
}
.footer_link a {
padding: 0 10px;
}
.copyright {
padding: 10px 20px;
}
/* -------------------- */
/* Barre Recherche */
/* -------------------- */
.form {
width: 300px;
margin: 0 50px;
font-size: 0.9rem;
display: flex;
gap: 0.5rem;
align-items: center;
position: relative;
isolation: isolate;
--input-text-color: #363636;
--input-bg-color: #eff1f3;
--focus-input-bg-color: transparent;
--text-color: #2a2a2a;
--active-color: #eff1f3;
--width-of-input: 250px;
--inline-padding-of-input: 1.2em;
--gap: 0.9rem;
}
.fancy-bg {
position: absolute;
width: 100%;
inset: 0;
background: var(--input-bg-color);
border-radius: 30px;
height: 100%;
z-index: -1;
pointer-events: none;
box-shadow: rgba(0, 0, 0, 0.16) 0px 1px 4px;
}
label {
padding: 0.2em;
height: 40px;
padding-inline: var(--inline-padding-of-input);
display: flex;
align-items: center;
}
.search,
.close-btn {
position: absolute;
}
.search {
fill: var(--text-color);
left: var(--inline-padding-of-input);
}
svg {
width: 17px;
display: block;
}
.input {
color: var(--input-text-color);
width: 100%;
background: none;
border: none;
font-size: 1rem;
margin-inline: min(2em, calc(var(--inline-padding-of-input) + var(--gap)));
}
.input:focus {
outline: none;
color: #eff1f3;
}
.input:focus ~ .fancy-bg {
border: 1px solid var(--active-color);
background: var(--focus-input-bg-color);
}
.input:focus ~ .search {
fill: var(--active-color);
}
.input:valid ~ .close-btn {
visibility: visible;
color: #eff1f3;
}
.close-btn {
right: var(--inline-padding-of-input);
background: none;
border: none;
box-shadow: none;
width: auto;
height: auto;
padding: 0;
opacity: 1;
color: #eff1f3;
visibility: hidden;
font-size: 20px;
cursor: pointer;
}
.close-btn:hover,
.close-btn:focus {
background: none;
font-weight: bold;
transform: scale(1.2);
}
.input:not(:focus):not(:placeholder-shown) {
color: #2a2a2a;
}
.input:not(:focus):not(:placeholder-shown) ~ .search {
fill: #2a2a2a;
}
.input:not(:focus):not(:placeholder-shown) ~ .close-btn {
color: #2a2a2a;
}
.header-membres {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 70px;
margin-bottom: 60px;
flex-wrap: wrap;
}
.header-membres h1 {
font-size: 60px;
margin: 0;
color: #eff1f3;
}
/* ----------------------------- */
/* Responsive Design */
/* ----------------------------- */
/* GRAND ÉCRAN (> 1440px) */
@media (min-width: 1440px) {
.right-panel h1,
.header-membres h1 {
font-size: 72px;
}
.left-panel img {
max-width: 500px;
}
}
/* TABLETTE LARGE (1025px à 1440px) */
@media (max-width: 1440px) and (min-width: 1025px) {
.right-panel h1,
.header-membres h1 {
font-size: 56px;
padding-left: 80px;
}
.header-membres {
padding: 0 50px;
}
#membres-container {
padding: 20px 40px;
}
.left-panel img {
max-width: 400px;
}
}
/* TABLETTE MOYENNE (769px à 1024px) */
@media (max-width: 1024px) and (min-width: 769px) {
header {
flex-wrap: wrap;
justify-content: center;
padding: 20px;
gap: 20px;
}
#smart_building {
width: 100px;
margin: 0;
}
.header_text {
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
gap: 30px;
}
.bouton_connexion {
padding: 8px 16px;
font-size: 14px;
}
main {
flex-direction: column;
padding: 40px;
}
.right-panel,
.left-panel {
width: 100%;
flex: none;
}
.features {
grid-template-columns: repeat(2, 1fr);
}
.right-panel h1,
.header-membres h1 {
font-size: 42px;
padding-left: 0;
text-align: center;
}
.header-membres {
padding: 0 30px;
justify-content: center;
gap: 30px;
}
.form {
margin: 0 auto;
}
#membres-container {
grid-template-columns: repeat(2, 1fr);
padding: 20px;
}
}
/* MOBILE (≤ 768px) */
@media (max-width: 768px) {
header {
flex-direction: column;
align-items: center;
gap: 15px;
}
#smart_building {
width: 90px;
height: auto;
margin: 0 auto;
}
.header_text {
flex-direction: column;
align-items: center;
gap: 10px;
font-size: 16px;
}
.bouton_connexion {
padding: 10px 20px;
width: auto;
text-align: center;
}
main {
flex-direction: column;
padding: 20px;
}
.left-panel,
.right-panel {
width: 100%;
flex: unset;
}
.right-panel h1,
.header-membres h1 {
font-size: 36px;
padding-left: 0;
text-align: center;
margin-bottom: 30px;
}
.header-membres {
flex-direction: column;
align-items: center;
gap: 20px;
padding: 0 20px;
}
.form {
width: 100%;
max-width: 300px;
}
#membres-container {
grid-template-columns: 1fr;
padding: 10px;
}
.membre {
max-width: 100%;
}
footer {
flex-direction: column;
text-align: center;
gap: 10px;
align-items: center;
}
.footer_link {
flex-direction: column;
gap: 10px;
}
.copyright {
padding: 0;
}
}
/* MOBILE TRÈS PETIT (≤ 480px) */
@media (max-width: 480px) {
.right-panel h1,
.header-membres h1 {
font-size: 28px;
margin-bottom: 20px;
}
.form {
width: 90%;
max-width: 280px;
}
.input {
font-size: 0.9rem;
}
.close-btn {
font-size: 16px;
}
#membres-container {
gap: 20px;
}
.membre {
padding: 16px;
font-size: 14px;
}
.membre h3 {
font-size: 20px;
}
.membre h4 {
font-size: 16px;
}
.membre p {
font-size: 14px;
}
}
/* ------------------------ */
/* Transitions Générales */
/* ------------------------ */
* {
transition: all 0.3s ease-in-out;
}

View File

@ -0,0 +1,472 @@
/* ------------- */
/* HTML */
/* ------------- */
html,
body {
font-family: "Inter", sans-serif;
background-color: #1e1e1e;
color: #eff1f3;
margin: 0;
padding: 0;
overflow: auto;
height: 100%; /* Définit la hauteur de la page à 100% */
display: flex; /* Flexbox sur le body */
flex-direction: column; /* Aligne le contenu du body en colonne */
}
/* --------------- */
/* Header */
/* --------------- */
header {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
padding: 10px 40px;
}
header a {
text-decoration: none;
color: #eff1f3;
}
header ul {
list-style: none;
display: flex;
align-items: center;
}
header li {
cursor: pointer;
}
.header_text {
display: flex;
flex-grow: 1; /* Le conteneur des liens prend toute la place disponible */
justify-content: center; /* Centre les liens horizontalement */
gap: 15%; /* Espace entre les liens */
font-weight: 600;
}
.header_text a {
padding: 0 10px;
}
.bouton_connexion {
padding: 10px 20px;
border-radius: 900px;
font-size: 16px; /* Taille de la police */
border-color: #00a8e8;
color: #eff1f3;
background-color: #00a8e8;
margin-top: 0;
cursor: pointer;
}
.bouton_connexion:hover {
border-color: #eff1f3;
color: #00a8e8;
background-color: #eff1f3; /* Changer la couleur au survol */
}
#smart_building {
width: 13%;
margin-left: -40px;
height: auto;
}
/* --------------- */
/* Objets */
/* --------------- */
main {
display: flex;
flex-grow: 1; /* Permet à la section principale d'occuper l'espace restant */
justify-content: center; /* Centre verticalement le contenu */
align-items: center; /* Centre horizontalement le contenu */
padding-bottom: 50px;
gap: 30px; /* Espace entre les 2 sections */
}
.left-panel {
flex: 3; /* 30% de la largeur totale */
display: flex;
justify-content: center; /* Centrer l'image horizontalement */
align-items: center; /* Centrer l'image verticalement */
}
.left-panel img {
width: 100%; /* L'image prend toute la largeur de son conteneur */
max-width: 450px; /* Maximum de 300px de largeur */
height: auto; /* La hauteur de l'image s'ajuste automatiquement */
border-radius: 32px;
}
.right-panel {
flex: 7; /* 70% de la largeur totale */
}
.right-panel h1 {
font-size: 60px;
margin-bottom: 60px;
}
.header-objets {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 70px;
margin-bottom: 60px;
flex-wrap: wrap;
}
.header-objets h1 {
font-size: 60px;
margin: 0;
color: #eff1f3;
}
#objets-container {
display: grid;
grid-template-columns: repeat(3, 1fr); /* 3 cartes par ligne */
gap: 20px;
justify-items: center;
padding: 20px 60px;
}
.objet {
width: 100%;
max-width: 300px; /* Limite la largeur des cartes pour que ça soit plus joli */
padding: 20px;
background-color: #2c2c2c;
color: #eff1f3;
border-radius: 10px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
transition: transform 0.2s ease, background-color 0.3s ease;
text-align: center;
}
.objet:hover {
transform: translateY(-5px);
background-color: #dfe3e6;
color: #2c2c2c;
cursor: pointer;
}
.objet h3 {
font-size: 22px;
margin: 10px 0 5px;
}
.etat-point {
width: 12px;
height: 12px;
border-radius: 50%;
position: absolute;
bottom: 5px;
right: 5px;
}
.etat-point.actif {
background-color: rgb(100, 236, 100);
}
.etat-point.inactif {
background-color: rgb(240, 84, 84);
}
.setting-btn {
background: none;
border: none;
text-align: center;
cursor: pointer;
}
.setting-btn i {
font-size: 20px;
color: #a8a3a3;
}
.setting-btn:hover i {
transform: scale(1.4);
transition: transform 0.2s ease;
}
.objet:hover i {
color: #2c2c2c;
}
.info_objet{
display: flex;
align-items: center;
justify-content: space-between;
}
.info_objet h2{
margin-bottom: 100px;
}
.info_objet h4{
margin:0px;
}
.actions{
display: flex;
align-items: center;
justify-content: space-between;
}
.edit-button {
background: none;
border: none;
padding: 0;
margin: 0;
cursor: pointer;
display: flex;
align-items: center;
}
.edit-button i {
font-size: 14px;
color: #eff1f3;
transition: transform 0.2s ease;
transform: translateY(-2px);
}
.edit-button:hover i {
transform: scale(1.3) translateY(-2px);
}
.inline-edit {
display: flex;
align-items: center;
gap: 10px;
}
.inline-edit h2,
.inline-edit p {
margin: 0;
}
[contenteditable="true"] {
border: 1px solid #eff1f3; /* Bordure fine autour de l'élément modifiable */
border-radius: 4px; /* Bords arrondis pour un style plus doux */
padding: 3px 5px; /* Un peu de padding pour l'esthétique */
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1); /* Ombre légère autour de l'élément */
}
/* Style modal */
.modal {
display: none;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(60, 60, 60, 0.98); /* Plus clair que le fond de base */
backdrop-filter: blur(12px); /* Plus de flou = plus de séparation */
padding: 30px;
border-radius: 20px;
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.8); /* Ombre plus forte */
color: #eff1f3;
max-width: 450px;
width: 90%;
z-index: 1000;
animation: fadeIn 0.3s ease;
border: 1px solid rgba(255, 255, 255, 0.1); /* Léger contour clair */
}
.modal-content {
display: flex;
flex-direction: column;
gap: 20px;
}
.modal h2 {
font-size: 24px;
margin-bottom: 10px;
color: #eff1f3;
}
.modal p {
font-size: 16px;
line-height: 1.4;
}
.modal .close {
position: absolute;
top: 10px;
right: 15px;
font-size: 22px;
font-weight: bold;
color: #eff1f3;
cursor: pointer;
transition: transform 0.2s ease, color 0.2s ease;
}
.modal .close:hover {
color: #ff4d4d;
transform: scale(1.2);
}
/* -------------------- */
/* Barre Recherche */
/* -------------------- */
.form {
width: 300px;
margin: 0;
font-size: 0.9rem;
display: flex;
gap: 0.5rem;
align-items: center;
position: relative;
isolation: isolate;
--input-text-color: #363636;
--input-bg-color: #eff1f3;
--focus-input-bg-color: transparent;
--text-color: #2a2a2a;
--active-color: #eff1f3;
--width-of-input: 250px;
--inline-padding-of-input: 1.2em;
--gap: 0.9rem;
}
.fancy-bg {
position: absolute;
width: 100%;
inset: 0;
background: var(--input-bg-color);
border-radius: 30px;
height: 100%;
z-index: -1;
pointer-events: none;
box-shadow: rgba(0, 0, 0, 0.16) 0px 1px 4px;
}
label {
padding: 0.2em;
height: 40px;
padding-inline: var(--inline-padding-of-input);
display: flex;
align-items: center;
}
.search,
.close-btn {
position: absolute;
}
.search {
fill: var(--text-color);
left: var(--inline-padding-of-input);
}
svg {
width: 17px;
display: block;
}
.input {
color: var(--input-text-color); /* La couleur du texte par défaut */
width: 100%;
margin-inline: min(2em, calc(var(--inline-padding-of-input) + var(--gap)));
background: none;
border: none;
}
.input:focus {
outline: none;
color: #eff1f3;
}
/* input background change in focus */
.input:focus ~ .fancy-bg {
border: 1px solid var(--active-color);
background: var(--focus-input-bg-color);
}
/* search icon color change in focus */
.input:focus ~ .search {
fill: var(--active-color);
}
/* showing close button when typing */
.input:valid ~ .close-btn {
visibility: visible; /* Si l'input est valide, rendre le bouton visible */
color: #eff1f3; /* Garde l'icône en blanc */
}
.close-btn {
right: var(--inline-padding-of-input);
background: none;
border: none;
box-shadow: none;
width: auto;
height: auto;
padding: 0;
opacity: 1;
color: #eff1f3;
visibility: hidden;
font-size: 20px;
cursor: pointer;
}
.close-btn:hover,
.close-btn:focus {
background: none;
font-weight: bold;
transform: scale(1.2); /* Légèrement agrandit l'icône pour la rendre plus évidente */
}
.input:not(:focus):not(:placeholder-shown) {
color: #2a2a2a; /* Couleur texte sombre si champ rempli mais pas focus */
}
.input:not(:focus):not(:placeholder-shown) ~ .search {
fill: #2a2a2a; /* Loupe sombre quand input rempli mais pas focus */
}
.input:not(:focus):not(:placeholder-shown) ~ .close-btn {
color: #2a2a2a; /* Croix sombre également */
}
/* --------------- */
/* Footer */
/* --------------- */
footer {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
font-size: 14px;
text-transform: uppercase;
padding: 20px;
margin-top: auto; /* Cela force le footer à se pousser vers le bas */
}
.copyright {
padding: 10px 20px;
}
footer a {
text-decoration: none;
color: #eff1f3;
}
.footer_link {
display: flex;
flex-grow: 1;
gap: 10%;
}
.footer_link a {
padding: 0 10px;
}
.copyright {
padding: 10px 20px;
}

View File

@ -0,0 +1,378 @@
/* ------------- */
/* HTML */
/* ------------- */
html,
body {
font-family: "Inter", sans-serif;
background-color: #1e1e1e;
color: #eff1f3;
margin: 0;
padding: 0;
overflow: auto;
height: 100%; /* Définit la hauteur de la page à 100% */
display: flex; /* Flexbox sur le body */
flex-direction: column; /* Aligne le contenu du body en colonne */
}
/* --------------- */
/* Header */
/* --------------- */
header {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
padding: 10px 40px;
}
header a {
text-decoration: none;
color: #eff1f3;
}
header ul {
list-style: none;
display: flex;
align-items: center;
}
header li {
cursor: pointer;
}
.header_text {
display: flex;
flex-grow: 1; /* Le conteneur des liens prend toute la place disponible */
justify-content: center; /* Centre les liens horizontalement */
gap: 15%; /* Espace entre les liens */
font-weight: 600;
}
.header_text a {
padding: 0 10px;
}
.header_text li a:hover {
color: #00a8e8;
}
.bouton_connexion {
padding: 10px 20px;
border-radius: 900px;
font-size: 16px; /* Taille de la police */
border-color: #00a8e8;
color: #eff1f3;
background-color: #00a8e8;
margin-top: 0;
cursor: pointer;
}
.bouton_connexion:hover {
border-color: #eff1f3;
color: #00a8e8;
background-color: #eff1f3; /* Changer la couleur au survol */
}
#smart_building {
width: 13%;
margin-left: -40px;
height: auto;
}
/* --------------- */
/* Profil */
/* --------------- */
/* Conteneur Profil */
.container {
width: 80%; /* Adaptation à la nouvelle structure, largeur plus grande */
max-width: 800px; /* Limite la taille maximale à 800px */
margin: 50px auto; /* Centre le conteneur et ajoute un espacement */
background-color: #2c2c2c;
padding: 30px 40px;
border-radius: 15px;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.6);
display: flex;
flex-direction: column; /* Assure un alignement vertical des éléments */
gap: 20px; /* Espacement entre les éléments internes */
z-index: 1; /* Pour s'assurer que ce contenu reste bien en-dessous de l'en-tête */
}
/* Titre */
h1 {
text-align: center;
margin-bottom: 30px;
color: #ffffff;
}
/* Formulaire Profil */
form div {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 8px;
font-weight: bold;
color: #ccc;
}
/* Champs de saisie */
input[type="text"],
input[type="password"],
input[type="file"],
select {
width: 100%;
padding: 10px 12px;
font-size: 14px;
background-color: #3a3a3a;
color: #ffffff;
border: 1px solid #555;
border-radius: 8px;
box-sizing: border-box;
transition: border-color 0.3s;
}
/* Champs désactivés */
input:disabled,
select:disabled {
background-color: #2c2c2c;
color: #888;
border: 1px solid #444;
cursor: not-allowed;
}
/* Image de profil */
.container img {
margin-top: 10px;
border-radius: 10px;
border: 2px solid #444;
max-width: 100px;
height: auto;
}
/* Boutons (Editer, Sauvegarder) */
button {
padding: 10px 16px;
border: none;
border-radius: 8px;
font-size: 15px;
cursor: pointer;
transition: background-color 0.3s ease;
color: white;
}
/* Bouton Modifier */
#editBtn {
background-color: #27ae60;
}
#editBtn:hover {
background-color: #219150;
}
/* Bouton Sauvegarder */
#saveBtn {
background-color: #e67e22;
}
#saveBtn:hover {
background-color: #d35400;
}
/* --------------- */
/* Footer */
/* --------------- */
footer {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
font-size: 14px;
text-transform: uppercase;
padding: 20px;
margin-top: auto; /* Cela force le footer à se pousser vers le bas */
}
.copyright {
padding: 10px 20px;
}
footer a {
text-decoration: none;
color: #eff1f3;
}
.footer_link {
display: flex;
flex-grow: 1;
gap: 10%;
}
.footer_link a {
padding: 0 10px;
}
.copyright {
padding: 10px 20px;
}
/* ------------------------- */
/* Responsive Design */
/* ------------------------- */
/* GRAND ÉCRAN (> 1440px) */
@media (min-width: 1440px) {
main {
padding: 60px;
max-width: 1200px;
}
.container {
width: 70%;
}
h1 {
font-size: 48px;
}
.bouton_connexion {
padding: 12px 24px;
font-size: 18px;
}
}
/* TABLETTE - Moyenne (1025px à 1440px) */
@media (max-width: 1440px) and (min-width: 1025px) {
.container {
width: 75%;
max-width: 1000px;
}
h1 {
font-size: 40px;
}
.bouton_connexion {
padding: 10px 20px;
font-size: 16px;
}
}
/* TABLETTE - Petite (769px à 1024px) */
@media (max-width: 1024px) and (min-width: 769px) {
header {
flex-wrap: wrap;
justify-content: center;
padding: 20px;
gap: 20px;
}
#smart_building {
width: 100px;
margin: 0;
}
.header_text {
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
gap: 30px;
}
.bouton_connexion {
padding: 8px 16px;
font-size: 14px;
}
.container {
width: 90%;
max-width: 1000px;
}
h1 {
font-size: 36px;
margin-bottom: 20px;
}
}
/* MOBILE (≤ 768px) */
@media (max-width: 768px) {
header {
flex-direction: column;
align-items: center;
gap: 15px;
}
#smart_building {
width: 90px;
height: auto;
margin: 0 auto;
}
.header_text {
flex-direction: column;
align-items: center;
gap: 10px;
font-size: 16px;
}
.bouton_connexion {
padding: 10px 20px;
width: auto;
text-align: center;
}
.container {
width: 100%;
max-width: 100%;
padding: 20px;
}
h1 {
font-size: 28px;
margin-bottom: 20px;
}
footer {
flex-direction: column;
text-align: center;
gap: 10px;
align-items: center;
}
.footer_link {
flex-direction: column;
align-items: center;
gap: 10px;
}
.copyright {
padding: 0;
}
}
/* TÉLÉPHONE TRÈS PETIT (≤ 480px) */
@media (max-width: 480px) {
.container {
width: 100%;
padding: 10px;
}
h1 {
font-size: 24px;
margin-bottom: 20px;
}
.bouton_connexion {
padding: 8px 16px;
font-size: 14px;
}
#smart_building {
width: 70px;
}
}

1
z1/webapp/query Normal file
View File

@ -0,0 +1 @@
mysql

115
z1/webapp/routes/admin.js Normal file
View File

@ -0,0 +1,115 @@
const express = require('express');
const router = express.Router();
const db = require('../config/db');
// Middleware de protection admin (défini ici si non global)
const requireAdmin = (req, res, next) => {
if (!req.session.utilisateur || req.session.utilisateur.statut !== 'administrateur') {
return res.send(`
<script>
alert("Tu n'es pas connecté en tant qu'admin !");
window.location.href = '/connexion';
</script>
`);
}
next();
};
// Applique le middleware à toutes les routes de ce fichier
router.use(requireAdmin);
// Route principale - admin dashboard
router.get('/', (req, res) => {
const usersQuery = 'SELECT * FROM utilisateur';
const objetsQuery = 'SELECT * FROM objet';
const contactsQuery = 'SELECT * FROM contact'; // Récupérer les messages de contact
db.query(usersQuery, (err, utilisateurs) => {
if (err) return res.status(500).send('Erreur récupération utilisateurs');
db.query(objetsQuery, (err2, objets) => {
if (err2) return res.status(500).send('Erreur récupération objets');
db.query(contactsQuery, (err3, contacts) => {
if (err3) return res.status(500).send('Erreur récupération messages');
res.render('admin', { utilisateurs, objets, contacts });
});
});
});
});
// Mise à jour utilisateur
router.post('/update-user', (req, res) => {
const { id, etat, statut } = req.body;
const sql = 'UPDATE utilisateur SET etat = ?, statut = ? WHERE id = ?';
db.query(sql, [etat, statut, id], (err) => {
if (err) {
console.error('Erreur update utilisateur :', err);
return res.status(500).send('Erreur update utilisateur');
}
res.redirect('/admin');
});
});
// Suppression utilisateur
router.post('/supprimer-user', (req, res) => {
const { id } = req.body;
const sql = 'DELETE FROM utilisateur WHERE id = ?';
db.query(sql, [id], (err) => {
if (err) {
console.error('Erreur suppression utilisateur :', err);
return res.status(500).send("Erreur lors de la suppression de l'utilisateur");
}
res.redirect('/admin');
});
});
// Mise à jour objet
router.post('/update-objet', (req, res) => {
const { adresse_ip, niveau, etat, type } = req.body;
const sql = 'UPDATE objet SET niveau = ?, etat = ?, type = ? WHERE adresse_ip = ?';
db.query(sql, [niveau, etat, type, adresse_ip], (err) => {
if (err) {
console.error('Erreur update objet :', err);
return res.status(500).send('Erreur update objet');
}
res.redirect('/admin');
});
});
// Suppression objet + dépendances
router.post('/supprimer-objet', (req, res) => {
const { adresse_ip } = req.body;
const deleteChildTables = ['climatisation_chauffage', 'thermostat', 'appareil_menager', 'securite'];
const deleteFromTable = (table) => {
return new Promise((resolve, reject) => {
const sql = `DELETE FROM ${table} WHERE adresse_ip = ?`;
db.query(sql, [adresse_ip], (err) => {
if (err) return reject(err);
resolve();
});
});
};
Promise.all(deleteChildTables.map(deleteFromTable))
.then(() => {
const sql = 'DELETE FROM objet WHERE adresse_ip = ?';
db.query(sql, [adresse_ip], (err) => {
if (err) {
console.error('Erreur suppression objet :', err);
return res.status(500).send("Erreur lors de la suppression de l'objet");
}
res.redirect('/admin');
});
})
.catch(err => {
console.error("Erreur suppression dépendances : ", err);
res.status(500).send("Erreur lors de la suppression des dépendances liées à l'objet");
});
});
module.exports = router;

View File

@ -0,0 +1,56 @@
const express = require('express');
const router = express.Router();
const db = require('../../config/db');
router.get('/', (req, res) => {
const sql = `
SELECT
objet.adresse_ip,
objet.niveau,
objet.denomination,
objet.etat,
objet.derniere_interaction,
objet.type,
thermostat.temperature_actuelle,
thermostat.temperature_cible,
thermostat.mode,
appareil_menager.depart_differe,
appareil_menager.etat_de_la_tache,
securite.situation AS situation_securite
FROM objet
LEFT JOIN thermostat ON objet.adresse_ip = thermostat.adresse_ip
LEFT JOIN appareil_menager ON objet.adresse_ip = appareil_menager.adresse_ip
LEFT JOIN securite ON objet.adresse_ip = securite.adresse_ip
`;
db.query(sql, (err, result) => {
if (err) {
console.error('Erreur lors de la récupération des objets :', err);
return res.status(500).send('Erreur serveur');
}
res.json(result);
});
});
router.put('/:adresse_ip', (req, res) => {
const adresse_ip = req.params.adresse_ip;
const champs = req.body;
const setValues = Object.keys(champs)
.map(key => `${key} = ?`)
.join(', ');
const sql = `UPDATE objet SET ${setValues} WHERE adresse_ip = ?`;
const values = [...Object.values(champs), adresse_ip];
db.query(sql, values, (err) => {
if (err) {
console.error('Erreur lors de la mise à jour de l\'objet :', err);
return res.status(500).send('Erreur serveur');
}
res.sendStatus(200);
});
});
module.exports = router;

View File

@ -0,0 +1,34 @@
const express = require('express');
const router = express.Router();
const db = require('../../config/db'); // Connexion à la base de données
// Route pour récupérer les ressources
router.get('/', (req, res) => {
const search = req.query.search || '';
if (search) {
const sql = 'SELECT * FROM ressources WHERE nom LIKE ?';
const values = [`%${search}%`];
db.query(sql, values, (err, results) => {
if (err) {
console.error('Erreur lors de la recherche des ressources :', err);
res.status(500).send('Erreur serveur');
} else {
res.json(results);
}
});
} else {
const sql = 'SELECT * FROM ressources';
db.query(sql, (err, results) => {
if (err) {
console.error('Erreur lors de la récupération des ressources :', err);
res.status(500).send('Erreur serveur');
} else {
res.json(results);
}
});
}
});
module.exports = router;

View File

@ -0,0 +1,18 @@
const express = require('express');
const router = express.Router();
const db = require('../../config/db'); // Connexion à la base de données
// Route pour récupérer les objets
router.get('/', (req, res) => {
const sql = 'SELECT * FROM utilisateur';
db.query(sql, (err, results) => {
if (err) {
console.error('Erreur lors de la récupération des objets :', err);
res.status(500).send('Erreur serveur');
} else {
res.json(results); // Retourne les objets sous forme de JSON
}
});
});
module.exports = router;

View File

@ -0,0 +1,77 @@
const express = require('express');
const router = express.Router();
const db = require('../config/db');
// Modifier un objet
router.post('/update-objet', (req, res) => {
const { adresse_ip, niveau, etat } = req.body;
const sql = 'UPDATE objet SET niveau = ?, etat = ? WHERE adresse_ip = ?';
db.query(sql, [niveau, etat, adresse_ip], (err) => {
if (err) {
console.error("Erreur lors de la modification de l'objet :", err);
return res.status(500).send("Erreur lors de la modification de l'objet");
}
res.redirect('/dashboard-complexe');
});
});
// Supprimer un objet (et ses dépendances)
router.post('/supprimer-objet', (req, res) => {
const { adresse_ip } = req.body;
console.log('Adresse IP reçue pour suppression :', adresse_ip);
const deleteChildTables = [
'climatisation_chauffage',
'thermostat',
'appareil_menager',
'securite'
];
const deleteFromTable = (table) => {
return new Promise((resolve, reject) => {
const sql = `DELETE FROM ${table} WHERE adresse_ip = ?`;
db.query(sql, [adresse_ip], (err) => {
if (err) {
console.error(`Erreur suppression dans ${table} :`, err);
return reject(err);
}
resolve();
});
});
};
Promise.all(deleteChildTables.map(deleteFromTable))
.then(() => {
db.query('DELETE FROM objet WHERE adresse_ip = ?', [adresse_ip], (err) => {
if (err) {
console.error('Erreur suppression objet :', err);
return res.status(500).send("Erreur lors de la suppression de l'objet");
}
res.redirect('/dashboard-complexe');
});
})
.catch(err => {
res.status(500).send("Erreur lors de la suppression d'une dépendance liée à l'objet");
});
});
// Ajouter un objet
router.post('/ajouter-objet', (req, res) => {
const { denomination, adresse_ip, type, niveau, etat } = req.body;
const sql = `
INSERT INTO objet (denomination, adresse_ip, type, niveau, etat)
VALUES (?, ?, ?, ?, ?)`;
db.query(sql, [denomination, adresse_ip, type, niveau, etat], (err) => {
if (err) {
console.error('Erreur ajout objet :', err);
return res.status(500).send("Erreur lors de l'ajout de l'objet");
}
res.redirect('/dashboard-complexe');
});
});
module.exports = router;

View File

@ -0,0 +1,52 @@
const express = require('express');
const router = express.Router();
const bcrypt = require('bcrypt');
const db = require('../config/db');
// Connexion - Formulaire
router.get('/', (req, res) => {
res.render('connexion');
});
// Connexion - Traitement
router.post('/', (req, res) => {
const { identifiant, mot_de_passe } = req.body;
const sql = 'SELECT * FROM utilisateur WHERE identifiant = ?';
db.query(sql, [identifiant], async (err, results) => {
if (err) {
console.error('Erreur lors de la requête :', err);
return res.send('Erreur serveur');
}
if (results.length === 0) {
return res.send('Identifiant incorrect');
}
const utilisateur = results[0];
const match = await bcrypt.compare(mot_de_passe, utilisateur.mot_de_passe);
if (!match) {
return res.send('Mot de passe incorrect');
}
// Enregistrement en session
req.session.utilisateur = utilisateur;
console.log("Utilisateur connecté :", req.session.utilisateur);
// Redirection selon le statut
switch (utilisateur.statut) {
case 'administrateur':
return res.redirect('/admin');
case 'complexe':
return res.redirect('/dashboard-complexe');
case 'simple':
return res.redirect('/objets'); // 🔄 Redirection mise à jour ici
case 'visiteur':
default:
return res.redirect('/');
}
});
});
module.exports = router;

View File

@ -0,0 +1,44 @@
const express = require('express');
const router = express.Router();
const bcrypt = require('bcrypt');
const db = require('../config/db');
const multer = require('multer');
const path = require('path');
const storage = multer.diskStorage({
destination: (req, file, cb) => {
const uploadDir = path.join(__dirname, '../img');
cb(null, uploadDir);
},
filename: (req, file, cb) => {
const uniqueName = `${Date.now()}_${file.originalname}`;
cb(null, uniqueName);
}
});
const upload = multer({ storage });
router.get('/', (req, res) => {
res.render('inscription');
});
router.post('/', upload.single('photo_profil'), async (req, res) => {
const { nom, prenom, sexe, age, date_naissance, identifiant, mot_de_passe, situation, email } = req.body;
const photo = req.file ? req.file.filename : null;
const hashedPassword = await bcrypt.hash(mot_de_passe, 10);
const sql = `INSERT INTO utilisateur (nom, prenom, sexe, age, date_naissance, identifiant, mot_de_passe, photo, situation, email)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`;
db.query(sql, [nom, prenom, sexe, age, date_naissance, identifiant, hashedPassword, photo, situation, email], (err, result) => {
if (err) {
console.error(err);
return res.send('Erreur lors de linscription');
}
res.redirect('/connexion');
});
});
module.exports = router;

View File

@ -0,0 +1,56 @@
const express = require('express');
const router = express.Router();
const ensureAuthenticated = require('../middleware/auth');
const db = require('../config/db');
const multer = require('multer');
const bcrypt = require('bcrypt');
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, 'photos');
},
filename: (req, file, cb) => {
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
cb(null, uniqueSuffix + '-' + file.originalname); // Nom unique pour éviter les conflits
}
});
const upload = multer({
storage: storage,
limits: { fileSize: 5 * 1024 * 1024 }, // Limite de taille (5 Mo)
}).fields([
{ name: 'photo', maxCount: 1 }, // Champ pour le fichier
]);
router.get('/', ensureAuthenticated, (req, res) => {
const utilisateur = req.session.utilisateur;
res.render('profil', { utilisateur });
});
router.post('/update', ensureAuthenticated, upload, (req, res) => {
const mot_de_passe = req.body.mot_de_passe ? bcrypt.hashSync(req.body.mot_de_passe, 10) : req.session.utilisateur.mot_de_passe;
const { identifiant, nom, prenom, sexe, situation } = req.body;
const photo = req.files && req.files.photo ? req.files.photo[0].filename : req.session.utilisateur.photo;
const id = req.session.utilisateur.id;
const sql = 'UPDATE utilisateur SET identifiant = ?, nom = ?, prenom = ?, sexe = ?, situation = ?, photo = ?, mot_de_passe = ? WHERE id = ?';
db.query(sql, [identifiant, nom, prenom, sexe, situation, photo, mot_de_passe, id], (err, result) => {
if (err) {
console.error(err);
return res.status(500).send("Erreur lors de la mise à jour");
}
req.session.utilisateur.identifiant = identifiant;
req.session.utilisateur.nom = nom;
req.session.utilisateur.prenom = prenom;
req.session.utilisateur.sexe = sexe;
req.session.utilisateur.situation = situation;
req.session.utilisateur.photo = photo;
req.session.utilisateur.mot_de_passe = mot_de_passe;
res.redirect('/profil');
});
});
module.exports = router;

156
z1/webapp/user.sql Normal file
View File

@ -0,0 +1,156 @@
-- Création de la base de données et sélection
CREATE DATABASE IF NOT EXISTS user;
USE user;
SET NAMES utf8mb4;
-- Table `utilisateur`
CREATE TABLE IF NOT EXISTS `utilisateur` (
`id` INT NOT NULL AUTO_INCREMENT,
`nom` VARCHAR(100) NOT NULL,
`prenom` VARCHAR(100) NOT NULL,
`sexe` ENUM('Homme', 'Femme', 'Autre') NOT NULL,
`age` INT DEFAULT NULL,
`date_naissance` DATE NOT NULL,
`identifiant` VARCHAR(50) NOT NULL UNIQUE,
`email` VARCHAR(255) NOT NULL UNIQUE,
`mot_de_passe` VARCHAR(255) NOT NULL,
`photo` VARCHAR(255) DEFAULT NULL,
`situation` VARCHAR(255) DEFAULT NULL,
`etat` ENUM('en attente', 'validé') DEFAULT 'en attente',
`statut` ENUM('visiteur','simple','complexe','administrateur') DEFAULT 'visiteur',
PRIMARY KEY (`id`)
);
INSERT INTO `utilisateur` VALUES
(1, 'Dupont', 'Jean', 'Homme', 30, '1994-05-12', 'jdupont', 'jean.dupont@mail.com', '$2b$10$sOFuc/cZzpK1P8fINfmsAOQ33K0xyCeEXD.OCilFH0SPyuMC.3LNC', './photos/jean.jpg', 'Ingénieur', 'validé', 'complexe'), -- mots de passe dupont
(2, 'Martin', 'Sophie', 'Femme', 25, '1999-08-21', 'smartin', 'sophie.martin@mail.com', '$2b$10$ydWLQyCvL8PVAN18KCWBWe.oMdtm7Y.R4oCVVJenP9fJqfyBLR26C', './photos/sophie.jpg', 'Étudiante', 'en attente', 'visiteur'), -- mots de passe martin
(3, 'Durand', 'Paul', 'Homme', 45, '1979-11-30', 'pdurand', 'paul.durand@mail.com', '$2b$10$HnhXjkKOPNLE.2erAwzg.e5zVVRK/wNeegd8GrNxWv2f3U660xQbW', './photos/paul.jpg', 'Directeur', 'validé', 'administrateur'), -- mots de passe durand
(4, 'Bernard', 'Alice', 'Femme', 28, '1996-02-15', 'aliceb', 'alice.bernard@mail.com', '$2b$10$6BslSk7oJc6fGzQx3YQRHu6Op8xtVQIGFznA0vsmZX40Ix4ywBTfy', './photos/alice.jpg', 'Développeuse', 'validé', 'simple'), -- mots de passe bernard
(5, 'Lemoine', 'Alex', 'Autre', 32, '1992-07-19', 'alemoine', 'alex.lemoine@mail.com', '$2b$10$lZebS.LIRJi9Z5yDaLSNxujY0piysZAb9BRjGOLVW3Hr2wDPoYTgO', './photos/alex.jpg', 'Freelance', 'en attente', 'simple'), -- mots de passe lemoine
(6, 'Arricastres', 'Guillaume', 'Homme', 26, '1998-01-15', 'garricastres', 'guillaume.arricastres@mail.com', '$2b$10$StE4wQ2Mox/CUpuR.o/1nObQMT/rAAndmKXOgAJ7r.V/YGvEs7isy', './photos/guillaume.jpg', 'Étudiant', 'validé', 'simple'), -- mots de passe 1234
(7, 'Chosson', 'Clément', 'Homme', 25, '1999-06-15', 'clement_cx', 'clement.chosson@mail.com', '$2b$10$StE4wQ2Mox/CUpuR.o/1nObQMT/rAAndmKXOgAJ7r.V/YGvEs7isy', NULL, 'Technicien', 'validé', 'complexe'), -- mots de passe 1234
(8, 'Admin', 'Root', 'Homme', 30, '1993-12-31', 'admin', 'admin@smartbuilding.com', '$2b$10$StE4wQ2Mox/CUpuR.o/1nObQMT/rAAndmKXOgAJ7r.V/YGvEs7isy', NULL, 'Administrateur', 'validé', 'administrateur'); -- mots de passe 1234
-- Table `objet`
CREATE TABLE IF NOT EXISTS `objet` (
`adresse_ip` VARCHAR(45) NOT NULL,
`niveau` ENUM('débutant','intermédiaire','avancé','expert') NOT NULL,
`denomination` VARCHAR(255) NOT NULL,
`etat` ENUM('Actif','Inactif') NOT NULL,
`derniere_interaction` DATETIME DEFAULT NULL,
`type` ENUM('Climatisation','Lumière','thermostat','securite','Enceinte connectée','Capteur','appareil_menager','Prise') NOT NULL,
PRIMARY KEY (`adresse_ip`)
);
INSERT INTO `objet` VALUES
('192.168.1.10', 'débutant', 'Ampoule connectée', 'Inactif', '2025-04-08 10:40:50', 'Lumière'),
('192.168.1.11', 'débutant', 'Cuisinière', 'Inactif', '2025-04-08 10:40:50', 'appareil_menager'),
('192.168.1.12', 'débutant', 'Machine à café', 'Inactif', '2025-04-08 10:40:50', 'appareil_menager'),
('192.168.1.20', 'intermédiaire', 'Thermostat intelligent', 'Actif', '2025-04-08 10:40:50', 'thermostat'),
('192.168.1.30', 'avancé', 'Caméra de surveillance', 'Actif', '2025-04-08 10:40:50', 'Capteur'),
('192.168.1.40', 'débutant', 'Machine à laver', 'Inactif', '2025-04-08 10:40:50', 'appareil_menager'),
('192.168.1.50', 'débutant', 'Enceinte intelligente', 'Inactif', '2025-04-08 10:40:50', 'Enceinte connectée'),
('192.168.1.60', 'intermédiaire', 'Prise connectée', 'Actif', '2025-04-08 10:40:50', 'Prise'),
('192.168.1.70', 'avancé', 'Serrure connectée', 'Actif', '2025-04-08 10:40:50', 'securite'),
('192.168.1.80', 'expert', 'Porte connectée salle de contrôle', 'Actif', '2025-04-08 10:40:50', 'securite'),
('192.168.1.90', 'avancé', 'Climatisation', 'Actif', '2025-04-08 10:40:50', 'Climatisation');
-- Table `thermostat`
CREATE TABLE `thermostat` (
`adresse_ip` VARCHAR(45) NOT NULL,
`temperature_actuelle` FLOAT DEFAULT NULL,
`temperature_cible` FLOAT DEFAULT NULL,
`mode` ENUM('Automatique','Manuel') NOT NULL,
PRIMARY KEY (`adresse_ip`),
FOREIGN KEY (`adresse_ip`) REFERENCES `objet` (`adresse_ip`)
);
INSERT INTO `thermostat` VALUES
('192.168.1.20', 17, 20, 'Automatique');
-- Table `climatisation_chauffage`
CREATE TABLE `climatisation_chauffage` (
`adresse_ip` VARCHAR(45) NOT NULL,
`thermostat` VARCHAR(45) NOT NULL,
PRIMARY KEY (`adresse_ip`),
FOREIGN KEY (`adresse_ip`) REFERENCES `objet` (`adresse_ip`),
FOREIGN KEY (`thermostat`) REFERENCES `thermostat` (`adresse_ip`)
);
INSERT INTO `climatisation_chauffage` VALUES
('192.168.1.90', '192.168.1.20');
-- Table `appareil_menager`
CREATE TABLE `appareil_menager` (
`adresse_ip` VARCHAR(45) NOT NULL,
`depart_differe` TIME DEFAULT NULL,
`etat_de_la_tache` ENUM('Complété','En cours','En attente','Aucune') NOT NULL,
PRIMARY KEY (`adresse_ip`),
FOREIGN KEY (`adresse_ip`) REFERENCES `objet` (`adresse_ip`) ON DELETE RESTRICT ON UPDATE CASCADE
);
INSERT INTO `appareil_menager` VALUES
('192.168.1.11', NULL, 'Aucune'),
('192.168.1.12', NULL, 'Aucune'),
('192.168.1.40', NULL, 'Aucune');
-- Table `securite`
CREATE TABLE `securite` (
`adresse_ip` VARCHAR(45) NOT NULL,
`situation` ENUM('Verouillé','Ouvert') NOT NULL,
PRIMARY KEY (`adresse_ip`),
FOREIGN KEY (`adresse_ip`) REFERENCES `objet` (`adresse_ip`) ON DELETE RESTRICT ON UPDATE CASCADE
);
INSERT INTO `securite` VALUES
('192.168.1.70', 'Ouvert'),
('192.168.1.80', 'Verouillé');
-- Table `gestion`
CREATE TABLE `gestion` (
`id` INT NOT NULL AUTO_INCREMENT,
`id_utilisateur` INT NOT NULL,
`competence` VARCHAR(255) NOT NULL,
`niveau` ENUM('débutant','intermédiaire','avancé','expert') NOT NULL,
`categorie_experience` FLOAT DEFAULT NULL,
PRIMARY KEY (`id`),
FOREIGN KEY (`id_utilisateur`) REFERENCES `utilisateur` (`id`) ON DELETE CASCADE
);
INSERT INTO `gestion` (`id`, `id_utilisateur`, `competence`, `niveau`, `categorie_experience`) VALUES
(1, 1, 'Informatique', 'expert', 5),
(2, 1, 'Electronique', 'avancé', 3.5),
(3, 2, 'Internet', 'intermédiaire', 2),
(4, 2, 'Assistance numerique', 'débutant', 1),
(5, 3, 'Cuisine', 'expert', 10),
(6, 3, 'Aménagement', 'avancé', 7),
(7, 4, 'Securite', 'avancé', 4),
(8, 4, 'Acceuil', 'intermédiaire', 2.5),
(9, 5, 'Entretien', 'intermédiaire', 3),
(10, 5, 'Securite', 'débutant', 1.5);
CREATE TABLE `ressources` (
`id` int NOT NULL,
`nom` varchar(50) NOT NULL,
`consommation` varchar(50) NOT NULL,
`consommation_max` varchar(50) NOT NULL,
`fournisseur` varchar(100) NOT NULL,
`abonnement` varchar(100) NOT NULL,
`echeance` varchar(50) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
INSERT INTO `ressources` (`id`, `nom`, `consommation`, `consommation_max`, `fournisseur`, `abonnement`, `echeance`) VALUES
(0, 'Électricité', '250 kWh', '500 kWh', 'EDF', '50 € / mois', '2025-12-30'),
(1, 'Gaz', '90 m³', '200 m³', 'Engie', '40 € / mois', '2025-06-30'),
(2, 'Eau', '1200 L', '3000 L', 'Veolia', '20 € / mois', '2025-08-15');
CREATE TABLE IF NOT EXISTS contact (
id INT AUTO_INCREMENT PRIMARY KEY,
nom VARCHAR(100) NOT NULL,
email VARCHAR(255) NOT NULL,
message TEXT NOT NULL,
date_envoi TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

View File

@ -0,0 +1,67 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<title>Page d'accueil - Smart Building</title>
<link rel="stylesheet" href="/style.css" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css"
rel="stylesheet"
/>
<script
src="https://kit.fontawesome.com/a076d05399.js"
crossorigin="anonymous"
></script>
</head>
<body>
<%- include('partials/header') %>
<main>
<section class="left-panel">
<img src="/images/batiment.png" alt="Bâtiment" />
</section>
<section class="right-panel">
<h1>Accueil</h1>
<div class="features">
<a href="/objets" style="text-decoration: none">
<div class="feature">
<div class="circle">
<i class="fas fa-cogs"></i>
</div>
<p>Objets connectés</p>
</div>
</a>
<a href="/membres" style="text-decoration: none">
<div class="feature">
<div class="circle">
<i class="fas fa-users"></i>
</div>
<p>Membres</p>
</div>
</a>
<a href="/ressources" class="text-decoration: none">
<div class="feature">
<div class="circle">
<i class="fas fa-map-marker-alt"></i>
</div>
<p>Ressources</p>
</div>
</a>
<a href="/description" class="feature">
<div class="circle">
<i class="fas fa-building"></i>
</div>
<p>Description bâtiment</p>
</a>
</div>
</section>
</main>
<%- include('partials/footer') %>
</body>
</html>

401
z1/webapp/views/admin.ejs Normal file
View File

@ -0,0 +1,401 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<title>Admin - Smart Building</title>
<link rel="stylesheet" href="/styleAdmin.css" />
<script>
function toggleForm(id) {
const form = document.getElementById(id);
form.style.display = form.style.display === 'none' ? 'block' : 'none';
}
</script>
</head>
<body>
<%- include('partials/header') %>
<main class="admin-container">
<h1>Gestion des utilisateurs</h1>
<div class="ajout-section">
<h2>Ajouter un utilisateur <span class="toggle-btn" onclick="toggleForm('form-ajout-user')"></span></h2>
<form id="form-ajout-user" action="/admin/ajouter-utilisateur" method="POST" style="display: none;">
<div class="form-group">
<span class="icon">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="size-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M15 9h3.75M15 12h3.75M15 15h3.75M4.5 19.5h15a2.25 2.25 0 0 0 2.25-2.25V6.75A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25v10.5A2.25 2.25 0 0 0 4.5 19.5Zm6-10.125a1.875 1.875 0 1 1-3.75 0 1.875 1.875 0 0 1 3.75 0Zm1.294 6.336a6.721 6.721 0 0 1-3.17.789 6.721 6.721 0 0 1-3.168-.789 3.376 3.376 0 0 1 6.338 0Z"
/>
</svg>
</span>
<input type="text" name="nom" class="form-control-admin" placeholder="Nom" required />
</div>
<div class="form-group">
<span class="icon">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="size-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M15 9h3.75M15 12h3.75M15 15h3.75M4.5 19.5h15a2.25 2.25 0 0 0 2.25-2.25V6.75A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25v10.5A2.25 2.25 0 0 0 4.5 19.5Zm6-10.125a1.875 1.875 0 1 1-3.75 0 1.875 1.875 0 0 1 3.75 0Zm1.294 6.336a6.721 6.721 0 0 1-3.17.789 6.721 6.721 0 0 1-3.168-.789 3.376 3.376 0 0 1 6.338 0Z"
/>
</svg>
</span>
<input type="text" name="prenom" class="form-control-admin" placeholder="Prénom" required />
</div>
<div class="form-row">
<label>Sexe / Genre :</label>
<div class="form-group">
<span class="icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512">
<path
d="M176 288a112 112 0 1 0 0-224 112 112 0 1 0 0 224zM352 176c0 86.3-62.1 158.1-144 173.1l0 34.9 32 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-32 0 0 32c0 17.7-14.3 32-32 32s-32-14.3-32-32l0-32-32 0c-17.7 0-32-14.3-32-32s14.3-32 32-32l32 0 0-34.9C62.1 334.1 0 262.3 0 176C0 78.8 78.8 0 176 0s176 78.8 176 176zM271.9 360.6c19.3-10.1 36.9-23.1 52.1-38.4c20 18.5 46.7 29.8 76.1 29.8c61.9 0 112-50.1 112-112s-50.1-112-112-112c-7.2 0-14.3 .7-21.1 2c-4.9-21.5-13-41.7-24-60.2C369.3 66 384.4 64 400 64c37 0 71.4 11.4 99.8 31l20.6-20.6L487 41c-6.9-6.9-8.9-17.2-5.2-26.2S494.3 0 504 0L616 0c13.3 0 24 10.7 24 24l0 112c0 9.7-5.8 18.5-14.8 22.2s-19.3 1.7-26.2-5.2l-33.4-33.4L545 140.2c19.5 28.4 31 62.7 31 99.8c0 97.2-78.8 176-176 176c-50.5 0-96-21.3-128.1-55.4z"
/>
</svg>
</span>
<select name="sexe" class="form-control-admin">
<option value="Homme">Homme</option>
<option value="Femme">Femme</option>
<option value="Autre">Autre</option></select
><br />
</div>
</div>
<div class="form-group">
<span class="icon">
<svg
xmlns="http://www.w3.org/2000/svg"
height="24"
viewBox="0 0 24 24"
width="24"
>
<path d="M0 0h24v24H0z" fill="none" />
<path
d="M12 6c1.11 0 2-.9 2-2 0-.38-.1-.73-.29-1.03L12 0l-1.71 2.97c-.19.3-.29.65-.29 1.03 0 1.1.9 2 2 2zm4.6 9.99l-1.07-1.07-1.08 1.07c-1.3 1.3-3.58 1.31-4.89 0l-1.07-1.07-1.09 1.07C6.75 16.64 5.88 17 4.96 17c-.73 0-1.4-.23-1.96-.61V21c0 .55.45 1 1 1h16c.55 0 1-.45 1-1v-4.61c-.56.38-1.23.61-1.96.61-.92 0-1.79-.36-2.44-1.01zM18 9h-5V7h-2v2H6c-1.66 0-3 1.34-3 3v1.54c0 1.08.88 1.96 1.96 1.96.52 0 1.02-.2 1.38-.57l2.14-2.13 2.13 2.13c.74.74 2.03.74 2.77 0l2.14-2.13 2.13 2.13c.37.37.86.57 1.38.57 1.08 0 1.96-.88 1.96-1.96V12C21 10.34 19.66 9 18 9z"
/>
</svg>
</span>
<input type="number" name="age" class="form-control-admin" placeholder="Âge" required />
</div>
<div class="form-row">
<label>Date de naissance :</label>
<div class="form-group">
<span class="icon">
<svg
xmlns="http://www.w3.org/2000/svg"
height="24px"
viewBox="0 -960 960 960"
width="24px"
fill="#e3e3e3"
>
<path
d="M200-80q-33 0-56.5-23.5T120-160v-560q0-33 23.5-56.5T200-800h40v-80h80v80h320v-80h80v80h40q33 0 56.5 23.5T840-720v560q0 33-23.5 56.5T760-80H200Zm0-80h560v-400H200v400Zm0-480h560v-80H200v80Zm0 0v-80 80Zm280 240q-17 0-28.5-11.5T440-440q0-17 11.5-28.5T480-480q17 0 28.5 11.5T520-440q0 17-11.5 28.5T480-400Zm-160 0q-17 0-28.5-11.5T280-440q0-17 11.5-28.5T320-480q17 0 28.5 11.5T360-440q0 17-11.5 28.5T320-400Zm320 0q-17 0-28.5-11.5T600-440q0-17 11.5-28.5T640-480q17 0 28.5 11.5T680-440q0 17-11.5 28.5T640-400ZM480-240q-17 0-28.5-11.5T440-280q0-17 11.5-28.5T480-320q17 0 28.5 11.5T520-280q0 17-11.5 28.5T480-240Zm-160 0q-17 0-28.5-11.5T280-280q0-17 11.5-28.5T320-320q17 0 28.5 11.5T360-280q0 17-11.5 28.5T320-240Zm320 0q-17 0-28.5-11.5T600-280q0-17 11.5-28.5T640-320q17 0 28.5 11.5T680-280q0 17-11.5 28.5T640-240Z"
/>
</svg>
</span>
<input type="date" name="date_naissance" class="form-control-admin" required />
</div>
</div>
<div class="form-group">
<span class="icon">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"
>
<path
d="M12 12c2.21 0 4-1.79 4-4S14.21 4 12 4 8 5.79 8 8s1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"
/>
</svg>
</span>
<input type="text" name="identifiant" class="form-control-admin" placeholder="Identifiant" required />
</div>
<div class="form-group">
<span class="icon">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"
>
<path
d="M20 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4l-8 5-8-5V6l8 5 8-5v2z"
/>
</svg>
</span>
<input type="email" name="email" class="form-control-admin" placeholder="Email" required />
</div>
<div class="form-group">
<span class="icon">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"
>
<path
d="M12 2C8.691 2 6 4.691 6 8v4c-1.104 0-2 .896-2 2v8c0 1.104.896 2 2 2h12c1.104 0 2-.896 2-2v-8c0-1.104-.896-2-2-2V8c0-3.309-2.691-6-6-6zm0 2c2.205 0 4 1.795 4 4v4H8V8c0-2.205 1.795-4 4-4zm-4 10h8v8H8v-8z"
/>
</svg>
</span>
<input type="password" name="mot_de_passe" class="form-control-admin" placeholder="Mot de passe" required />
</div>
<div class="form-group">
<span class="icon">
<svg
xmlns="http://www.w3.org/2000/svg"
color="#000000"
stroke-width="1.5"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M20.75 16.7143C20.75 16.7631 20.7453 16.8109 20.7364 16.8571C20.7453 16.9034 20.75 16.9511 20.75 17C20.75 17.4142 20.4142 17.75 20 17.75H6C5.30964 17.75 4.75 18.3096 4.75 19C4.75 19.6904 5.30964 20.25 6 20.25H20C20.4142 20.25 20.75 20.5858 20.75 21C20.75 21.4142 20.4142 21.75 20 21.75H6C4.48122 21.75 3.25 20.5188 3.25 19V5C3.25 3.48122 4.48122 2.25 6 2.25H19.4C20.1456 2.25 20.75 2.85442 20.75 3.6V16.7143ZM9 6.25C8.58579 6.25 8.25 6.58579 8.25 7C8.25 7.41421 8.58579 7.75 9 7.75H15C15.4142 7.75 15.75 7.41421 15.75 7C15.75 6.58579 15.4142 6.25 15 6.25H9Z"
fill="#000000"
></path>
</svg>
</span>
<input type="text" name="situation" class="form-control-admin" placeholder="Situation professionnelle" required />
</div>
<div class="form-row">
<label>Statut :</label>
<div class="form-group">
<span class="icon">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 2a10 10 0 1 0 7.07 2.93A9.95 9.95 0 0 0 12 2zm0 18a8 8 0 1 1 5.66-2.34A7.96 7.96 0 0 1 12 20z"/>
<circle cx="12" cy="12" r="4"/>
</svg>
</span>
<select name="statut" class="form-control-admin">
<option value="visiteur">Visiteur</option>
<option value="simple">Simple</option>
<option value="complexe">Complexe</option>
<option value="administrateur">Administrateur</option></select
><br />
</div>
</div>
<button class="bouton-admin" type="submit">Ajouter</button>
</form>
</div>
<table>
<thead>
<tr>
<th>Nom</th>
<th>Prénom</th>
<th>Statut</th>
<th>État</th>
<th>Modifier</th>
<th>Supprimer</th>
</tr>
</thead>
<tbody>
<% utilisateurs.forEach(user => { %>
<tr>
<form action="/admin/update-user" method="POST">
<input type="hidden" name="id" value="<%= user.id %>" />
<td><%= user.nom %></td>
<td><%= user.prenom %></td>
<td>
<select name="statut">
<option value="visiteur" <%= user.statut === 'visiteur' ? 'selected' : '' %>>Visiteur</option>
<option value="simple" <%= user.statut === 'simple' ? 'selected' : '' %>>Simple</option>
<option value="complexe" <%= user.statut === 'complexe' ? 'selected' : '' %>>Complexe</option>
<option value="administrateur" <%= user.statut === 'administrateur' ? 'selected' : '' %>>Administrateur</option>
</select>
</td>
<td>
<select name="etat">
<option value="en attente" <%= user.etat === 'en attente' ? 'selected' : '' %>>En attente</option>
<option value="validé" <%= user.etat === 'validé' ? 'selected' : '' %>>Validé</option>
</select>
</td>
<td><button type="submit">Modifier</button></td>
</form>
<td>
<form action="/admin/supprimer-user" method="POST" onsubmit="return confirm('Supprimer cet utilisateur ?')">
<input type="hidden" name="id" value="<%= user.id %>">
<button type="submit" style="background-color: #e74c3c; color: white;">Supprimer</button>
</form>
</td>
</tr>
<% }) %>
</tbody>
</table>
<h1>Gestion des objets</h1>
<div class="ajout-section">
<h2>Ajouter un objet <span class="toggle-btn" onclick="toggleForm('form-ajout-objet')"></span></h2>
<form id="form-ajout-objet" action="/admin/ajouter-objet" method="POST" style="display: none;">
<div class="ajout-section">
<div class="form-group">
<span class="icon">
<svg id="Layer_1" height="512" viewBox="0 0 24 24" width="512" xmlns="http://www.w3.org/2000/svg" data-name="Layer 1"><path d="m19 4h-4v-1a3 3 0 0 0 -6 0v1h-4a5.006 5.006 0 0 0 -5 5v10a5.006 5.006 0 0 0 5 5h14a5.006 5.006 0 0 0 5-5v-10a5.006 5.006 0 0 0 -5-5zm-8-1a1 1 0 0 1 2 0v2a1 1 0 0 1 -2 0zm11 16a3 3 0 0 1 -3 3h-14a3 3 0 0 1 -3-3v-10a3 3 0 0 1 3-3h4.184a2.982 2.982 0 0 0 5.632 0h4.184a3 3 0 0 1 3 3zm-12-9h-5a1 1 0 0 0 -1 1v8a1 1 0 0 0 1 1h5a1 1 0 0 0 1-1v-8a1 1 0 0 0 -1-1zm-1 8h-3v-6h3zm11-3a1 1 0 0 1 -1 1h-5a1 1 0 0 1 0-2h5a1 1 0 0 1 1 1zm0-4a1 1 0 0 1 -1 1h-5a1 1 0 0 1 0-2h5a1 1 0 0 1 1 1zm-2 8a1 1 0 0 1 -1 1h-3a1 1 0 0 1 0-2h3a1 1 0 0 1 1 1z"/></svg>
</span>
<input type="text" name="denomination" class="form-control-admin" placeholder="Nom" required />
</div>
<div class="form-group">
<span class="icon">
<svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 24 24">
<path d="m19.07,2.929c-1.889-1.889-4.399-2.929-7.07-2.929s-5.183,1.04-7.071,2.929c-3.899,3.899-3.899,10.243.008,14.149l7.063,6.909,7.07-6.917c1.89-1.889,2.93-4.4,2.93-7.071s-1.04-5.182-2.93-7.071Zm-1.406,12.72l-5.664,5.541-5.657-5.533c-1.511-1.511-2.343-3.52-2.343-5.657s.832-4.146,2.343-5.657,3.521-2.343,5.657-2.343,4.146.832,5.656,2.343c1.512,1.511,2.344,3.52,2.344,5.657s-.832,4.146-2.336,5.649ZM8.9,6h1.6v8h-1.6V6Zm5.6,0h-2.5v8h1.6v-3h.9c1.381,0,2.5-1.119,2.5-2.5s-1.119-2.5-2.5-2.5Zm0,3.4h-.9v-1.801h.9c.497,0,.9.403.9.9s-.403.9-.9.9Z"/>
</svg>
</span>
<input type="text" name="adresse_ip" class="form-control-admin" placeholder="Adresse IP" required />
</div>
<div class="form-row">
<label>Type :</label>
<div class="form-group">
<span class="icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="512" height="512"><g id="_01_align_center" data-name="01 align center"><path d="M15,24H9V20.487a9,9,0,0,1-2.849-1.646L3.107,20.6l-3-5.2L3.15,13.645a9.1,9.1,0,0,1,0-3.29L.107,8.6l3-5.2L6.151,5.159A9,9,0,0,1,9,3.513V0h6V3.513a9,9,0,0,1,2.849,1.646L20.893,3.4l3,5.2L20.85,10.355a9.1,9.1,0,0,1,0,3.29L23.893,15.4l-3,5.2-3.044-1.758A9,9,0,0,1,15,20.487Zm-4-2h2V18.973l.751-.194A6.984,6.984,0,0,0,16.994,16.9l.543-.553,2.623,1.515,1-1.732-2.62-1.513.206-.746a7.048,7.048,0,0,0,0-3.75l-.206-.746,2.62-1.513-1-1.732L17.537,7.649,16.994,7.1a6.984,6.984,0,0,0-3.243-1.875L13,5.027V2H11V5.027l-.751.194A6.984,6.984,0,0,0,7.006,7.1l-.543.553L3.84,6.134l-1,1.732L5.46,9.379l-.206.746a7.048,7.048,0,0,0,0,3.75l.206.746L2.84,16.134l1,1.732,2.623-1.515.543.553a6.984,6.984,0,0,0,3.243,1.875l.751.194Zm1-6a4,4,0,1,1,4-4A4,4,0,0,1,12,16Zm0-6a2,2,0,1,0,2,2A2,2,0,0,0,12,10Z"/></g></svg> </span>
<select name="type" class="form-control-admin" required>
<option value="Climatisation">Climatisation</option>
<option value="Lumière">Lumière</option>
<option value="thermostat">Thermostat</option>
<option value="securite">Sécurité</option>
<option value="Enceinte connectée">Enceinte connectée</option>
<option value="Capteur">Capteur</option>
<option value="appareil_menager">Appareil ménager</option>
<option value="Prise">Prise</option></select
><br />
</div>
</div>
<div class="form-row">
<label>Niveau :</label>
<div class="form-group">
<span class="icon">
<svg xmlns="http://www.w3.org/2000/svg" id="Outline" viewBox="0 0 24 24" width="512" height="512"><path d="M1,4.75H3.736a3.728,3.728,0,0,0,7.195,0H23a1,1,0,0,0,0-2H10.931a3.728,3.728,0,0,0-7.195,0H1a1,1,0,0,0,0,2ZM7.333,2a1.75,1.75,0,1,1-1.75,1.75A1.752,1.752,0,0,1,7.333,2Z"/><path d="M23,11H20.264a3.727,3.727,0,0,0-7.194,0H1a1,1,0,0,0,0,2H13.07a3.727,3.727,0,0,0,7.194,0H23a1,1,0,0,0,0-2Zm-6.333,2.75A1.75,1.75,0,1,1,18.417,12,1.752,1.752,0,0,1,16.667,13.75Z"/><path d="M23,19.25H10.931a3.728,3.728,0,0,0-7.195,0H1a1,1,0,0,0,0,2H3.736a3.728,3.728,0,0,0,7.195,0H23a1,1,0,0,0,0-2ZM7.333,22a1.75,1.75,0,1,1,1.75-1.75A1.753,1.753,0,0,1,7.333,22Z"/></svg> </span>
<select name="niveau" class="form-control-admin" required>
<option value="débutant">Débutant</option>
<option value="intermédiaire">Intermédiaire</option>
<option value="avancé">Avancé</option>
<option value="expert">Expert</option></select
><br />
</div>
</div>
<div class="form-row">
<label>État :</label>
<div class="form-group">
<span class="icon">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 2a10 10 0 1 0 7.07 2.93A9.95 9.95 0 0 0 12 2zm0 18a8 8 0 1 1 5.66-2.34A7.96 7.96 0 0 1 12 20z"/>
<circle cx="12" cy="12" r="4"/>
</svg>
</span>
<select name="etat" class="form-control-admin">
<option value="Actif">Actif</option>
<option value="Inactif">Inactif</option></select
><br />
</div>
</div>
<button class="bouton-admin" type="submit">Ajouter</button>
</form>
</div>
<table>
<thead>
<tr>
<th>Nom</th>
<th>Adresse IP</th>
<th>Type</th>
<th>Niveau</th>
<th>État</th>
<th>Modifier</th>
<th>Supprimer</th>
</tr>
</thead>
<tbody>
<% objets.forEach(objet => { %>
<tr>
<form action="/admin/update-objet" method="POST">
<input type="hidden" name="adresse_ip" value="<%= objet.adresse_ip %>" />
<td><%= objet.denomination %></td>
<td><%= objet.adresse_ip %></td>
<td><%= objet.type %></td>
<td>
<select name="niveau">
<option value="débutant" <%= objet.niveau === 'débutant' ? 'selected' : '' %>>Débutant</option>
<option value="intermédiaire" <%= objet.niveau === 'intermédiaire' ? 'selected' : '' %>>Intermédiaire</option>
<option value="avancé" <%= objet.niveau === 'avancé' ? 'selected' : '' %>>Avancé</option>
<option value="expert" <%= objet.niveau === 'expert' ? 'selected' : '' %>>Expert</option>
</select>
</td>
<td>
<select name="etat">
<option value="Actif" <%= objet.etat === 'Actif' ? 'selected' : '' %>>Actif</option>
<option value="Inactif" <%= objet.etat === 'Inactif' ? 'selected' : '' %>>Inactif</option>
</select>
</td>
<td><button type="submit">Modifier</button></td>
</form>
<td>
<form action="/admin/supprimer-objet" method="POST" onsubmit="return confirm('Supprimer cet objet ?')">
<input type="hidden" name="adresse_ip" value="<%= objet.adresse_ip %>">
<button type="submit" style="background-color: #e74c3c; color: white;">Supprimer</button>
</form>
</td>
</tr>
<% }) %>
</tbody>
</table>
<h1>Messages de contact</h1>
<table>
<thead>
<tr>
<th>Nom</th>
<th>Email</th>
<th>Message</th>
<th>Date</th>
</tr>
</thead>
<tbody>
<% contacts.forEach(c => { %>
<tr>
<td><%= c.nom %></td>
<td><%= c.email %></td>
<td><%= c.message %></td>
<td><%= new Date(c.date_envoi).toLocaleString('fr-FR') %></td>
</tr>
<% }) %>
</tbody>
</table>
</main>
<%- include('partials/footer') %>
</body>
</html>

View File

@ -0,0 +1,71 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<title>Page d'accueil - Smart Building</title>
<link rel="stylesheet" href="/styleConnexion.css" />
</head>
<body>
<%- include('partials/header') %>
<div id="connexion">
<div class="building"></div>
<div class="cellule">
<div class="page-connexion">
<h1>Connexion</h1>
<form action="/connexion" method="POST">
<div class="form-group">
<span class="icon">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"
>
<path
d="M12 12c2.21 0 4-1.79 4-4S14.21 4 12 4 8 5.79 8 8s1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"
/>
</svg>
</span>
<input
class="form-control-connexion"
type="text"
id="identifiant"
name="identifiant"
placeholder="Identifiant"
required
/><br /><br />
</div>
<div class="form-group">
<span class="icon">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"
>
<path
d="M12 2C8.691 2 6 4.691 6 8v4c-1.104 0-2 .896-2 2v8c0 1.104.896 2 2 2h12c1.104 0 2-.896 2-2v-8c0-1.104-.896-2-2-2V8c0-3.309-2.691-6-6-6zm0 2c2.205 0 4 1.795 4 4v4H8V8c0-2.205 1.795-4 4-4zm-4 10h8v8H8v-8z"
/>
</svg>
</span>
<input
class="form-control-connexion"
type="password"
id="mot_de_passe"
name="mot_de_passe"
placeholder="Mot de passe"
required
/><br /><br />
</div>
<input class="bouton-connexion" type="submit" value="Connexion" />
</form>
</div>
</div>
</div>
<%- include('partials/footer') %>
</body>
</html>

View File

@ -0,0 +1,31 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<title>Contact - Smart Building</title>
<link rel="stylesheet" href="/styleContact.css" />
</head>
<body>
<%- include('partials/header') %>
<main class="contact-container">
<h1>Nous contacter</h1>
<p>Une question ? Une suggestion ? N'hésitez pas à nous écrire !</p>
<form action="/contact" method="POST" class="contact-form">
<label for="nom">Nom</label>
<input type="text" id="nom" name="nom" required />
<label for="email">Adresse e-mail</label>
<input type="email" id="email" name="email" required />
<label for="message">Message</label>
<textarea id="message" name="message" rows="5" required></textarea>
<button type="submit">Envoyer</button>
</form>
</main>
<%- include('partials/footer') %>
</body>
</html>

View File

@ -0,0 +1,105 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<title>Dashboard Complexe - Smart Building</title>
<link rel="stylesheet" href="/styleComplexe.css" />
<script>
function toggleForm(id) {
const form = document.getElementById(id);
form.style.display = form.style.display === 'none' ? 'block' : 'none';
}
</script>
</head>
<body>
<%- include('partials/header') %>
<main class="admin-container">
<h1>Gestion des objets</h1>
<div class="ajout-section">
<h2>Ajouter un objet <span class="toggle-btn" onclick="toggleForm('form-ajout-objet')"></span></h2>
<form id="form-ajout-objet" action="/complexe/ajouter-objet" method="POST" style="display: none;">
<input type="text" name="denomination" placeholder="Nom" required />
<input type="text" name="adresse_ip" placeholder="Adresse IP" required />
<select name="type" required>
<option value="Climatisation">Climatisation</option>
<option value="Lumière">Lumière</option>
<option value="thermostat">Thermostat</option>
<option value="securite">Sécurité</option>
<option value="Enceinte connectée">Enceinte connectée</option>
<option value="Capteur">Capteur</option>
<option value="appareil_menager">Appareil ménager</option>
<option value="Prise">Prise</option>
</select>
<select name="niveau">
<option value="débutant">Débutant</option>
<option value="intermédiaire">Intermédiaire</option>
<option value="avancé">Avancé</option>
<option value="expert">Expert</option>
</select>
<select name="etat">
<option value="Actif">Actif</option>
<option value="Inactif">Inactif</option>
</select>
<button type="submit">Ajouter</button>
</form>
</div>
<table>
<thead>
<tr>
<th>Nom</th>
<th>Adresse IP</th>
<th>Type</th>
<th>Niveau</th>
<th>État</th>
<th>Modifier</th>
<th>Supprimer</th>
</tr>
</thead>
<tbody>
<% objets.forEach(objet => { %>
<tr>
<form action="/complexe/update-objet" method="POST">
<input type="hidden" name="adresse_ip" value="<%= objet.adresse_ip %>" />
<td><%= objet.denomination %></td>
<td><%= objet.adresse_ip %></td>
<td><%= objet.type %></td>
<td>
<select name="niveau">
<option value="débutant" <%= objet.niveau === 'débutant' ? 'selected' : '' %>>Débutant</option>
<option value="intermédiaire" <%= objet.niveau === 'intermédiaire' ? 'selected' : '' %>>Intermédiaire</option>
<option value="avancé" <%= objet.niveau === 'avancé' ? 'selected' : '' %>>Avancé</option>
<option value="expert" <%= objet.niveau === 'expert' ? 'selected' : '' %>>Expert</option>
</select>
</td>
<td>
<select name="etat">
<option value="Actif" <%= objet.etat === 'Actif' ? 'selected' : '' %>>Actif</option>
<option value="Inactif" <%= objet.etat === 'Inactif' ? 'selected' : '' %>>Inactif</option>
</select>
</td>
<td>
<button type="submit">Modifier</button>
</td>
</form>
<td>
<form action="/complexe/supprimer-objet" method="POST" onsubmit="return confirm('Supprimer cet objet ?')">
<input type="hidden" name="adresse_ip" value="<%= objet.adresse_ip %>">
<button type="submit" style="background-color: #e74c3c; color: white;">Supprimer</button>
</form>
</td>
</tr>
<% }) %>
</tbody>
</table>
</main>
<%- include('partials/footer') %>
</body>
</html>

View File

@ -0,0 +1,42 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<title>Présentation de la maison - Smart Building</title>
<link rel="stylesheet" href="/styleObjets.css" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css" rel="stylesheet" />
</head>
<body>
<%- include('partials/header') %>
<main>
<section class="left-panel">
<img src="/images/batiment.png" alt="Image de la maison connectée" />
</section>
<section class="right-panel">
<div class="header-objets">
<h1>Présentation de la maison connectée</h1>
</div>
<div style="padding: 0 70px; font-size: 18px; line-height: 1.8;">
<p>
Notre maison connectée est conçue pour offrir confort, sécurité et efficacité énergétique. Équipée de capteurs intelligents, dun système de gestion centralisée et dune connectivité en temps réel, elle permet une surveillance continue des équipements et une optimisation automatique des ressources.
</p>
<p>
Avec une surface habitable estimée à environ 300 m² répartie sur 4 à 5 niveaux, la maison connectée propose de vastes espaces ouverts et lumineux. Chaque étage est conçu pour maximiser lapport en lumière naturelle grâce aux nombreuses baies vitrées. Les terrasses végétalisées apportent un lien direct avec la nature, tout en favorisant lisolation thermique.
</p>
<p>
Le bâtiment comprend de nombreux équipements connectés tels que léclairage intelligent, le chauffage programmable, des outils ménager et une surveillance vidéo connectée. Toutes les données sont centralisées via une interface web qui permet un contrôle à distance via son smartphone ou son ordinateur. De plus en fonction des droits que possèdes votre compte vous avez accés à différentes fonctionnalités.
</p>
</div>
</section>
</main>
<%- include('partials/footer') %>
</body>
</html>

View File

@ -0,0 +1,273 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<title>Page d'accueil - Smart Building</title>
<link rel="stylesheet" href="/styleInscription.css" />
</head>
<body>
<%- include('partials/header') %>
<div id="inscription">
<div class="case">
<form action="/connexion" method="GET">
<button class="lien" type="submit">
Déjà inscrit ? Se connecter
</button>
</form>
<div class="page-inscription">
<h1>Inscription</h1>
<form
action="/inscription"
method="POST"
enctype="multipart/form-data"
>
<div class="form-group">
<span class="icon">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="size-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M15 9h3.75M15 12h3.75M15 15h3.75M4.5 19.5h15a2.25 2.25 0 0 0 2.25-2.25V6.75A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25v10.5A2.25 2.25 0 0 0 4.5 19.5Zm6-10.125a1.875 1.875 0 1 1-3.75 0 1.875 1.875 0 0 1 3.75 0Zm1.294 6.336a6.721 6.721 0 0 1-3.17.789 6.721 6.721 0 0 1-3.168-.789 3.376 3.376 0 0 1 6.338 0Z"
/>
</svg>
</span>
<input
type="text"
class="form-control-inscription"
name="nom"
placeholder="Nom"
required
/>
</div>
<div class="form-group">
<span class="icon">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="size-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M15 9h3.75M15 12h3.75M15 15h3.75M4.5 19.5h15a2.25 2.25 0 0 0 2.25-2.25V6.75A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25v10.5A2.25 2.25 0 0 0 4.5 19.5Zm6-10.125a1.875 1.875 0 1 1-3.75 0 1.875 1.875 0 0 1 3.75 0Zm1.294 6.336a6.721 6.721 0 0 1-3.17.789 6.721 6.721 0 0 1-3.168-.789 3.376 3.376 0 0 1 6.338 0Z"
/>
</svg>
</span>
<input
type="text"
class="form-control-inscription"
name="prenom"
placeholder="Prénom"
required
/>
</div>
<div class="form-row">
<label>Sexe / Genre :</label>
<div class="form-group">
<span class="icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512">
<path
d="M176 288a112 112 0 1 0 0-224 112 112 0 1 0 0 224zM352 176c0 86.3-62.1 158.1-144 173.1l0 34.9 32 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-32 0 0 32c0 17.7-14.3 32-32 32s-32-14.3-32-32l0-32-32 0c-17.7 0-32-14.3-32-32s14.3-32 32-32l32 0 0-34.9C62.1 334.1 0 262.3 0 176C0 78.8 78.8 0 176 0s176 78.8 176 176zM271.9 360.6c19.3-10.1 36.9-23.1 52.1-38.4c20 18.5 46.7 29.8 76.1 29.8c61.9 0 112-50.1 112-112s-50.1-112-112-112c-7.2 0-14.3 .7-21.1 2c-4.9-21.5-13-41.7-24-60.2C369.3 66 384.4 64 400 64c37 0 71.4 11.4 99.8 31l20.6-20.6L487 41c-6.9-6.9-8.9-17.2-5.2-26.2S494.3 0 504 0L616 0c13.3 0 24 10.7 24 24l0 112c0 9.7-5.8 18.5-14.8 22.2s-19.3 1.7-26.2-5.2l-33.4-33.4L545 140.2c19.5 28.4 31 62.7 31 99.8c0 97.2-78.8 176-176 176c-50.5 0-96-21.3-128.1-55.4z"
/>
</svg>
</span>
<select name="sexe" class="form-control-inscription">
<option value="Homme">Homme</option>
<option value="Femme">Femme</option>
<option value="Autre">Autre</option></select
><br />
</div>
</div>
<div class="form-group">
<span class="icon">
<svg
xmlns="http://www.w3.org/2000/svg"
height="24"
viewBox="0 0 24 24"
width="24"
>
<path d="M0 0h24v24H0z" fill="none" />
<path
d="M12 6c1.11 0 2-.9 2-2 0-.38-.1-.73-.29-1.03L12 0l-1.71 2.97c-.19.3-.29.65-.29 1.03 0 1.1.9 2 2 2zm4.6 9.99l-1.07-1.07-1.08 1.07c-1.3 1.3-3.58 1.31-4.89 0l-1.07-1.07-1.09 1.07C6.75 16.64 5.88 17 4.96 17c-.73 0-1.4-.23-1.96-.61V21c0 .55.45 1 1 1h16c.55 0 1-.45 1-1v-4.61c-.56.38-1.23.61-1.96.61-.92 0-1.79-.36-2.44-1.01zM18 9h-5V7h-2v2H6c-1.66 0-3 1.34-3 3v1.54c0 1.08.88 1.96 1.96 1.96.52 0 1.02-.2 1.38-.57l2.14-2.13 2.13 2.13c.74.74 2.03.74 2.77 0l2.14-2.13 2.13 2.13c.37.37.86.57 1.38.57 1.08 0 1.96-.88 1.96-1.96V12C21 10.34 19.66 9 18 9z"
/>
</svg>
</span>
<input
type="number"
class="form-control-inscription"
name="age"
min="0"
max="120"
placeholder="Âge"
required
/>
</div>
<div class="form-row">
<label>Date de naissance :</label>
<div class="form-group">
<span class="icon">
<svg
xmlns="http://www.w3.org/2000/svg"
height="24px"
viewBox="0 -960 960 960"
width="24px"
fill="#e3e3e3"
>
<path
d="M200-80q-33 0-56.5-23.5T120-160v-560q0-33 23.5-56.5T200-800h40v-80h80v80h320v-80h80v80h40q33 0 56.5 23.5T840-720v560q0 33-23.5 56.5T760-80H200Zm0-80h560v-400H200v400Zm0-480h560v-80H200v80Zm0 0v-80 80Zm280 240q-17 0-28.5-11.5T440-440q0-17 11.5-28.5T480-480q17 0 28.5 11.5T520-440q0 17-11.5 28.5T480-400Zm-160 0q-17 0-28.5-11.5T280-440q0-17 11.5-28.5T320-480q17 0 28.5 11.5T360-440q0 17-11.5 28.5T320-400Zm320 0q-17 0-28.5-11.5T600-440q0-17 11.5-28.5T640-480q17 0 28.5 11.5T680-440q0 17-11.5 28.5T640-400ZM480-240q-17 0-28.5-11.5T440-280q0-17 11.5-28.5T480-320q17 0 28.5 11.5T520-280q0 17-11.5 28.5T480-240Zm-160 0q-17 0-28.5-11.5T280-280q0-17 11.5-28.5T320-320q17 0 28.5 11.5T360-280q0 17-11.5 28.5T320-240Zm320 0q-17 0-28.5-11.5T600-280q0-17 11.5-28.5T640-320q17 0 28.5 11.5T680-280q0 17-11.5 28.5T640-240Z"
/>
</svg>
</span>
<input
type="date"
class="form-control-inscription"
name="date_naissance"
required
/>
</div>
</div>
<div class="form-group">
<span class="icon">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"
>
<path
d="M12 12c2.21 0 4-1.79 4-4S14.21 4 12 4 8 5.79 8 8s1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"
/>
</svg>
</span>
<input
type="text"
class="form-control-inscription"
name="identifiant"
placeholder="Identifiant"
required
/>
</div>
<div class="form-group">
<span class="icon">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"
>
<path
d="M20 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4l-8 5-8-5V6l8 5 8-5v2z"
/>
</svg>
</span>
<input
type="email"
class="form-control-inscription"
name="email"
placeholder="Adresse mail"
required
/>
</div>
<div class="form-group">
<span class="icon">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"
>
<path
d="M12 2C8.691 2 6 4.691 6 8v4c-1.104 0-2 .896-2 2v8c0 1.104.896 2 2 2h12c1.104 0 2-.896 2-2v-8c0-1.104-.896-2-2-2V8c0-3.309-2.691-6-6-6zm0 2c2.205 0 4 1.795 4 4v4H8V8c0-2.205 1.795-4 4-4zm-4 10h8v8H8v-8z"
/>
</svg>
</span>
<input
type="password"
class="form-control-inscription"
name="mot_de_passe"
placeholder="Mot de Passe"
required
/>
</div>
<div class="form-group">
<span class="icon">
<svg
xmlns="http://www.w3.org/2000/svg"
color="#000000"
stroke-width="1.5"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M20.75 16.7143C20.75 16.7631 20.7453 16.8109 20.7364 16.8571C20.7453 16.9034 20.75 16.9511 20.75 17C20.75 17.4142 20.4142 17.75 20 17.75H6C5.30964 17.75 4.75 18.3096 4.75 19C4.75 19.6904 5.30964 20.25 6 20.25H20C20.4142 20.25 20.75 20.5858 20.75 21C20.75 21.4142 20.4142 21.75 20 21.75H6C4.48122 21.75 3.25 20.5188 3.25 19V5C3.25 3.48122 4.48122 2.25 6 2.25H19.4C20.1456 2.25 20.75 2.85442 20.75 3.6V16.7143ZM9 6.25C8.58579 6.25 8.25 6.58579 8.25 7C8.25 7.41421 8.58579 7.75 9 7.75H15C15.4142 7.75 15.75 7.41421 15.75 7C15.75 6.58579 15.4142 6.25 15 6.25H9Z"
fill="#000000"
></path>
</svg>
</span>
<input
type="text"
class="form-control-inscription"
name="situation"
placeholder="Situation professionnelle"
required
/>
</div>
<div class="boutons">
<label for="photo_profil" class="custom-file-upload">
<span class="icon">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
class="upload-icon"
viewBox="0 0 24 24"
>
<path
d="M19 15v4H5v-4H3v4a2 2 0 002 2h14a2 2 0 002-2v-4h-2zm-7-1l5-5h-3V4h-4v5H7l5 5z"
/>
</svg>
Photo de profil (facultatif)
</span>
</label>
<input
id="photo_profil"
type="file"
name="photo_profil"
accept="image/*"
hidden
/>
<button class="bouton-inscription" type="submit">
S'inscrire
</button>
</div>
</form>
</div>
</div>
<div class="tour"></div>
</div>
<%- include('partials/footer') %>
</body>
</html>

View File

@ -0,0 +1,31 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<title>Page d'accueil - Smart Building</title>
<link rel="stylesheet" href="/styleMembres.css" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Inter&display=swap"
rel="stylesheet"
/>
<script
src="https://kit.fontawesome.com/a076d05399.js"
crossorigin="anonymous"
></script>
</head>
<body>
<%- include('partials/header') %>
<main>
<h1>Member Details</h1>
<p>Member ID: <%= id %></p>
<!-- Display the ID passed from the route -->
<!-- More member details would go here, once you fetch them -->
</main>
<%- include('partials/footer') %>
</body>
</html>

119
z1/webapp/views/membres.ejs Normal file
View File

@ -0,0 +1,119 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<title>Page d'accueil - Smart Building</title>
<link rel="stylesheet" href="/styleMembres.css" />
<link
href="https://fonts.googleapis.com/css2?family=Inter&display=swap"
rel="stylesheet"
/>
<script
src="https://kit.fontawesome.com/a076d05399.js"
crossorigin="anonymous"
></script>
</head>
<body>
<%- include('partials/header') %>
<main>
<section class="left-panel">
<img src="/images/batiment.png" alt="Bâtiment" />
</section>
<section class="right-panel">
<div class="header-membres">
<h1>Membres</h1>
<form class="form">
<label for="search">
<input
class="input"
type="text"
placeholder="Rechercher"
id="search"
/>
<div class="fancy-bg"></div>
<div class="search">
<svg viewBox="0 0 24 24" aria-hidden="true">
<path
d="M21.53 20.47l-3.66-3.66C19.19 15.24 20 13.21 20 11c0-4.97-4.03-9-9-9s-9 4.03-9 9 4.03 9 9 9c2.21 0 4.24-.8 5.81-2.13l3.66 3.66c.29.29.76.29 1.06 0s.29-.76 0-1.06zM3.5 11c0-4.14 3.36-7.5 7.5-7.5s7.5 3.36 7.5 7.5-3.36 7.5-7.5 7.5S3.5 15.14 3.5 11z"
/>
</svg>
</div>
<button class="close-btn" type="reset" onClick="clearInput()">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
clip-rule="evenodd"
/>
</svg>
</button>
</label>
</form>
</div>
<div id="membres-container"></div>
</section>
</main>
<%- include('partials/footer') %>
</body>
<script>
const membresContainer = document.getElementById("membres-container");
async function fetchMembres() {
try {
const response = await fetch("/api/utilisateurs");
const membres = await response.json();
const statutColor = {
visiteur: "#ffbe0b",
simple: "#4ecdc4",
complexe: "#f72585",
administrateur: "#e63946",
};
membresContainer.innerHTML = "";
membres.forEach((membre) => {
const div = document.createElement("div");
div.classList.add("membre");
div.innerHTML = `
<h3>${membre.nom}</h3>
<h4>${membre.prenom}</h4>
<p>${membre.age} ans</p>
<p style="color: ${
statutColor[membre.statut] || "#888"
}; font-weight: bold;">${membre.statut}</p>
`;
membresContainer.appendChild(div);
});
} catch (err) {
console.error("Erreur lors du chargement des membres :", err);
}
}
// Fonction pour réinitialiser l'input et les objets affichés
function clearInput() {
const inputField = document.getElementById("search"); // Sélectionne l'input par son id
inputField.value = ""; // Vide l'input
inputField.dispatchEvent(new Event("input")); // Déclenche l'événement 'input' pour actualiser l'affichage des objets
}
document.getElementById("search").addEventListener("input", function (e) {
const val = e.target.value.toLowerCase();
document.querySelectorAll(".membre").forEach((div) => {
const content = div.innerText.toLowerCase();
div.style.display = content.includes(val) ? "block" : "none";
});
});
window.onload = fetchMembres;
</script>
</html>

200
z1/webapp/views/objets.ejs Normal file
View File

@ -0,0 +1,200 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<title>Page d'accueil - Smart Building</title>
<link rel="stylesheet" href="/styleObjets.css" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css" rel="stylesheet" />
<script src="https://kit.fontawesome.com/a076d05399.js" crossorigin="anonymous"></script>
<style>
.setting-btn.disabled {
opacity: 0.4;
pointer-events: none;
cursor: not-allowed;
}
</style>
</head>
<body>
<%- include('partials/header') %>
<main>
<section class="left-panel">
<img src="/images/batiment.png" alt="Bâtiment" />
</section>
<section class="right-panel">
<div class="header-objets">
<h1>Objets Connectés</h1>
<form class="form">
<label for="search">
<input class="input" type="text" required placeholder="Rechercher" id="search" />
<div class="fancy-bg"></div>
<div class="search">
<svg viewBox="0 0 24 24" aria-hidden="true" class="r-yyyyoo">
<g>
<path d="M21.53 20.47l-3.66-3.66C19.195 15.24 20 13.214 20 11c0-4.97-4.03-9-9-9s-9 4.03-9 9 4.03 9 9 9c2.215 0 4.24-.804 5.808-2.13l3.66 3.66c.147.146.34.22.53.22s.385-.073.53-.22c.295-.293.295-.767.002-1.06zM3.5 11c0-4.135 3.365-7.5 7.5-7.5s7.5 3.365 7.5 7.5-3.365 7.5-7.5 7.5-7.5-3.365-7.5-7.5z"></path>
</g>
</svg>
</div>
<span class="close-btn" aria-label="Reset" onClick="clearInput()">&times;</span>
</label>
</form>
</div>
<div id="objets-container" data-statut="<%= session.utilisateur ? session.utilisateur.statut : '' %>"></div>
</section>
<div id="myModal" class="modal">
<div class="modal-content">
<span class="close">&times;</span>
<div id="modal-info"></div>
</div>
</div>
</main>
<%- include('partials/footer') %>
</body>
</html>
<script>
const span = document.getElementsByClassName("close")[0];
span.onclick = () => document.getElementById('myModal').style.display = "none";
function openModal(objet) {
const modal = document.getElementById('myModal');
const modalInfo = document.getElementById('modal-info');
modalInfo.innerHTML = `
<div class='info_objet'>
<h2 id='denomination-edit'>${objet.denomination}</h2>
<button type="submit" onClick="toggleEdit('${objet.adresse_ip}','edit-icon_denomination', 'denomination')">
<i id="edit-icon_denomination" class="fas fa-pen"></i>
</button>
</div>
<div class='info_objet'>
<p id='adresse_ip-edit'>${objet.adresse_ip}</p>
<button type="submit" onClick="toggleEdit('${objet.adresse_ip}','edit-icon_adresse_ip', 'adresse_ip')">
<i id="edit-icon_adresse_ip" class="fas fa-pen"></i>
</button>
</div>
<div class='info_objet'>
<p id='niveau-edit'>${objet.niveau}</p>
<button type="submit" onClick="toggleEdit('${objet.adresse_ip}','edit-icon_niveau', 'niveau')">
<i id="edit-icon_niveau" class="fas fa-pen"></i>
</button>
</div>
<div class='info_objet'>
<p id='type-edit'>${objet.type}</p>
<button type="submit" onClick="toggleEdit('${objet.adresse_ip}','edit-icon_type', 'type')">
<i id="edit-icon_type" class="fas fa-pen"></i>
</button>
</div>
<div class='info_objet'>
<p><strong>Dernière interaction:</strong> ${objet.derniere_interaction}</p>
</div>
<div class='info_objet'>
<p><strong>État:</strong> ${objet.etat}</p>
</div>
`;
modal.style.display = 'block';
}
function toggleEdit(adresse_ip, edit_icon, field) {
const editIcon = document.getElementById(edit_icon);
const paragraph = document.getElementById(field + "-edit");
if (editIcon.className === "fas fa-pen") {
editIcon.className = "fas fa-check";
paragraph.contentEditable = true;
paragraph.style.backgroundColor = "transparent";
} else {
editIcon.className = "fas fa-pen";
paragraph.contentEditable = false;
paragraph.style.backgroundColor = "rgba(60, 60, 60, 0.98)";
saveChanges(adresse_ip, field, paragraph.innerText);
}
}
async function saveChanges(adresse_ip, field, newValue) {
document.getElementById('h3_' + adresse_ip).innerText = newValue;
try {
const response = await fetch('/api/objets/' + encodeURIComponent(adresse_ip), {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ [field]: newValue })
});
if (response.ok) {
console.log('Objet mis à jour avec succès !');
} else {
console.error('Erreur serveur :', response.statusText);
}
} catch (error) {
console.error('Erreur réseau :', error);
}
}
function clearInput() {
const input = document.getElementById("search");
input.value = "";
input.dispatchEvent(new Event("input"));
fetchObjets();
}
async function fetchObjets() {
try {
const response = await fetch('/api/objets');
const objets = await response.json();
const container = document.getElementById('objets-container');
const userStatut = container.getAttribute("data-statut");
container.innerHTML = "";
const niveauColor = {
débutant: "#2ecc71",
intermédiaire: "#3498db",
avancé: "#f39c12",
expert: "#e74c3c",
};
const peutModifier = ['administrateur', 'complexe', 'simple'].includes(userStatut);
objets.forEach(objet => {
console.log(objet);
const div = document.createElement('div');
div.className = 'objet';
div.innerHTML = `
<button class="setting-btn ${peutModifier ? '' : 'disabled'}"
${peutModifier ? `onclick='openModal(${JSON.stringify(objet)})'` : ''}>
<i class="fas fa-cog"></i>
</button>
<h3 id='h3_${objet.adresse_ip}'>${objet.denomination}</h3>
<p>${objet.adresse_ip}</p>
<p><strong>Type</strong> : ${objet.type}</p>
${objet.temperature_actuelle !== null ? `<p><strong>Temperature Actuelle</strong> : ${objet.temperature_actuelle}°</p>` : ''}
${objet.temperature_cible !== null ? `<p><strong>Temperature Cible</strong> : ${objet.temperature_cible}°</p>` : ''}
${objet.mode !== null ? `<p><strong>Mode</strong> : ${objet.mode}</p>` : ''}
<p><strong>Niveau:</strong> <span style="color: ${niveauColor[objet.niveau]}">${objet.niveau}</span></p>
<div class="etat-point ${objet.etat === 'Actif' ? 'actif' : 'inactif'}"></div>
`;
container.appendChild(div);
});
} catch (error) {
console.error('Erreur fetchObjets :', error);
}
}
document.getElementById('search').addEventListener('input', function (e) {
const searchTerm = e.target.value.toLowerCase();
document.querySelectorAll('.objet').forEach(objet => {
const text = objet.innerText.toLowerCase();
objet.style.display = text.includes(searchTerm) ? 'block' : 'none';
});
});
window.onload = fetchObjets;
</script>

View File

@ -0,0 +1,9 @@
<footer>
<div class="footer_link">
<a href="/description">À PROPOS</a>
<a href="/contact">CONTACT</a>
</div>
<div class="copyright">© 404 NOT FOUND | CY-TECH</div>
</footer>

View File

@ -0,0 +1,39 @@
<header>
<a href="/">
<img id="smart_building" alt="smart_building" src="/images/smart_building.png" />
</a>
<ul class="header_text">
<% if (!session.utilisateur) { %>
<li><a href="/">Accueil</a></li>
<li><a href="/inscription">Inscription</a></li>
<% } else if (session.utilisateur.statut === 'administrateur') { %>
<% if (currentRoute === '/admin') { %>
<li><a href="/">Accueil</a></li>
<li><a href="/profil">Profil</a></li>
<% } else { %>
<li><a href="/admin">Dashboard</a></li>
<li><a href="/">Accueil</a></li>
<li><a href="/profil">Profil</a></li>
<% } %>
<% } else if (session.utilisateur.statut === 'complexe') { %>
<% if (currentRoute === '/dashboard-complexe') { %>
<li><a href="/">Accueil</a></li>
<li><a href="/profil">Profil</a></li>
<% } else { %>
<li><a href="/dashboard-complexe">Dashboard</a></li>
<li><a href="/">Accueil</a></li>
<li><a href="/profil">Profil</a></li>
<% } %>
<% } else { %>
<li><a href="/">Accueil</a></li>
<li><a href="/profil">Profil</a></li>
<% } %>
</ul>
<% if (!session.utilisateur) { %>
<a class="bouton_connexion" href="/connexion">Connexion</a>
<% } else { %>
<a class="bouton_connexion" href="/deconnexion">Déconnexion</a>
<% } %>
</header>

View File

@ -0,0 +1,70 @@
<!-- views/profil.ejs -->
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Mon Profil</title>
<link rel="stylesheet" href="/styleProfil.css">
</head>
<body>
<%- include("partials/header") %>
<div class="container">
<h1>Mon Profil</h1>
<form id="profilForm" action="/profil/update" method="POST" enctype="multipart/form-data">
<div>
<label>Login :</label>
<input type="text" name="identifiant" value="<%= session.utilisateur.identifiant %>" disabled>
</div>
<div>
<label>Nom :</label>
<input type="text" name="nom" value="<%= session.utilisateur.nom %>" disabled>
</div>
<div>
<label>Prénom :</label>
<input type="text" name="prenom" value="<%= session.utilisateur.prenom %>" disabled>
</div>
<div>
<label>Sexe :</label>
<select name="sexe" disabled>
<option value="Homme" <%= session.utilisateur.sexe === 'Homme' ? 'selected' : '' %>>Homme</option>
<option value="Femme" <%= session.utilisateur.sexe === 'Femme' ? 'selected' : '' %>>Femme</option>
<option value="Autre" <%= session.utilisateur.sexe === 'Autre' ? 'selected' : '' %>>Autre</option>
</select>
</div>
<div>
<label>Situation professionnelle :</label>
<input type="text" name="situation" value="<%= session.utilisateur.situation %>" disabled>
</div>
<div>
<label>Photo de profil :</label><br>
<img src="../photos/<%= session.utilisateur.photo %>" width="100" alt=""><br>
<input type="file" name="photo" disabled>
</div>
<div>
<label>Mot de passe :</label>
<input type="password" name="mot_de_passe" placeholder="nouveau mot de passe si désiré" disabled>
</div>
<button type="button" id="editBtn">Modifier mon profil</button>
<button type="submit" id="saveBtn" style="display: none;">Enregistrer</button>
</form>
</div>
<script>
const editBtn = document.getElementById('editBtn');
const saveBtn = document.getElementById('saveBtn');
const inputs = document.querySelectorAll('#profilForm input, #profilForm select');
editBtn.addEventListener('click', () => {
inputs.forEach(input => {
input.disabled = false;
});
editBtn.style.display = 'none';
saveBtn.style.display = 'inline-block';
});
</script>
<%- include("partials/footer") %>
</body>
</html>

View File

@ -0,0 +1,162 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<title>Page d'accueil - Smart Building</title>
<link rel="stylesheet" href="/styleObjets.css" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css" rel="stylesheet" />
<script src="https://kit.fontawesome.com/a076d05399.js" crossorigin="anonymous"></script>
</head>
<body>
<%- include('partials/header') %>
<main>
<section class="left-panel">
<img src="/images/batiment.png" alt="Bâtiment" />
</section>
<section class="right-panel">
<div class="header-objets">
<h1>Gestion des ressources</h1>
</div>
<div id="objets-container"></div>
</section>
<div id="myModal" class="modal">
<!-- Modal content -->
<div class="modal-content">
<span class="close">&times;</span>
<div id="modal-info"></div>
<!-- Conteneur pour les informations -->
</div>
</div>
</main>
<%- include('partials/footer') %>
</body>
</html>
<script>
let objetsContainer = document.getElementById('objets-container');
function openModal(objet) {
const modal = document.getElementById('myModal');
const modalInfo = document.getElementById('modal-info');
modalInfo.innerHTML = `
<div class='info_objet'>
<h2 id='denomination-edit'>${objet.denomination}</h2>
<button type="submit" id="edit-button" onClick="toggleEdit('edit-icon_denomination', 'denomination-edit')">
<i id="edit-icon_denomination" class="fas fa-pen"></i>
</button>
</div>
<div class='info_objet'>
<p id='address_ip-edit'>${objet.adresse_ip}</p>
<button type="submit" id="edit-button" onClick="toggleEdit('edit-icon_adresse_ip', 'address_ip-edit')">
<i id="edit-icon_adresse_ip" class="fas fa-pen"></i>
</button>
</div>
<div class='info_objet'>
<p id='etat-edit'>${objet.etat}</p>
<button type="submit" id="edit-button" onClick="toggleEdit('edit-icon_etat', 'etat-edit')">
<i id="edit-icon_etat" class="fas fa-pen"></i>
</button>
</div>
<div class='info_objet'>
<p id='type-edit'>${objet.type}</p>
<button type="submit" id="edit-button" onClick="toggleEdit('edit-icon_type', 'type-edit')">
<i id="edit-icon_type" class="fas fa-pen"></i>
</button>
</div>
<div class='info_objet'>
<p><strong>Consommation actuelle :</strong> ${objet.consommation}</p>
</div>
<div class='info_objet'>
<p><strong>Consommation maximale :</strong> ${objet.consommation_max}</p>
</div>
<div class='info_objet'>
<p><strong>Fournisseur :</strong> ${objet.fournisseur}</p>
</div>
<div class='info_objet'>
<p><strong>Abonnement :</strong> ${objet.abonnement}</p>
</div>
<div class='info_objet'>
<p><strong>Échéance :</strong> ${objet.echeance}</p>
</div>
`;
modal.style.display = 'block';
}
function toggleEdit(edit_icon, field) {
const editIcon = document.getElementById(edit_icon);
const paragraph = document.getElementById(field);
if (editIcon.className == "fas fa-pen") {
editIcon.className = "fas fa-check";
editIcon.alt = "Check";
paragraph.contentEditable = true;
paragraph.style.backgroundColor = "#cfcfcf";
} else {
if (editIcon.className == "fas fa-check") {
editIcon.className = "fas fa-pen";
editIcon.alt = "Stylo";
paragraph.contentEditable = false;
paragraph.style.backgroundColor = "#666666";
}
}
}
var span = document.getElementsByClassName("close")[0];
span.onclick = function () {
const modal = document.getElementById('myModal');
modal.style.display = "none";
}
// Fonction pour réinitialiser l'input et les objets affichés
function clearInput() {
const inputField = document.getElementById("search"); // Sélectionne l'input par son id
inputField.value = ""; // Vide l'input
inputField.dispatchEvent(new Event("input")); // Déclenche l'événement 'input' pour actualiser l'affichage des objets
fetchObjets(); // Recharge les objets
}
async function fetchObjets() {
const objetsContainer = document.getElementById('objets-container');
objetsContainer.innerHTML = "";
try {
const response = await fetch('/api/ressources');
const ressources = await response.json();
ressources.forEach(r => {
const div = document.createElement('div');
div.classList.add('objet');
div.innerHTML = `
<h3>${r.nom}</h3>
<p><strong>Consommation actuelle :</strong> ${r.consommation}</p>
<p><strong>Max atteignable :</strong> ${r.consommation_max}</p>
<p><strong>Fournisseur :</strong> ${r.fournisseur}</p>
<p><strong>Abonnement :</strong> ${r.abonnement}</p>
<p><strong>Date paiement :</strong> ${r.echeance}</p>
`;
objetsContainer.appendChild(div);
});
} catch (err) {
console.error("Erreur lors du chargement des ressources :", err);
}
}
// Charger les objets au premier chargement de la page
window.onload = function () {
fetchObjets(); // Charge les objets au premier chargement
};
</script>