changement au niveau de requetes adaptés aux collaborateurs AD
This commit is contained in:
@@ -7,6 +7,7 @@ import Requests from './pages/Requests';
|
||||
import Calendar from './pages/Calendar';
|
||||
import Manager from './pages/Manager';
|
||||
import ProtectedRoute from './components/ProtectedRoute';
|
||||
import EmployeeDetails from './pages/EmployeeDetails';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
@@ -34,6 +35,7 @@ function App() {
|
||||
<Manager />
|
||||
</ProtectedRoute>
|
||||
} />
|
||||
<Route path="/employee/:id" element={<ProtectedRoute><EmployeeDetails /></ProtectedRoute>} />
|
||||
<Route path="/" element={<Navigate to="/dashboard" replace />} />
|
||||
</Routes>
|
||||
</Router>
|
||||
|
||||
@@ -4,9 +4,19 @@ export const msalConfig = {
|
||||
clientId: "4bb4cc24-bac3-427c-b02c-5d14fc67b561", // Application (client) ID dans Azure
|
||||
authority: "https://login.microsoftonline.com/9840a2a0-6ae1-4688-b03d-d2ec291be0f9", // Directory (tenant) ID
|
||||
redirectUri: "http://localhost:5173"
|
||||
},
|
||||
cache: {
|
||||
cacheLocation: "sessionStorage",
|
||||
storeAuthStateInCookie: false,
|
||||
}
|
||||
};
|
||||
|
||||
export const loginRequest = {
|
||||
scopes: ["User.Read"] // Permet de lire le profil utilisateur
|
||||
scopes: [
|
||||
"User.Read",
|
||||
"User.Read.All", // Pour lire les profils des autres utilisateurs
|
||||
"Group.Read.All", // Pour lire les groupes
|
||||
"GroupMember.Read.All", // Pour lire les membres des groupes
|
||||
"Mail.Send" //Envoyer les emails.
|
||||
]
|
||||
};
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { X, Calendar, Clock, AlertCircle, RotateCcw } from 'lucide-react';
|
||||
import { useMsal } from "@azure/msal-react";
|
||||
import { loginRequest } from "../AuthConfig";
|
||||
|
||||
|
||||
const NewLeaveRequestModal = ({
|
||||
onClose,
|
||||
availableLeaveCounters,
|
||||
userId,
|
||||
userEmail,
|
||||
userName,
|
||||
accessToken,
|
||||
onRequestSubmitted,
|
||||
preselectedStartDate = null,
|
||||
preselectedEndDate = null,
|
||||
preselectedType = null
|
||||
preselectedType = null,
|
||||
|
||||
}) => {
|
||||
const [formData, setFormData] = useState({
|
||||
types: preselectedType ? [preselectedType] : [],
|
||||
@@ -18,6 +25,7 @@ const NewLeaveRequestModal = ({
|
||||
medicalDocuments: []
|
||||
});
|
||||
|
||||
|
||||
const [typeDistribution, setTypeDistribution] = useState({});
|
||||
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
@@ -27,6 +35,25 @@ const NewLeaveRequestModal = ({
|
||||
const [isOtherChecked, setIsOtherChecked] = useState(false);
|
||||
const [otherLeaveType, setOtherLeaveType] = useState('');
|
||||
|
||||
const { instance, accounts } = useMsal();
|
||||
|
||||
|
||||
// --- Helper pour garantir un token Graph valide
|
||||
const ensureGraphToken = async () => {
|
||||
if (!accounts[0]) throw new Error("Aucun utilisateur connecté");
|
||||
|
||||
const request = { ...loginRequest, account: accounts[0] };
|
||||
|
||||
try {
|
||||
const response = await instance.acquireTokenSilent(request);
|
||||
return response.accessToken;
|
||||
} catch (err) {
|
||||
console.warn("⚠️ Silent token failed, trying popup:", err);
|
||||
const response = await instance.acquireTokenPopup(request);
|
||||
return response.accessToken;
|
||||
}
|
||||
};
|
||||
|
||||
// Vérifier si des valeurs sont pré-sélectionnées
|
||||
useEffect(() => {
|
||||
if (preselectedStartDate || preselectedEndDate || preselectedType) {
|
||||
@@ -273,55 +300,50 @@ const NewLeaveRequestModal = ({
|
||||
}));
|
||||
|
||||
const requestData = {
|
||||
EmployeeId: userId,
|
||||
DateDebut: formData.startDate,
|
||||
DateFin: formData.endDate,
|
||||
Commentaire: formData.reason,
|
||||
NombreJours: calculatedDays,
|
||||
Repartition: repartition
|
||||
Repartition: repartition,
|
||||
Email: userEmail,
|
||||
Nom: userName
|
||||
};
|
||||
console.log("Payload envoyé au backend :", JSON.stringify(requestData, null, 2));
|
||||
|
||||
const formDataToSend = new FormData();
|
||||
formDataToSend.append('data', JSON.stringify(requestData));
|
||||
|
||||
// Ajouter les fichiers
|
||||
formData.medicalDocuments.forEach((file, index) => {
|
||||
formDataToSend.append(`medicalDocuments[]`, file);
|
||||
});
|
||||
console.log("📤 Payload envoyé au backend :", JSON.stringify(requestData, null, 2));
|
||||
|
||||
const response = await fetch('http://localhost/GTA/project/public/php/submitLeaveRequest.php', {
|
||||
method: 'POST',
|
||||
body: formDataToSend
|
||||
headers: {
|
||||
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(requestData)
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
console.log("📥 Réponse backend :", result);
|
||||
|
||||
const text = await response.text();
|
||||
let result;
|
||||
try {
|
||||
result = JSON.parse(text);
|
||||
} catch (err) {
|
||||
console.error("Réponse non JSON:", text);
|
||||
setError("Erreur serveur : réponse invalide.");
|
||||
if (!result.success) {
|
||||
setError(result.message || "Erreur lors de l'enregistrement");
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.success) {
|
||||
onRequestSubmitted?.();
|
||||
onClose();
|
||||
} else {
|
||||
setError(result.message || 'Erreur lors de la soumission');
|
||||
}
|
||||
console.log("✅ Demande enregistrée (et mails envoyés côté PHP)");
|
||||
|
||||
// Fermer modal et rafraîchir
|
||||
onRequestSubmitted?.();
|
||||
onClose();
|
||||
|
||||
} catch (error) {
|
||||
console.error('Erreur:', error);
|
||||
setError('Erreur de connexion au serveur');
|
||||
console.error('❌ Erreur handleSubmit:', error);
|
||||
setError("Erreur de connexion au serveur");
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
const getTypeLabel = (type) => {
|
||||
switch (type) {
|
||||
case 'CP': return 'Congés payés';
|
||||
|
||||
@@ -13,7 +13,7 @@ const Sidebar = ({ isOpen, onToggle }) => {
|
||||
switch (role) {
|
||||
case 'Admin':
|
||||
return 'bg-red-100 text-red-800';
|
||||
case 'Manager':
|
||||
case 'Validateur':
|
||||
return 'bg-green-100 text-green-800';
|
||||
default:
|
||||
return 'bg-blue-100 text-blue-800';
|
||||
@@ -109,7 +109,7 @@ const Sidebar = ({ isOpen, onToggle }) => {
|
||||
<span className="font-medium">Calendrier</span>
|
||||
</Link>
|
||||
|
||||
{(user?.role === 'Manager' || user?.role === 'Admin' || user?.role === 'Employe') && (
|
||||
{(user?.role === 'Validateur' || user?.role === 'Admin' || user?.role === 'Collaborateur') && (
|
||||
<Link
|
||||
to="/manager"
|
||||
onClick={() => window.innerWidth < 1024 && onToggle()}
|
||||
@@ -118,7 +118,7 @@ const Sidebar = ({ isOpen, onToggle }) => {
|
||||
>
|
||||
<Users className="w-5 h-5" />
|
||||
<span className="font-medium">
|
||||
{user?.role === 'Employe' ? 'Mon équipe' : 'Équipe'}
|
||||
{user?.role === 'Collaborateur' ? 'Mon équipe' : 'Équipe'}
|
||||
</span>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { createContext, useContext, useState, useEffect } from 'react';
|
||||
import * as msal from '@azure/msal-browser';
|
||||
import { msalConfig, loginRequest } from '../AuthConfig';
|
||||
|
||||
const AuthContext = createContext();
|
||||
|
||||
@@ -11,70 +12,196 @@ export const useAuth = () => {
|
||||
return context;
|
||||
};
|
||||
|
||||
const msalConfig = {
|
||||
auth: {
|
||||
clientId: '4bb4cc24-bac3-427c-b02c-5d14fc67b561',
|
||||
authority: 'https://login.microsoftonline.com/9840a2a0-6ae1-4688-b03d-d2ec291be0f9',
|
||||
redirectUri: window.location.origin,
|
||||
},
|
||||
};
|
||||
|
||||
const msalInstance = new msal.PublicClientApplication(msalConfig);
|
||||
|
||||
export const AuthProvider = ({ children }) => {
|
||||
const [user, setUser] = useState(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [userGroups, setUserGroups] = useState([]);
|
||||
const [isAuthorized, setIsAuthorized] = useState(false);
|
||||
|
||||
// Initialise MSAL au montage
|
||||
// Fonction pour obtenir l'URL de l'API backend
|
||||
const getApiUrl = (endpoint) => {
|
||||
const possibleUrls = [
|
||||
'http://localhost/GTA/project/public/php/',
|
||||
'http://localhost:80/GTA/project/public/php/',
|
||||
'http://localhost/GTA/public/php/',
|
||||
'http://localhost/public/php/'
|
||||
];
|
||||
return possibleUrls[0] + endpoint; // Utilisez votre URL préférée
|
||||
};
|
||||
|
||||
// Vérifier les groupes utilisateur via l'API backend
|
||||
const checkUserAuthorization = async (userPrincipalName, accessToken) => {
|
||||
try {
|
||||
const response = await fetch(getApiUrl('check-user-groups.php'), {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${accessToken}`
|
||||
},
|
||||
body: JSON.stringify({ userPrincipalName })
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
setUserGroups(data.groups || []);
|
||||
setIsAuthorized(data.authorized || false);
|
||||
return data;
|
||||
}
|
||||
return { authorized: false, groups: [] };
|
||||
} catch (error) {
|
||||
console.error('Erreur vérification groupes:', error);
|
||||
return { authorized: false, groups: [] };
|
||||
}
|
||||
};
|
||||
|
||||
// Synchroniser l'utilisateur avec la base locale
|
||||
const syncUserToDatabase = async (entraUser, accessToken) => {
|
||||
try {
|
||||
const response = await fetch(getApiUrl('check-user-groups.php'), {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${accessToken}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
entraUserId: entraUser.id,
|
||||
userPrincipalName: entraUser.userPrincipalName,
|
||||
email: entraUser.mail || entraUser.userPrincipalName,
|
||||
displayName: entraUser.displayName,
|
||||
givenName: entraUser.givenName,
|
||||
surname: entraUser.surname,
|
||||
jobTitle: entraUser.jobTitle,
|
||||
department: entraUser.department,
|
||||
officeLocation: entraUser.officeLocation
|
||||
})
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
return await response.json();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Erreur synchronisation utilisateur:', error);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
// Initialisation MSAL
|
||||
useEffect(() => {
|
||||
const initializeMsal = async () => {
|
||||
try {
|
||||
await msalInstance.initialize();
|
||||
|
||||
// Vérifier si il y a un utilisateur connecté
|
||||
const accounts = msalInstance.getAllAccounts();
|
||||
if (accounts.length > 0) {
|
||||
// Essayer de récupérer un token silencieusement
|
||||
try {
|
||||
const response = await msalInstance.acquireTokenSilent({
|
||||
...loginRequest,
|
||||
account: accounts[0]
|
||||
});
|
||||
|
||||
await handleSuccessfulAuth(response);
|
||||
} catch (error) {
|
||||
console.log('Token silent acquisition failed:', error);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Erreur d'initialisation MSAL:", error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
initializeMsal();
|
||||
|
||||
const savedUser = localStorage.getItem('user');
|
||||
if (savedUser) {
|
||||
try {
|
||||
setUser(JSON.parse(savedUser));
|
||||
} catch (error) {
|
||||
console.error("Erreur parsing utilisateur sauvegardé:", error);
|
||||
localStorage.removeItem('user');
|
||||
}
|
||||
}
|
||||
setIsLoading(false);
|
||||
}, []);
|
||||
|
||||
const login = async (email, password) => {
|
||||
// Gérer l'authentification réussie
|
||||
const handleSuccessfulAuth = async (authResponse) => {
|
||||
try {
|
||||
const possibleUrls = [
|
||||
'http://localhost/GTA/project/public/php/login.php',
|
||||
'http://localhost:80/GTA/project/public/php/login.php',
|
||||
'http://localhost/GTA/public/php/login.php',
|
||||
'http://localhost/public/php/login.php'
|
||||
];
|
||||
const account = authResponse.account;
|
||||
const accessToken = authResponse.accessToken;
|
||||
|
||||
let response = null;
|
||||
for (const url of possibleUrls) {
|
||||
// Récupérer profil Microsoft Graph
|
||||
const graphResponse = await fetch('https://graph.microsoft.com/v1.0/me', {
|
||||
headers: { 'Authorization': `Bearer ${accessToken}` }
|
||||
});
|
||||
|
||||
let entraUser = {
|
||||
id: account.homeAccountId,
|
||||
displayName: account.name,
|
||||
userPrincipalName: account.username,
|
||||
mail: account.username
|
||||
};
|
||||
|
||||
if (graphResponse.ok) {
|
||||
const graphData = await graphResponse.json();
|
||||
entraUser = { ...entraUser, ...graphData };
|
||||
}
|
||||
|
||||
// 🔹 Synchroniser l’utilisateur dans la DB
|
||||
const syncResult = await syncUserToDatabase(entraUser, accessToken);
|
||||
|
||||
// 🚀 NEW : si admin → lancer full-sync.php
|
||||
if (syncResult?.role === "Admin") {
|
||||
try {
|
||||
console.log("Test URL:", url);
|
||||
response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email, mot_de_passe: password }),
|
||||
const syncResp = await fetch(getApiUrl('full-sync.php'), {
|
||||
method: "POST",
|
||||
headers: { "Authorization": `Bearer ${accessToken}` }
|
||||
});
|
||||
if (response.ok) break;
|
||||
} catch {
|
||||
continue;
|
||||
const syncData = await syncResp.json();
|
||||
console.log("Résultat Full Sync:", syncData);
|
||||
} catch (err) {
|
||||
console.error("Erreur synchronisation groupes:", err);
|
||||
}
|
||||
}
|
||||
|
||||
if (!response || !response.ok) {
|
||||
throw new Error('Aucune URL de connexion accessible');
|
||||
// 🔹 Vérifier autorisation via groupes DB
|
||||
const authResult = await checkUserAuthorization(entraUser.userPrincipalName, accessToken);
|
||||
|
||||
if (authResult.authorized) {
|
||||
const userData = {
|
||||
id: syncResult?.localUserId || entraUser.id,
|
||||
entraUserId: entraUser.id,
|
||||
name: entraUser.displayName,
|
||||
prenom: entraUser.givenName || entraUser.displayName?.split(' ')[0] || '',
|
||||
nom: entraUser.surname || entraUser.displayName?.split(' ')[1] || '',
|
||||
email: entraUser.mail || entraUser.userPrincipalName,
|
||||
userPrincipalName: entraUser.userPrincipalName,
|
||||
role: syncResult?.role || 'Employe',
|
||||
service: syncResult?.service || 'Non défini',
|
||||
jobTitle: entraUser.jobTitle,
|
||||
department: entraUser.department,
|
||||
officeLocation: entraUser.officeLocation,
|
||||
groups: authResult.groups
|
||||
};
|
||||
|
||||
setUser(userData);
|
||||
setIsAuthorized(true);
|
||||
return true;
|
||||
} else {
|
||||
throw new Error('Utilisateur non autorisé - pas membre des groupes requis');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de la gestion de l\'authentification:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Connexion classique (email/mot de passe)
|
||||
const login = async (email, password) => {
|
||||
try {
|
||||
const response = await fetch(getApiUrl('login.php'), {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email, mot_de_passe: password }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Erreur de connexion');
|
||||
}
|
||||
|
||||
const text = await response.text();
|
||||
@@ -98,49 +225,79 @@ export const AuthProvider = ({ children }) => {
|
||||
};
|
||||
|
||||
setUser(userData);
|
||||
localStorage.setItem('user', JSON.stringify(userData));
|
||||
setIsAuthorized(true);
|
||||
return true;
|
||||
} else {
|
||||
console.error("Échec connexion:", data.message);
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
} catch (error) {
|
||||
console.error("Erreur de connexion:", error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Connexion Office 365
|
||||
const loginWithO365 = async () => {
|
||||
try {
|
||||
const loginResponse = await msalInstance.loginPopup({
|
||||
scopes: ["user.read"]
|
||||
});
|
||||
const account = loginResponse.account;
|
||||
if (account) {
|
||||
const userData = {
|
||||
id: account.homeAccountId,
|
||||
name: account.name,
|
||||
email: account.username,
|
||||
role: 'Employe',
|
||||
service: 'Non défini',
|
||||
};
|
||||
setUser(userData);
|
||||
localStorage.setItem('user', JSON.stringify(userData));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
const authResponse = await msalInstance.loginPopup(loginRequest);
|
||||
await handleSuccessfulAuth(authResponse);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Erreur login Office 365:', error);
|
||||
return false;
|
||||
if (error.message?.includes('non autorisé')) {
|
||||
throw new Error('Accès refusé: Vous n\'êtes pas membre d\'un groupe autorisé.');
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const logout = () => {
|
||||
setUser(null);
|
||||
localStorage.removeItem('user');
|
||||
// Déconnexion
|
||||
const logout = async () => {
|
||||
try {
|
||||
const accounts = msalInstance.getAllAccounts();
|
||||
if (accounts.length > 0) {
|
||||
await msalInstance.logoutPopup({
|
||||
account: accounts[0]
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de la déconnexion:', error);
|
||||
} finally {
|
||||
setUser(null);
|
||||
setUserGroups([]);
|
||||
setIsAuthorized(false);
|
||||
}
|
||||
};
|
||||
|
||||
const value = { user, login, loginWithO365, logout, isLoading };
|
||||
// Obtenir un token pour l'API
|
||||
const getAccessToken = async () => {
|
||||
try {
|
||||
const accounts = msalInstance.getAllAccounts();
|
||||
if (accounts.length === 0) {
|
||||
throw new Error('Aucun compte connecté');
|
||||
}
|
||||
|
||||
const response = await msalInstance.acquireTokenSilent({
|
||||
...loginRequest,
|
||||
account: accounts[0]
|
||||
});
|
||||
|
||||
return response.accessToken;
|
||||
} catch (error) {
|
||||
console.error('Erreur obtention token:', error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const value = {
|
||||
user,
|
||||
userGroups,
|
||||
isAuthorized,
|
||||
login,
|
||||
loginWithO365,
|
||||
logout,
|
||||
isLoading,
|
||||
getAccessToken
|
||||
};
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={value}>
|
||||
@@ -149,4 +306,4 @@ export const AuthProvider = ({ children }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default AuthContext;
|
||||
export default AuthContext;
|
||||
@@ -1,10 +1,17 @@
|
||||
import { StrictMode } from 'react';
|
||||
import { StrictMode } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import App from './App.jsx';
|
||||
import './index.css';
|
||||
import { MsalProvider } from "@azure/msal-react";
|
||||
import { PublicClientApplication } from "@azure/msal-browser";
|
||||
import { msalConfig } from "./AuthConfig";
|
||||
|
||||
const msalInstance = new PublicClientApplication(msalConfig);
|
||||
|
||||
createRoot(document.getElementById('root')).render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
<StrictMode>
|
||||
<MsalProvider instance={msalInstance}>
|
||||
<App />
|
||||
</MsalProvider>
|
||||
</StrictMode>
|
||||
);
|
||||
@@ -642,7 +642,7 @@ END:VEVENT`;
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Ton rendu de calendrier ici */}
|
||||
|
||||
</div>
|
||||
|
||||
{/* Modal nouvelle demande */}
|
||||
@@ -654,9 +654,12 @@ END:VEVENT`;
|
||||
}}
|
||||
availableLeaveCounters={leaveCounters}
|
||||
userId={user?.id}
|
||||
|
||||
onRequestSubmitted={() => {
|
||||
resetSelection();
|
||||
}}
|
||||
userEmail={user.email}
|
||||
userName={`${user.prenom} ${user.nom}`}
|
||||
preselectedStartDate={selectedDate}
|
||||
preselectedEndDate={selectedEndDate}
|
||||
preselectedType={preselectedType}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useAuth } from '../context/AuthContext';
|
||||
import Sidebar from '../components/Sidebar';
|
||||
import { Calendar as CalendarIcon, Clock, Users, TrendingUp, Plus, Settings, RefreshCw, Menu, FileText } from 'lucide-react';
|
||||
import { Calendar as CalendarIcon, Clock, Plus, Settings, RefreshCw, Menu, FileText } from 'lucide-react';
|
||||
import NewLeaveRequestModal from '../components/NewLeaveRequestModal';
|
||||
import { useMsal } from "@azure/msal-react";
|
||||
import { loginRequest } from "../authConfig";
|
||||
|
||||
const Dashboard = () => {
|
||||
const { user } = useAuth();
|
||||
const [graphToken, setGraphToken] = useState(null);
|
||||
const [sidebarOpen, setSidebarOpen] = useState(false);
|
||||
const [leaveCounters, setLeaveCounters] = useState({
|
||||
availableCP: 0,
|
||||
@@ -20,54 +23,82 @@ const Dashboard = () => {
|
||||
const [recentRequests, setRecentRequests] = useState([]);
|
||||
const [allRequests, setAllRequests] = useState([]);
|
||||
|
||||
const { instance, accounts } = useMsal();
|
||||
|
||||
// Récupération du bon ID utilisateur (CollaborateurAD)
|
||||
const userId = user?.id || user?.CollaborateurADId || user?.ID;
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (user?.id) {
|
||||
console.log("🔎 Utilisateur chargé dans Dashboard:", user);
|
||||
}, [user]);
|
||||
|
||||
useEffect(() => {
|
||||
if (accounts.length > 0) {
|
||||
const request = {
|
||||
...loginRequest,
|
||||
account: accounts[0],
|
||||
};
|
||||
|
||||
instance.acquireTokenSilent(request)
|
||||
.then((response) => {
|
||||
console.log("✅ Token Graph récupéré (silent):", response.accessToken);
|
||||
setGraphToken(response.accessToken);
|
||||
})
|
||||
.catch(async (err) => {
|
||||
console.warn("⚠️ Silent échoué, on tente un popup:", err);
|
||||
try {
|
||||
const tokenResponse = await instance.acquireTokenPopup(request);
|
||||
console.log("✅ Token Graph récupéré (popup):", tokenResponse.accessToken);
|
||||
setGraphToken(tokenResponse.accessToken);
|
||||
} catch (popupErr) {
|
||||
console.error("❌ Impossible d'obtenir un token:", popupErr);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [accounts, instance]);
|
||||
|
||||
// Vérification rapide du token
|
||||
useEffect(() => {
|
||||
if (graphToken) {
|
||||
console.log("🔎 Token prêt ?",
|
||||
graphToken.split(".").length === 3 ? "✅ JWT valide" : "❌ Token invalide: " + graphToken
|
||||
);
|
||||
}
|
||||
}, [graphToken]);
|
||||
|
||||
// 🔄 Récupération compteurs et demandes
|
||||
useEffect(() => {
|
||||
if (userId) {
|
||||
fetchLeaveCounters();
|
||||
fetchAllRequests();
|
||||
}
|
||||
}, [user]);
|
||||
}, [userId]);
|
||||
|
||||
const fetchLeaveCounters = async () => {
|
||||
try {
|
||||
const url = `http://localhost/GTA/project/public/php/getLeaveCounters.php?user_id=${user.id}`;
|
||||
console.log(' Dashboard - Récupération des compteurs:', url);
|
||||
console.log(' Dashboard - User ID utilisé:', user.id);
|
||||
|
||||
const url = `http://localhost/GTA/project/public/php/getLeaveCounters.php?user_id=${userId}`;
|
||||
const response = await fetch(url);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Erreur HTTP: ${response.status}`);
|
||||
}
|
||||
if (!response.ok) throw new Error(`Erreur HTTP: ${response.status}`);
|
||||
|
||||
const text = await response.text();
|
||||
console.log(' Dashboard - Réponse brute compteurs:', text);
|
||||
console.log(' Dashboard - Longueur de la réponse:', text.length);
|
||||
console.log(' Dashboard - Premiers 500 caractères:', text.substring(0, 500));
|
||||
console.log("🔎 Réponse brute getLeaveCounters:", text);
|
||||
|
||||
let data;
|
||||
try {
|
||||
data = JSON.parse(text);
|
||||
} catch (parseError) {
|
||||
console.error(' Dashboard - Erreur parsing JSON:', parseError);
|
||||
console.error(' Dashboard - Texte qui a causé l\'erreur:', text);
|
||||
throw new Error('Réponse PHP invalide: ' + text.substring(0, 200));
|
||||
} catch (e) {
|
||||
throw new Error("Le serveur PHP ne renvoie pas un JSON valide");
|
||||
}
|
||||
|
||||
console.log(' Dashboard - Compteurs parsés:', data);
|
||||
|
||||
if (data.success) {
|
||||
console.log(' Dashboard - Compteurs récupérés:', data.counters);
|
||||
setLeaveCounters(data.counters);
|
||||
} else {
|
||||
console.error(' Dashboard - Erreur API compteurs:', data.message);
|
||||
console.error(' Dashboard - Données complètes:', data);
|
||||
throw new Error('API Error: ' + (data.message || 'Erreur inconnue'));
|
||||
throw new Error(data.message || "Erreur lors de la récupération des compteurs");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('💥 Dashboard - Erreur lors de la récupération des compteurs:', error);
|
||||
|
||||
// Fallback avec des données par défaut
|
||||
console.log(' Dashboard - Utilisation des données par défaut');
|
||||
console.error("💥 Erreur compteurs:", error);
|
||||
setLeaveCounters({
|
||||
availableCP: 25,
|
||||
availableRTT: 10,
|
||||
@@ -75,78 +106,63 @@ const Dashboard = () => {
|
||||
rttInProcess: 0,
|
||||
absenteism: 0
|
||||
});
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
const fetchAllRequests = async () => {
|
||||
console.log(' Dashboard - Début fetchAllRequests pour user:', user?.id);
|
||||
|
||||
try {
|
||||
const url = `http://localhost/GTA/project/public/php/getRequests.php?user_id=${user.id}`;
|
||||
console.log(' Dashboard - URL appelée:', url);
|
||||
|
||||
const url = `http://localhost/GTA/project/public/php/getRequests.php?user_id=${userId}`;
|
||||
const response = await fetch(url);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Erreur HTTP: ${response.status}`);
|
||||
}
|
||||
if (!response.ok) throw new Error(`Erreur HTTP: ${response.status}`);
|
||||
|
||||
const text = await response.text();
|
||||
console.log(' Dashboard - Réponse brute:', text);
|
||||
console.log("🔎 Réponse brute getRequests:", text);
|
||||
|
||||
const data = JSON.parse(text);
|
||||
console.log(' Dashboard - Données parsées:', data);
|
||||
let data;
|
||||
try {
|
||||
data = JSON.parse(text);
|
||||
} catch {
|
||||
throw new Error("Le serveur PHP ne renvoie pas un JSON valide");
|
||||
}
|
||||
|
||||
if (data.success) {
|
||||
console.log('Dashboard - Demandes récupérées:', data.requests?.length || 0);
|
||||
setAllRequests(data.requests || []);
|
||||
setRecentRequests(data.requests?.slice(0, 3) || []);
|
||||
} else {
|
||||
throw new Error(data.message || 'Erreur lors de la récupération des demandes');
|
||||
throw new Error(data.message || "Erreur lors de la récupération des demandes");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(' Dashboard - Erreur lors de la récupération des demandes:', error);
|
||||
|
||||
// En cas d'erreur, on garde des tableaux vides
|
||||
console.error("💥 Erreur demandes:", error);
|
||||
setAllRequests([]);
|
||||
setRecentRequests([]);
|
||||
}
|
||||
};
|
||||
|
||||
const handleResetCounters = async () => {
|
||||
if (!confirm(' ATTENTION !\n\nCette action va réinitialiser TOUS les compteurs de congés selon les règles de gestion :\n\n• Congés Payés : 25 jours (exercice 01/06 au 31/05)\n• RTT : 10 jours pour 2025 (exercice 01/01 au 31/12)\n• Congés Maladie : 0 jours\n\nCette action est IRRÉVERSIBLE !\n\nÊtes-vous sûr de vouloir continuer ?')) {
|
||||
return;
|
||||
}
|
||||
if (!confirm("⚠️ Voulez-vous vraiment réinitialiser les compteurs ?")) return;
|
||||
|
||||
try {
|
||||
const response = await fetch('http://localhost/GTA/project/public/php/resetLeaveCounters.php', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ manual_reset: true }),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
alert(` Réinitialisation réussie !\n\n• ${data.details.employees_updated} employés mis à jour\n• Exercice CP : ${data.details.leave_year}\n• Année RTT : ${data.details.rtt_year}\n• Date : ${data.details.reset_date}`);
|
||||
alert("✅ Réinitialisation réussie !");
|
||||
fetchLeaveCounters();
|
||||
} else {
|
||||
alert(` Erreur lors de la réinitialisation :\n${data.message}`);
|
||||
alert(`❌ Erreur : ${data.message}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Erreur:', error);
|
||||
alert(' Erreur de connexion au serveur');
|
||||
console.error("Erreur:", error);
|
||||
alert("❌ Erreur serveur");
|
||||
}
|
||||
};
|
||||
|
||||
const openManualResetPage = () => {
|
||||
window.open('http://localhost/GTA/project/public/php/manualResetCounters.php', '_blank');
|
||||
};
|
||||
|
||||
const getStatusColor = (status) => {
|
||||
switch (status) {
|
||||
case 'Approuvé':
|
||||
@@ -392,7 +408,10 @@ const Dashboard = () => {
|
||||
<NewLeaveRequestModal
|
||||
onClose={() => setShowNewRequestModal(false)}
|
||||
availableLeaveCounters={leaveCounters}
|
||||
userId={user?.id}
|
||||
accessToken={graphToken}
|
||||
userId={userId}
|
||||
userEmail={user.email}
|
||||
userName={`${user.prenom} ${user.nom}`}
|
||||
onRequestSubmitted={() => {
|
||||
fetchLeaveCounters();
|
||||
fetchAllRequests();
|
||||
|
||||
114
project/src/pages/EmployeeDetails.jsx
Normal file
114
project/src/pages/EmployeeDetails.jsx
Normal file
@@ -0,0 +1,114 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import Sidebar from '../components/Sidebar';
|
||||
import { Calendar, Clock, CheckCircle, XCircle } from 'lucide-react';
|
||||
|
||||
const EmployeeDetails = () => {
|
||||
const { id } = useParams();
|
||||
const [employee, setEmployee] = useState(null);
|
||||
const [requests, setRequests] = useState([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
fetchEmployeeData();
|
||||
}, [id]);
|
||||
|
||||
const fetchEmployeeData = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
// 1️⃣ Données employé
|
||||
const resEmployee = await fetch(`http://localhost/GTA/project/public/php/getEmploye.php?id=${id}`);
|
||||
const dataEmployee = await resEmployee.json();
|
||||
|
||||
if (!dataEmployee.success) {
|
||||
setEmployee(null);
|
||||
return;
|
||||
}
|
||||
setEmployee(dataEmployee.employee);
|
||||
|
||||
// 2️⃣ Historique des demandes
|
||||
const resRequests = await fetch(`http://localhost/GTA/project/public/php/getEmployeRequest.php?id=${id}`);
|
||||
const dataRequests = await resRequests.json();
|
||||
setRequests(dataRequests.requests || []);
|
||||
|
||||
// 3️⃣ Compteurs de congés et RTT
|
||||
const resCounters = await fetch(`http://localhost/GTA/project/public/php/getLeaveCounters.php?user_id=${id}`);
|
||||
const dataCounters = await resCounters.json();
|
||||
|
||||
if (dataCounters.success) {
|
||||
setEmployee(prev => ({
|
||||
...prev,
|
||||
conges_restants: dataCounters.counters.availableCP,
|
||||
rtt_restants: dataCounters.counters.availableRTT
|
||||
}));
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.error("Erreur récupération données collaborateur:", err);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const getStatusIcon = (status) => {
|
||||
switch (status) {
|
||||
case 'Validée':
|
||||
return <CheckCircle className="inline text-green-500 mr-1" />;
|
||||
case 'Refusée':
|
||||
case 'Annulée':
|
||||
return <XCircle className="inline text-red-500 mr-1" />;
|
||||
default:
|
||||
return <Clock className="inline text-yellow-500 mr-1" />;
|
||||
}
|
||||
};
|
||||
|
||||
if (isLoading) return <p className="text-center p-6">Chargement...</p>;
|
||||
if (!employee) return <p className="text-center p-6">Collaborateur introuvable</p>;
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 flex">
|
||||
<Sidebar />
|
||||
|
||||
<div className="flex-1 lg:ml-60 p-6">
|
||||
<h1 className="text-2xl font-bold mb-2">{employee.Prenom} {employee.Nom}</h1>
|
||||
<p className="text-gray-600 mb-6">{employee.Email}</p>
|
||||
|
||||
{/* Compteurs congés/RTT */}
|
||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4 mb-8">
|
||||
<div className="bg-white p-4 rounded-xl shadow">
|
||||
<p className="text-sm text-gray-600">Congés restants</p>
|
||||
<p className="text-xl font-bold">{employee.conges_restants || 0} jours</p>
|
||||
</div>
|
||||
<div className="bg-white p-4 rounded-xl shadow">
|
||||
<p className="text-sm text-gray-600">RTT restants</p>
|
||||
<p className="text-xl font-bold">{employee.rtt_restants || 0} jours</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Historique des congés */}
|
||||
<h2 className="text-lg font-semibold mb-4">Historique des congés</h2>
|
||||
<div className="space-y-3">
|
||||
{requests.length === 0 ? (
|
||||
<p className="text-gray-500">Aucune demande</p>
|
||||
) : (
|
||||
requests.map((r) => (
|
||||
<div key={r.Id} className="bg-white p-4 rounded-xl shadow border flex justify-between items-center">
|
||||
<div>
|
||||
<p className="font-medium">{r.type} - {r.days}j</p>
|
||||
<p className="text-sm text-gray-600">{r.date_display}</p>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
{getStatusIcon(r.status)}
|
||||
<span className="text-sm text-gray-700">{r.status}</span>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default EmployeeDetails;
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useAuth } from '../context/AuthContext';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Building2, Mail, Lock, Eye, EyeOff } from 'lucide-react';
|
||||
import { Building2, Mail, Lock, Eye, EyeOff, AlertTriangle } from 'lucide-react';
|
||||
|
||||
const Login = () => {
|
||||
const [email, setEmail] = useState('');
|
||||
@@ -9,25 +9,88 @@ const Login = () => {
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
const [authMethod, setAuthMethod] = useState(''); // Pour tracker la méthode d'auth utilisée
|
||||
|
||||
const navigate = useNavigate();
|
||||
const { login, loginWithO365 } = useAuth();
|
||||
const { login, loginWithO365, isAuthorized } = useAuth();
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
setIsLoading(true);
|
||||
setError('');
|
||||
setAuthMethod('local');
|
||||
|
||||
const success = await login(email, password);
|
||||
if (success) {
|
||||
navigate('/dashboard');
|
||||
} else {
|
||||
setError('Identifiants incorrects. Veuillez réessayer.');
|
||||
try {
|
||||
const success = await login(email, password);
|
||||
if (success) {
|
||||
navigate('/dashboard');
|
||||
} else {
|
||||
setError('Identifiants incorrects. Veuillez réessayer.');
|
||||
}
|
||||
} catch (error) {
|
||||
setError(error.message || 'Erreur lors de la connexion');
|
||||
}
|
||||
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
const handleO365Login = async () => {
|
||||
setIsLoading(true);
|
||||
setError('');
|
||||
setAuthMethod('o365');
|
||||
|
||||
try {
|
||||
// Étape 1 : Login O365
|
||||
const success = await loginWithO365();
|
||||
|
||||
if (!success) {
|
||||
setError("Erreur lors de la connexion Office 365");
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Étape 2 : Récupération du token d’authentification (si ton context le fournit)
|
||||
const token = localStorage.getItem("o365_token");
|
||||
// ⚠️ Ici j’imagine que tu stockes ton token quelque part (dans ton AuthContext ou localStorage).
|
||||
// Adapte selon ton implémentation de loginWithO365
|
||||
|
||||
// Étape 3 : Appel de ton API PHP
|
||||
const response = await fetch("http://localhost/GTA/project/public/php/initial-sync.php", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Authorization": `Bearer ${token}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
console.log("Résultat syncGroups :", data);
|
||||
|
||||
if (!data.success) {
|
||||
setError("Erreur de synchronisation des groupes : " + data.message);
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Étape 4 : Redirection vers le dashboard
|
||||
navigate('/dashboard');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Erreur O365:', error);
|
||||
|
||||
if (error.message?.includes('non autorisé') || error.message?.includes('Accès refusé')) {
|
||||
setError('Accès refusé : Vous devez être membre d\'un groupe autorisé dans votre organisation.');
|
||||
} else if (error.message?.includes('AADSTS')) {
|
||||
setError('Erreur d\'authentification Azure AD. Contactez votre administrateur.');
|
||||
} else {
|
||||
setError(error.message || "Erreur lors de la connexion Office 365");
|
||||
}
|
||||
}
|
||||
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 flex flex-col lg:flex-row">
|
||||
{/* Image côté gauche */}
|
||||
@@ -50,7 +113,38 @@ const Login = () => {
|
||||
<p className="text-sm lg:text-base text-gray-600">Gestion de congés</p>
|
||||
</div>
|
||||
|
||||
{/* Form */}
|
||||
{/* Connexion Office 365 prioritaire */}
|
||||
<div className="mb-6">
|
||||
<button
|
||||
onClick={handleO365Login}
|
||||
disabled={isLoading}
|
||||
type="button"
|
||||
className="w-full bg-blue-600 text-white py-3 rounded-lg font-medium hover:bg-blue-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center space-x-2"
|
||||
>
|
||||
{isLoading && authMethod === 'o365' ? (
|
||||
<span>Connexion Office 365...</span>
|
||||
) : (
|
||||
<>
|
||||
<svg className="w-5 h-5" viewBox="0 0 21 21" fill="currentColor">
|
||||
<path d="M10.5 0L0 7v7l10.5 7L21 14V7L10.5 0zM3.5 8.5L10.5 3l7 5.5v5L10.5 19l-7-5.5v-5z" />
|
||||
</svg>
|
||||
<span>Se connecter avec Office 365</span>
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Séparateur */}
|
||||
<div className="relative mb-6">
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
<div className="w-full border-t border-gray-300"></div>
|
||||
</div>
|
||||
<div className="relative flex justify-center text-sm">
|
||||
<span className="px-2 bg-white text-gray-500">ou</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Formulaire classique */}
|
||||
<form onSubmit={handleSubmit} className="space-y-4 lg:space-y-6">
|
||||
<div>
|
||||
<label htmlFor="email" className="block text-sm lg:text-base font-medium text-gray-700 mb-2">
|
||||
@@ -66,6 +160,7 @@ const Login = () => {
|
||||
className="w-full pl-9 lg:pl-10 pr-4 py-2 lg:py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm lg:text-base"
|
||||
placeholder="votre.email@entreprise.com"
|
||||
required
|
||||
disabled={isLoading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -84,11 +179,13 @@ const Login = () => {
|
||||
className="w-full pl-9 lg:pl-10 pr-10 lg:pr-12 py-2 lg:py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm lg:text-base"
|
||||
placeholder="••••••••"
|
||||
required
|
||||
disabled={isLoading}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500 hover:text-gray-900"
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500 hover:text-gray-900 disabled:opacity-50"
|
||||
disabled={isLoading}
|
||||
title={showPassword ? "Masquer le mot de passe" : "Afficher le mot de passe"}
|
||||
>
|
||||
{showPassword ? <EyeOff className="w-5 h-5" /> : <Eye className="w-5 h-5" />}
|
||||
@@ -96,37 +193,41 @@ const Login = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 text-center">
|
||||
<button
|
||||
onClick={async () => {
|
||||
const success = await loginWithO365();
|
||||
if (success) {
|
||||
navigate('/dashboard');
|
||||
} else {
|
||||
setError("Erreur lors de la connexion Office 365");
|
||||
}
|
||||
}}
|
||||
type="button"
|
||||
className="w-full bg-gray-700 text-white py-3 rounded-lg font-medium hover:bg-green-700 transition-colors"
|
||||
>
|
||||
Se connecter avec Office 365
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Affichage des erreurs */}
|
||||
{error && (
|
||||
<div className="p-2 lg:p-3 bg-red-50 border border-red-200 rounded-lg">
|
||||
<p className="text-red-600 text-xs lg:text-sm">{error}</p>
|
||||
<div className="p-3 bg-red-50 border border-red-200 rounded-lg">
|
||||
<div className="flex items-start space-x-2">
|
||||
<AlertTriangle className="w-5 h-5 text-red-500 flex-shrink-0 mt-0.5" />
|
||||
<div className="flex-1">
|
||||
<p className="text-red-700 text-sm font-medium">
|
||||
{error.includes('Accès refusé') ? 'Accès refusé' : 'Erreur de connexion'}
|
||||
</p>
|
||||
<p className="text-red-600 text-xs mt-1">{error}</p>
|
||||
{error.includes('groupe autorisé') && (
|
||||
<p className="text-red-600 text-xs mt-2">
|
||||
Contactez votre administrateur pour être ajouté aux groupes appropriés.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isLoading}
|
||||
className="w-full bg-blue-600 text-white py-2 lg:py-3 px-4 rounded-lg font-medium hover:bg-blue-700 focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed transition-colors text-sm lg:text-base"
|
||||
className="w-full bg-gray-600 text-white py-2 lg:py-3 px-4 rounded-lg font-medium hover:bg-gray-700 focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed transition-colors text-sm lg:text-base"
|
||||
>
|
||||
{isLoading ? 'Connexion...' : 'Se connecter'}
|
||||
{isLoading && authMethod === 'local' ? 'Connexion...' : 'Connexion locale'}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{/* Info sur l'authentification */}
|
||||
<div className="mt-6 text-center">
|
||||
<p className="text-xs text-gray-500">
|
||||
Utilisez votre compte Office 365 pour une connexion sécurisée
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -134,4 +235,4 @@ const Login = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default Login;
|
||||
export default Login;
|
||||
@@ -3,6 +3,7 @@ import { useAuth } from '../context/AuthContext';
|
||||
import Sidebar from '../components/Sidebar';
|
||||
import { Plus, Search, Filter, Eye, Menu, X } from 'lucide-react';
|
||||
import NewLeaveRequestModal from '../components/NewLeaveRequestModal';
|
||||
import { useMsal } from "@azure/msal-react";
|
||||
|
||||
const Requests = () => {
|
||||
const { user } = useAuth();
|
||||
@@ -32,8 +33,30 @@ const Requests = () => {
|
||||
|
||||
const [showFilters, setShowFilters] = useState(false);
|
||||
|
||||
const [graphToken, setGraphToken] = useState(null);
|
||||
const { instance, accounts } = useMsal();
|
||||
const userId = user?.id || user?.CollaborateurADId || user?.ID;
|
||||
|
||||
useEffect(() => {
|
||||
if (user?.id) {
|
||||
if (accounts.length > 0) {
|
||||
const request = {
|
||||
scopes: ["User.Read", "Mail.Send"],
|
||||
account: accounts[0],
|
||||
};
|
||||
|
||||
instance.acquireTokenSilent(request)
|
||||
.then((response) => {
|
||||
setGraphToken(response.accessToken);
|
||||
console.log("✅ Token Graph récupéré :", response.accessToken);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("❌ Erreur récupération token Graph:", err);
|
||||
});
|
||||
}
|
||||
}, [accounts, instance]);
|
||||
|
||||
useEffect(() => {
|
||||
if (userId) {
|
||||
fetchLeaveCounters();
|
||||
fetchAllRequests();
|
||||
}
|
||||
@@ -69,7 +92,7 @@ const Requests = () => {
|
||||
|
||||
const fetchLeaveCounters = async () => {
|
||||
try {
|
||||
const response = await fetch(`http://localhost/GTA/project/public/php/getLeaveCounters.php?user_id=${user.id}`);
|
||||
const response = await fetch(`http://localhost/GTA/project/public/php/getLeaveCounters.php?user_id=${userId}`);
|
||||
const text = await response.text();
|
||||
let data;
|
||||
try {
|
||||
@@ -89,7 +112,7 @@ const Requests = () => {
|
||||
|
||||
const fetchAllRequests = async () => {
|
||||
try {
|
||||
const url = `http://localhost/GTA/project/public/php/getRequests.php?user_id=${user.id}`;
|
||||
const url = `http://localhost/GTA/project/public/php/getRequests.php?user_id=${userId}`;
|
||||
const response = await fetch(url);
|
||||
const text = await response.text();
|
||||
let data;
|
||||
@@ -426,6 +449,8 @@ const Requests = () => {
|
||||
onClose={() => setShowNewRequestModal(false)}
|
||||
availableLeaveCounters={leaveCounters}
|
||||
userId={user?.id}
|
||||
userEmail={user.email}
|
||||
userName={`${user.prenom} ${user.nom}`}
|
||||
onRequestSubmitted={() => {
|
||||
fetchLeaveCounters();
|
||||
fetchAllRequests();
|
||||
|
||||
Reference in New Issue
Block a user