Add language preferences : English or French
This commit is contained in:
parent
d573327eb7
commit
23a10672f4
113
Front-end/package-lock.json
generated
113
Front-end/package-lock.json
generated
@ -13,12 +13,15 @@
|
||||
"@emotion/styled": "^11.14.0",
|
||||
"@mui/material": "^7.0.1",
|
||||
"axios": "^1.8.4",
|
||||
"i18next": "^26.0.1",
|
||||
"i18next-browser-languagedetector": "^8.2.1",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"lucide-react": "^0.427.0",
|
||||
"react": "^18.3.1",
|
||||
"react-charts": "^3.0.0-beta.57",
|
||||
"react-circle-progress-bar": "^0.1.4",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-i18next": "^17.0.1",
|
||||
"react-router-dom": "^7.4.0",
|
||||
"recharts": "^2.15.1"
|
||||
},
|
||||
@ -266,13 +269,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.27.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz",
|
||||
"integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==",
|
||||
"version": "7.29.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz",
|
||||
"integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"regenerator-runtime": "^0.14.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
@ -3807,6 +3807,56 @@
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/html-parse-stringify": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
|
||||
"integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"void-elements": "3.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/i18next": {
|
||||
"version": "26.0.1",
|
||||
"resolved": "https://registry.npmjs.org/i18next/-/i18next-26.0.1.tgz",
|
||||
"integrity": "sha512-vtz5sXU4+nkCm8yEU+JJ6yYIx0mkg9e68W0G0PXpnOsmzLajNsW5o28DJMqbajxfsfq0gV3XdrBudsDQnwxfsQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://www.locize.com/i18next"
|
||||
},
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
|
||||
},
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://www.locize.com"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.29.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5 || ^6"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/i18next-browser-languagedetector": {
|
||||
"version": "8.2.1",
|
||||
"resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.2.1.tgz",
|
||||
"integrity": "sha512-bZg8+4bdmaOiApD7N7BPT9W8MLZG+nPTOFlLiJiT8uzKXFjhxw4v2ierCXOwB5sFDMtuA5G4kgYZ0AznZxQ/cw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.23.2"
|
||||
}
|
||||
},
|
||||
"node_modules/ignore": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
||||
@ -5259,6 +5309,33 @@
|
||||
"react": "^18.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/react-i18next": {
|
||||
"version": "17.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-17.0.1.tgz",
|
||||
"integrity": "sha512-iG65FGnFHcYyHNuT01ukffYWCOBFTWSdVD8EZd/dCVWgtjFPObcSsvYYNwcsokO/rDcTb5d6D8Acv8MrOdm6Hw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.29.2",
|
||||
"html-parse-stringify": "^3.0.1",
|
||||
"use-sync-external-store": "^1.6.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"i18next": ">= 26.0.1",
|
||||
"react": ">= 16.8.0",
|
||||
"typescript": "^5 || ^6"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
},
|
||||
"react-native": {
|
||||
"optional": true
|
||||
},
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-is": {
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
|
||||
@ -5421,12 +5498,6 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/regenerator-runtime": {
|
||||
"version": "0.14.1",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
|
||||
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/regexp.prototype.flags": {
|
||||
"version": "1.5.4",
|
||||
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz",
|
||||
@ -6307,6 +6378,15 @@
|
||||
"punycode": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/use-sync-external-store": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
|
||||
"integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
@ -6468,6 +6548,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/void-elements": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
|
||||
"integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
|
||||
@ -14,12 +14,15 @@
|
||||
"@emotion/styled": "^11.14.0",
|
||||
"@mui/material": "^7.0.1",
|
||||
"axios": "^1.8.4",
|
||||
"i18next": "^26.0.1",
|
||||
"i18next-browser-languagedetector": "^8.2.1",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"lucide-react": "^0.427.0",
|
||||
"react": "^18.3.1",
|
||||
"react-charts": "^3.0.0-beta.57",
|
||||
"react-circle-progress-bar": "^0.1.4",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-i18next": "^17.0.1",
|
||||
"react-router-dom": "^7.4.0",
|
||||
"recharts": "^2.15.1"
|
||||
},
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
import React from "react";
|
||||
import { Battery } from "lucide-react";
|
||||
import Progress from "react-circle-progress-bar";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
function BatterieInfo({ object }) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div key={object.id} className="bg-white p-6 rounded-xl min-w-5xl">
|
||||
<div className="flex align-items gap-6">
|
||||
@ -10,13 +12,13 @@ function BatterieInfo({ object }) {
|
||||
<Battery className="text-indigo-600" size={24} />
|
||||
</div>
|
||||
<h1 className="text-black text-2xl font-bold mb-1 ">
|
||||
Etat de la batterie
|
||||
{t('components.batterieInfo.title')}
|
||||
</h1>
|
||||
</div>
|
||||
<div className="flex flex-col items-center">
|
||||
<Progress progress={object.batterie} />
|
||||
<h1 className="font-bold">
|
||||
Type de batterie :{" "}
|
||||
{t('components.batterieInfo.batteryType')}{" "}
|
||||
<span className="capitalize font-normal">{object.type_batterie}</span>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
@ -3,8 +3,10 @@ import { BadgePlus } from "lucide-react";
|
||||
import axios from "axios";
|
||||
import { API_BASE_URL } from "../config";
|
||||
import { useAuth } from "../AuthContext";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
function FormNewObject({ isAdmin }) {
|
||||
const { t } = useTranslation();
|
||||
const { user } = useAuth();
|
||||
const [categorie, setCategorie] = useState();
|
||||
const [description, setDescription] = useState("");
|
||||
@ -35,12 +37,12 @@ function FormNewObject({ isAdmin }) {
|
||||
proprio_id,
|
||||
})
|
||||
.then((response) => {
|
||||
setMessRequete("Votre objet à bien été enregistré !");
|
||||
setMessRequete(t('components.formNewObject.successRecord'));
|
||||
setEnregistre(true);
|
||||
console.log("Ajout de l'objet réussit :", response.data);
|
||||
})
|
||||
.catch((error) => {
|
||||
setMessRequete("Il y a eu une erreur dans l'ajout de votre objet !");
|
||||
setMessRequete(t('components.formNewObject.errorRecord'));
|
||||
console.error("Erreur lors de l'ajout de l'objet :", error);
|
||||
});
|
||||
setVerif(false);
|
||||
@ -54,7 +56,7 @@ function FormNewObject({ isAdmin }) {
|
||||
.get(`${API_BASE_URL}/getCategories`)
|
||||
.then((response) => {
|
||||
if (response.data.length === 0) {
|
||||
console.warn("Aucune catégorie disponible.");
|
||||
console.warn(t('components.formNewObject.noCategory'));
|
||||
} else {
|
||||
setCategorie(response.data);
|
||||
}
|
||||
@ -93,7 +95,7 @@ function FormNewObject({ isAdmin }) {
|
||||
<div className="flex align-items gap-9">
|
||||
{isAdmin ? (
|
||||
<h2 className="text-2xl font-semibold mb-3">
|
||||
Ajouter un nouvel objet
|
||||
{t('components.formNewObject.addTitle')}
|
||||
</h2>
|
||||
) : (
|
||||
<>
|
||||
@ -102,8 +104,8 @@ function FormNewObject({ isAdmin }) {
|
||||
</div>
|
||||
<h1 className="text-black text-2xl font-bold mb-1">
|
||||
{!verif
|
||||
? "Entrez les données de votre nouvel objet"
|
||||
: "Êtes-vous sûr de ces données ?"}
|
||||
? t('components.formNewObject.enterData')
|
||||
: t('components.formNewObject.confirmData')}
|
||||
</h1>
|
||||
</>
|
||||
)}
|
||||
@ -113,7 +115,7 @@ function FormNewObject({ isAdmin }) {
|
||||
htmlFor="nom"
|
||||
className="block mb-2 text-sm font-medium text-gray-900"
|
||||
>
|
||||
Nom :
|
||||
{t('components.formNewObject.name')}
|
||||
</label>
|
||||
<input
|
||||
id="nom"
|
||||
@ -130,7 +132,7 @@ function FormNewObject({ isAdmin }) {
|
||||
htmlFor="description"
|
||||
className="block mb-2 text-sm font-medium text-gray-900"
|
||||
>
|
||||
Description :
|
||||
{t('components.formNewObject.description')}
|
||||
</label>
|
||||
<input
|
||||
id="description"
|
||||
@ -148,7 +150,7 @@ function FormNewObject({ isAdmin }) {
|
||||
htmlFor="type"
|
||||
className="block mb-2 text-sm font-medium text-gray-900"
|
||||
>
|
||||
Type :
|
||||
{t('components.formNewObject.type')}
|
||||
</label>
|
||||
<select
|
||||
id="type"
|
||||
@ -158,7 +160,7 @@ function FormNewObject({ isAdmin }) {
|
||||
required
|
||||
disabled={verif}
|
||||
>
|
||||
<option value="">-- Sélectionner un type --</option>
|
||||
<option value="">{t('components.formNewObject.selectType')}</option>
|
||||
{categorie&&categorie.map((cat, index) => (
|
||||
<option key={index} value={cat}>
|
||||
{cat}
|
||||
@ -172,7 +174,7 @@ function FormNewObject({ isAdmin }) {
|
||||
htmlFor="location"
|
||||
className="block mb-2 text-sm font-medium text-gray-900"
|
||||
>
|
||||
Localisation :
|
||||
{t('components.formNewObject.location')}
|
||||
</label>
|
||||
<input
|
||||
id="location"
|
||||
@ -189,7 +191,7 @@ function FormNewObject({ isAdmin }) {
|
||||
htmlFor="batterieType"
|
||||
className="block mb-2 text-sm font-medium text-gray-900"
|
||||
>
|
||||
Type de batterie :
|
||||
{t('components.formNewObject.batteryType')}
|
||||
</label>
|
||||
<input
|
||||
id="batterieType"
|
||||
@ -206,7 +208,7 @@ function FormNewObject({ isAdmin }) {
|
||||
htmlFor="proprio_id"
|
||||
className="block mb-2 text-sm font-medium text-gray-900"
|
||||
>
|
||||
Propriétaire :
|
||||
{t('components.formNewObject.owner')}
|
||||
</label>
|
||||
<input
|
||||
id="proprio_id"
|
||||
@ -221,14 +223,14 @@ function FormNewObject({ isAdmin }) {
|
||||
|
||||
<div className="mb-5">
|
||||
<label className="block mb-2 text-sm font-medium text-gray-900">
|
||||
Status :
|
||||
{t('components.formNewObject.status')}
|
||||
</label>
|
||||
<div className="inline-flex items-center gap-2">
|
||||
<label
|
||||
htmlFor="switch-component-on"
|
||||
className="text-slate-600 text-sm cursor-pointer"
|
||||
>
|
||||
Inactive
|
||||
{t('components.formNewObject.inactive')}
|
||||
</label>
|
||||
<div className="relative inline-block w-11 h-5">
|
||||
<input
|
||||
@ -248,7 +250,7 @@ function FormNewObject({ isAdmin }) {
|
||||
htmlFor="switch-component-on"
|
||||
className="text-slate-600 text-sm cursor-pointer"
|
||||
>
|
||||
Active
|
||||
{t('components.formNewObject.active')}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@ -258,14 +260,14 @@ function FormNewObject({ isAdmin }) {
|
||||
type={"submit"}
|
||||
className="text-blue-500 hover:cursor-pointer hover:underline mb-2"
|
||||
>
|
||||
{!verif ? "Confirmer les informations" : "Oui je suis sûr !"}
|
||||
{!verif ? t('components.formNewObject.confirmInfos') : t('components.formNewObject.sureBtn')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="text-red-500 hover:cursor-pointer hover:underline"
|
||||
onClick={handleCancel}
|
||||
>
|
||||
{!verif ? "Supprimer les informations" : "Non je veux changer !"}
|
||||
{!verif ? t('components.formNewObject.deleteInfos') : t('components.formNewObject.changeBtn')}
|
||||
</button>
|
||||
</div>
|
||||
<p className={enregistre ? "text-green-700" : "text-red-700"}>
|
||||
|
||||
@ -1,9 +1,12 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { X, Menu, LogIn, UserPlus, LogOut, User } from "lucide-react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useAuth } from "../AuthContext";
|
||||
import LanguageSwitcher from "./LanguageSwitcher";
|
||||
|
||||
function Header() {
|
||||
const { t } = useTranslation();
|
||||
const { token, user, logout } = useAuth();
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
const [showAdminDropdown, setShowAdminDropdown] = useState(false);
|
||||
@ -37,7 +40,7 @@ function Header() {
|
||||
onClick={() => setIsMenuOpen(false)}
|
||||
className="text-gray-600 hover:text-indigo-600"
|
||||
>
|
||||
Accueil
|
||||
{t('header.home')}
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
@ -46,7 +49,7 @@ function Header() {
|
||||
onClick={() => setIsMenuOpen(false)}
|
||||
className="text-gray-600 hover:text-indigo-600"
|
||||
>
|
||||
À propos
|
||||
{t('header.about')}
|
||||
</Link>
|
||||
</li>
|
||||
{!token ? (
|
||||
@ -58,7 +61,7 @@ function Header() {
|
||||
className="hover:text-indigo-600 flex items-center gap-2"
|
||||
>
|
||||
<LogIn size={20} />
|
||||
Connexion
|
||||
{t('header.login')}
|
||||
</Link>
|
||||
</li>
|
||||
<li className="sm:hidden">
|
||||
@ -68,7 +71,7 @@ function Header() {
|
||||
className="flex items-center gap-2 bg-indigo-600 text-white px-4 py-2 rounded-lg hover:bg-indigo-700"
|
||||
>
|
||||
<UserPlus size={20} />
|
||||
Inscription
|
||||
{t('header.signup')}
|
||||
</Link>
|
||||
</li>
|
||||
</>
|
||||
@ -81,7 +84,7 @@ function Header() {
|
||||
onClick={() => setIsMenuOpen(false)}
|
||||
className="text-gray-600 hover:text-indigo-600"
|
||||
>
|
||||
Visualisation
|
||||
{t('header.visualizer')}
|
||||
</Link>
|
||||
</li>
|
||||
) : (
|
||||
@ -91,7 +94,7 @@ function Header() {
|
||||
onClick={() => setIsMenuOpen(false)}
|
||||
className="text-gray-600 hover:text-indigo-600"
|
||||
>
|
||||
Gestion
|
||||
{t('header.manage')}
|
||||
</Link>
|
||||
</li>
|
||||
)}
|
||||
@ -101,7 +104,7 @@ function Header() {
|
||||
onClick={toggleAdminDropdown}
|
||||
className="flex items-center text-gray-600 hover:text-indigo-600 focus:outline-none"
|
||||
>
|
||||
Admin
|
||||
{t('header.admin')}
|
||||
<svg
|
||||
className="ml-1 h-4 w-4 fill-current"
|
||||
viewBox="0 0 20 20"
|
||||
@ -116,21 +119,21 @@ function Header() {
|
||||
className="block px-4 py-2 text-gray-700 hover:bg-gray-100"
|
||||
onClick={() => {setShowAdminDropdown(false);setIsMenuOpen(false);}}
|
||||
>
|
||||
Dashboard
|
||||
{t('header.dashboard')}
|
||||
</Link>
|
||||
<Link
|
||||
to="/user"
|
||||
className="block px-4 py-2 text-gray-700 hover:bg-gray-100"
|
||||
onClick={() => {setShowAdminDropdown(false);setIsMenuOpen(false);}}
|
||||
>
|
||||
Gestion des Utilisateurs
|
||||
{t('header.manageUsers')}
|
||||
</Link>
|
||||
<Link
|
||||
to="/adminobjet"
|
||||
className="block px-4 py-2 text-gray-700 hover:bg-gray-100"
|
||||
onClick={() => {setShowAdminDropdown(false);setIsMenuOpen(false);}}
|
||||
>
|
||||
Gestion des Objets Connectés
|
||||
{t('header.manageObjects')}
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
@ -144,8 +147,7 @@ function Header() {
|
||||
className="flex items-center gap-2 text-gray-600 hover:text-indigo-600"
|
||||
>
|
||||
<User size={20} />
|
||||
<span>Profil</span>
|
||||
<span></span>
|
||||
<span>{t('header.profile')}</span>
|
||||
</Link>
|
||||
</li>
|
||||
<li className="sm:hidden">
|
||||
@ -157,7 +159,7 @@ function Header() {
|
||||
className="flex items-center gap-2 text-gray-600 hover:text-red-600"
|
||||
>
|
||||
<LogOut size={20} />
|
||||
<span>Déconnexion</span>
|
||||
<span>{t('header.logout')}</span>
|
||||
</button>
|
||||
</li>
|
||||
</>
|
||||
@ -166,14 +168,15 @@ function Header() {
|
||||
</ul>
|
||||
</nav>
|
||||
{!token ? (
|
||||
<div className="hidden sm:flex gap-4 ">
|
||||
<div className="hidden sm:flex gap-4 items-center">
|
||||
<LanguageSwitcher />
|
||||
<Link
|
||||
to="/login"
|
||||
onClick={() => setIsMenuOpen(false)}
|
||||
className="hover:text-indigo-600 flex items-center gap-2"
|
||||
>
|
||||
<LogIn size={20} />
|
||||
Connexion
|
||||
{t('header.login')}
|
||||
</Link>
|
||||
<Link
|
||||
to="/signup"
|
||||
@ -181,25 +184,25 @@ function Header() {
|
||||
className="flex items-center gap-2 bg-indigo-600 text-white px-4 py-2 rounded-lg hover:bg-indigo-700"
|
||||
>
|
||||
<UserPlus size={20} />
|
||||
Inscription
|
||||
{t('header.signup')}
|
||||
</Link>
|
||||
</div>
|
||||
) : (
|
||||
<div className="hidden sm:flex gap-4">
|
||||
<div className="hidden sm:flex items-center gap-4">
|
||||
<LanguageSwitcher />
|
||||
<Link
|
||||
to="/profil"
|
||||
onClick={() => setIsMenuOpen(false)}
|
||||
className="flex items-center gap-2 text-gray-600 hover:text-indigo-600"
|
||||
>
|
||||
<User size={20} />
|
||||
<span></span>
|
||||
</Link>
|
||||
<button
|
||||
onClick={logout}
|
||||
className="flex items-center gap-2 text-gray-600 hover:text-red-600"
|
||||
>
|
||||
<LogOut size={20} />
|
||||
<span>Déconnexion</span>
|
||||
<span>{t('header.logout')}</span>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
import React from "react";
|
||||
import { Info } from "lucide-react";
|
||||
import { useAuth } from "../AuthContext";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
function InfoObject({ object,defafficherModif }) {
|
||||
const { t } = useTranslation();
|
||||
const {user} = useAuth();
|
||||
return (
|
||||
<div key={object.id} className="bg-white p-6 rounded-xl min-w-5xl">
|
||||
@ -10,37 +12,37 @@ function InfoObject({ object,defafficherModif }) {
|
||||
<div className="w-12 h-12 bg-indigo-100 rounded-lg flex items-center justify-center mb-1">
|
||||
<Info className="text-indigo-600" size={24} />
|
||||
</div>
|
||||
<h1 className="text-black text-2xl font-bold mb-1 ">Informations</h1>
|
||||
<h1 className="text-black text-2xl font-bold mb-1 ">{t('components.infoObject.title')}</h1>
|
||||
</div>
|
||||
<div className="mb-5">
|
||||
<p className="text-black-900 font-bold">Description :</p>
|
||||
<p className="text-black-900 font-bold">{t('components.infoObject.description')}</p>
|
||||
<p className="text-gray-600 capitalize">{object.description}</p>
|
||||
</div>
|
||||
|
||||
<div className="mb-5">
|
||||
<p className="text-black-900 font-bold">Type :</p>
|
||||
<p className="text-black-900 font-bold">{t('components.infoObject.type')}</p>
|
||||
<p className="text-gray-600 capitalize">{object.type}</p>
|
||||
</div>
|
||||
|
||||
<div className="mb-5">
|
||||
<p className="text-black-900 font-bold">Localisation :</p>
|
||||
<p className="text-black-900 font-bold">{t('components.infoObject.location')}</p>
|
||||
<p className="text-gray-600">{object.location}</p>
|
||||
</div>
|
||||
|
||||
<div className="mb-5">
|
||||
<p className="text-black-900 font-bold">Status :</p>
|
||||
<p className="text-black-900 font-bold">{t('components.infoObject.status')}</p>
|
||||
<p className="text-gray-600 capitalize">{object.status}</p>
|
||||
</div>
|
||||
|
||||
<div className="mb-5">
|
||||
<p className="text-black-900 font-bold">
|
||||
Derniere mise à jour :
|
||||
{t('components.infoObject.lastUpdate')}
|
||||
</p>
|
||||
<p className="text-gray-600">{object.last_update}</p>
|
||||
</div>
|
||||
{user?.role!=="user"&&(
|
||||
<div className="flex items-center gap-4 mb-1">
|
||||
<a className="text-blue-500 hover:cursor-pointer" onClick={(()=>defafficherModif(true))}>Modifier ces infos</a>
|
||||
<a className="text-blue-500 hover:cursor-pointer" onClick={(()=>defafficherModif(true))}>{t('components.infoObject.modify')}</a>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
27
Front-end/src/components/LanguageSwitcher.jsx
Normal file
27
Front-end/src/components/LanguageSwitcher.jsx
Normal file
@ -0,0 +1,27 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export default function LanguageSwitcher() {
|
||||
const { i18n } = useTranslation();
|
||||
|
||||
const changeLanguage = (lng) => {
|
||||
i18n.changeLanguage(lng);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex space-x-2">
|
||||
<button
|
||||
onClick={() => changeLanguage('fr')}
|
||||
className={`px-2 py-1 text-sm rounded transition-colors ${i18n.resolvedLanguage === 'fr' ? 'bg-blue-600 text-white font-bold' : 'bg-gray-200 text-gray-700 hover:bg-gray-300'}`}
|
||||
>
|
||||
FR
|
||||
</button>
|
||||
<button
|
||||
onClick={() => changeLanguage('en')}
|
||||
className={`px-2 py-1 text-sm rounded transition-colors ${i18n.resolvedLanguage === 'en' ? 'bg-blue-600 text-white font-bold' : 'bg-gray-200 text-gray-700 hover:bg-gray-300'}`}
|
||||
>
|
||||
EN
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -14,8 +14,10 @@ import {
|
||||
import { Wind } from "lucide-react";
|
||||
import axios from "axios";
|
||||
import { API_BASE_URL } from "../config";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
function MeteoGraph({ object, categorie, Logo,reference}) {
|
||||
const { t } = useTranslation();
|
||||
const [rawData, setRawData] = useState([]);
|
||||
const identifiant = object.id;
|
||||
useEffect(() => {
|
||||
@ -51,15 +53,15 @@ function MeteoGraph({ object, categorie, Logo,reference}) {
|
||||
</div>
|
||||
{categorie === "temperature" ? (
|
||||
<h1 className="text-black text-2xl font-bold mb-1 ">
|
||||
Historique de la température
|
||||
{t('components.meteoGraph.historyTemp')}
|
||||
</h1>
|
||||
) : categorie === "humidity" ? (
|
||||
<h1 className="text-black text-2xl font-bold mb-1 ">
|
||||
Historique de l'humidité
|
||||
{t('components.meteoGraph.historyHum')}
|
||||
</h1>
|
||||
) : (
|
||||
<h1 className="text-black text-2xl font-bold mb-1 ">
|
||||
Historique de la pression
|
||||
{t('components.meteoGraph.historyPres')}
|
||||
</h1>
|
||||
)}
|
||||
</div>
|
||||
@ -86,7 +88,7 @@ function MeteoGraph({ object, categorie, Logo,reference}) {
|
||||
stroke="#8884d8"
|
||||
activeDot={{ r: 8 }}
|
||||
/>
|
||||
<ReferenceLine y={getAvg()} label="Moyenne" stroke="red" />
|
||||
<ReferenceLine y={getAvg()} label={t('components.meteoGraph.average')} stroke="red" />
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
|
||||
@ -5,8 +5,10 @@ import { API_BASE_URL } from "../config";
|
||||
import BoutonGraphique from "./BoutonGraphique";
|
||||
import AlertInactive from "./AlertInactive";
|
||||
import ParticularMeteo from "./ParticularMeteo";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
function MeteoInfos({ object, graphStates, setGraphStates, graphRefs }) {
|
||||
const { t } = useTranslation();
|
||||
const [rawData, setRawData] = useState([]);
|
||||
const [AffAlert, setAffAlert] = useState(false);
|
||||
const [AffRegles, setAffRegles] = useState(false);
|
||||
@ -25,15 +27,14 @@ function MeteoInfos({ object, graphStates, setGraphStates, graphRefs }) {
|
||||
return (
|
||||
<div key={object.id} className="bg-white p-6 rounded-xl min-w-5xl">
|
||||
{AffAlert && object.status === "active" && (
|
||||
<AlertInactive affAlert={AffAlert} setAffAlert={setAffAlert} message={`Cet objet peut être inactif dû à son manque de données.
|
||||
Vous pouvez le rendre inactif en changeant son statut.`}
|
||||
<AlertInactive affAlert={AffAlert} setAffAlert={setAffAlert} message={t('components.meteoInfos.alertInactive')}
|
||||
/>
|
||||
)}
|
||||
<div className="flex align-items gap-6">
|
||||
<div className="w-12 h-12 bg-indigo-100 rounded-lg flex items-center justify-center mb-4">
|
||||
<Sun className="text-indigo-600" size={24} />
|
||||
</div>
|
||||
<h1 className="text-black text-2xl font-bold mb-1 ">Météo actuelle</h1>
|
||||
<h1 className="text-black text-2xl font-bold mb-1 ">{t('components.meteoInfos.currentWeather')}</h1>
|
||||
</div>
|
||||
{lastData ? (
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
@ -41,7 +42,7 @@ function MeteoInfos({ object, graphStates, setGraphStates, graphRefs }) {
|
||||
type="temperature"
|
||||
data={lastData}
|
||||
Icon={Thermometer}
|
||||
texte1="Température"
|
||||
texte1={t('components.meteoInfos.temperature')}
|
||||
texte2="°C"
|
||||
graphStates={graphStates}
|
||||
setGraphStates={setGraphStates}
|
||||
@ -52,7 +53,7 @@ function MeteoInfos({ object, graphStates, setGraphStates, graphRefs }) {
|
||||
type="pressure"
|
||||
data={lastData}
|
||||
Icon={CircleGauge}
|
||||
texte1="Pression"
|
||||
texte1={t('components.meteoInfos.pressure')}
|
||||
texte2="hPa"
|
||||
graphStates={graphStates}
|
||||
setGraphStates={setGraphStates}
|
||||
@ -63,18 +64,18 @@ function MeteoInfos({ object, graphStates, setGraphStates, graphRefs }) {
|
||||
type="humidity"
|
||||
data={lastData}
|
||||
Icon={Droplet}
|
||||
texte1="Humidité"
|
||||
texte1={t('components.meteoInfos.humidity')}
|
||||
texte2="%"
|
||||
graphStates={graphStates}
|
||||
setGraphStates={setGraphStates}
|
||||
graphRefs={graphRefs}
|
||||
/>
|
||||
<h1 className="text-gray-500 text-sm">
|
||||
Dernier enregistrement : {lastData.timestamp}
|
||||
{t('components.meteoInfos.lastRecord')} {lastData.timestamp}
|
||||
</h1>
|
||||
</div>
|
||||
) : (
|
||||
<p>Chargement des données...</p>
|
||||
<p>{t('components.meteoInfos.loading')}</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -3,8 +3,10 @@ import { Info } from "lucide-react";
|
||||
import axios from "axios";
|
||||
import { API_BASE_URL } from "../config";
|
||||
import {useAuth} from "../AuthContext";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
function ModifObject({ object, defafficherModif }) {
|
||||
const { t } = useTranslation();
|
||||
const {user}=useAuth();
|
||||
const [description, setDescription] = useState(object.description || "");
|
||||
const [type, setType] = useState(object.type || "");
|
||||
@ -53,7 +55,7 @@ function ModifObject({ object, defafficherModif }) {
|
||||
<Info className="text-indigo-600" size={24} />
|
||||
</div>
|
||||
<h1 className="text-black text-2xl font-bold mb-1">
|
||||
Modifier les infos
|
||||
{t('components.modifObject.title')}
|
||||
</h1>
|
||||
</div>
|
||||
<div className="mb-5">
|
||||
@ -61,7 +63,7 @@ function ModifObject({ object, defafficherModif }) {
|
||||
htmlFor="description"
|
||||
className="block mb-2 text-sm font-medium text-gray-900"
|
||||
>
|
||||
Description
|
||||
{t('components.modifObject.description')}
|
||||
</label>
|
||||
<input
|
||||
id="description"
|
||||
@ -78,7 +80,7 @@ function ModifObject({ object, defafficherModif }) {
|
||||
htmlFor="type"
|
||||
className="block mb-2 text-sm font-medium text-gray-900"
|
||||
>
|
||||
Type :
|
||||
{t('components.modifObject.type')}
|
||||
</label>
|
||||
<input
|
||||
id="type"
|
||||
@ -95,7 +97,7 @@ function ModifObject({ object, defafficherModif }) {
|
||||
htmlFor="location"
|
||||
className="block mb-2 text-sm font-medium text-gray-900"
|
||||
>
|
||||
Localisation :
|
||||
{t('components.modifObject.location')}
|
||||
</label>
|
||||
<input
|
||||
id="location"
|
||||
@ -109,14 +111,14 @@ function ModifObject({ object, defafficherModif }) {
|
||||
|
||||
<div className="mb-5">
|
||||
<label className="block mb-2 text-sm font-medium text-gray-900">
|
||||
Status :
|
||||
{t('components.modifObject.status')}
|
||||
</label>
|
||||
<div className="inline-flex items-center gap-2">
|
||||
<label
|
||||
htmlFor="switch-component-on"
|
||||
className="text-slate-600 text-sm cursor-pointer"
|
||||
>
|
||||
Inactive
|
||||
{t('components.modifObject.inactive')}
|
||||
</label>
|
||||
<div className="relative inline-block w-11 h-5">
|
||||
<input
|
||||
@ -135,7 +137,7 @@ function ModifObject({ object, defafficherModif }) {
|
||||
htmlFor="switch-component-on"
|
||||
className="text-slate-600 text-sm cursor-pointer"
|
||||
>
|
||||
Active
|
||||
{t('components.modifObject.active')}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@ -145,14 +147,14 @@ function ModifObject({ object, defafficherModif }) {
|
||||
type="submit"
|
||||
className="text-blue-500 hover:cursor-pointer hover:underline"
|
||||
>
|
||||
Confirmer les modifications
|
||||
{t('components.modifObject.confirmMods')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="text-red-500 hover:cursor-pointer hover:underline"
|
||||
onClick={handleCancel}
|
||||
>
|
||||
Annuler les modifications
|
||||
{t('components.modifObject.cancelMods')}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@ -5,6 +5,7 @@ import Slider from "@mui/material/Slider";
|
||||
import { API_BASE_URL } from "../config";
|
||||
import axios from "axios";
|
||||
import { useAuth } from "../AuthContext";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const identifiant = new URLSearchParams(window.location.search).get("id");
|
||||
function ParticularMeteo({
|
||||
@ -17,6 +18,7 @@ function ParticularMeteo({
|
||||
setGraphStates,
|
||||
graphRefs,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const {user} = useAuth();
|
||||
const [affRegles, setAffRegles] = useState(false);
|
||||
const [rangeValue, setRangeValue] = useState([0, 0]);
|
||||
@ -133,7 +135,7 @@ function ParticularMeteo({
|
||||
{affRegles && (
|
||||
<div className="p-6">
|
||||
<h1 className="text-red-500 text-l font-semibold">
|
||||
Définissez la valeur seuil pour l'alerte :
|
||||
{t('components.particularMeteo.defineLimit')}
|
||||
</h1>
|
||||
<div className="p-4">
|
||||
<Slider
|
||||
@ -149,14 +151,14 @@ function ParticularMeteo({
|
||||
/>
|
||||
</div>
|
||||
{color=="text-red-600" &&(
|
||||
<p className="text-red-500 text-l mb-2">Attention, la valeur actuelle est hors des bornes que vous avez définit !</p>
|
||||
<p className="text-red-500 text-l mb-2">{t('components.particularMeteo.outOfBounds')}</p>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => defRangeData()}
|
||||
className="text-blue-700 hover:text-white border border-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center me-2 mb-2 dark:border-blue-500 dark:text-blue-500 dark:hover:text-white dark:hover:bg-blue-500 dark:focus:ring-blue-800"
|
||||
>
|
||||
Définir alerte
|
||||
{t('components.particularMeteo.setAlert')}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -2,9 +2,10 @@ import React,{useEffect, useState} from "react";
|
||||
import { User } from "lucide-react";
|
||||
import axios from "axios";
|
||||
import { API_BASE_URL } from "../config";
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
function UserInfosObject({ user}) {
|
||||
const { t } = useTranslation();
|
||||
const [userInfo,setuserInfo]=useState({});
|
||||
useEffect(()=>{
|
||||
console.log(user);
|
||||
@ -27,20 +28,20 @@ function UserInfosObject({ user}) {
|
||||
<div className="w-12 h-12 bg-indigo-100 rounded-lg flex items-center justify-center mb-1">
|
||||
<User className="text-indigo-600" size={24} />
|
||||
</div>
|
||||
<h1 className="text-black text-2xl font-bold mb-1 ">Propriétaire</h1>
|
||||
<h1 className="text-black text-2xl font-bold mb-1 ">{t('components.userInfosObject.title')}</h1>
|
||||
</div>
|
||||
<div className="mb-5">
|
||||
<p className="text-black-900 font-bold">Pseudo :</p>
|
||||
<p className="text-black-900 font-bold">{t('components.userInfosObject.pseudo')}</p>
|
||||
<p className="text-gray-600 capitalize">{userInfo.pseudo}</p>
|
||||
</div>
|
||||
|
||||
<div className="mb-5">
|
||||
<p className="text-black-900 font-bold">Genre :</p>
|
||||
<p className="text-black-900 font-bold">{t('components.userInfosObject.gender')}</p>
|
||||
<p className="text-gray-600 capitalize">{userInfo.gender}</p>
|
||||
</div>
|
||||
|
||||
<div className="mb-5">
|
||||
<p className="text-black-900 font-bold">Nombre de points :</p>
|
||||
<p className="text-black-900 font-bold">{t('components.userInfosObject.points')}</p>
|
||||
<p className="text-gray-600">{userInfo.points}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -4,8 +4,10 @@ import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, Responsi
|
||||
import { Wind } from "lucide-react";
|
||||
import axios from "axios";
|
||||
import { API_BASE_URL } from "../config";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
function WindGraph({ object,reference }) {
|
||||
const { t } = useTranslation();
|
||||
const [rawData, setRawData] = useState([]);
|
||||
const identifiant = object.id;
|
||||
useEffect(() => {
|
||||
@ -18,9 +20,9 @@ function WindGraph({ object,reference }) {
|
||||
const { wind_speed, timestamp,wind_direction } = payload[0].payload;
|
||||
return (
|
||||
<div className="custom-tooltip">
|
||||
<p><strong>Date:</strong> {timestamp}</p>
|
||||
<p><strong>Vitesse du vent:</strong> {wind_speed} km/h</p>
|
||||
<p><strong>Direction du vent:</strong> {wind_direction}</p>
|
||||
<p><strong>{t('components.windGraph.date')}</strong> {timestamp}</p>
|
||||
<p><strong>{t('components.windGraph.windSpeed')}</strong> {wind_speed} km/h</p>
|
||||
<p><strong>{t('components.windGraph.windDirection')}</strong> {wind_direction}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -38,7 +40,7 @@ function WindGraph({ object,reference }) {
|
||||
<div className="w-12 h-12 bg-indigo-100 rounded-lg flex items-center justify-center mb-4">
|
||||
<Wind className="text-indigo-600" size={24} />
|
||||
</div>
|
||||
<h1 className="text-black text-2xl font-bold mb-1 ">Historique du vent</h1>
|
||||
<h1 className="text-black text-2xl font-bold mb-1 ">{t('components.windGraph.title')}</h1>
|
||||
</div>
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<LineChart
|
||||
|
||||
@ -4,8 +4,10 @@ import React, { useEffect, useState } from "react";
|
||||
import axios from "axios";
|
||||
import { API_BASE_URL } from "../config";
|
||||
import BoutonGraphique from "./BoutonGraphique";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
function WindInfo({ object, setGraphStates, graphStates, graphRefs, reference}) {
|
||||
const { t } = useTranslation();
|
||||
const [rawData, setRawData] = useState([]);
|
||||
const identifiant = object.id;
|
||||
useEffect(() => {
|
||||
@ -27,7 +29,7 @@ function WindInfo({ object, setGraphStates, graphStates, graphRefs, reference})
|
||||
<div className="w-12 h-12 bg-indigo-100 rounded-lg flex items-center justify-center mb-1">
|
||||
<Wind className="text-indigo-600" size={24} />
|
||||
</div>
|
||||
<h1 className="text-black text-2xl font-bold">Vent actuel</h1>
|
||||
<h1 className="text-black text-2xl font-bold">{t('components.windInfo.currentWind')}</h1>
|
||||
</div>
|
||||
{lastData ? (
|
||||
<div className="flex flex-col items-center gap-1">
|
||||
@ -46,7 +48,7 @@ function WindInfo({ object, setGraphStates, graphStates, graphRefs, reference})
|
||||
<Wind className="text-indigo-600" size={40} />
|
||||
</div>
|
||||
<div className="flex flex-col items-start">
|
||||
<h1 className="text-indigo-600 text-xl font-bold ">Valeur</h1>
|
||||
<h1 className="text-indigo-600 text-xl font-bold ">{t('components.windInfo.value')}</h1>
|
||||
<h1 className="text-gray-700 text-4xl font-bold">
|
||||
{lastData.wind_speed} <span className="text-lg">Km/h</span>
|
||||
</h1>
|
||||
@ -61,11 +63,11 @@ function WindInfo({ object, setGraphStates, graphStates, graphRefs, reference})
|
||||
</div>
|
||||
</div>
|
||||
<h1 className="text-gray-500 text-sm">
|
||||
Dernier enregistrement : {lastData.timestamp}
|
||||
{t('components.windInfo.lastRecord')} {lastData.timestamp}
|
||||
</h1>
|
||||
</div>
|
||||
) : (
|
||||
<p>Chargement des données...</p>
|
||||
<p>{t('components.windInfo.loading')}</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
24
Front-end/src/i18n.js
Normal file
24
Front-end/src/i18n.js
Normal file
@ -0,0 +1,24 @@
|
||||
import i18n from 'i18next';
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
import LanguageDetector from 'i18next-browser-languagedetector';
|
||||
|
||||
import frTranslation from './locales/fr.json';
|
||||
import enTranslation from './locales/en.json';
|
||||
|
||||
const resources = {
|
||||
fr: { translation: frTranslation },
|
||||
en: { translation: enTranslation }
|
||||
};
|
||||
|
||||
i18n
|
||||
.use(LanguageDetector)
|
||||
.use(initReactI18next)
|
||||
.init({
|
||||
resources,
|
||||
fallbackLng: 'fr',
|
||||
interpolation: {
|
||||
escapeValue: false
|
||||
}
|
||||
});
|
||||
|
||||
export default i18n;
|
||||
363
Front-end/src/locales/en.json
Normal file
363
Front-end/src/locales/en.json
Normal file
@ -0,0 +1,363 @@
|
||||
{
|
||||
"header": {
|
||||
"home": "Home",
|
||||
"about": "About",
|
||||
"login": "Log in",
|
||||
"signup": "Sign up",
|
||||
"visualizer": "Visualizer",
|
||||
"manage": "Manage",
|
||||
"dashboard": "Dashboard",
|
||||
"manageUsers": "Manage Users",
|
||||
"manageObjects": "Manage Connected Objects",
|
||||
"profile": "Profile",
|
||||
"logout": "Log out",
|
||||
"admin": "Admin"
|
||||
},
|
||||
"home": {
|
||||
"title": "Real-time weather",
|
||||
"subtitle": "Accurate and personalized forecasts to help you plan your day with peace of mind.",
|
||||
"searchPlaceholder": "Search for a city ...",
|
||||
"noCityFound": "No city found.",
|
||||
"partlyCloudy": "Partly cloudy",
|
||||
"feelsLike": "Feels like",
|
||||
"humidity": "Humidity",
|
||||
"wind": "Wind",
|
||||
"rain": "Precipitation",
|
||||
"sunrise": "Sunrise",
|
||||
"sunset": "Sunset",
|
||||
"hourlyForecast": "Hourly Forecast",
|
||||
"dailyForecast": "5-day Forecast",
|
||||
"weatherServices": "Weather Services",
|
||||
"viewObjectsTitle": "Visualization of connected weather devices",
|
||||
"viewObjectsDesc": "Browse all connected devices and their data throughout France",
|
||||
"viewObjectsBtn": "View objects",
|
||||
"signupTitle": "Sign up!",
|
||||
"signupDesc": "Having an account on our site allows you to consult all connected weather objects throughout France.",
|
||||
"signupBtn": "Sign up",
|
||||
"loginTitle": "Log in!",
|
||||
"loginDesc": "Happy to see you again! Log back into your account just as you left it!",
|
||||
"loginBtn": "Log in",
|
||||
"consultObjectsTitle": "Consult connected weather devices",
|
||||
"consultObjectsDesc": "Access real-time data from connected weather devices, modify their parameters and check the history of measurements.",
|
||||
"consultObjectsBtn": "Explore objects",
|
||||
"addObjTitle": "Add a new connected device",
|
||||
"addObjDesc": "Easily integrate a new connected device by entering its information and configuring its parameters for optimal management.",
|
||||
"addObjBtn": "Add a device",
|
||||
"adminDashTitle": "Access the Administration dashboard",
|
||||
"adminDashDesc": "You will be able to manage the site's users as well as the connected devices",
|
||||
"adminDashBtn": "Administration Dashboard",
|
||||
"manageObjTitle": "Management of connected devices",
|
||||
"manageObjDesc": "This module allows you to easily and effectively manage sensors and connected stations in France.",
|
||||
"manageObjBtn": "Device Management",
|
||||
"footerRights": "© 2025 VigiMétéo. All rights reserved."
|
||||
},
|
||||
"auth": {
|
||||
"login": {
|
||||
"title": "Log in",
|
||||
"emailLabel": "Email:",
|
||||
"passwordLabel": "Password:",
|
||||
"submitButton": "Log in",
|
||||
"noAccount": "Don't have an account?",
|
||||
"signupLink": "Sign up here",
|
||||
"loading": "Logging in...",
|
||||
"success": "Login successful! Redirecting...",
|
||||
"missingToken": "Authentication failed: missing token in response",
|
||||
"incorrectAuth": "Incorrect email or password",
|
||||
"invalidData": "Invalid form data",
|
||||
"serverError": "Server error. Please try again later.",
|
||||
"networkError": "Cannot reach the server. Check your internet connection.",
|
||||
"genericError": "An error occurred. Please try again."
|
||||
},
|
||||
"signup": {
|
||||
"title": "Sign up",
|
||||
"firstNameLabel": "First Name:",
|
||||
"lastNameLabel": "Last Name:",
|
||||
"pseudoLabel": "Username:",
|
||||
"genderLabel": "Gender:",
|
||||
"genderMale": "Male",
|
||||
"genderFemale": "Female",
|
||||
"emailLabel": "Email:",
|
||||
"passwordLabel": "Password:",
|
||||
"confirmPasswordLabel": "Confirm Password:",
|
||||
"submitButton": "Sign up",
|
||||
"hasAccount": "Already have an account?",
|
||||
"loginLink": "Log in here",
|
||||
"passNoMatch": "Passwords do not match!",
|
||||
"success": "Sign up successful!",
|
||||
"error": "Error during sign up"
|
||||
}
|
||||
},
|
||||
"profile": {
|
||||
"title": "My Profile",
|
||||
"personalInfo": "Personal Information",
|
||||
"loyaltyPoints": "Loyalty points:",
|
||||
"firstName": "First Name:",
|
||||
"lastName": "Last Name:",
|
||||
"pseudo": "Username:",
|
||||
"email": "Email:",
|
||||
"save": "Save",
|
||||
"firstNameL": "First Name",
|
||||
"lastNameL": "Last Name",
|
||||
"pseudoL": "Username",
|
||||
"emailL": "Email",
|
||||
"changePasswordTitle": "Change Password",
|
||||
"currentPassword": "Current password:",
|
||||
"newPassword": "New password:",
|
||||
"confirmNewPassword": "Confirm new password:",
|
||||
"errorMismatch": "The new passwords do not match!",
|
||||
"successPass": "Password successfully changed!",
|
||||
"successUpdate": "Profile successfully updated!",
|
||||
"errorGeneric": "An error occurred"
|
||||
},
|
||||
"about": {
|
||||
"missionTitle": "Our Mission",
|
||||
"missionDesc": "Our mission is to provide a complete and innovative solution for climate and environmental monitoring of the French territory. By combining high-quality precise weather forecasts with efficient IoT device management, we aim to offer a centralized platform to monitor local weather conditions in real time, while facilitating the analysis of data collected by IoT devices deployed across the country.",
|
||||
"whoTitle": "Who are we?",
|
||||
"whoDesc": "We are a team of tech, innovation, and environmental enthusiasts. We firmly believe that combining real-time weather data and the Internet of Things (IoT) can have a major impact on territorial management. Whether for local authorities, businesses, or public actors, our platform offers the tools needed for proactive and responsive environmental management.",
|
||||
"visionTitle": "Our Vision",
|
||||
"visionDesc": "In a world where climate conditions change rapidly, it is essential to anticipate and react effectively to meteorological phenomena. Thanks to our connected devices and intuitive interface, we allow users to track conditions in real time and act accordingly. From climate risk management to urban planning, our platform helps decision-makers make informed choices based on reliable, local data.",
|
||||
"objectivesTitle": "The Objectives of Our Platform",
|
||||
"obj1Desc": "Thanks to our connected devices, we collect local weather data, allowing continuous monitoring of climate conditions across the French territory.",
|
||||
"obj1Title": "Real-time Monitoring",
|
||||
"obj2Desc": "Using the best weather forecasting technologies, we provide you with accurate forecasts, whether it's temperature, wind speed, or air quality.",
|
||||
"obj2Title": "Reliable Prediction",
|
||||
"obj3Desc": "We allow users to easily manage their connected devices (weather stations, sensors, etc.) through a simple interface, while providing real-time tracking of their status and data.",
|
||||
"obj3Title": "IoT Device Management",
|
||||
"obj4Desc": "Our platform sends you instant alerts regarding extreme weather phenomena, allowing you to make quick and appropriate decisions.",
|
||||
"obj4Title": "Rapid Response to Climate Alerts"
|
||||
},
|
||||
"admin": {
|
||||
"sidebar": {
|
||||
"panel": "Admin Panel",
|
||||
"dashboard": "Dashboard",
|
||||
"users": "Users",
|
||||
"objects": "Device Management"
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Dashboard",
|
||||
"manageWidgetsEnd": "Done Managing",
|
||||
"manageWidgets": "Manage Widgets",
|
||||
"summary": "Dashboard Summary",
|
||||
"totalUsers": "Total Users",
|
||||
"lastLog": "Last Log",
|
||||
"noLog": "No log available",
|
||||
"usersList": "User Management",
|
||||
"username": "Username",
|
||||
"email": "Email",
|
||||
"access": "Access",
|
||||
"noUser": "No users available",
|
||||
"seeMore": "See more",
|
||||
"objectsManagement": "Connected Device Management",
|
||||
"consultObjects": "Consult connected devices",
|
||||
"addObject": "Add a new device",
|
||||
"objectsList": "List of Devices and Tools/Services",
|
||||
"requestDelete": "Device delete request",
|
||||
"generateReports": "Generate usage reports:",
|
||||
"requestObjects": "Request devices",
|
||||
"reportsStats": "Reports and Statistics",
|
||||
"exportCsv": "Export to CSV",
|
||||
"energyConsumption": "Total energy consumption",
|
||||
"energyConsumptionDesc": "1372 kWh accumulated (estimated)",
|
||||
"connectionRate": "User connection rate",
|
||||
"connectionRateDesc": "87% of users active this month",
|
||||
"mostUsedServices": "Most used services",
|
||||
"addWidget": "Add a widget",
|
||||
"chooseWidget": "Choose a widget type",
|
||||
"widgetSummary": "Dashboard Summary",
|
||||
"widgetUsers": "User Management",
|
||||
"widgetObjects": "Connected Device Management",
|
||||
"widgetObjectsList": "List of Devices & Tools",
|
||||
"widgetReports": "Reports and Statistics",
|
||||
"widgetDelete": "Device delete request",
|
||||
"cancel": "Cancel"
|
||||
},
|
||||
"user": {
|
||||
"title": "User Management",
|
||||
"subtitle": "Add a user from this form",
|
||||
"firstName": "First Name",
|
||||
"lastName": "Last Name",
|
||||
"pseudo": "Username",
|
||||
"email": "Email",
|
||||
"password": "Password",
|
||||
"genderMale": "Male",
|
||||
"genderFemale": "Female",
|
||||
"genderOther": "Other",
|
||||
"addUserBtn": "Add User",
|
||||
"manageTitle": "Manage users from this panel.",
|
||||
"gender": "Gender",
|
||||
"accessLevel": "Access Level",
|
||||
"points": "Points",
|
||||
"actions": "Actions",
|
||||
"changeBtn": "Change",
|
||||
"deleteBtn": "Delete",
|
||||
"logsTitle": "Connection history and logs",
|
||||
"action": "Action",
|
||||
"timestamp": "Timestamp",
|
||||
"downloadLogs": "Download Logs",
|
||||
"successAdd": "User successfully added!",
|
||||
"errorAdd": "Error adding user!",
|
||||
"confirmDelete": "Are you sure you want to delete user {name}? This action may result in the deletion of associated objects.",
|
||||
"successDelete": "User successfully deleted!",
|
||||
"successLevel": "Access level successfully changed!",
|
||||
"errorLevel": "There was an error changing the access level!",
|
||||
"successPoints": "Points successfully saved!",
|
||||
"errorPoints": "There was an error saving the points!"
|
||||
},
|
||||
"adminObjet": {
|
||||
"title": "Administration of Devices and Tools/Services",
|
||||
"catsTitle": "Category Management",
|
||||
"newCatPlaceholder": "New category",
|
||||
"addBtn": "Add",
|
||||
"deleteBtn": "Delete",
|
||||
"listTitle": "List of Devices and Tools/Services",
|
||||
"sortBy": "-- Sort by --",
|
||||
"sortOwner": "Owner",
|
||||
"sortLocation": "Location",
|
||||
"sortType": "Type",
|
||||
"sortStatus": "Status",
|
||||
"colName": "Name",
|
||||
"colDesc": "Description",
|
||||
"colType": "Type",
|
||||
"colLocation": "Location",
|
||||
"colOwner": "Owner",
|
||||
"colStatus": "Status",
|
||||
"noObjects": "No object or service available.",
|
||||
"requestsTitle": "Device Deletion Requests",
|
||||
"colObjId": "Device ID",
|
||||
"colUserId": "User ID",
|
||||
"colReqDate": "Request Date",
|
||||
"acceptBtn": "Accept",
|
||||
"rejectBtn": "Reject",
|
||||
"noRequests": "No deletion requests found.",
|
||||
"confirmCatDelete": "Are you sure you want to delete category {cat}? This action might impact devices.",
|
||||
"successRecord": "Your device has been recorded successfully!",
|
||||
"successReqDelete": "The request has been successfully deleted!",
|
||||
"successObjDelete": "Your device has been successfully deleted!",
|
||||
"errorReqDelete": "There was an error deleting the request!",
|
||||
"errorObjDelete": "There was an error deleting your device!"
|
||||
}
|
||||
},
|
||||
"gestion": {
|
||||
"title": "Welcome to the module",
|
||||
"moduleName": "Management",
|
||||
"description": "This module allows you to easily and effectively manage connected sensors and stations in France.",
|
||||
"consultTitle": "Consult connected weather devices",
|
||||
"consultDesc": "Access real-time data from connected weather devices, modify their parameters and view the history of measurements.",
|
||||
"exploreBtn": "Explore devices",
|
||||
"addTitle": "Add a new connected device",
|
||||
"addDesc": "Easily integrate a new connected device by providing its information and configuring its parameters for optimal management.",
|
||||
"addBtn": "Add a device",
|
||||
"objectManagement": {
|
||||
"successDeleteReq": "Deletion request sent to administrator.",
|
||||
"errorDeleteReq": "Error, request already sent to administrator.",
|
||||
"titleAdmin": "Management",
|
||||
"titleUser": "Visualization",
|
||||
"titleSuffix": "of",
|
||||
"titleObjects": "Connected Devices.",
|
||||
"searchPlaceholder": "Search by name, category, or keywords...",
|
||||
"filterAll": "All",
|
||||
"filterStation": "Weather Station",
|
||||
"filterSensor": "Sensor",
|
||||
"filterActive": "Active",
|
||||
"filterInactive": "Inactive",
|
||||
"noObjects": "No device found",
|
||||
"moreInfo": "More info",
|
||||
"deleteItem": "Delete device",
|
||||
"seeMore": "See more"
|
||||
},
|
||||
"objet": {
|
||||
"dashboardTitle": "Dashboard - ",
|
||||
"errorFetch": "Error fetching device"
|
||||
},
|
||||
"addObject": {
|
||||
"title": "New device"
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"formNewObject": {
|
||||
"successRecord": "Your device has been successfully registered!",
|
||||
"errorRecord": "There was an error adding your device!",
|
||||
"noCategory": "No category available.",
|
||||
"addTitle": "Add a new device",
|
||||
"enterData": "Enter the data for your new device",
|
||||
"confirmData": "Are you sure about this data?",
|
||||
"name": "Name:",
|
||||
"description": "Description:",
|
||||
"type": "Type:",
|
||||
"selectType": "-- Select a type --",
|
||||
"location": "Location:",
|
||||
"batteryType": "Battery type:",
|
||||
"owner": "Owner:",
|
||||
"status": "Status:",
|
||||
"inactive": "Inactive",
|
||||
"active": "Active",
|
||||
"confirmInfos": "Confirm information",
|
||||
"sureBtn": "Yes, I am sure!",
|
||||
"deleteInfos": "Delete information",
|
||||
"changeBtn": "No, I want to change!"
|
||||
},
|
||||
"infoObject": {
|
||||
"title": "Information",
|
||||
"description": "Description:",
|
||||
"type": "Type:",
|
||||
"location": "Location:",
|
||||
"status": "Status:",
|
||||
"lastUpdate": "Last update:",
|
||||
"modify": "Modify this information"
|
||||
},
|
||||
"modifObject": {
|
||||
"title": "Modify information",
|
||||
"description": "Description:",
|
||||
"type": "Type:",
|
||||
"location": "Location:",
|
||||
"status": "Status:",
|
||||
"inactive": "Inactive",
|
||||
"active": "Active",
|
||||
"confirmMods": "Confirm modifications",
|
||||
"cancelMods": "Cancel modifications"
|
||||
},
|
||||
"particularMeteo": {
|
||||
"defineLimit": "Set the threshold value for the alert:",
|
||||
"outOfBounds": "Warning, the current value is out of the bounds you defined!",
|
||||
"setAlert": "Set alert"
|
||||
},
|
||||
"meteoInfos": {
|
||||
"alertInactive": "This device might be inactive due to lack of data. You can make it inactive by changing its status.",
|
||||
"currentWeather": "Current Weather",
|
||||
"temperature": "Temperature",
|
||||
"pressure": "Pressure",
|
||||
"humidity": "Humidity",
|
||||
"lastRecord": "Last record:",
|
||||
"loading": "Loading data..."
|
||||
},
|
||||
"batterieInfo": {
|
||||
"title": "Battery Status",
|
||||
"batteryType": "Battery type:"
|
||||
},
|
||||
"userInfosObject": {
|
||||
"title": "Owner",
|
||||
"pseudo": "Username:",
|
||||
"gender": "Gender:",
|
||||
"points": "Number of points:"
|
||||
},
|
||||
"windGraph": {
|
||||
"title": "Wind History",
|
||||
"date": "Date:",
|
||||
"windSpeed": "Wind speed:",
|
||||
"windDirection": "Wind direction:"
|
||||
},
|
||||
"meteoGraph": {
|
||||
"historyTemp": "Temperature History",
|
||||
"historyHum": "Humidity History",
|
||||
"historyPres": "Pressure History",
|
||||
"average": "Average"
|
||||
},
|
||||
"windInfo": {
|
||||
"currentWind": "Current Wind",
|
||||
"value": "Value",
|
||||
"lastRecord": "Last record:",
|
||||
"loading": "Loading data..."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
363
Front-end/src/locales/fr.json
Normal file
363
Front-end/src/locales/fr.json
Normal file
@ -0,0 +1,363 @@
|
||||
{
|
||||
"header": {
|
||||
"home": "Accueil",
|
||||
"about": "À propos",
|
||||
"login": "Connexion",
|
||||
"signup": "Inscription",
|
||||
"visualizer": "Visualisation",
|
||||
"manage": "Gestion",
|
||||
"dashboard": "Dashboard",
|
||||
"manageUsers": "Gestion des Utilisateurs",
|
||||
"manageObjects": "Gestion des Objets Connectés",
|
||||
"profile": "Profil",
|
||||
"logout": "Déconnexion",
|
||||
"admin": "Admin"
|
||||
},
|
||||
"home": {
|
||||
"title": "La météo en temps réel",
|
||||
"subtitle": "Prévisions précises et personnalisées pour vous aider à planifier votre journée en toute sérénité.",
|
||||
"searchPlaceholder": "Rechercher une ville ...",
|
||||
"noCityFound": "Aucune ville trouvée.",
|
||||
"partlyCloudy": "Partiellement nuageux",
|
||||
"feelsLike": "Ressenti",
|
||||
"humidity": "Humidité",
|
||||
"wind": "Vent",
|
||||
"rain": "Précipitations",
|
||||
"sunrise": "Lever du soleil",
|
||||
"sunset": "Coucher du soleil",
|
||||
"hourlyForecast": "Prévisions horaires",
|
||||
"dailyForecast": "Prévisions 5 jours",
|
||||
"weatherServices": "Services météo",
|
||||
"viewObjectsTitle": "Visualisation des objets connectés météorologiques",
|
||||
"viewObjectsDesc": "Consultez l'ensemble des objets connectés ainsi que leurs données dans l'ensemble de la France",
|
||||
"viewObjectsBtn": "Voir les objets",
|
||||
"signupTitle": "Inscrivez-vous !",
|
||||
"signupDesc": "Avoir un compte sur notre site permet de consulter l'intégralité des objets connectés météorologiques dans l'ensemble de la France",
|
||||
"signupBtn": "S'inscrire",
|
||||
"loginTitle": "Connectez-vous !",
|
||||
"loginDesc": "Heureux de vous retrouver ! Retrouvez votre compte tel que vous l'avez laissé !",
|
||||
"loginBtn": "Se connecter",
|
||||
"consultObjectsTitle": "Consulter les objets connectés météorologiques",
|
||||
"consultObjectsDesc": "Accédez aux données en temps réel des objets connectés météorologiques, modifiez leurs paramètres et consultez l'historique des mesures.",
|
||||
"consultObjectsBtn": "Explorer les objets",
|
||||
"addObjTitle": "Ajouter un nouvel objet connecté",
|
||||
"addObjDesc": "Intégrez facilement un nouvel objet connecté en renseignant ses informations et en configurant ses paramètres pour une gestion optimale.",
|
||||
"addObjBtn": "Ajouter un objet",
|
||||
"adminDashTitle": "Accéder au tableau d'Administration",
|
||||
"adminDashDesc": "Vous pourrez gérer les utilisateurs du site mais aussi les objets connectés",
|
||||
"adminDashBtn": "Tableau d'Administration",
|
||||
"manageObjTitle": "Gestion des objets connectés",
|
||||
"manageObjDesc": "Ce module vous permet de gérer les capteurs et stations connectés de France de manière simple et efficace.",
|
||||
"manageObjBtn": "Gestion des objets",
|
||||
"footerRights": "© 2025 VigiMétéo. Tous droits réservés."
|
||||
},
|
||||
"auth": {
|
||||
"login": {
|
||||
"title": "Connexion",
|
||||
"emailLabel": "Email:",
|
||||
"passwordLabel": "Mot de passe:",
|
||||
"submitButton": "Se connecter",
|
||||
"noAccount": "Vous n'avez pas de compte ?",
|
||||
"signupLink": "Inscrivez-vous ici",
|
||||
"loading": "Connexion en cours...",
|
||||
"success": "Connexion réussie! Redirection...",
|
||||
"missingToken": "Authentification échouée : token manquant dans la réponse",
|
||||
"incorrectAuth": "Email ou mot de passe incorrect",
|
||||
"invalidData": "Données de formulaire invalides",
|
||||
"serverError": "Erreur serveur. Veuillez réessayer plus tard.",
|
||||
"networkError": "Impossible de joindre le serveur. Vérifiez votre connexion internet.",
|
||||
"genericError": "Une erreur s'est produite. Veuillez réessayer."
|
||||
},
|
||||
"signup": {
|
||||
"title": "Inscription",
|
||||
"firstNameLabel": "Prénom:",
|
||||
"lastNameLabel": "Nom:",
|
||||
"pseudoLabel": "Pseudo:",
|
||||
"genderLabel": "Sexe:",
|
||||
"genderMale": "Homme",
|
||||
"genderFemale": "Femme",
|
||||
"emailLabel": "Email:",
|
||||
"passwordLabel": "Mot de passe:",
|
||||
"confirmPasswordLabel": "Confirmer le mot de passe:",
|
||||
"submitButton": "S'inscrire",
|
||||
"hasAccount": "Vous avez déjà un compte ?",
|
||||
"loginLink": "Connectez-vous ici",
|
||||
"passNoMatch": "Les mots de passe ne correspondent pas !",
|
||||
"success": "Inscription réussie !",
|
||||
"error": "Erreur lors de l'inscription"
|
||||
}
|
||||
},
|
||||
"profile": {
|
||||
"title": "Mon Profil",
|
||||
"personalInfo": "Informations Personnelles",
|
||||
"loyaltyPoints": "Points de fidélité:",
|
||||
"firstName": "Prénom:",
|
||||
"lastName": "Nom:",
|
||||
"pseudo": "Pseudo:",
|
||||
"email": "Email:",
|
||||
"save": "Sauvegarder",
|
||||
"firstNameL": "Prénom",
|
||||
"lastNameL": "Nom",
|
||||
"pseudoL": "Pseudo",
|
||||
"emailL": "Email",
|
||||
"changePasswordTitle": "Modifier le mot de passe",
|
||||
"currentPassword": "Mot de passe actuel:",
|
||||
"newPassword": "Nouveau mot de passe:",
|
||||
"confirmNewPassword": "Confirmer le nouveau mot de passe:",
|
||||
"errorMismatch": "Les nouveaux mots de passe ne correspondent pas !",
|
||||
"successPass": "Mot de passe modifié avec succès !",
|
||||
"successUpdate": "Profil mis à jour avec succès !",
|
||||
"errorGeneric": "Une erreur est survenue"
|
||||
},
|
||||
"about": {
|
||||
"missionTitle": "Notre mission",
|
||||
"missionDesc": "Notre mission est de fournir une solution complète et innovante pour la surveillance climatique et environnementale du territoire français. En combinant des prévisions météorologiques de haute qualité avec une gestion efficace des objets connectés, nous visons à offrir une plateforme centralisée permettant de surveiller en temps réel les conditions météorologiques locales, tout en facilitant l'analyse des données collectées par des objets connectés déployés à travers le pays.",
|
||||
"whoTitle": "Qui sommes-nous ?",
|
||||
"whoDesc": "Nous sommes une équipe de passionnés de technologie, d’innovation et d’environnement. Nous croyons fermement que la combinaison de la donnée météorologique en temps réel et de l’Internet des Objets (IoT) peut avoir un impact majeur sur la gestion des territoires. Que ce soit pour les collectivités locales, les entreprises ou les acteurs publics, notre plateforme offre les outils nécessaires pour une gestion proactive et réactive de l’environnement.",
|
||||
"visionTitle": "Notre Vision",
|
||||
"visionDesc": "Dans un monde où les conditions climatiques évoluent rapidement, il est essentiel de pouvoir anticiper et réagir efficacement face aux phénomènes météorologiques. Grâce à nos objets connectés et à notre interface intuitive, nous permettons aux utilisateurs de suivre les conditions en temps réel et d’agir en conséquence. De la gestion des risques climatiques à la planification urbaine, notre plateforme aide les décideurs à prendre des décisions éclairées basées sur des données fiables et locales.",
|
||||
"objectivesTitle": "Les Objectifs de Notre Plateforme",
|
||||
"obj1Desc": "Grâce à nos objets connectés, nous collectons des données météorologiques locales, permettant une surveillance continue des conditions climatiques sur tout le territoire français.",
|
||||
"obj1Title": "Surveillance en temps réel",
|
||||
"obj2Desc": "En utilisant les meilleures technologies de prévision météorologique, nous vous fournissons des prévisions précises, qu’il s’agisse de la température, de la vitesse du vent ou de la qualité de l’air.",
|
||||
"obj2Title": "Prédiction fiable",
|
||||
"obj3Desc": "Nous permettons aux utilisateurs de gérer facilement leurs objets connectés (stations météo, capteurs, etc.) à travers une interface simple, tout en offrant un suivi en temps réel de leur statut et de leurs données.",
|
||||
"obj3Title": "Gestion des objets connectés",
|
||||
"obj4Desc": "Notre plateforme vous envoie des alertes instantanées concernant les phénomènes météorologiques extrêmes, vous permettant de prendre des décisions rapides et adaptées.",
|
||||
"obj4Title": "Réponse rapide aux alertes climatiques"
|
||||
},
|
||||
"admin": {
|
||||
"sidebar": {
|
||||
"panel": "Admin Panel",
|
||||
"dashboard": "Tableau de bord",
|
||||
"users": "Utilisateurs",
|
||||
"objects": "Gestion des objets"
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Dashboard",
|
||||
"manageWidgetsEnd": "Terminer la gestion",
|
||||
"manageWidgets": "Gérer les widgets",
|
||||
"summary": "Résumé du tableau de bord",
|
||||
"totalUsers": "Total Utilisateur",
|
||||
"lastLog": "Dernier Log",
|
||||
"noLog": "Aucun log",
|
||||
"usersList": "Gestion des Utilisateurs",
|
||||
"username": "Username",
|
||||
"email": "Email",
|
||||
"access": "Access",
|
||||
"noUser": "Aucun utilisateur disponible",
|
||||
"seeMore": "Voir plus",
|
||||
"objectsManagement": "Gestion des Objets Connectés",
|
||||
"consultObjects": "Consulter les objets connectés",
|
||||
"addObject": "Ajouter un nouvel objet",
|
||||
"objectsList": "Liste des Objets et Outils/Services",
|
||||
"requestDelete": "Requête suppression objets",
|
||||
"generateReports": "Générer des rapports d'utilisation :",
|
||||
"requestObjects": "Requête objets",
|
||||
"reportsStats": "Rapports et Statistiques",
|
||||
"exportCsv": "Exporter en CSV",
|
||||
"energyConsumption": "Consommation énergétique totale",
|
||||
"energyConsumptionDesc": "1372 kWh cumulés (estimation)",
|
||||
"connectionRate": "Taux de connexion des utilisateurs",
|
||||
"connectionRateDesc": "87% des utilisateurs actifs ce mois-ci",
|
||||
"mostUsedServices": "Services les plus utilisés",
|
||||
"addWidget": "Ajouter un widget",
|
||||
"chooseWidget": "Choisir un type de widget",
|
||||
"widgetSummary": "Dashboard Summary",
|
||||
"widgetUsers": "Gestion des Utilisateurs",
|
||||
"widgetObjects": "Gestion des Objets Connectés",
|
||||
"widgetObjectsList": "Liste des Objets et Outils/Services",
|
||||
"widgetReports": "Rapports et Statistiques",
|
||||
"widgetDelete": "Demande de suppression d'objets",
|
||||
"cancel": "Annuler"
|
||||
},
|
||||
"user": {
|
||||
"title": "Gestion des utilisateurs",
|
||||
"subtitle": "Ajoutez un utilisateur à partir de ce formulaire",
|
||||
"firstName": "Prénom",
|
||||
"lastName": "Nom",
|
||||
"pseudo": "Pseudo",
|
||||
"email": "Email",
|
||||
"password": "Mot de passe",
|
||||
"genderMale": "Homme",
|
||||
"genderFemale": "Femme",
|
||||
"genderOther": "Autre",
|
||||
"addUserBtn": "Ajouter utilisateur",
|
||||
"manageTitle": "Gérez les utilisateurs à partir de ce panneau.",
|
||||
"gender": "Genre",
|
||||
"accessLevel": "Niveau d'accès",
|
||||
"points": "Points",
|
||||
"actions": "Actions",
|
||||
"changeBtn": "Changer",
|
||||
"deleteBtn": "Supprimer",
|
||||
"logsTitle": "Historique des connexions et journal des logs",
|
||||
"action": "Action",
|
||||
"timestamp": "Timestamp",
|
||||
"downloadLogs": "Télécharger les logs",
|
||||
"successAdd": "Ajout de l'utilisateur réussi !",
|
||||
"errorAdd": "Erreur lors de l'ajout de l'utilisateur !",
|
||||
"confirmDelete": "Êtes-vous sûr de vouloir supprimer l'utilisateur {name} ? Cette action pourrait entraîner la suppression des objets associés.",
|
||||
"successDelete": "L'utilisateur a bien été supprimé !",
|
||||
"successLevel": "Le changement de niveau a bien été enregistré !",
|
||||
"errorLevel": "Il y a eu une erreur dans le changement de niveau !",
|
||||
"successPoints": "Les points ont bien été enregistrés !",
|
||||
"errorPoints": "Il y a eu une erreur dans l'ajout des points !"
|
||||
},
|
||||
"adminObjet": {
|
||||
"title": "Administration des Objets et Outils/Services",
|
||||
"catsTitle": "Gestion des Catégories",
|
||||
"newCatPlaceholder": "Nouvelle catégorie",
|
||||
"addBtn": "Ajouter",
|
||||
"deleteBtn": "Supprimer",
|
||||
"listTitle": "Liste des Objets et Outils/Services",
|
||||
"sortBy": "-- Trier par --",
|
||||
"sortOwner": "Propriétaire",
|
||||
"sortLocation": "Lieux",
|
||||
"sortType": "Type",
|
||||
"sortStatus": "Status",
|
||||
"colName": "Nom",
|
||||
"colDesc": "Description",
|
||||
"colType": "Type",
|
||||
"colLocation": "Localisation",
|
||||
"colOwner": "Propriétaire",
|
||||
"colStatus": "Status",
|
||||
"noObjects": "Aucun objet ou service disponible.",
|
||||
"requestsTitle": "Demandes de Suppression d'Objets",
|
||||
"colObjId": "Objet ID",
|
||||
"colUserId": "Utilisateur ID",
|
||||
"colReqDate": "Date de Requête",
|
||||
"acceptBtn": "Accepter",
|
||||
"rejectBtn": "Refuser",
|
||||
"noRequests": "Aucune demande de suppression trouvée.",
|
||||
"confirmCatDelete": "Êtes-vous sûr de vouloir supprimer la catégorie {cat} ? Cette action pourrait impacter des objets.",
|
||||
"successRecord": "Votre objet a bien été enregistré !",
|
||||
"successReqDelete": "La demande a bien été supprimée !",
|
||||
"successObjDelete": "Votre objet a bien été supprimé !",
|
||||
"errorReqDelete": "Il y a eu une erreur dans la suppression de la demande !",
|
||||
"errorObjDelete": "Il y a eu une erreur dans la suppression de votre objet !"
|
||||
}
|
||||
},
|
||||
"gestion": {
|
||||
"title": "Bienvenue dans le module",
|
||||
"moduleName": "Gestion",
|
||||
"description": "Ce module vous permet de gérer les capteurs et stations connectés de France de manière simple et efficace.",
|
||||
"consultTitle": "Consulter les objets connectés météorologiques",
|
||||
"consultDesc": "Accédez aux données en temps réel des objets connectés météorologiques, modifiez leurs paramètres et consultez l'historique des mesures.",
|
||||
"exploreBtn": "Explorer les objets",
|
||||
"addTitle": "Ajouter un nouvel objet connecté",
|
||||
"addDesc": "Intégrez facilement un nouvel objet connecté en renseignant ses informations et en configurant ses paramètres pour une gestion optimale.",
|
||||
"addBtn": "Ajouter un objet",
|
||||
"objectManagement": {
|
||||
"successDeleteReq": "Demande de suppression envoyée à l'administrateur.",
|
||||
"errorDeleteReq": "Erreur, demande déjà envoyée à l'administrateur.",
|
||||
"titleAdmin": "Gestion",
|
||||
"titleUser": "Visualisation",
|
||||
"titleSuffix": "des",
|
||||
"titleObjects": "Objets connectés.",
|
||||
"searchPlaceholder": "Chercher par nom, catégorie ou mot clés...",
|
||||
"filterAll": "Tout",
|
||||
"filterStation": "Station météo",
|
||||
"filterSensor": "Capteur",
|
||||
"filterActive": "Actif",
|
||||
"filterInactive": "Inactif",
|
||||
"noObjects": "Aucun objet trouvé",
|
||||
"moreInfo": "Plus d'infos",
|
||||
"deleteItem": "Supprimer l'objet",
|
||||
"seeMore": "Voir plus"
|
||||
},
|
||||
"objet": {
|
||||
"dashboardTitle": "Tableau de bord - ",
|
||||
"errorFetch": "Erreur de récupération de l'objet"
|
||||
},
|
||||
"addObject": {
|
||||
"title": "Nouvel objet"
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"formNewObject": {
|
||||
"successRecord": "Votre objet a bien été enregistré !",
|
||||
"errorRecord": "Il y a eu une erreur dans l'ajout de votre objet !",
|
||||
"noCategory": "Aucune catégorie disponible.",
|
||||
"addTitle": "Ajouter un nouvel objet",
|
||||
"enterData": "Entrez les données de votre nouvel objet",
|
||||
"confirmData": "Êtes-vous sûr de ces données ?",
|
||||
"name": "Nom :",
|
||||
"description": "Description :",
|
||||
"type": "Type :",
|
||||
"selectType": "-- Sélectionner un type --",
|
||||
"location": "Localisation :",
|
||||
"batteryType": "Type de batterie :",
|
||||
"owner": "Propriétaire :",
|
||||
"status": "Status :",
|
||||
"inactive": "Inactive",
|
||||
"active": "Active",
|
||||
"confirmInfos": "Confirmer les informations",
|
||||
"sureBtn": "Oui je suis sûr !",
|
||||
"deleteInfos": "Supprimer les informations",
|
||||
"changeBtn": "Non je veux changer !"
|
||||
},
|
||||
"infoObject": {
|
||||
"title": "Informations",
|
||||
"description": "Description :",
|
||||
"type": "Type :",
|
||||
"location": "Localisation :",
|
||||
"status": "Status :",
|
||||
"lastUpdate": "Dernière mise à jour :",
|
||||
"modify": "Modifier ces infos"
|
||||
},
|
||||
"modifObject": {
|
||||
"title": "Modifier les infos",
|
||||
"description": "Description :",
|
||||
"type": "Type :",
|
||||
"location": "Localisation :",
|
||||
"status": "Status :",
|
||||
"inactive": "Inactive",
|
||||
"active": "Active",
|
||||
"confirmMods": "Confirmer les modifications",
|
||||
"cancelMods": "Annuler les modifications"
|
||||
},
|
||||
"particularMeteo": {
|
||||
"defineLimit": "Définissez la valeur seuil pour l'alerte :",
|
||||
"outOfBounds": "Attention, la valeur actuelle est hors des bornes que vous avez définies !",
|
||||
"setAlert": "Définir alerte"
|
||||
},
|
||||
"meteoInfos": {
|
||||
"alertInactive": "Cet objet peut être inactif dû à son manque de données. Vous pouvez le rendre inactif en changeant son statut.",
|
||||
"currentWeather": "Météo actuelle",
|
||||
"temperature": "Température",
|
||||
"pressure": "Pression",
|
||||
"humidity": "Humidité",
|
||||
"lastRecord": "Dernier enregistrement :",
|
||||
"loading": "Chargement des données..."
|
||||
},
|
||||
"batterieInfo": {
|
||||
"title": "Etat de la batterie",
|
||||
"batteryType": "Type de batterie :"
|
||||
},
|
||||
"userInfosObject": {
|
||||
"title": "Propriétaire",
|
||||
"pseudo": "Pseudo :",
|
||||
"gender": "Genre :",
|
||||
"points": "Nombre de points :"
|
||||
},
|
||||
"windGraph": {
|
||||
"title": "Historique du vent",
|
||||
"date": "Date :",
|
||||
"windSpeed": "Vitesse du vent :",
|
||||
"windDirection": "Direction du vent :"
|
||||
},
|
||||
"meteoGraph": {
|
||||
"historyTemp": "Historique de la température",
|
||||
"historyHum": "Historique de l'humidité",
|
||||
"historyPres": "Historique de la pression",
|
||||
"average": "Moyenne"
|
||||
},
|
||||
"windInfo": {
|
||||
"currentWind": "Vent actuel",
|
||||
"value": "Valeur",
|
||||
"lastRecord": "Dernier enregistrement :",
|
||||
"loading": "Chargement des données..."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ import { StrictMode } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import App from './App.jsx';
|
||||
import './index.css';
|
||||
import './i18n';
|
||||
|
||||
createRoot(document.getElementById('root')).render(
|
||||
<StrictMode>
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
function About() {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-50">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
||||
@ -10,17 +12,10 @@ function About() {
|
||||
{/* Section Notre mission */}
|
||||
<div className="order-1 md:order-1">
|
||||
<h1 className="text-2xl font-bold text-gray-900 mb-6">
|
||||
Notre mission
|
||||
{t('about.missionTitle')}
|
||||
</h1>
|
||||
<p className="text-gray-700 leading-relaxed">
|
||||
Notre mission est de fournir une solution complète et innovante
|
||||
pour la surveillance climatique et environnementale du
|
||||
territoire français. En combinant des prévisions météorologiques
|
||||
de haute qualité avec une gestion efficace des objets connectés,
|
||||
nous visons à offrir une plateforme centralisée permettant de
|
||||
surveiller en temps réel les conditions météorologiques locales,
|
||||
tout en facilitant l'analyse des données collectées par des
|
||||
objets connectés déployés à travers le pays.
|
||||
{t('about.missionDesc')}
|
||||
</p>
|
||||
</div>
|
||||
<img
|
||||
@ -37,35 +32,20 @@ function About() {
|
||||
/>
|
||||
<div className="order-3 md:order-4">
|
||||
<h1 className="text-2xl font-bold text-gray-900 mb-6">
|
||||
Qui sommes-nous ?
|
||||
{t('about.whoTitle')}
|
||||
</h1>
|
||||
<p className="text-gray-700 leading-relaxed">
|
||||
Nous sommes une équipe de passionnés de technologie,
|
||||
d’innovation et d’environnement. Nous croyons fermement que la
|
||||
combinaison de la donnée météorologique en temps réel et de
|
||||
l’Internet des Objets (IoT) peut avoir un impact majeur sur la
|
||||
gestion des territoires. Que ce soit pour les collectivités
|
||||
locales, les entreprises ou les acteurs publics, notre
|
||||
plateforme offre les outils nécessaires pour une gestion
|
||||
proactive et réactive de l’environnement.
|
||||
{t('about.whoDesc')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Section Notre Vision */}
|
||||
<div className="order-5 md:order-5">
|
||||
<h1 className="text-2xl font-bold text-gray-900 mb-6">
|
||||
Notre Vision
|
||||
{t('about.visionTitle')}
|
||||
</h1>
|
||||
<p className="text-gray-700 leading-relaxed">
|
||||
Dans un monde où les conditions climatiques évoluent rapidement,
|
||||
il est essentiel de pouvoir anticiper et réagir efficacement
|
||||
face aux phénomènes météorologiques. Grâce à nos objets
|
||||
connectés et à notre interface intuitive, nous permettons aux
|
||||
utilisateurs de suivre les conditions en temps réel et d’agir en
|
||||
conséquence. De la gestion des risques climatiques à la
|
||||
planification urbaine, notre plateforme aide les décideurs à
|
||||
prendre des décisions éclairées basées sur des données fiables
|
||||
et locales.
|
||||
{t('about.visionDesc')}
|
||||
</p>
|
||||
</div>
|
||||
<img
|
||||
@ -78,7 +58,7 @@ function About() {
|
||||
{/* Section Objectifs */}
|
||||
<div className="text-center col-span-2 order-7">
|
||||
<h1 className="text-2xl font-bold text-gray-900 mb-10 mt-20">
|
||||
Les Objectifs de Notre Plateforme
|
||||
{t('about.objectivesTitle')}
|
||||
</h1>
|
||||
<div className="grid sm:grid-cols-2 lg:grid-cols-4 gap-10">
|
||||
{/* Objectif 1 */}
|
||||
@ -90,14 +70,11 @@ function About() {
|
||||
/>
|
||||
<div className="absolute inset-0 bg-blue-600 opacity-0 group-hover:opacity-100 transition-opacity duration-500 rounded-xl flex items-center justify-center">
|
||||
<p className="text-white text-lg font-bold px-4">
|
||||
Grâce à nos objets connectés, nous collectons des données
|
||||
météorologiques locales, permettant une surveillance
|
||||
continue des conditions climatiques sur tout le territoire
|
||||
français.
|
||||
{t('about.obj1Desc')}
|
||||
</p>
|
||||
</div>
|
||||
<h1 className="text-xl font-bold mt-4 ">
|
||||
Surveillance en temps réel
|
||||
{t('about.obj1Title')}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
@ -110,13 +87,10 @@ function About() {
|
||||
/>
|
||||
<div className="absolute inset-0 bg-blue-600 opacity-0 group-hover:opacity-100 transition-opacity duration-500 rounded-xl flex items-center justify-center">
|
||||
<p className="text-white text-lg font-bold px-4">
|
||||
En utilisant les meilleures technologies de prévision
|
||||
météorologique, nous vous fournissons des prévisions
|
||||
précises, qu’il s’agisse de la température, de la vitesse du
|
||||
vent ou de la qualité de l’air.
|
||||
{t('about.obj2Desc')}
|
||||
</p>
|
||||
</div>
|
||||
<h1 className="text-xl font-bold mt-4 mb-6">Prédiction fiable</h1>
|
||||
<h1 className="text-xl font-bold mt-4 mb-6">{t('about.obj2Title')}</h1>
|
||||
</div>
|
||||
|
||||
{/* Objectif 3 */}
|
||||
@ -128,14 +102,11 @@ function About() {
|
||||
/>
|
||||
<div className="absolute inset-0 bg-blue-600 opacity-0 group-hover:opacity-100 transition-opacity duration-500 rounded-xl flex items-center justify-center">
|
||||
<p className="text-white text-lg font-bold px-4">
|
||||
Nous permettons aux utilisateurs de gérer facilement leurs
|
||||
objets connectés (stations météo, capteurs, etc.) à travers
|
||||
une interface simple, tout en offrant un suivi en temps réel
|
||||
de leur statut et de leurs données.
|
||||
{t('about.obj3Desc')}
|
||||
</p>
|
||||
</div>
|
||||
<h1 className="text-xl font-bold mt-4 mb-6">
|
||||
Gestion des objets connectés
|
||||
{t('about.obj3Title')}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
@ -148,13 +119,11 @@ function About() {
|
||||
/>
|
||||
<div className="absolute inset-0 bg-blue-600 opacity-0 group-hover:opacity-100 transition-opacity duration-500 rounded-xl flex items-center justify-center">
|
||||
<p className="text-white text-lg font-bold px-4">
|
||||
Notre plateforme vous envoie des alertes instantanées
|
||||
concernant les phénomènes météorologiques extrêmes, vous
|
||||
permettant de prendre des décisions rapides et adaptées.
|
||||
{t('about.obj4Desc')}
|
||||
</p>
|
||||
</div>
|
||||
<h1 className="text-xl font-bold mt-4 mb-6">
|
||||
Réponse rapide aux alertes climatiques
|
||||
{t('about.obj4Title')}
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import Sidebar from "./sidebar.jsx";
|
||||
import axios from "axios";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { API_BASE_URL } from "../../config";
|
||||
import AddObject from "../Gestion/AddObject.jsx";
|
||||
import FormNewObject from "../../components/FormNewObject.jsx";
|
||||
|
||||
function AdminObjet() {
|
||||
const { t } = useTranslation();
|
||||
const [categories, setCategories] = useState();
|
||||
const [newCategory, setNewCategory] = useState("");
|
||||
const [objects, setObjects] = useState([]);
|
||||
@ -49,7 +51,7 @@ function AdminObjet() {
|
||||
};
|
||||
const handleDeleteCategory = (categoryToDelete) => {
|
||||
const confirmation = window.confirm(
|
||||
`Êtes-vous sûr de vouloir supprimer la catégorie "${categoryToDelete}" ? Cette action pourrait impacter des objets.`
|
||||
t('admin.adminObjet.confirmCatDelete').replace('{cat}', categoryToDelete)
|
||||
);
|
||||
|
||||
if (confirmation) {
|
||||
@ -108,7 +110,7 @@ function AdminObjet() {
|
||||
status: status,
|
||||
};
|
||||
setObjects([...objects, newObj]);
|
||||
setMessRequete("Votre objet a bien été enregistré !");
|
||||
setMessRequete(t('admin.adminObjet.successRecord'));
|
||||
setEnregistre(true);
|
||||
setVerif(false);
|
||||
resetForm();
|
||||
@ -123,13 +125,13 @@ function AdminObjet() {
|
||||
id,
|
||||
})
|
||||
.then((response) => {
|
||||
setMessRequete("La demande à bien été supprimé !");
|
||||
setMessRequete(t('admin.adminObjet.successReqDelete'));
|
||||
console.log("La demande à été supprimée :", response.data);
|
||||
window.location.reload();
|
||||
})
|
||||
.catch((error) => {
|
||||
setMessRequete(
|
||||
"Il y a eu une erreur dans la suppression de la demande !"
|
||||
t('admin.adminObjet.errorReqDelete')
|
||||
);
|
||||
console.error("Erreur lors de la suppression de la demande :", error);
|
||||
});
|
||||
@ -141,13 +143,13 @@ function AdminObjet() {
|
||||
id,
|
||||
})
|
||||
.then((response) => {
|
||||
alert("Votre objet à bien été supprimé !");
|
||||
alert(t('admin.adminObjet.successObjDelete'));
|
||||
console.log("Votre objet à été supprimé :", response.data);
|
||||
window.location.reload();
|
||||
})
|
||||
.catch((error) => {
|
||||
alert(
|
||||
"Il y a eu une erreur dans la suppression de votre objet !"
|
||||
t('admin.adminObjet.errorObjDelete')
|
||||
);
|
||||
console.error("Erreur lors de la suppression de l'objet :", error);
|
||||
});
|
||||
@ -168,17 +170,17 @@ function AdminObjet() {
|
||||
<div className="flex-1 bg-gradient-to-br from-blue-50 to-indigo-50 p-8 overflow-auto scrollbar-hide">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<h1 className="text-4xl font-bold text-gray-900 text-center mb-12">
|
||||
Administration des Objets et Outils/Services
|
||||
{t('admin.adminObjet.title')}
|
||||
</h1>
|
||||
|
||||
<section className="bg-white p-6 rounded-xl shadow-md mb-12">
|
||||
<h2 className="text-2xl font-semibold mb-4">
|
||||
Gestion des Catégories
|
||||
{t('admin.adminObjet.catsTitle')}
|
||||
</h2>
|
||||
<div className="flex items-center mb-4">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Nouvelle catégorie"
|
||||
placeholder={t('admin.adminObjet.newCatPlaceholder')}
|
||||
value={newCategory}
|
||||
onChange={(e) => setNewCategory(e.target.value)}
|
||||
className="flex-1 border border-gray-300 rounded-lg p-2 mr-4"
|
||||
@ -187,7 +189,7 @@ function AdminObjet() {
|
||||
onClick={handleAddCategory}
|
||||
className="bg-indigo-600 text-white px-4 py-2 rounded-lg"
|
||||
>
|
||||
Ajouter
|
||||
{t('admin.adminObjet.addBtn')}
|
||||
</button>
|
||||
</div>
|
||||
<ul>
|
||||
@ -202,7 +204,7 @@ function AdminObjet() {
|
||||
onClick={() => handleDeleteCategory(cat)}
|
||||
className="text-red-600 hover:underline"
|
||||
>
|
||||
Supprimer
|
||||
{t('admin.adminObjet.deleteBtn')}
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
@ -213,18 +215,18 @@ function AdminObjet() {
|
||||
<section className="bg-white p-6 rounded-xl shadow-md mt-12 mb-12">
|
||||
<div className="flex flex-col md:flex-row md:items-center md:justify-between mb-4 gap-4">
|
||||
<h2 className="text-2xl font-semibold">
|
||||
Liste des Objets et Outils/Services
|
||||
{t('admin.adminObjet.listTitle')}
|
||||
</h2>
|
||||
<select
|
||||
value={sortCriteria}
|
||||
onChange={(e) => setSortCriteria(e.target.value)}
|
||||
className="border border-gray-300 rounded-lg p-2 w-full md:w-auto"
|
||||
>
|
||||
<option value="">-- Trier par --</option>
|
||||
<option value="proprietaire">Propriétaire</option>
|
||||
<option value="location">Lieux</option>
|
||||
<option value="type">Type</option>
|
||||
<option value="status">Status</option>
|
||||
<option value="">{t('admin.adminObjet.sortBy')}</option>
|
||||
<option value="proprietaire">{t('admin.adminObjet.sortOwner')}</option>
|
||||
<option value="location">{t('admin.adminObjet.sortLocation')}</option>
|
||||
<option value="type">{t('admin.adminObjet.sortType')}</option>
|
||||
<option value="status">{t('admin.adminObjet.sortStatus')}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="overflow-x-auto">
|
||||
@ -232,22 +234,22 @@ function AdminObjet() {
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Nom
|
||||
{t('admin.adminObjet.colName')}
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider w-48">
|
||||
Description
|
||||
{t('admin.adminObjet.colDesc')}
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Type
|
||||
{t('admin.adminObjet.colType')}
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Localisation
|
||||
{t('admin.adminObjet.colLocation')}
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Propriétaire
|
||||
{t('admin.adminObjet.colOwner')}
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Status
|
||||
{t('admin.adminObjet.colStatus')}
|
||||
</th>
|
||||
<th className="px-6 py-3"></th>
|
||||
</tr>
|
||||
@ -283,7 +285,7 @@ function AdminObjet() {
|
||||
onClick={() => handleDeleteObject(obj.id)}
|
||||
className="text-red-600 hover:underline"
|
||||
>
|
||||
Supprimer
|
||||
{t('admin.adminObjet.deleteBtn')}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
@ -294,7 +296,7 @@ function AdminObjet() {
|
||||
colSpan="7"
|
||||
className="px-6 py-4 text-center text-sm text-gray-500"
|
||||
>
|
||||
Aucun objet ou service disponible.
|
||||
{t('admin.adminObjet.noObjects')}
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
@ -307,7 +309,7 @@ function AdminObjet() {
|
||||
|
||||
<section className="bg-white p-6 rounded-xl shadow-md mt-12">
|
||||
<h2 className="text-2xl font-semibold mb-4">
|
||||
Demandes de Suppression d'Objets
|
||||
{t('admin.adminObjet.requestsTitle')}
|
||||
</h2>
|
||||
|
||||
<div className="overflow-x-auto">
|
||||
@ -315,10 +317,10 @@ function AdminObjet() {
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">ID</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Objet ID</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Utilisateur ID</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Date de Requête</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{t('admin.adminObjet.colObjId')}</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{t('admin.adminObjet.colUserId')}</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{t('admin.adminObjet.colReqDate')}</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{t('admin.user.actions')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="bg-white divide-y divide-gray-200">
|
||||
@ -333,13 +335,13 @@ function AdminObjet() {
|
||||
className="bg-green-500 hover:bg-green-600 text-white px-3 py-1 rounded-md text-sm"
|
||||
onClick={() => handleDeleteObject(request.object_id)}
|
||||
>
|
||||
Accepter
|
||||
{t('admin.adminObjet.acceptBtn')}
|
||||
</button>
|
||||
<button
|
||||
className="bg-red-500 hover:bg-red-600 text-white px-3 py-1 rounded-md text-sm"
|
||||
onClick={() => handleReject(request.id)}
|
||||
>
|
||||
Refuser
|
||||
{t('admin.adminObjet.rejectBtn')}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
@ -350,7 +352,7 @@ function AdminObjet() {
|
||||
colSpan="5"
|
||||
className="px-6 py-4 text-center text-sm text-gray-500"
|
||||
>
|
||||
Aucune demande de suppression trouvée.
|
||||
{t('admin.adminObjet.noRequests')}
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import Sidebar from "./sidebar.jsx";
|
||||
import { RadioTower,Minus, ArrowRight, BadgePlus, Settings } from "lucide-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { API_BASE_URL } from "../../config.js";
|
||||
import axios from "axios";
|
||||
|
||||
@ -41,6 +42,7 @@ const initialWidgets = [
|
||||
];
|
||||
|
||||
function Dashboard() {
|
||||
const { t } = useTranslation();
|
||||
const [users, setUsers] = useState([]);
|
||||
|
||||
const [logs, setLogs] = useState([
|
||||
@ -94,18 +96,18 @@ function Dashboard() {
|
||||
<Sidebar />
|
||||
<main className="flex-1 bg-gradient-to-br from-blue-50 to-indigo-50 p-8 overflow-auto">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h1 className="text-3xl font-bold truncate mr-2">Dashboard</h1>
|
||||
<h1 className="text-3xl font-bold truncate mr-2">{t('admin.dashboard.title')}</h1>
|
||||
<button
|
||||
onClick={() => setManageMode(!manageMode)}
|
||||
className="flex items-center justify-center rounded-md hover:bg-gray-300"
|
||||
aria-label={
|
||||
manageMode ? "Terminer la gestion" : "Gérer les widgets"
|
||||
manageMode ? t('admin.dashboard.manageWidgetsEnd') : t('admin.dashboard.manageWidgets')
|
||||
}
|
||||
>
|
||||
<span className="p-2 sm:bg-gray-200 sm:px-4 sm:py-2 sm:rounded-md flex items-center">
|
||||
<Settings size={20} className="sm:mr-2" />
|
||||
<span className="hidden sm:inline">
|
||||
{manageMode ? "Terminer la gestion" : "Gérer les widgets"}
|
||||
{manageMode ? t('admin.dashboard.manageWidgetsEnd') : t('admin.dashboard.manageWidgets')}
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
@ -129,21 +131,21 @@ function Dashboard() {
|
||||
{widget.type === "summary" && (
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold mb-4">
|
||||
Résumé du tableau de bord
|
||||
{t('admin.dashboard.summary')}
|
||||
</h2>
|
||||
<div className="mb-4">
|
||||
<h3 className="text-lg font-medium">Total Utilisateur</h3>
|
||||
<h3 className="text-lg font-medium">{t('admin.dashboard.totalUsers')}</h3>
|
||||
<p className="text-2xl">{users.length}</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg font-medium">Dernier Log</h3>
|
||||
<h3 className="text-lg font-medium">{t('admin.dashboard.lastLog')}</h3>
|
||||
{logs.length > 0 ? (
|
||||
<p>
|
||||
{logs[logs.length - 1].username} -{" "}
|
||||
{logs[logs.length - 1].action}
|
||||
</p>
|
||||
) : (
|
||||
<p>Aucun log</p>
|
||||
<p>{t('admin.dashboard.noLog')}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@ -152,20 +154,20 @@ function Dashboard() {
|
||||
{widget.type === "users" && (
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold mb-4">
|
||||
Gestion des Utilisateurs
|
||||
{t('admin.dashboard.usersList')}
|
||||
</h2>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="min-w-[320px] w-full border border-gray-200">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="px-2 py-1 border border-gray-200 bg-gray-100 text-left">
|
||||
Username
|
||||
{t('admin.dashboard.username')}
|
||||
</th>
|
||||
<th className="px-2 py-1 border border-gray-200 bg-gray-100 text-left">
|
||||
Email
|
||||
{t('admin.dashboard.email')}
|
||||
</th>
|
||||
<th className="px-2 py-1 border border-gray-200 bg-gray-100 text-left">
|
||||
Access
|
||||
{t('admin.dashboard.access')}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -189,7 +191,7 @@ function Dashboard() {
|
||||
colSpan="3"
|
||||
className="px-2 py-1 border border-gray-200 text-center"
|
||||
>
|
||||
Aucun utilisateur disponible
|
||||
{t('admin.dashboard.noUser')}
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
@ -200,7 +202,7 @@ function Dashboard() {
|
||||
className="mt-4 px-4 py-2 bg-blue-600 text-white rounded-md"
|
||||
onClick={() => (window.location.href = "/user")}
|
||||
>
|
||||
Voir plus
|
||||
{t('admin.dashboard.seeMore')}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
@ -208,7 +210,7 @@ function Dashboard() {
|
||||
{widget.type === "objects" && (
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold mb-4">
|
||||
Gestion des Objets Connectés
|
||||
{t('admin.dashboard.objectsManagement')}
|
||||
</h2>
|
||||
<div className="mb-4">
|
||||
<a
|
||||
@ -216,7 +218,7 @@ function Dashboard() {
|
||||
className="flex items-center text-indigo-600 hover:text-indigo-700"
|
||||
>
|
||||
<RadioTower size={24} className="mr-2" />
|
||||
Consulter les objets connectés
|
||||
{t('admin.dashboard.consultObjects')}
|
||||
<ArrowRight size={16} className="ml-2" />
|
||||
</a>
|
||||
</div>
|
||||
@ -226,7 +228,7 @@ function Dashboard() {
|
||||
className="flex items-center text-indigo-600 hover:text-indigo-700"
|
||||
>
|
||||
<BadgePlus size={24} className="mr-2" />
|
||||
Ajouter un nouvel objet
|
||||
{t('admin.dashboard.addObject')}
|
||||
<ArrowRight size={16} className="ml-2" />
|
||||
</a>
|
||||
</div>
|
||||
@ -236,7 +238,7 @@ function Dashboard() {
|
||||
{widget.type === "adminobjet" && (
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold mb-4">
|
||||
Liste des Objets et Outils/Services
|
||||
{t('admin.dashboard.objectsList')}
|
||||
</h2>
|
||||
<ul className="mb-4 space-y-2">
|
||||
{adminObjects.slice(0, 2).map((obj) => (
|
||||
@ -254,7 +256,7 @@ function Dashboard() {
|
||||
onClick={() => (window.location.href = "/adminobjet")}
|
||||
className="mt-4 px-4 py-2 bg-blue-600 text-white rounded-md"
|
||||
>
|
||||
Voir plus
|
||||
{t('admin.dashboard.seeMore')}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
@ -262,19 +264,19 @@ function Dashboard() {
|
||||
{widget.type === "requestObject" && (
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold mb-4">
|
||||
Requête suppression objets
|
||||
{t('admin.dashboard.requestDelete')}
|
||||
</h2>
|
||||
|
||||
<div className="mb-4">
|
||||
<p className="text-gray-700 mb-2">
|
||||
Générer des rapports d'utilisation :
|
||||
{t('admin.dashboard.generateReports')}
|
||||
</p>
|
||||
<div className="flex gap-4">
|
||||
<button
|
||||
className="bg-indigo-600 text-white px-4 py-2 rounded-md hover:bg-indigo-700"
|
||||
onClick={() => exportCSV()}
|
||||
>
|
||||
Requête objets
|
||||
{t('admin.dashboard.requestObjects')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -283,19 +285,19 @@ function Dashboard() {
|
||||
{widget.type === "reporting" && (
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold mb-4">
|
||||
Rapports et Statistiques
|
||||
{t('admin.dashboard.reportsStats')}
|
||||
</h2>
|
||||
|
||||
<div className="mb-4">
|
||||
<p className="text-gray-700 mb-2">
|
||||
Générer des rapports d'utilisation :
|
||||
{t('admin.dashboard.generateReports')}
|
||||
</p>
|
||||
<div className="flex gap-4">
|
||||
<button
|
||||
className="bg-indigo-600 text-white px-4 py-2 rounded-md hover:bg-indigo-700"
|
||||
onClick={() => exportCSV()}
|
||||
>
|
||||
Exporter en CSV
|
||||
{t('admin.dashboard.exportCsv')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -303,23 +305,23 @@ function Dashboard() {
|
||||
<div className="mt-4 space-y-2">
|
||||
<div>
|
||||
<h4 className="text-md font-medium">
|
||||
Consommation énergétique totale
|
||||
{t('admin.dashboard.energyConsumption')}
|
||||
</h4>
|
||||
<p className="text-gray-600">
|
||||
1372 kWh cumulés (estimation)
|
||||
{t('admin.dashboard.energyConsumptionDesc')}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-md font-medium">
|
||||
Taux de connexion des utilisateurs
|
||||
{t('admin.dashboard.connectionRate')}
|
||||
</h4>
|
||||
<p className="text-gray-600">
|
||||
87% des utilisateurs actifs ce mois-ci
|
||||
{t('admin.dashboard.connectionRateDesc')}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-md font-medium">
|
||||
Services les plus utilisés
|
||||
{t('admin.dashboard.mostUsedServices')}
|
||||
</h4>
|
||||
<ul className="list-disc ml-6 text-gray-600">
|
||||
<li>Consultation des données météo</li>
|
||||
@ -339,7 +341,7 @@ function Dashboard() {
|
||||
>
|
||||
<button className="flex items-center">
|
||||
<span className="text-3xl font-bold mr-2">+</span>
|
||||
<span>Ajouter un widget</span>
|
||||
<span>{t('admin.dashboard.addWidget')}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -349,51 +351,51 @@ function Dashboard() {
|
||||
<div className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 z-50">
|
||||
<div className="bg-white rounded-lg p-6 w-80">
|
||||
<h3 className="text-xl font-semibold mb-4">
|
||||
Choisir un type de widget
|
||||
{t('admin.dashboard.chooseWidget')}
|
||||
</h3>
|
||||
<div className="flex flex-col gap-4">
|
||||
<button
|
||||
onClick={() => handleWidgetSelection("summary")}
|
||||
className="px-4 py-2 bg-gray-200 hover:bg-gray-300 rounded-md text-left"
|
||||
>
|
||||
Dashboard Summary
|
||||
{t('admin.dashboard.widgetSummary')}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleWidgetSelection("users")}
|
||||
className="px-4 py-2 bg-gray-200 hover:bg-gray-300 rounded-md text-left"
|
||||
>
|
||||
Gestion des Utilisateurs
|
||||
{t('admin.dashboard.widgetUsers')}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleWidgetSelection("objects")}
|
||||
className="px-4 py-2 bg-gray-200 hover:bg-gray-300 rounded-md text-left"
|
||||
>
|
||||
Gestion des Objets Connectés
|
||||
{t('admin.dashboard.widgetObjects')}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleWidgetSelection("adminobjet")}
|
||||
className="px-4 py-2 bg-gray-200 hover:bg-gray-300 rounded-md text-left"
|
||||
>
|
||||
Liste des Objets et Outils/Services
|
||||
{t('admin.dashboard.widgetObjectsList')}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleWidgetSelection("reporting")}
|
||||
className="px-4 py-2 bg-gray-200 hover:bg-gray-300 rounded-md text-left"
|
||||
>
|
||||
Rapports et Statistiques
|
||||
{t('admin.dashboard.widgetReports')}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleWidgetSelection("requestObject")}
|
||||
className="px-4 py-2 bg-gray-200 hover:bg-gray-300 rounded-md text-left"
|
||||
>
|
||||
Demande de suppression d'objets
|
||||
{t('admin.dashboard.widgetDelete')}
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setShowAddWidgetModal(false)}
|
||||
className="mt-4 px-4 py-2 bg-red-500 text-white rounded-md w-full"
|
||||
>
|
||||
Annuler
|
||||
{t('admin.dashboard.cancel')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import Sidebar from "./sidebar.jsx";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { API_BASE_URL } from "../../config.js";
|
||||
import axios from "axios";
|
||||
|
||||
@ -7,6 +8,7 @@ const thTd = "p-2 border border-gray-300 text-left";
|
||||
const th = `${thTd} bg-gray-100`;
|
||||
|
||||
function User() {
|
||||
const { t } = useTranslation();
|
||||
const [users, setUsers] = useState([]);
|
||||
const [logs, setLogs] = useState([]);
|
||||
|
||||
@ -33,11 +35,11 @@ function User() {
|
||||
})
|
||||
.then((response) => {
|
||||
logAction(name, "Utilisateur ajouté");
|
||||
alert("Ajout de l'utilisateur réussit !");
|
||||
alert(t('admin.user.successAdd'));
|
||||
window.location.reload();
|
||||
})
|
||||
.catch((error) => {
|
||||
alert("Erreur lors de l'ajout de l'utilisateur !");
|
||||
alert(t('admin.user.errorAdd'));
|
||||
});
|
||||
};
|
||||
|
||||
@ -52,7 +54,7 @@ function User() {
|
||||
|
||||
if (user) {
|
||||
const confirmation = window.confirm(
|
||||
`Êtes-vous sûr de vouloir supprimer l'utilisateur "${user.name}" ? Cette action pourrait entraîner la suppression des objets associés.`
|
||||
t('admin.user.confirmDelete').replace('{name}', user.name)
|
||||
);
|
||||
if (confirmation) {
|
||||
axios
|
||||
@ -60,7 +62,7 @@ function User() {
|
||||
id: userId,
|
||||
})
|
||||
.then((response) => {
|
||||
alert("L'utilisateur a bien été supprimé !");
|
||||
alert(t('admin.user.successDelete'));
|
||||
console.log("L'utilisateur a été supprimé :", response.data);
|
||||
window.location.reload();
|
||||
})
|
||||
@ -99,11 +101,11 @@ function User() {
|
||||
points: user.points,
|
||||
})
|
||||
.then((response) => {
|
||||
alert("Le changement de niveau a bien été enregistré !");
|
||||
alert(t('admin.user.successLevel'));
|
||||
console.log("Changement de niveau réussit :", response.data);
|
||||
})
|
||||
.catch((error) => {
|
||||
alert("Il y a eu une erreur dans le changement de niveau !");
|
||||
alert(t('admin.user.errorLevel'));
|
||||
console.error("Erreur lors du changement de niveau :", error);
|
||||
});
|
||||
logAction(
|
||||
@ -128,11 +130,11 @@ function User() {
|
||||
points: user.points,
|
||||
})
|
||||
.then((response) => {
|
||||
alert("Les points ont bien été enregistrés !");
|
||||
alert(t('admin.user.successPoints'));
|
||||
console.log("Ajout des points réussi :", response.data);
|
||||
})
|
||||
.catch((error) => {
|
||||
alert("Il y a eu une erreur dans l'ajout des points !");
|
||||
alert(t('admin.user.errorPoints'));
|
||||
console.error("Erreur lors de l'ajout des points :", error);
|
||||
});
|
||||
logAction(user.name, `Points ajustés à ${pointsToAdd}`);
|
||||
@ -167,9 +169,9 @@ function User() {
|
||||
<main className="flex-grow overflow-x-auto p-5">
|
||||
<section className="mt-5">
|
||||
<h1 className="text-2xl font-bold text-gray-900 mb-5">
|
||||
Gestion des utilisateurs
|
||||
{t('admin.user.title')}
|
||||
</h1>
|
||||
<h2 className="text-xl font-bold text-gray-600 mb-2">Ajoutez un utilisateur à partir de ce formulaire</h2>
|
||||
<h2 className="text-xl font-bold text-gray-600 mb-2">{t('admin.user.subtitle')}</h2>
|
||||
|
||||
<form className="grid gap-4 mb-5" onSubmit={handleAddUser}>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-4">
|
||||
@ -177,7 +179,7 @@ function User() {
|
||||
<input
|
||||
className="w-full p-3 border rounded-md focus:ring-2 focus:ring-blue-300 focus:border-blue-500 focus:outline-none"
|
||||
type="text"
|
||||
placeholder="Nom"
|
||||
placeholder={t('admin.user.lastName')}
|
||||
value={name}
|
||||
onChange={(e) => setname(e.target.value)}
|
||||
required
|
||||
@ -187,7 +189,7 @@ function User() {
|
||||
<input
|
||||
className="w-full p-3 border rounded-md focus:ring-2 focus:ring-blue-300 focus:border-blue-500 focus:outline-none"
|
||||
type="text"
|
||||
placeholder="Prénom"
|
||||
placeholder={t('admin.user.firstName')}
|
||||
value={surname}
|
||||
onChange={(e) => setSurname(e.target.value)}
|
||||
required
|
||||
@ -197,7 +199,7 @@ function User() {
|
||||
<input
|
||||
className="w-full p-3 border rounded-md focus:ring-2 focus:ring-blue-300 focus:border-blue-500 focus:outline-none"
|
||||
type="text"
|
||||
placeholder="Pseudo"
|
||||
placeholder={t('admin.user.pseudo')}
|
||||
value={pseudo}
|
||||
onChange={(e) => setPseudo(e.target.value)}
|
||||
required
|
||||
@ -207,7 +209,7 @@ function User() {
|
||||
<input
|
||||
className="w-full p-3 border rounded-md focus:ring-2 focus:ring-blue-300 focus:border-blue-500 focus:outline-none"
|
||||
type="email"
|
||||
placeholder="Email"
|
||||
placeholder={t('admin.user.email')}
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
required
|
||||
@ -217,7 +219,7 @@ function User() {
|
||||
<input
|
||||
className="w-full p-3 border rounded-md focus:ring-2 focus:ring-blue-300 focus:border-blue-500 focus:outline-none"
|
||||
type="password"
|
||||
placeholder="Mot de passe"
|
||||
placeholder={t('admin.user.password')}
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
required
|
||||
@ -229,9 +231,9 @@ function User() {
|
||||
value={gender}
|
||||
onChange={(e) => setGender(e.target.value)}
|
||||
>
|
||||
<option value="Homme">Homme</option>
|
||||
<option value="Femme">Femme</option>
|
||||
<option value="Autre">Autre</option>
|
||||
<option value="Homme">{t('admin.user.genderMale')}</option>
|
||||
<option value="Femme">{t('admin.user.genderFemale')}</option>
|
||||
<option value="Autre">{t('admin.user.genderOther')}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@ -240,7 +242,7 @@ function User() {
|
||||
className="w-full sm:w-auto px-6 py-3 bg-green-600 hover:bg-green-700 text-white font-medium border-none rounded-md transition-colors"
|
||||
type="submit"
|
||||
>
|
||||
Ajouter utilisateur
|
||||
{t('admin.user.addUserBtn')}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@ -248,18 +250,18 @@ function User() {
|
||||
</section>
|
||||
{/*Tableau utilisateur*/}
|
||||
<div className="w-full overflow-x-auto">
|
||||
<h2 className="text-xl font-bold text-gray-600 mb-2">Gérez les utilisateurs à partir de ce panneau.</h2>
|
||||
<h2 className="text-xl font-bold text-gray-600 mb-2">{t('admin.user.manageTitle')}</h2>
|
||||
<table className="w-full table-auto border border-collapse">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className={th}>Nom</th>
|
||||
<th className={th}>Prénom</th>
|
||||
<th className={th}>Pseudo</th>
|
||||
<th className={th}>Email</th>
|
||||
<th className={th}>Genre</th>
|
||||
<th className={th}>Niveau d'accès</th>
|
||||
<th className={th}>Points</th>
|
||||
<th className={th}>Actions</th>
|
||||
<th className={th}>{t('admin.user.lastName')}</th>
|
||||
<th className={th}>{t('admin.user.firstName')}</th>
|
||||
<th className={th}>{t('admin.user.pseudo')}</th>
|
||||
<th className={th}>{t('admin.user.email')}</th>
|
||||
<th className={th}>{t('admin.user.gender')}</th>
|
||||
<th className={th}>{t('admin.user.accessLevel')}</th>
|
||||
<th className={th}>{t('admin.user.points')}</th>
|
||||
<th className={th}>{t('admin.user.actions')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -300,7 +302,7 @@ function User() {
|
||||
className="ml-2 p-2 bg-green-600 text-white border-none rounded-md"
|
||||
onClick={() => handleAdjustPoints(user.id)}
|
||||
>
|
||||
Changer
|
||||
{t('admin.user.changeBtn')}
|
||||
</button>
|
||||
</td>
|
||||
<td className={thTd}>
|
||||
@ -308,7 +310,7 @@ function User() {
|
||||
className="p-2 bg-red-600 text-white border-none rounded-md"
|
||||
onClick={() => handleDeleteUser(user.id)}
|
||||
>
|
||||
Supprimer
|
||||
{t('admin.user.deleteBtn')}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
@ -319,15 +321,15 @@ function User() {
|
||||
|
||||
<section className="user-logs mt-10">
|
||||
<h2 className="text-2xl font-bold text-gray-900 mb-5">
|
||||
Historique des connexions et journal des logs{" "}
|
||||
{t('admin.user.logsTitle')}
|
||||
</h2>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className={th}>Nom</th>
|
||||
<th className={th}>Action</th>
|
||||
<th className={th}>Timestamp</th>
|
||||
<th className={th}>{t('admin.user.lastName')}</th>
|
||||
<th className={th}>{t('admin.user.action')}</th>
|
||||
<th className={th}>{t('admin.user.timestamp')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -345,7 +347,7 @@ function User() {
|
||||
onClick={downloadLogs}
|
||||
className="mt-4 px-4 py-2 bg-blue-600 text-white rounded-md"
|
||||
>
|
||||
Télécharger les logs
|
||||
{t('admin.user.downloadLogs')}
|
||||
</button>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import React from "react";
|
||||
import { Menu, X } from "lucide-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
function Sidebar({ isOpen, toggleSidebar }) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<aside
|
||||
className={`
|
||||
@ -12,13 +14,13 @@ function Sidebar({ isOpen, toggleSidebar }) {
|
||||
`}
|
||||
>
|
||||
<div className="flex justify-between items-center md:hidden mb-4">
|
||||
<h2 className="text-xl font-bold">Admin Panel</h2>
|
||||
<h2 className="text-xl font-bold">{t('admin.sidebar.panel')}</h2>
|
||||
<button onClick={toggleSidebar} className="focus:outline-none">
|
||||
<X size={24} />
|
||||
</button>
|
||||
</div>
|
||||
<div className="hidden md:block mb-4">
|
||||
<h2 className="text-xl font-bold">Admin Panel</h2>
|
||||
<h2 className="text-xl font-bold">{t('admin.sidebar.panel')}</h2>
|
||||
</div>
|
||||
<nav>
|
||||
<ul className="list-none p-0">
|
||||
@ -27,12 +29,12 @@ function Sidebar({ isOpen, toggleSidebar }) {
|
||||
className="text-white no-underline hover:underline"
|
||||
href="/dashboard"
|
||||
>
|
||||
Tableau de bord
|
||||
{t('admin.sidebar.dashboard')}
|
||||
</a>
|
||||
</li>
|
||||
<li className="mb-3">
|
||||
<a className="text-white no-underline hover:underline" href="/user">
|
||||
Utilisateurs
|
||||
{t('admin.sidebar.users')}
|
||||
</a>
|
||||
</li>
|
||||
<li className="mb-3">
|
||||
@ -40,7 +42,7 @@ function Sidebar({ isOpen, toggleSidebar }) {
|
||||
className="text-white no-underline hover:underline"
|
||||
href="/adminobjet"
|
||||
>
|
||||
Gestion des objets
|
||||
{t('admin.sidebar.objects')}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@ -1,13 +1,15 @@
|
||||
import React, { useState } from "react";
|
||||
import FormNewObject from "../../components/FormNewObject";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
function AddObject() {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-50">
|
||||
<div className=" max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
||||
<div className="text-center mb-5">
|
||||
<h2 className="text-4xl font-bold text-gray-900 mb-12">
|
||||
Nouvel objet
|
||||
{t('gestion.addObject.title')}
|
||||
</h2>
|
||||
</div>
|
||||
<FormNewObject />
|
||||
|
||||
@ -12,18 +12,21 @@ import {
|
||||
BadgePlus,
|
||||
} from "lucide-react";
|
||||
import { useAuth } from "../../AuthContext";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
function Gestion() {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-50">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
||||
<div className="text-center mb-12">
|
||||
|
||||
<h2 className="text-4xl font-bold text-gray-900 mb-4">
|
||||
Bienvenue dans le module <b>Gestion</b>.
|
||||
{t('gestion.title')} <b>{t('gestion.moduleName')}</b>.
|
||||
</h2>
|
||||
|
||||
<p className="text-xl text-gray-600 max-w-3xl mx-auto">
|
||||
Ce module vous permet de gérer les capteurs et stations connectés de France de manière simple et efficace.
|
||||
{t('gestion.description')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@ -33,16 +36,16 @@ function Gestion() {
|
||||
<RadioTower className="text-indigo-600" size={24} />
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold mb-2">
|
||||
Consulter les objets connectés météorologiques
|
||||
{t('gestion.consultTitle')}
|
||||
</h3>
|
||||
<p className="text-gray-600 mb-4">
|
||||
Accédez aux données en temps réel des objets connectés météorologiques, modifiez leurs paramètres et consultez l'historique des mesures.
|
||||
{t('gestion.consultDesc')}
|
||||
</p>
|
||||
<a
|
||||
href="/gestionObjets"
|
||||
className="flex items-center text-indigo-600 hover:text-indigo-700"
|
||||
>
|
||||
Explorer les objets <ArrowRight size={16} className="ml-2" />
|
||||
{t('gestion.exploreBtn')} <ArrowRight size={16} className="ml-2" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@ -51,16 +54,16 @@ function Gestion() {
|
||||
<BadgePlus className="text-indigo-600" size={24} />
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold mb-2">
|
||||
Ajouter un nouvel objet connecté
|
||||
{t('gestion.addTitle')}
|
||||
</h3>
|
||||
<p className="text-gray-600 mb-4">
|
||||
Intégrez facilement un nouvel objet connecté en renseignant ses informations et en configurant ses paramètres pour une gestion optimale.
|
||||
{t('gestion.addDesc')}
|
||||
</p>
|
||||
<a
|
||||
href="/ajouterObjet"
|
||||
className="flex items-center text-indigo-600 hover:text-indigo-700"
|
||||
>
|
||||
Ajouter un objet <ArrowRight size={16} className="ml-2" />
|
||||
{t('gestion.addBtn')} <ArrowRight size={16} className="ml-2" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -4,10 +4,12 @@ import { useEffect, useState } from "react";
|
||||
import axios from "axios";
|
||||
import { API_BASE_URL } from "../../config";
|
||||
import { useAuth } from "../../AuthContext";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Alert from "../../components/Alert";
|
||||
import AlertInactive from "../../components/AlertInactive";
|
||||
|
||||
function ObjectManagement() {
|
||||
const { t } = useTranslation();
|
||||
const { user } = useAuth();
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [activeFilter, setActiveFilter] = useState("");
|
||||
@ -62,7 +64,7 @@ function ObjectManagement() {
|
||||
);
|
||||
|
||||
console.log("Réponse du serveur:", response.data);
|
||||
setMessageAlert("Demande de suppression envoyée à l'administrateur.");
|
||||
setMessageAlert(t('gestion.objectManagement.successDeleteReq'));
|
||||
setAffAlert(true);
|
||||
setSuccess(false);
|
||||
} catch (error) {
|
||||
@ -70,7 +72,7 @@ function ObjectManagement() {
|
||||
"Erreur lors de la requête :",
|
||||
error.response?.data || error.message
|
||||
);
|
||||
setMessageAlert("Erreur demande déjà envoyé à l'administrateur.");
|
||||
setMessageAlert(t('gestion.objectManagement.errorDeleteReq'));
|
||||
setAffAlert(true);
|
||||
setSuccess(true);
|
||||
}
|
||||
@ -93,8 +95,8 @@ function ObjectManagement() {
|
||||
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-4xl font-bold text-gray-900 mb-4">
|
||||
{user?.role !== "user" ? "Gestion" : "Visualisation"} des{" "}
|
||||
<b>Objets</b> connectés.
|
||||
{user?.role !== "user" ? t('gestion.objectManagement.titleAdmin') : t('gestion.objectManagement.titleUser')} {" "}
|
||||
{t('gestion.objectManagement.titleSuffix')} <b>{t('gestion.objectManagement.titleObjects')}</b>
|
||||
</h2>
|
||||
</div>
|
||||
<div className="max-w-3xl mx-auto mb-12">
|
||||
@ -105,7 +107,7 @@ function ObjectManagement() {
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Chercher par nom, catégorie ou mot clés..."
|
||||
placeholder={t('gestion.objectManagement.searchPlaceholder')}
|
||||
className="w-full pl-12 pr-4 py-4 rounded-xl border border-gray-200 focus:outline-none focus:ring-2 focus:ring-indigo-500"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
@ -122,7 +124,7 @@ function ObjectManagement() {
|
||||
: "bg-white text-gray-600"
|
||||
}`}
|
||||
>
|
||||
Tout
|
||||
{t('gestion.objectManagement.filterAll')}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveFilter("Station")}
|
||||
@ -132,7 +134,7 @@ function ObjectManagement() {
|
||||
: "bg-white text-gray-600"
|
||||
}`}
|
||||
>
|
||||
Station météo
|
||||
{t('gestion.objectManagement.filterStation')}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveFilter("Capteur")}
|
||||
@ -142,7 +144,7 @@ function ObjectManagement() {
|
||||
: "bg-white text-gray-600"
|
||||
}`}
|
||||
>
|
||||
Capteur
|
||||
{t('gestion.objectManagement.filterSensor')}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveFilter("Active")}
|
||||
@ -152,7 +154,7 @@ function ObjectManagement() {
|
||||
: "bg-white text-gray-600"
|
||||
}`}
|
||||
>
|
||||
Actif
|
||||
{t('gestion.objectManagement.filterActive')}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveFilter("Inactive")}
|
||||
@ -162,14 +164,14 @@ function ObjectManagement() {
|
||||
: "bg-white text-gray-600"
|
||||
}`}
|
||||
>
|
||||
Inactif
|
||||
{t('gestion.objectManagement.filterInactive')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/* Grille responsive pour les objets */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-8">
|
||||
{filteredDATA.length === 0 ? (
|
||||
<p className="text-center col-span-full">Aucun objet trouvé</p>
|
||||
<p className="text-center col-span-full">{t('gestion.objectManagement.noObjects')}</p>
|
||||
) : (
|
||||
filteredDATA.slice(0, nbAffObject).map((object) => (
|
||||
<div
|
||||
@ -201,13 +203,13 @@ function ObjectManagement() {
|
||||
href={`/objet?id=${object.id}`}
|
||||
className="flex items-center text-indigo-600 hover:text-indigo-700"
|
||||
>
|
||||
Plus d'infos <ArrowRight size={16} className="ml-2" />
|
||||
{t('gestion.objectManagement.moreInfo')} <ArrowRight size={16} className="ml-2" />
|
||||
</a>
|
||||
{user?.role !== "user" && (
|
||||
<button
|
||||
onClick={() => handleRequestDeletion(object.id)}
|
||||
className="text-red-500 hover:text-red-700"
|
||||
title="Supprimer l'objet"
|
||||
title={t('gestion.objectManagement.deleteItem')}
|
||||
>
|
||||
<Trash size={20} />
|
||||
</button>
|
||||
@ -227,7 +229,7 @@ function ObjectManagement() {
|
||||
>
|
||||
<Plus size={40} className="text-indigo-600" />
|
||||
</button>
|
||||
<label className="text-indigo-600 font-medium">Voir plus</label>
|
||||
<label className="text-indigo-600 font-medium">{t('gestion.objectManagement.seeMore')}</label>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -13,8 +13,10 @@ import MeteoInfos from "../../components/MeteoInfos";
|
||||
import MeteoGraph from "../../components/MeteoGraph";
|
||||
import BatterieInfo from "../../components/BatterieInfo";
|
||||
import { useAuth } from "../../AuthContext";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import UserInfosObject from "../../components/UserInfosObject";
|
||||
function Objet() {
|
||||
const { t } = useTranslation();
|
||||
const {user} =useAuth();
|
||||
const identifiant = new URLSearchParams(window.location.search).get("id");
|
||||
const [object, setObject] = useState({});
|
||||
@ -50,7 +52,7 @@ function Objet() {
|
||||
<div className=" max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
||||
<div className="text-center mb-5">
|
||||
<h2 className="text-4xl font-bold text-gray-900 mb-12">
|
||||
Tableau de bord - {object.name}
|
||||
{t('gestion.objet.dashboardTitle')} {object.name}
|
||||
</h2>
|
||||
</div>
|
||||
<div className="grid md:grid-cols-1 lg:grid-cols-3 gap-8 mb-5">
|
||||
@ -104,7 +106,7 @@ function Objet() {
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<h1>Erreur de récupération de l'objet</h1>
|
||||
<h1>{t('gestion.objet.errorFetch')}</h1>
|
||||
);
|
||||
}
|
||||
export default Objet;
|
||||
|
||||
@ -21,9 +21,11 @@ import {
|
||||
} from "lucide-react";
|
||||
import { useAuth } from "../AuthContext";
|
||||
import axios from "axios";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { API_BASE_URL } from "../config";
|
||||
|
||||
function EnhancedWeatherHome() {
|
||||
const { t } = useTranslation();
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [locations, setLocations] = useState([]);
|
||||
const [infoMeteo, setInfoMeteo] = useState([]);
|
||||
@ -185,15 +187,14 @@ function EnhancedWeatherHome() {
|
||||
isDayTime ? "text-gray-900" : "text-white"
|
||||
} animate-fade-in`}
|
||||
>
|
||||
La météo en temps réel
|
||||
{t('home.title')}
|
||||
</h1>
|
||||
<p
|
||||
className={`text-xl ${
|
||||
isDayTime ? "text-gray-700" : "text-gray-200"
|
||||
} max-w-3xl mx-auto mb-10`}
|
||||
>
|
||||
Prévisions précises et personnalisées pour vous aider à planifier
|
||||
votre journée en toute sérénité.
|
||||
{t('home.subtitle')}
|
||||
</p>
|
||||
|
||||
<div className="max-w-3xl mx-auto relative z-10 mb-16 transition-all duration-300 hover:transform hover:-translate-y-1">
|
||||
@ -206,7 +207,7 @@ function EnhancedWeatherHome() {
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Rechercher une ville ..."
|
||||
placeholder={t('home.searchPlaceholder')}
|
||||
className={`w-full pl-12 pr-4 py-4 rounded-xl border ${
|
||||
isDayTime
|
||||
? "border-gray-200 bg-white"
|
||||
@ -233,7 +234,7 @@ function EnhancedWeatherHome() {
|
||||
))
|
||||
) : (
|
||||
<li className="text-gray-900 dark:text-gray-500">
|
||||
Aucune ville trouvée.
|
||||
{t('home.noCityFound')}
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
@ -292,7 +293,7 @@ function EnhancedWeatherHome() {
|
||||
isDayTime ? "text-gray-600" : "text-gray-300"
|
||||
}`}
|
||||
>
|
||||
Partiellement nuageux
|
||||
{t('home.partlyCloudy')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -309,7 +310,7 @@ function EnhancedWeatherHome() {
|
||||
isDayTime ? "text-gray-500" : "text-gray-400"
|
||||
}`}
|
||||
>
|
||||
Ressenti
|
||||
{t('home.feelsLike')}
|
||||
</p>
|
||||
<div className="flex items-center mt-1">
|
||||
<Thermometer
|
||||
@ -337,7 +338,7 @@ function EnhancedWeatherHome() {
|
||||
isDayTime ? "text-gray-500" : "text-gray-400"
|
||||
}`}
|
||||
>
|
||||
Humidité
|
||||
{t('home.humidity')}
|
||||
</p>
|
||||
<div className="flex items-center mt-1">
|
||||
<Droplets
|
||||
@ -365,7 +366,7 @@ function EnhancedWeatherHome() {
|
||||
isDayTime ? "text-gray-500" : "text-gray-400"
|
||||
}`}
|
||||
>
|
||||
Vent
|
||||
{t('home.wind')}
|
||||
</p>
|
||||
<div className="flex items-center mt-1">
|
||||
<Wind
|
||||
@ -393,7 +394,7 @@ function EnhancedWeatherHome() {
|
||||
isDayTime ? "text-gray-500" : "text-gray-400"
|
||||
}`}
|
||||
>
|
||||
Précipitations
|
||||
{t('home.rain')}
|
||||
</p>
|
||||
<div className="flex items-center mt-1">
|
||||
<CloudRain
|
||||
@ -421,7 +422,7 @@ function EnhancedWeatherHome() {
|
||||
isDayTime ? "text-gray-500" : "text-gray-400"
|
||||
}`}
|
||||
>
|
||||
Lever du soleil
|
||||
{t('home.sunrise')}
|
||||
</p>
|
||||
<div className="flex items-center mt-1">
|
||||
<Sun
|
||||
@ -449,7 +450,7 @@ function EnhancedWeatherHome() {
|
||||
isDayTime ? "text-gray-500" : "text-gray-400"
|
||||
}`}
|
||||
>
|
||||
Coucher du soleil
|
||||
{t('home.sunset')}
|
||||
</p>
|
||||
<div className="flex items-center mt-1">
|
||||
<Moon
|
||||
@ -479,7 +480,7 @@ function EnhancedWeatherHome() {
|
||||
isDayTime ? "text-gray-800" : "text-gray-200"
|
||||
}`}
|
||||
>
|
||||
Prévisions horaires
|
||||
{t('home.hourlyForecast')}
|
||||
</h3>
|
||||
<div className="flex overflow-x-auto pb-4 gap-6">
|
||||
{hourlyForecast.map((item, index) => (
|
||||
@ -521,7 +522,7 @@ function EnhancedWeatherHome() {
|
||||
isDayTime ? "text-gray-800" : "text-gray-200"
|
||||
}`}
|
||||
>
|
||||
Prévisions 5 jours
|
||||
{t('home.dailyForecast')}
|
||||
</h3>
|
||||
<div className="space-y-6">
|
||||
{dailyForecast.map((item, index) => (
|
||||
@ -559,7 +560,7 @@ function EnhancedWeatherHome() {
|
||||
isDayTime ? "text-gray-800" : "text-gray-200"
|
||||
}`}
|
||||
>
|
||||
Services météo
|
||||
{t('home.weatherServices')}
|
||||
</h3>
|
||||
{user?.role === "user" && (
|
||||
<div
|
||||
@ -582,15 +583,14 @@ function EnhancedWeatherHome() {
|
||||
isDayTime ? "text-gray-800" : "text-white"
|
||||
}`}
|
||||
>
|
||||
Visualisation des objets connectés météorologiques
|
||||
{t('home.viewObjectsTitle')}
|
||||
</h3>
|
||||
<p
|
||||
className={`${
|
||||
isDayTime ? "text-gray-600" : "text-gray-300"
|
||||
} mb-4`}
|
||||
>
|
||||
Consultez l'ensemble des objets connectés ainsi que leurs
|
||||
données dans l'ensemble de la France
|
||||
{t('home.viewObjectsDesc')}
|
||||
</p>
|
||||
<a
|
||||
href="/gestionObjets"
|
||||
@ -600,7 +600,7 @@ function EnhancedWeatherHome() {
|
||||
: "text-indigo-400 hover:text-indigo-300"
|
||||
}`}
|
||||
>
|
||||
Voir les objets <ArrowRight size={16} className="ml-2" />
|
||||
{t('home.viewObjectsBtn')} <ArrowRight size={16} className="ml-2" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@ -628,16 +628,14 @@ function EnhancedWeatherHome() {
|
||||
isDayTime ? "text-gray-800" : "text-white"
|
||||
}`}
|
||||
>
|
||||
Inscrivez-vous !{" "}
|
||||
{t('home.signupTitle')}
|
||||
</h3>
|
||||
<p
|
||||
className={`${
|
||||
isDayTime ? "text-gray-600" : "text-gray-300"
|
||||
} mb-4`}
|
||||
>
|
||||
Avoir un compte sur notre site permet de consulter
|
||||
l'intégralité des objets connectés météorologiques dans
|
||||
l'ensemble de la France
|
||||
{t('home.signupDesc')}
|
||||
</p>
|
||||
<a
|
||||
href="/signup"
|
||||
@ -647,7 +645,7 @@ function EnhancedWeatherHome() {
|
||||
: "text-indigo-400 hover:text-indigo-300"
|
||||
}`}
|
||||
>
|
||||
S'inscrire <ArrowRight size={16} className="ml-2" />
|
||||
{t('home.signupBtn')} <ArrowRight size={16} className="ml-2" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@ -671,15 +669,14 @@ function EnhancedWeatherHome() {
|
||||
isDayTime ? "text-gray-800" : "text-white"
|
||||
}`}
|
||||
>
|
||||
Connectez-vous !
|
||||
{t('home.loginTitle')}
|
||||
</h3>
|
||||
<p
|
||||
className={`${
|
||||
isDayTime ? "text-gray-600" : "text-gray-300"
|
||||
} mb-4`}
|
||||
>
|
||||
Heureux de vous retrouver ! Retrouvez votre compte tel que
|
||||
vous l'avez laissez !
|
||||
{t('home.loginDesc')}
|
||||
</p>
|
||||
<a
|
||||
href="/login"
|
||||
@ -689,7 +686,7 @@ function EnhancedWeatherHome() {
|
||||
: "text-indigo-400 hover:text-indigo-300"
|
||||
}`}
|
||||
>
|
||||
Se connecter <ArrowRight size={16} className="ml-2" />
|
||||
{t('home.loginBtn')} <ArrowRight size={16} className="ml-2" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@ -717,16 +714,14 @@ function EnhancedWeatherHome() {
|
||||
isDayTime ? "text-gray-800" : "text-white"
|
||||
}`}
|
||||
>
|
||||
Consulter les objets connectés météorologiques
|
||||
{t('home.consultObjectsTitle')}
|
||||
</h3>
|
||||
<p
|
||||
className={`${
|
||||
isDayTime ? "text-gray-600" : "text-gray-300"
|
||||
} mb-4`}
|
||||
>
|
||||
Accédez aux données en temps réel des objets connectés
|
||||
météorologiques, modifiez leurs paramètres et consultez
|
||||
l'historique des mesures.{" "}
|
||||
{t('home.consultObjectsDesc')}
|
||||
</p>
|
||||
<a
|
||||
href="/gestionObjets"
|
||||
@ -736,7 +731,7 @@ function EnhancedWeatherHome() {
|
||||
: "text-indigo-400 hover:text-indigo-300"
|
||||
}`}
|
||||
>
|
||||
Explorer les objets <ArrowRight size={16} className="ml-2" />
|
||||
{t('home.consultObjectsBtn')} <ArrowRight size={16} className="ml-2" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@ -761,16 +756,14 @@ function EnhancedWeatherHome() {
|
||||
isDayTime ? "text-gray-800" : "text-white"
|
||||
}`}
|
||||
>
|
||||
Ajouter un nouvel objet connecté
|
||||
{t('home.addObjTitle')}
|
||||
</h3>
|
||||
<p
|
||||
className={`${
|
||||
isDayTime ? "text-gray-600" : "text-gray-300"
|
||||
} mb-4`}
|
||||
>
|
||||
Intégrez facilement un nouvel objet connecté en renseignant
|
||||
ses informations et en configurant ses paramètres pour une
|
||||
gestion optimale.
|
||||
{t('home.addObjDesc')}
|
||||
</p>
|
||||
<a
|
||||
href="/ajouterObjet"
|
||||
@ -780,7 +773,7 @@ function EnhancedWeatherHome() {
|
||||
: "text-indigo-400 hover:text-indigo-300"
|
||||
}`}
|
||||
>
|
||||
Ajouter un objet <ArrowRight size={16} className="ml-2" />
|
||||
{t('home.addObjBtn')} <ArrowRight size={16} className="ml-2" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@ -808,15 +801,14 @@ function EnhancedWeatherHome() {
|
||||
isDayTime ? "text-gray-800" : "text-white"
|
||||
}`}
|
||||
>
|
||||
Accéder au tableau d'Administration
|
||||
{t('home.adminDashTitle')}
|
||||
</h3>
|
||||
<p
|
||||
className={`${
|
||||
isDayTime ? "text-gray-600" : "text-gray-300"
|
||||
} mb-4`}
|
||||
>
|
||||
Vous pourrez gérer les utilisateurs du site mais aussi les
|
||||
objets connectés
|
||||
{t('home.adminDashDesc')}
|
||||
</p>
|
||||
<a
|
||||
href="/dashboard"
|
||||
@ -826,7 +818,7 @@ function EnhancedWeatherHome() {
|
||||
: "text-indigo-400 hover:text-indigo-300"
|
||||
}`}
|
||||
>
|
||||
Tableau d'Administration{" "}
|
||||
{t('home.adminDashBtn')}
|
||||
<ArrowRight size={16} className="ml-2" />
|
||||
</a>
|
||||
</div>
|
||||
@ -852,15 +844,14 @@ function EnhancedWeatherHome() {
|
||||
isDayTime ? "text-gray-800" : "text-white"
|
||||
}`}
|
||||
>
|
||||
Gestion des objets connectés
|
||||
{t('home.manageObjTitle')}
|
||||
</h3>
|
||||
<p
|
||||
className={`${
|
||||
isDayTime ? "text-gray-600" : "text-gray-300"
|
||||
} mb-4`}
|
||||
>
|
||||
Ce module vous permet de gérer les capteurs et stations
|
||||
connectés de France de manière simple et efficace.
|
||||
{t('home.manageObjDesc')}
|
||||
</p>
|
||||
<a
|
||||
href="/gestion"
|
||||
@ -870,7 +861,7 @@ function EnhancedWeatherHome() {
|
||||
: "text-indigo-400 hover:text-indigo-300"
|
||||
}`}
|
||||
>
|
||||
Gestion des objets <ArrowRight size={16} className="ml-2" />
|
||||
{t('home.manageObjBtn')} <ArrowRight size={16} className="ml-2" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@ -900,7 +891,7 @@ function EnhancedWeatherHome() {
|
||||
isDayTime ? "text-gray-600" : "text-gray-400"
|
||||
}`}
|
||||
>
|
||||
© 2025 VigiMétéo. Tous droits réservés.
|
||||
{t('home.footerRights')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
import React, { useState } from "react";
|
||||
import { Mail, Lock, AlertCircle, CheckCircle, Info, X } from "lucide-react";
|
||||
import { useNavigate, Link } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import axios from "axios";
|
||||
import { useAuth } from "../AuthContext";
|
||||
import { API_BASE_URL } from "../config";
|
||||
|
||||
function Login() {
|
||||
const { t } = useTranslation();
|
||||
const [formData, setFormData] = useState({
|
||||
email: "",
|
||||
password: "",
|
||||
@ -48,7 +50,7 @@ function Login() {
|
||||
|
||||
try {
|
||||
// Afficher un message de chargement
|
||||
showAlert("info", "Connexion en cours...");
|
||||
showAlert("info", t('auth.login.loading'));
|
||||
|
||||
const response = await axios.post(`${API_BASE_URL}/login`, formData, {
|
||||
headers: {
|
||||
@ -58,7 +60,7 @@ function Login() {
|
||||
const data = response.data;
|
||||
|
||||
if (data.token) {
|
||||
showAlert("success", "Connexion réussie! Redirection...");
|
||||
showAlert("success", t('auth.login.success'));
|
||||
login(data.token);
|
||||
|
||||
// Court délai pour montrer le message de succès avant la redirection
|
||||
@ -66,25 +68,25 @@ function Login() {
|
||||
navigate("/");
|
||||
}, 1000);
|
||||
} else {
|
||||
showAlert("error", "Authentification échouée : token manquant dans la réponse");
|
||||
showAlert("error", t('auth.login.missingToken'));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Erreur lors de la connexion", error);
|
||||
|
||||
if (error.response) {
|
||||
if (error.response.status === 401) {
|
||||
showAlert("error", "Email ou mot de passe incorrect");
|
||||
showAlert("error", t('auth.login.incorrectAuth'));
|
||||
} else if (error.response.status === 422) {
|
||||
showAlert("error", "Données de formulaire invalides");
|
||||
showAlert("error", t('auth.login.invalidData'));
|
||||
} else if (error.response.status >= 500) {
|
||||
showAlert("error", "Erreur serveur. Veuillez réessayer plus tard.");
|
||||
showAlert("error", t('auth.login.serverError'));
|
||||
} else {
|
||||
showAlert("error", error.response.data.message || "Une erreur s'est produite lors de la connexion");
|
||||
showAlert("error", error.response.data.message || t('auth.login.genericError'));
|
||||
}
|
||||
} else if (error.request) {
|
||||
showAlert("error", "Impossible de joindre le serveur. Vérifiez votre connexion internet.");
|
||||
showAlert("error", t('auth.login.networkError'));
|
||||
} else {
|
||||
showAlert("error", "Une erreur s'est produite. Veuillez réessayer.");
|
||||
showAlert("error", t('auth.login.genericError'));
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -121,7 +123,7 @@ function Login() {
|
||||
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-50 py-12 px-4 sm:px-6 lg:px-8">
|
||||
<div className="md:w-96 w-full bg-white rounded-lg shadow-md p-6 mx-auto">
|
||||
<h2 className="text-2xl font-bold text-gray-800 mb-6 text-center">
|
||||
Connexion
|
||||
{t('auth.login.title')}
|
||||
</h2>
|
||||
|
||||
{/* Système d'alertes */}
|
||||
@ -144,7 +146,7 @@ function Login() {
|
||||
{/* Email */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Email:
|
||||
{t('auth.login.emailLabel')}
|
||||
</label>
|
||||
<div className="relative">
|
||||
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
@ -164,7 +166,7 @@ function Login() {
|
||||
{/* Mot de passe */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Mot de passe:
|
||||
{t('auth.login.passwordLabel')}
|
||||
</label>
|
||||
<div className="relative">
|
||||
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
@ -189,20 +191,19 @@ function Login() {
|
||||
type="submit"
|
||||
className="w-full flex justify-center py-2.5 px-4 border border-transparent rounded-lg shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
>
|
||||
Se connecter
|
||||
{t('auth.login.submitButton')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Lien vers la page d'inscription */}
|
||||
<div className="mt-4 text-sm text-center">
|
||||
<p>
|
||||
Vous n'avez pas de compte ?
|
||||
{t('auth.login.noAccount')}
|
||||
<Link
|
||||
to="/signup"
|
||||
className="text-indigo-600 hover:text-indigo-700 font-medium"
|
||||
className="text-indigo-600 hover:text-indigo-700 font-medium ml-1"
|
||||
>
|
||||
{" "}
|
||||
Inscrivez-vous ici
|
||||
{t('auth.login.signupLink')}
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Mail, User, Lock, Edit, Save } from 'lucide-react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { API_BASE_URL } from "../config";
|
||||
import { useAuth } from "../AuthContext";
|
||||
import axios from "axios";
|
||||
|
||||
function Profil() {
|
||||
const { t } = useTranslation();
|
||||
const [userData, setUserData] = useState({});
|
||||
|
||||
const { user } = useAuth();
|
||||
@ -61,7 +63,7 @@ function Profil() {
|
||||
setSuccessMessage('');
|
||||
|
||||
if (formData.newPassword !== formData.confirmPassword) {
|
||||
setErrorMessage("Les nouveaux mots de passe ne correspondent pas !");
|
||||
setErrorMessage(t('profile.errorMismatch'));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -74,7 +76,7 @@ function Profil() {
|
||||
})
|
||||
.then((response) => {
|
||||
console.log("Modification du mot de passe réussie :", response.data);
|
||||
setSuccessMessage("Mot de passe modifié avec succès !");
|
||||
setSuccessMessage(t('profile.successPass'));
|
||||
setFormData({
|
||||
oldPassword: '',
|
||||
newPassword: '',
|
||||
@ -83,17 +85,17 @@ function Profil() {
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Erreur lors de la modification du mot de passe :", error);
|
||||
setErrorMessage(error.response?.data?.error || "Une erreur est survenue");
|
||||
setErrorMessage(error.response?.data?.error || t('profile.errorGeneric'));
|
||||
});
|
||||
|
||||
setSuccessMessage("Mot de passe modifié avec succès !");
|
||||
setSuccessMessage(t('profile.successPass'));
|
||||
setFormData({
|
||||
oldPassword: '',
|
||||
newPassword: '',
|
||||
confirmPassword: ''
|
||||
});
|
||||
} catch (error) {
|
||||
setErrorMessage(error.message || "Une erreur est survenue");
|
||||
setErrorMessage(error.message || t('profile.errorGeneric'));
|
||||
}
|
||||
};
|
||||
|
||||
@ -113,11 +115,11 @@ function Profil() {
|
||||
|
||||
.catch((error) => {
|
||||
console.error("Erreur lors de la mise à jour du profil :", error);
|
||||
setErrorMessage(error.response?.data?.error || "Une erreur est survenue");
|
||||
setErrorMessage(error.response?.data?.error || t('profile.errorGeneric'));
|
||||
})
|
||||
.then((response) => {
|
||||
console.log("Mise à jour du profil réussie :", response.data);
|
||||
setSuccessMessage("Profil mis à jour avec succès !");
|
||||
setSuccessMessage(t('profile.successUpdate'));
|
||||
setEditMode(false);
|
||||
});
|
||||
};
|
||||
@ -125,7 +127,7 @@ function Profil() {
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-50 py-12 px-4 sm:px-6 lg:px-8">
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<h1 className="text-3xl font-bold text-gray-900 mb-8 text-center">Mon Profil</h1>
|
||||
<h1 className="text-3xl font-bold text-gray-900 mb-8 text-center">{t('profile.title')}</h1>
|
||||
|
||||
{errorMessage && (
|
||||
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative mb-4">
|
||||
@ -143,7 +145,7 @@ function Profil() {
|
||||
{/* Informations du profil */}
|
||||
<div className="bg-white rounded-lg shadow-md p-6">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h2 className="text-xl font-semibold text-gray-800">Informations Personnelles</h2>
|
||||
<h2 className="text-xl font-semibold text-gray-800">{t('profile.personalInfo')}</h2>
|
||||
<button
|
||||
onClick={() => setEditMode(!editMode)}
|
||||
className="text-indigo-600 hover:text-indigo-800"
|
||||
@ -164,13 +166,13 @@ function Profil() {
|
||||
</div>
|
||||
|
||||
<div className="bg-indigo-50 p-3 rounded-lg mb-4">
|
||||
<p className="text-sm text-gray-700">Points de fidélité: <span className="font-semibold">{userData.points}</span> ({userData.role})</p>
|
||||
<p className="text-sm text-gray-700">{t('profile.loyaltyPoints')} <span className="font-semibold">{userData.points}</span> ({userData.role})</p>
|
||||
</div>
|
||||
|
||||
{editMode ? (
|
||||
<>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Prénom:</label>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">{t('profile.firstName')}</label>
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
@ -181,7 +183,7 @@ function Profil() {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Nom:</label>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">{t('profile.lastName')}</label>
|
||||
<input
|
||||
type="text"
|
||||
name="surname"
|
||||
@ -191,7 +193,7 @@ function Profil() {
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Pseudo:</label>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">{t('profile.pseudo')}</label>
|
||||
<input
|
||||
type="text"
|
||||
name="pseudo"
|
||||
@ -202,7 +204,7 @@ function Profil() {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Email:</label>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">{t('profile.email')}</label>
|
||||
<div className="relative">
|
||||
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<Mail className="h-5 w-5 text-gray-400" />
|
||||
@ -221,25 +223,25 @@ function Profil() {
|
||||
type="submit"
|
||||
className="w-full flex justify-center py-2 px-4 border border-transparent rounded-lg shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
>
|
||||
Sauvegarder
|
||||
{t('profile.save')}
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-500">Prénom</p>
|
||||
<p className="text-sm font-medium text-gray-500">{t('profile.firstNameL')}</p>
|
||||
<p className="mt-1">{userData.name}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-500">Nom</p>
|
||||
<p className="text-sm font-medium text-gray-500">{t('profile.lastNameL')}</p>
|
||||
<p className="mt-1">{userData.surname}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-500">Pseudo</p>
|
||||
<p className="text-sm font-medium text-gray-500">{t('profile.pseudoL')}</p>
|
||||
<p className="mt-1">{userData.pseudo}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-500">Email</p>
|
||||
<p className="text-sm font-medium text-gray-500">{t('profile.emailL')}</p>
|
||||
<p className="mt-1">{userData.email}</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -249,11 +251,11 @@ function Profil() {
|
||||
|
||||
{/* Changement de mot de passe */}
|
||||
<div className="bg-white rounded-lg shadow-md p-6">
|
||||
<h2 className="text-xl font-semibold text-gray-800 mb-4">Modifier le mot de passe</h2>
|
||||
<h2 className="text-xl font-semibold text-gray-800 mb-4">{t('profile.changePasswordTitle')}</h2>
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Mot de passe actuel:
|
||||
{t('profile.currentPassword')}
|
||||
</label>
|
||||
<div className="relative">
|
||||
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
@ -273,7 +275,7 @@ function Profil() {
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Nouveau mot de passe:
|
||||
{t('profile.newPassword')}
|
||||
</label>
|
||||
<div className="relative">
|
||||
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
@ -293,7 +295,7 @@ function Profil() {
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Confirmer le nouveau mot de passe:
|
||||
{t('profile.confirmNewPassword')}
|
||||
</label>
|
||||
<div className="relative">
|
||||
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
@ -315,7 +317,7 @@ function Profil() {
|
||||
type="submit"
|
||||
className="w-full flex justify-center py-2.5 px-4 border border-transparent rounded-lg shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
>
|
||||
Modifier le mot de passe
|
||||
{t('profile.changePasswordTitle')}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Mail, User, Lock } from 'lucide-react';
|
||||
import { useNavigate, Link} from 'react-router-dom'; // Importation du hook useNavigate
|
||||
import { useNavigate, Link} from 'react-router-dom';
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { API_BASE_URL } from "../config.js";
|
||||
|
||||
function Signup() {
|
||||
const { t } = useTranslation();
|
||||
const [formData, setFormData] = useState({
|
||||
name: '',
|
||||
surname: '',
|
||||
@ -27,7 +29,7 @@ function Signup() {
|
||||
e.preventDefault();
|
||||
|
||||
if (formData.password !== formData.confirmPassword) {
|
||||
alert("Les mots de passe ne correspondent pas !");
|
||||
alert(t('auth.signup.passNoMatch'));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -43,10 +45,10 @@ function Signup() {
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || "Erreur lors de l'inscription");
|
||||
throw new Error(data.error || t('auth.signup.error'));
|
||||
}
|
||||
|
||||
alert("Inscription réussie !");
|
||||
alert(t('auth.signup.success'));
|
||||
|
||||
|
||||
navigate("/");
|
||||
@ -58,11 +60,11 @@ function Signup() {
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-50 py-12 px-4 sm:px-6 lg:px-8">
|
||||
<div className="w-full md:w-96 bg-white rounded-lg shadow-md p-6 mx-auto">
|
||||
<h2 className="text-2xl font-bold text-gray-800 mb-6 text-center">Inscription</h2>
|
||||
<h2 className="text-2xl font-bold text-gray-800 mb-6 text-center">{t('auth.signup.title')}</h2>
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Prénom:
|
||||
{t('auth.signup.firstNameLabel')}
|
||||
</label>
|
||||
<div className="relative">
|
||||
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
@ -81,7 +83,7 @@ function Signup() {
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Nom:
|
||||
{t('auth.signup.lastNameLabel')}
|
||||
</label>
|
||||
<div className="relative">
|
||||
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
@ -100,7 +102,7 @@ function Signup() {
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Pseudo:
|
||||
{t('auth.signup.pseudoLabel')}
|
||||
</label>
|
||||
<div className="relative">
|
||||
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
@ -120,7 +122,7 @@ function Signup() {
|
||||
{/* Sexe */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Sexe:
|
||||
{t('auth.signup.genderLabel')}
|
||||
</label>
|
||||
<div className="flex gap-6 items-center">
|
||||
<label className="inline-flex items-center">
|
||||
@ -132,7 +134,7 @@ function Signup() {
|
||||
onChange={handleChange}
|
||||
className="form-radio h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300"
|
||||
/>
|
||||
<span className="ml-2">Homme</span>
|
||||
<span className="ml-2">{t('auth.signup.genderMale')}</span>
|
||||
</label>
|
||||
<label className="inline-flex items-center">
|
||||
<input
|
||||
@ -143,7 +145,7 @@ function Signup() {
|
||||
onChange={handleChange}
|
||||
className="form-radio h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300"
|
||||
/>
|
||||
<span className="ml-2">Femme</span>
|
||||
<span className="ml-2">{t('auth.signup.genderFemale')}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@ -151,7 +153,7 @@ function Signup() {
|
||||
{/* Email */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Email:
|
||||
{t('auth.signup.emailLabel')}
|
||||
</label>
|
||||
<div className="relative">
|
||||
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
@ -171,7 +173,7 @@ function Signup() {
|
||||
{/* Mot de passe */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Mot de passe:
|
||||
{t('auth.signup.passwordLabel')}
|
||||
</label>
|
||||
<div className="relative">
|
||||
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
@ -192,7 +194,7 @@ function Signup() {
|
||||
{/* Confirmer mot de passe */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Confirmer le mot de passe:
|
||||
{t('auth.signup.confirmPasswordLabel')}
|
||||
</label>
|
||||
<div className="relative">
|
||||
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
@ -216,15 +218,15 @@ function Signup() {
|
||||
type="submit"
|
||||
className="w-full flex justify-center py-2.5 px-4 border border-transparent rounded-lg shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
>
|
||||
S'inscrire
|
||||
{t('auth.signup.submitButton')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/*Si il a déjà un compte*/}
|
||||
<div className="mt-4 text-sm text-center">
|
||||
<p>
|
||||
Vous avez déjà un compte ?
|
||||
<Link to="/login" className="text-indigo-600 hover:text-indigo-700 font-medium"> Connectez-vous ici</Link>
|
||||
{t('auth.signup.hasAccount')}
|
||||
<Link to="/login" className="text-indigo-600 hover:text-indigo-700 font-medium ml-1"> {t('auth.signup.loginLink')}</Link>
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user