mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-01-29 07:53:23 +00:00
feat: Création d'un profile selector [#37,#38]
This commit is contained in:
@ -9,7 +9,7 @@ class Profile(AbstractUser):
|
||||
|
||||
USERNAME_FIELD = 'email'
|
||||
REQUIRED_FIELDS = ('password', )
|
||||
|
||||
roleIndexLoginDefault = models.IntegerField(default=0)
|
||||
code = models.CharField(max_length=200, default="", blank=True)
|
||||
datePeremption = models.CharField(max_length=200, default="", blank=True)
|
||||
|
||||
|
||||
@ -14,7 +14,7 @@ class ProfileSerializer(serializers.ModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = Profile
|
||||
fields = ['id', 'password', 'email', 'code', 'datePeremption', 'username', 'roles']
|
||||
fields = ['id', 'password', 'email', 'code', 'datePeremption', 'username', 'roles', 'roleIndexLoginDefault']
|
||||
extra_kwargs = {'password': {'write_only': True}}
|
||||
|
||||
def get_roles(self, obj):
|
||||
@ -53,10 +53,10 @@ class ProfileSerializer(serializers.ModelSerializer):
|
||||
def update(self, instance, validated_data):
|
||||
password = validated_data.pop('password', None)
|
||||
instance = super().update(instance, validated_data)
|
||||
|
||||
|
||||
if password:
|
||||
instance.set_password(password)
|
||||
|
||||
|
||||
instance.full_clean()
|
||||
instance.save()
|
||||
return instance
|
||||
@ -114,7 +114,7 @@ class ProfileRoleSerializer(serializers.ModelSerializer):
|
||||
if obj.profile:
|
||||
return obj.profile.email
|
||||
return None
|
||||
|
||||
|
||||
def get_associated_person(self, obj):
|
||||
if obj.role_type == ProfileRole.RoleType.PROFIL_PARENT:
|
||||
guardian = Guardian.objects.filter(profile_role=obj).first()
|
||||
|
||||
@ -54,6 +54,7 @@ class SessionView(APIView):
|
||||
'user': openapi.Schema(type=openapi.TYPE_OBJECT, properties={
|
||||
'id': openapi.Schema(type=openapi.TYPE_INTEGER),
|
||||
'email': openapi.Schema(type=openapi.TYPE_STRING),
|
||||
'roleIndexLoginDefault': openapi.Schema(type=openapi.TYPE_INTEGER),
|
||||
'roles': openapi.Schema(type=openapi.TYPE_ARRAY, items=openapi.Items(type=openapi.TYPE_OBJECT, properties={
|
||||
'role_type': openapi.Schema(type=openapi.TYPE_STRING),
|
||||
'establishment': openapi.Schema(type=openapi.TYPE_STRING)
|
||||
@ -65,18 +66,16 @@ class SessionView(APIView):
|
||||
)
|
||||
def get(self, request):
|
||||
token = request.META.get('HTTP_AUTHORIZATION', '').split('Bearer ')[-1]
|
||||
|
||||
try:
|
||||
decoded_token = jwt.decode(token, settings.SECRET_KEY, algorithms=['HS256'])
|
||||
userid = decoded_token.get('user_id')
|
||||
user = Profile.objects.get(id=userid)
|
||||
|
||||
roles = ProfileRole.objects.filter(profile=user).values('role_type', 'establishment__name')
|
||||
|
||||
response_data = {
|
||||
'user': {
|
||||
'id': user.id,
|
||||
'email': user.email,
|
||||
'roleIndexLoginDefault': user.roleIndexLoginDefault,
|
||||
'roles': list(roles)
|
||||
}
|
||||
}
|
||||
@ -157,11 +156,10 @@ class LoginView(APIView):
|
||||
operation_description="Connexion utilisateur",
|
||||
request_body=openapi.Schema(
|
||||
type=openapi.TYPE_OBJECT,
|
||||
required=['email', 'password', 'role_type'],
|
||||
required=['email', 'password'],
|
||||
properties={
|
||||
'email': openapi.Schema(type=openapi.TYPE_STRING),
|
||||
'password': openapi.Schema(type=openapi.TYPE_STRING),
|
||||
'role_type': openapi.Schema(type=openapi.TYPE_STRING)
|
||||
'password': openapi.Schema(type=openapi.TYPE_STRING)
|
||||
}
|
||||
),
|
||||
responses={
|
||||
@ -194,38 +192,15 @@ class LoginView(APIView):
|
||||
password=data.get('password'),
|
||||
)
|
||||
if user is not None:
|
||||
role_type = data.get('role_type')
|
||||
primary_role = ProfileRole.objects.filter(profile=user, role_type=role_type, is_active=True).first()
|
||||
|
||||
if not primary_role:
|
||||
# Vérifier si l'utilisateur a un role actif
|
||||
has_active_role = ProfileRole.objects.filter(profile=user, is_active=True).first()
|
||||
if not has_active_role:
|
||||
return JsonResponse({"errorMessage": "Profil inactif"}, status=status.HTTP_401_UNAUTHORIZED)
|
||||
|
||||
login(request, user)
|
||||
user.save()
|
||||
retour = ''
|
||||
|
||||
# Récupérer tous les rôles de l'utilisateur avec le type spécifié
|
||||
roles = ProfileRole.objects.filter(profile=user, role_type=role_type).values('role_type', 'establishment__id', 'establishment__name')
|
||||
|
||||
# Générer le JWT avec la bonne syntaxe datetime
|
||||
access_payload = {
|
||||
'user_id': user.id,
|
||||
'email': user.email,
|
||||
'roles': list(roles),
|
||||
'type': 'access',
|
||||
'exp': datetime.utcnow() + settings.SIMPLE_JWT['ACCESS_TOKEN_LIFETIME'],
|
||||
'iat': datetime.utcnow(),
|
||||
}
|
||||
|
||||
access_token = jwt.encode(access_payload, settings.SIMPLE_JWT['SIGNING_KEY'], algorithm=settings.SIMPLE_JWT['ALGORITHM'])
|
||||
# Générer le Refresh Token (exp: 7 jours)
|
||||
refresh_payload = {
|
||||
'user_id': user.id,
|
||||
'type': 'refresh',
|
||||
'exp': datetime.utcnow() + settings.SIMPLE_JWT['REFRESH_TOKEN_LIFETIME'],
|
||||
'iat': datetime.utcnow(),
|
||||
}
|
||||
refresh_token = jwt.encode(refresh_payload, settings.SIMPLE_JWT['SIGNING_KEY'], algorithm=settings.SIMPLE_JWT['ALGORITHM'])
|
||||
access_token, refresh_token = makeToken(user)
|
||||
|
||||
return JsonResponse({
|
||||
'token': access_token,
|
||||
@ -299,35 +274,10 @@ class RefreshJWTView(APIView):
|
||||
|
||||
# Récupérer les informations utilisateur
|
||||
user = Profile.objects.get(id=payload['user_id'])
|
||||
role_type = payload.get('role_type')
|
||||
if not user:
|
||||
return JsonResponse({'errorMessage': 'Utilisateur non trouvé'}, status=404)
|
||||
|
||||
# Récupérer le rôle principal de l'utilisateur
|
||||
primary_role = ProfileRole.objects.filter(profile=user, role_type=role_type, is_active=True).first()
|
||||
|
||||
if not primary_role:
|
||||
return JsonResponse({'errorMessage': 'Profil inactif'}, status=400)
|
||||
|
||||
# Générer un nouveau Access Token avec les informations complètes
|
||||
new_access_payload = {
|
||||
'user_id': user.id,
|
||||
'email': user.email,
|
||||
'role_type': primary_role.get_role_type_display(),
|
||||
'establishment': primary_role.establishment.id,
|
||||
'type': 'access',
|
||||
'exp': datetime.utcnow() + settings.SIMPLE_JWT['ACCESS_TOKEN_LIFETIME'],
|
||||
'iat': datetime.utcnow(),
|
||||
}
|
||||
|
||||
new_access_token = jwt.encode(new_access_payload, settings.SIMPLE_JWT['SIGNING_KEY'], algorithm=settings.SIMPLE_JWT['ALGORITHM'])
|
||||
|
||||
new_refresh_payload = {
|
||||
'user_id': user.id,
|
||||
'role_type': role_type,
|
||||
'type': 'refresh',
|
||||
'exp': datetime.utcnow() + settings.SIMPLE_JWT['REFRESH_TOKEN_LIFETIME'],
|
||||
'iat': datetime.utcnow(),
|
||||
}
|
||||
new_refresh_token = jwt.encode(new_refresh_payload, settings.SIMPLE_JWT['SIGNING_KEY'], algorithm=settings.SIMPLE_JWT['ALGORITHM'])
|
||||
new_access_payload, new_refresh_token = makeToken(user)
|
||||
|
||||
return JsonResponse({'token': new_access_token, 'refresh': new_refresh_token}, status=200)
|
||||
|
||||
@ -341,6 +291,38 @@ class RefreshJWTView(APIView):
|
||||
logger.error(f"Erreur inattendue: {str(e)}")
|
||||
return JsonResponse({'errorMessage': f'Erreur inattendue: {str(e)}'}, status=400)
|
||||
|
||||
def makeToken(user):
|
||||
"""
|
||||
Fonction pour créer un token JWT pour l'utilisateur donné.
|
||||
"""
|
||||
try:
|
||||
# Récupérer tous les rôles de l'utilisateur actifs
|
||||
roles = ProfileRole.objects.filter(profile=user, is_active=True).values('role_type', 'establishment__id', 'establishment__name')
|
||||
|
||||
# Générer le JWT avec la bonne syntaxe datetime
|
||||
access_payload = {
|
||||
'user_id': user.id,
|
||||
'email': user.email,
|
||||
'roleIndexLoginDefault':user.roleIndexLoginDefault,
|
||||
'roles': list(roles),
|
||||
'type': 'access',
|
||||
'exp': datetime.utcnow() + settings.SIMPLE_JWT['ACCESS_TOKEN_LIFETIME'],
|
||||
'iat': datetime.utcnow(),
|
||||
}
|
||||
|
||||
access_token = jwt.encode(access_payload, settings.SIMPLE_JWT['SIGNING_KEY'], algorithm=settings.SIMPLE_JWT['ALGORITHM'])
|
||||
# Générer le Refresh Token (exp: 7 jours)
|
||||
refresh_payload = {
|
||||
'user_id': user.id,
|
||||
'type': 'refresh',
|
||||
'exp': datetime.utcnow() + settings.SIMPLE_JWT['REFRESH_TOKEN_LIFETIME'],
|
||||
'iat': datetime.utcnow(),
|
||||
}
|
||||
refresh_token = jwt.encode(refresh_payload, settings.SIMPLE_JWT['SIGNING_KEY'], algorithm=settings.SIMPLE_JWT['ALGORITHM'])
|
||||
return access_token, refresh_token
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors de la création du token: {str(e)}")
|
||||
return None
|
||||
|
||||
@method_decorator(csrf_protect, name='dispatch')
|
||||
@method_decorator(ensure_csrf_cookie, name='dispatch')
|
||||
|
||||
@ -131,7 +131,6 @@ export default function Layout({
|
||||
if (roleIndex === -1) {
|
||||
roleIndex = 0;
|
||||
}
|
||||
setCurrentRoleIndex(roleIndex);
|
||||
const role = session.user.roles[roleIndex].role_type;
|
||||
setProfileRole(role);
|
||||
}}
|
||||
|
||||
@ -10,7 +10,7 @@ import logger from '@/utils/logger';
|
||||
import { fetchRegisterForms } from '@/app/actions/subscriptionAction';
|
||||
import { fetchUpcomingEvents } from '@/app/actions/planningAction';
|
||||
import { getSession } from 'next-auth/react';
|
||||
import { getCurrentRoleIndex } from '@/store/Store';
|
||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||
|
||||
|
||||
// Composant EventCard pour afficher les événements
|
||||
@ -42,14 +42,11 @@ export default function DashboardPage() {
|
||||
|
||||
const [classes, setClasses] = useState([]);
|
||||
const [establishmentId, setEstablishmentId] = useState(null);
|
||||
|
||||
const { selectedEstablishmentId } = useEstablishment();
|
||||
useEffect(() => {
|
||||
getSession()
|
||||
.then(session => {
|
||||
if (session && session.user) {
|
||||
const establishmentId = session.user.roles[getCurrentRoleIndex()].establishment__id;
|
||||
setEstablishmentId(establishmentId);
|
||||
}
|
||||
setEstablishmentId(selectedEstablishmentId);
|
||||
})
|
||||
.catch(err => {
|
||||
logger.error('Error fetching session:', err);
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
// src/components/Layout.js
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import DropdownMenu from '@/components/DropdownMenu';
|
||||
import ProfileSelector from '@/components/ProfileSelector';
|
||||
import { useRouter } from 'next/navigation'; // Ajout de l'importation
|
||||
import { User, MessageSquare, LogOut, Settings, Home } from 'lucide-react'; // Ajout de l'importation de l'icône Home
|
||||
import Logo from '@/components/Logo'; // Ajout de l'importation du composant Logo
|
||||
@ -11,11 +12,11 @@ import ProtectedRoute from '@/components/ProtectedRoute';
|
||||
import { disconnect } from '@/app/actions/authAction';
|
||||
import Popup from '@/components/Popup';
|
||||
import logger from '@/utils/logger';
|
||||
import { useSession } from 'next-auth/react';
|
||||
import { FE_USERS_LOGIN_URL } from '@/utils/Url';
|
||||
import { getRightStr, RIGHTS } from '@/utils/rights';
|
||||
import { getGravatarUrl } from '@/utils/gravatar';
|
||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||
import Image from 'next/image';
|
||||
import Footer from '@/components/Footer';
|
||||
|
||||
export default function Layout({
|
||||
children,
|
||||
@ -23,11 +24,12 @@ export default function Layout({
|
||||
|
||||
const router = useRouter(); // Définition de router
|
||||
const [messages, setMessages] = useState([]);
|
||||
const { data: session, status } = useSession();
|
||||
const [userId, setUserId] = useState(null);
|
||||
const [user, setUser] = useState(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [isPopupVisible, setIsPopupVisible] = useState(false);
|
||||
const { profileRole, user } = useEstablishment();
|
||||
const softwareName = "N3WT School";
|
||||
const softwareVersion = `${process.env.NEXT_PUBLIC_APP_VERSION}`;
|
||||
|
||||
|
||||
const handleDisconnect = () => {
|
||||
setIsPopupVisible(true);
|
||||
@ -36,42 +38,15 @@ export default function Layout({
|
||||
const confirmDisconnect = () => {
|
||||
setIsPopupVisible(false);
|
||||
disconnect();
|
||||
};
|
||||
};
|
||||
|
||||
// useEffect(() => {
|
||||
// if (status === 'loading') return;
|
||||
// if (!session) {
|
||||
// router.push(`${FE_USERS_LOGIN_URL}`);
|
||||
// }
|
||||
|
||||
// const userIdFromSession = session.user.id;
|
||||
// setUserId(userIdFromSession);
|
||||
// setIsLoading(true);
|
||||
// fetchMessages(userId)
|
||||
// .then(data => {
|
||||
// if (data) {
|
||||
// setMessages(data);
|
||||
// }
|
||||
// logger.debug('Success :', data);
|
||||
// })
|
||||
// .catch(error => {
|
||||
// logger.error('Error fetching data:', error);
|
||||
// })
|
||||
// .finally(() => {
|
||||
// setIsLoading(false);
|
||||
// });
|
||||
// }, [userId]);
|
||||
|
||||
// if (isLoading) {
|
||||
// return <div>Loading...</div>;
|
||||
// }
|
||||
const dropdownItems = [
|
||||
{
|
||||
type: 'info',
|
||||
content: (
|
||||
<div className="px-4 py-2">
|
||||
<div className="font-medium">{user?.email || 'Utilisateur'}</div>
|
||||
<div className="text-xs text-gray-400">{getRightStr(user?.roles[0]?.role_type) || ''}</div>
|
||||
<div className="text-xs text-gray-400">{getRightStr(profileRole) || ''}</div>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
@ -91,11 +66,14 @@ const dropdownItems = [
|
||||
<ProtectedRoute requiredRight={RIGHTS.PARENT}>
|
||||
<div className="flex flex-col min-h-screen bg-gray-50">
|
||||
{/* Entête */}
|
||||
<header className="bg-white border-b border-gray-200 px-4 py-2 md:px-8 md:py-4 flex items-center justify-between fixed top-0 left-0 right-0 z-10">
|
||||
<header className="h-16 bg-white border-b border-gray-200 px-4 md:px-8 py-4 flex items-center justify-between fixed top-0 left-0 right-0 z-10">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Logo className="h-6 w-6 md:h-8 md:w-8" /> {/* Utilisation du composant Logo */}
|
||||
|
||||
<div className="text-lg md:text-xl font-semibold">Accueil</div>
|
||||
<div className="border-b border-gray-200 ">
|
||||
<ProfileSelector
|
||||
className="w-64 border-r"
|
||||
/>
|
||||
</div>
|
||||
<div className="text-lg md:text-xl p-2 font-semibold">Accueil</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2 md:space-x-4">
|
||||
<button
|
||||
@ -116,6 +94,7 @@ const dropdownItems = [
|
||||
<span className="absolute top-0 right-0 block h-2 w-2 rounded-full bg-emerald-600"></span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<DropdownMenu
|
||||
buttonContent={<Image
|
||||
src={getGravatarUrl(user?.email)}
|
||||
@ -135,6 +114,8 @@ const dropdownItems = [
|
||||
<div className="pt-16 md:pt-20 p-4 md:p-8 flex-1"> {/* Ajout de flex-1 pour utiliser toute la hauteur disponible */}
|
||||
{children}
|
||||
</div>
|
||||
{/* Footer responsive */}
|
||||
<Footer softwareName={softwareName} softwareVersion={softwareVersion} />
|
||||
</div>
|
||||
<Popup
|
||||
visible={isPopupVisible}
|
||||
|
||||
@ -7,51 +7,33 @@ import StatusLabel from '@/components/StatusLabel';
|
||||
import { FE_PARENTS_EDIT_INSCRIPTION_URL } from '@/utils/Url';
|
||||
import { fetchChildren } from '@/app/actions/subscriptionAction';
|
||||
import logger from '@/utils/logger';
|
||||
import { useSession } from 'next-auth/react';
|
||||
import { FE_USERS_LOGIN_URL } from '@/utils/Url';
|
||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||
import ProfileSelector from '@/components/ProfileSelector';
|
||||
|
||||
export default function ParentHomePage() {
|
||||
const [children, setChildren] = useState([]);
|
||||
const { data: session, status } = useSession();
|
||||
const [userId, setUserId] = useState(null);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [establishments, setEstablishments] = useState([]);
|
||||
const { selectedEstablishmentId, setSelectedEstablishmentId } = useEstablishment();
|
||||
const { user, setProfileRole, selectedEstablishmentId, setSelectedEstablishmentId, establishments } = useEstablishment();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
if (status === 'loading') return;
|
||||
|
||||
if (!session || !session.user) {
|
||||
router.push(`${FE_USERS_LOGIN_URL}`);
|
||||
} else {
|
||||
const userIdFromSession = session.user.user_id;
|
||||
const userIdFromSession = user.user_id;
|
||||
setUserId(userIdFromSession);
|
||||
|
||||
const userEstablishments = session.user.roles.map(role => ({
|
||||
id: role.establishment__id,
|
||||
name: role.establishment__name,
|
||||
role_type: role.role_type
|
||||
}));
|
||||
setEstablishments(userEstablishments);
|
||||
|
||||
if (!selectedEstablishmentId && userEstablishments.length > 0) {
|
||||
setSelectedEstablishmentId(userEstablishments[0].id);
|
||||
}
|
||||
console.log(selectedEstablishmentId)
|
||||
fetchChildren(userIdFromSession, selectedEstablishmentId).then(data => {
|
||||
setChildren(data);
|
||||
});
|
||||
}
|
||||
}, [status, session, selectedEstablishmentId]);
|
||||
|
||||
}, [ selectedEstablishmentId]);
|
||||
|
||||
const handleEstablishmentChange = (e) => {
|
||||
const establishmentId = parseInt(e.target.value, 10);
|
||||
setSelectedEstablishmentId(establishmentId);
|
||||
const role = establishments.find(est => est.id === establishmentId)?.role_type;
|
||||
setProfileRole(role);
|
||||
};
|
||||
|
||||
function handleEdit(eleveId) {
|
||||
// Logique pour éditer le dossier de l'élève
|
||||
logger.debug(`Edit dossier for student id: ${eleveId}`);
|
||||
@ -107,22 +89,6 @@ export default function ParentHomePage() {
|
||||
<Users className="h-6 w-6 text-emerald-600" />
|
||||
Enfants
|
||||
</h2>
|
||||
{establishments.length > 1 && (
|
||||
<div className="mb-4">
|
||||
<label className="block text-gray-700 text-sm font-bold mb-2">Sélectionnez un établissement :</label>
|
||||
<select
|
||||
value={selectedEstablishmentId}
|
||||
onChange={handleEstablishmentChange}
|
||||
className="block w-full mt-1 p-2 border border-gray-300 rounded-md"
|
||||
>
|
||||
{establishments.map((establishment, index) => (
|
||||
<option key={`${establishment.id}-${index}`} value={establishment.id}>
|
||||
{establishment.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
<div className="overflow-x-auto">
|
||||
<Table
|
||||
data={children}
|
||||
|
||||
@ -9,16 +9,16 @@ import Button from '@/components/Button'; // Importez le composant Button
|
||||
import { User, KeySquare } from 'lucide-react'; // Importez directement les icônes nécessaires
|
||||
import {
|
||||
FE_USERS_NEW_PASSWORD_URL,
|
||||
FE_ADMIN_SUBSCRIPTIONS_URL,
|
||||
FE_PARENTS_HOME_URL
|
||||
getRedirectUrlFromRole
|
||||
} from '@/utils/Url';
|
||||
import { login } from '@/app/actions/authAction';
|
||||
import { getSession } from 'next-auth/react';
|
||||
import { useCsrfToken } from '@/context/CsrfContext'; // Importez le hook useCsrfToken
|
||||
import logger from '@/utils/logger';
|
||||
import ProfileSelector from '@/components/ProfileSelector'; // Importez le composant ProfileSelector
|
||||
import { RIGHTS } from '@/utils/rights';
|
||||
import { setCurrentRoleIndex } from '@/store/Store';
|
||||
|
||||
|
||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||
|
||||
|
||||
|
||||
export default function Page() {
|
||||
@ -26,8 +26,7 @@ export default function Page() {
|
||||
const [errorMessage, setErrorMessage] = useState("");
|
||||
const [userFieldError, setUserFieldError] = useState("")
|
||||
const [passwordFieldError, setPasswordFieldError] = useState("")
|
||||
const [selectedProfile, setSelectedProfile] = useState(1); // Par défaut, sélectionnez ADMIN
|
||||
|
||||
const { setUser } = useEstablishment();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const router = useRouter();
|
||||
@ -37,14 +36,15 @@ export default function Page() {
|
||||
return data.errorMessage === ""
|
||||
}
|
||||
|
||||
|
||||
|
||||
function handleFormLogin(formData) {
|
||||
setIsLoading(true);
|
||||
setErrorMessage("");
|
||||
|
||||
login({
|
||||
email: formData.get('login'),
|
||||
password: formData.get('password'),
|
||||
role_type: selectedProfile
|
||||
password: formData.get('password')
|
||||
}).then(result => {
|
||||
logger.debug('Sign In Result', result);
|
||||
|
||||
@ -58,18 +58,16 @@ export default function Page() {
|
||||
}
|
||||
const user = session.user;
|
||||
logger.debug('User Session:', user);
|
||||
|
||||
const roles = user.roles.filter(role => role.role_type === selectedProfile);
|
||||
if (roles.length > 0) {
|
||||
// Redirection en fonction du rôle
|
||||
// Ne pas désactiver le loader avant la redirection
|
||||
const currentRoleIndex = 0;
|
||||
setCurrentRoleIndex(currentRoleIndex);
|
||||
const role = roles[currentRoleIndex].role_type;
|
||||
if (role === RIGHTS.ADMIN || role === RIGHTS.TEACHER) {
|
||||
router.push(FE_ADMIN_SUBSCRIPTIONS_URL);
|
||||
} else if (role === RIGHTS.PARENT) {
|
||||
router.push(FE_PARENTS_HOME_URL);
|
||||
setUser(session.user);
|
||||
if (session.user.roles && session.user.roles.length > 0) {
|
||||
let roleIndex = 0;
|
||||
if( session.user.roles.length > session.user.roleIndexLoginDefault){
|
||||
roleIndex = session.user.roleIndexLoginDefault;
|
||||
}
|
||||
const role = session.user.roles[roleIndex].role_type;
|
||||
const url = getRedirectUrlFromRole(role);
|
||||
if (url) {
|
||||
router.push(url);
|
||||
} else {
|
||||
setIsLoading(false);
|
||||
setErrorMessage('Type de rôle non géré');
|
||||
@ -104,7 +102,6 @@ export default function Page() {
|
||||
<DjangoCSRFToken csrfToken={csrfToken} />
|
||||
<InputTextIcon name="login" type="text" IconItem={User} label="Identifiant" placeholder="Identifiant" errorMsg={userFieldError} className="w-full mb-5" />
|
||||
<InputTextIcon name="password" type="password" IconItem={KeySquare} label="Mot de passe" placeholder="Mot de passe" errorMsg={passwordFieldError} className="w-full mb-5" />
|
||||
<ProfileSelector selectedProfile={selectedProfile} setSelectedProfile={setSelectedProfile} />
|
||||
<div className="input-group mb-4">
|
||||
</div>
|
||||
<label className="text-red-500">{errorMessage}</label>
|
||||
|
||||
@ -1,29 +1,65 @@
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||
import DropdownMenu from '@/components/DropdownMenu';
|
||||
import { getRightStr } from '@/utils/rights';
|
||||
import { ChevronDown } from 'lucide-react'; // Import de l'icône
|
||||
|
||||
const ProfileSelector = ({ onEstablishmentChange, className = '' }) => {
|
||||
const { establishments, selectedEstablishmentId, setSelectedEstablishmentId, setProfileRole } = useEstablishment();
|
||||
const [dropdownOpen, setDropdownOpen] = useState(false);
|
||||
|
||||
const handleEstablishmentChange = (establishmentId) => {
|
||||
setSelectedEstablishmentId(establishmentId);
|
||||
const role = establishments.find(est => est.id === establishmentId)?.role_type;
|
||||
setProfileRole(role);
|
||||
|
||||
if (onEstablishmentChange) {
|
||||
onEstablishmentChange(establishmentId);
|
||||
}
|
||||
setDropdownOpen(false); // Fermer le menu après sélection
|
||||
};
|
||||
|
||||
// Si on a pas de rôle ou un seul rôle, on n'affiche pas le sélecteur
|
||||
if (!establishments || establishments.length === 0 || establishments.length === 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const selectedEstablishment = establishments.find(est => est.id === selectedEstablishmentId);
|
||||
|
||||
const ProfileSelector = ({ selectedProfile, setSelectedProfile }) => {
|
||||
return (
|
||||
<div className="flex space-x-4">
|
||||
<button
|
||||
type="button"
|
||||
className={`px-4 py-2 rounded-lg shadow-md focus:outline-none focus:ring-2 focus:ring-blue-300 ${selectedProfile === 1 ? 'bg-blue-500 text-white ring-2 ring-blue-500' : 'bg-gray-200'}`}
|
||||
onClick={() => setSelectedProfile(1)}
|
||||
>
|
||||
ADMINISTRATEUR
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`px-4 py-2 rounded-lg shadow-md focus:outline-none focus:ring-2 focus:ring-blue-300 ${selectedProfile === 0 ? 'bg-blue-500 text-white ring-2 ring-blue-500' : 'bg-gray-200'}`}
|
||||
onClick={() => setSelectedProfile(0)}
|
||||
>
|
||||
PROFESSEUR
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`px-4 py-2 rounded-lg shadow-md focus:outline-none focus:ring-2 focus:ring-blue-300 ${selectedProfile === 2 ? 'bg-blue-500 text-white ring-2 ring-blue-500' : 'bg-gray-200'}`}
|
||||
onClick={() => setSelectedProfile(2)}
|
||||
>
|
||||
PARENT
|
||||
</button>
|
||||
<div className={`relative ${className}`}>
|
||||
<DropdownMenu
|
||||
buttonContent={
|
||||
<div className="h-16 flex items-center gap-2 cursor-pointer px-4 bg-white">
|
||||
<div className="flex-1">
|
||||
<div className="font-bold text-left">{getRightStr(selectedEstablishment?.role_type) || ''}</div>
|
||||
<div className="text-sm text-gray-500 text-left">
|
||||
{selectedEstablishment?.name || 'Sélectionnez un établissement'}
|
||||
</div>
|
||||
</div>
|
||||
{/* Icône ChevronDown avec rotation conditionnelle */}
|
||||
<ChevronDown
|
||||
className={`w-5 h-5 transition-transform duration-200 ${
|
||||
dropdownOpen ? 'rotate-180' : 'rotate-0'
|
||||
}`}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
items={establishments.map(establishment => ({
|
||||
type: 'item',
|
||||
label: (
|
||||
<div className="text-left">
|
||||
<div className="font-bold">{getRightStr(establishment.role_type)}</div>
|
||||
<div className="text-sm text-gray-500">{establishment.name}</div>
|
||||
</div>
|
||||
),
|
||||
onClick: () => handleEstablishmentChange(establishment.id),
|
||||
}))}
|
||||
buttonClassName="w-full"
|
||||
menuClassName="absolute mt-2 w-full bg-white border border-gray-200 rounded shadow-lg z-10"
|
||||
dropdownOpen={dropdownOpen}
|
||||
setDropdownOpen={setDropdownOpen}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,33 +1,33 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useSession } from 'next-auth/react';
|
||||
import Loader from '@/components/Loader'; // Importez le composant Loader
|
||||
import { FE_USERS_LOGIN_URL } from '@/utils/Url';
|
||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||
import { FE_USERS_LOGIN_URL,getRedirectUrlFromRole } from '@/utils/Url';
|
||||
|
||||
const ProtectedRoute = ({ children, requiredRight }) => {
|
||||
const { data: session, status } = useSession({
|
||||
required: true,
|
||||
onUnauthenticated() {
|
||||
router.push(`${FE_USERS_LOGIN_URL}`);
|
||||
}
|
||||
});
|
||||
const router = useRouter();
|
||||
|
||||
// Ne vérifier que si le statut est définitif
|
||||
if (status === 'loading') {
|
||||
return <Loader />;
|
||||
}
|
||||
const { user, profileRole } = useEstablishment();
|
||||
const router = useRouter();
|
||||
const hasRequiredRight = (profileRole === requiredRight);
|
||||
|
||||
// Vérifier si l'utilisateur a au moins un rôle correspondant au requiredRight
|
||||
const hasRequiredRight = session?.user?.roles?.some(role => role.role_type === requiredRight);
|
||||
useEffect(() => {
|
||||
if(user){
|
||||
// Vérifier si l'utilisateur a le droit requis mais pas le bon role on le redirige la page d'accueil associé au role
|
||||
if (!hasRequiredRight) {
|
||||
const redirectUrl = getRedirectUrlFromRole(profileRole);
|
||||
router.push(`${redirectUrl}`);
|
||||
}
|
||||
|
||||
}else{
|
||||
// User non authentifié
|
||||
router.push(`${FE_USERS_LOGIN_URL}`);
|
||||
}
|
||||
}, [profileRole]);
|
||||
|
||||
|
||||
if (session && requiredRight && !hasRequiredRight) {
|
||||
router.push(`${FE_USERS_LOGIN_URL}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Autoriser l'affichage si authentifié et rôle correct
|
||||
return session ? children : null;
|
||||
return hasRequiredRight ? children : null;
|
||||
};
|
||||
|
||||
export default ProtectedRoute;
|
||||
@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||
import ProfileSelector from '@/components/ProfileSelector';
|
||||
|
||||
const SidebarItem = ({ icon: Icon, text, active, url, onClick }) => (
|
||||
<div
|
||||
@ -15,9 +15,9 @@ const SidebarItem = ({ icon: Icon, text, active, url, onClick }) => (
|
||||
</div>
|
||||
);
|
||||
|
||||
function Sidebar({ establishments, currentPage, items, onCloseMobile, onEstablishmentChange }) {
|
||||
function Sidebar({ currentPage, items, onCloseMobile, onEstablishmentChange }) {
|
||||
const router = useRouter();
|
||||
const { selectedEstablishmentId, setSelectedEstablishmentId, setProfileRole } = useEstablishment();
|
||||
|
||||
const [selectedItem, setSelectedItem] = useState(currentPage);
|
||||
|
||||
useEffect(() => {
|
||||
@ -32,31 +32,15 @@ function Sidebar({ establishments, currentPage, items, onCloseMobile, onEstablis
|
||||
}
|
||||
};
|
||||
|
||||
const handleEstablishmentChange = (e) => {
|
||||
const establishmentId = parseInt(e.target.value, 10);
|
||||
setSelectedEstablishmentId(establishmentId);
|
||||
const role = establishments.find(est => est.id === establishmentId)?.role_type;
|
||||
setProfileRole(role);
|
||||
onEstablishmentChange(establishmentId);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-64 bg-white border-r h-full border-gray-200 py-6 px-4">
|
||||
<div className="flex items-center mb-8 px-2">
|
||||
<select
|
||||
value={selectedEstablishmentId || ''}
|
||||
onChange={handleEstablishmentChange}
|
||||
className="form-select block w-full mt-1"
|
||||
>
|
||||
{establishments.map(establishment => (
|
||||
<option key={establishment.id} value={establishment.id}>
|
||||
{establishment.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<div className="w-64 bg-white border-r h-full border-gray-200">
|
||||
<div className="border-b border-gray-200 ">
|
||||
<ProfileSelector
|
||||
onEstablishmentChange={onEstablishmentChange}
|
||||
className="border-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<nav className="space-y-1">
|
||||
<nav className="space-y-1 px-4 py-6">
|
||||
{items.map((item) => (
|
||||
<SidebarItem
|
||||
key={item.id}
|
||||
|
||||
@ -1,37 +1,41 @@
|
||||
import React, { createContext, useContext, useState, useEffect } from 'react';
|
||||
import { useSession } from 'next-auth/react';
|
||||
import logger from '@/utils/logger';
|
||||
|
||||
const EstablishmentContext = createContext();
|
||||
|
||||
export const EstablishmentProvider = ({ children }) => {
|
||||
const { data: session, status } = useSession();
|
||||
const [selectedEstablishmentId, setSelectedEstablishmentId] = useState(null);
|
||||
const [profileRole, setProfileRole] = useState(null);
|
||||
const [establishments, setEstablishments] = useState([]);
|
||||
const [user, setUser] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (status === 'loading') return; // Attendre que le statut de la session soit défini
|
||||
|
||||
if (session && session.user) {
|
||||
setUser(session.user);
|
||||
const userEstablishments = session.user.roles.map(role => ({
|
||||
if (user) {
|
||||
logger.debug('Establishments User= ', user);
|
||||
// Au changement de l'utilisateur on sette par défaut le premier établissement
|
||||
// et le premier rôle
|
||||
const userEstablishments = user.roles.map(role => ({
|
||||
id: role.establishment__id,
|
||||
name: role.establishment__name,
|
||||
role_type: role.role_type
|
||||
}));
|
||||
setEstablishments(userEstablishments);
|
||||
// Sélectionner l'établissement depuis la session ou le premier établissement par défaut
|
||||
logger.debug('Establishments', user.roleIndexLoginDefault);
|
||||
let roleIndexDefault = 0;
|
||||
user.roleIndexLoginDefault;
|
||||
// Sélectionner l'établissement au login par défaut
|
||||
if (userEstablishments.length > user.roleIndexLoginDefault) {
|
||||
roleIndexDefault = user.roleIndexLoginDefault;
|
||||
}
|
||||
if (userEstablishments.length > 0) {
|
||||
setSelectedEstablishmentId(userEstablishments[0].id);
|
||||
setProfileRole(userEstablishments[0].role_type);
|
||||
setSelectedEstablishmentId(userEstablishments[roleIndexDefault].id);
|
||||
setProfileRole(userEstablishments[roleIndexDefault].role_type);
|
||||
}
|
||||
}
|
||||
}, [session, status]);
|
||||
}, [user]);
|
||||
|
||||
return (
|
||||
<EstablishmentContext.Provider value={{ selectedEstablishmentId, setSelectedEstablishmentId, profileRole, setProfileRole, establishments, user }}>
|
||||
<EstablishmentContext.Provider value={{ selectedEstablishmentId, setSelectedEstablishmentId, profileRole, setProfileRole, establishments, setUser, user }}>
|
||||
{children}
|
||||
</EstablishmentContext.Provider>
|
||||
);
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import { createContext, useContext, useEffect, useState } from 'react';
|
||||
import { createPlanning, fetchEvents, fetchPlannings, updatePlanning, createEvent, deleteEvent, updateEvent } from '@/app/actions/planningAction';
|
||||
import { useCsrfToken } from './CsrfContext';
|
||||
import { ESTABLISHMENT_ID } from '@/utils/Url';
|
||||
import logger from '@/utils/logger';
|
||||
import { useSession } from 'next-auth/react';
|
||||
import { useEstablishment }from '@/context/EstablishmentContext';
|
||||
|
||||
|
||||
|
||||
@ -27,13 +28,15 @@ export function PlanningProvider({ children }) {
|
||||
const [currentDate, setCurrentDate] = useState(new Date());
|
||||
const [viewType, setViewType] = useState('week'); // Changer 'month' en 'week'
|
||||
const [hiddenSchedules, setHiddenSchedules] = useState([]);
|
||||
const { selectedEstablishmentId } = useEstablishment();
|
||||
|
||||
const csrfToken = useCsrfToken();
|
||||
useEffect(()=>{
|
||||
|
||||
fetchPlannings().then((data) => {
|
||||
setSchedules(data)
|
||||
setSelectedSchedule(data[0].id);
|
||||
setSchedules(data)
|
||||
if(data.length > 0){
|
||||
setSelectedSchedule(data[0].id);
|
||||
}
|
||||
});
|
||||
fetchEvents().then((data)=>{
|
||||
setEvents(data);
|
||||
@ -47,9 +50,6 @@ export function PlanningProvider({ children }) {
|
||||
createEvent(newEvent).then((data) => {
|
||||
setEvents((prevEvents) => [...prevEvents, data]);
|
||||
});
|
||||
console.log('newEvent',newEvent);
|
||||
|
||||
//dssetEvents((prevEvents) => [...prevEvents, newEvent]);
|
||||
};
|
||||
|
||||
const handleUpdateEvent = (id, updatedEvent) => {
|
||||
@ -69,10 +69,8 @@ export function PlanningProvider({ children }) {
|
||||
};
|
||||
|
||||
const addSchedule = (newSchedule) => {
|
||||
//FIXME:Gerenr lestablshment
|
||||
logger.debug('newSchedule',newSchedule);
|
||||
newSchedule.establishment = ESTABLISHMENT_ID;
|
||||
|
||||
newSchedule.establishment = selectedEstablishmentId;
|
||||
createPlanning(newSchedule,csrfToken).then((data) => {
|
||||
setSchedules((prevSchedules) => [...prevSchedules, data]);
|
||||
});
|
||||
|
||||
@ -11,15 +11,13 @@ const options = {
|
||||
name: 'Credentials',
|
||||
credentials: {
|
||||
email: { label: 'Email', type: 'email' },
|
||||
password: { label: 'Password', type: 'password' },
|
||||
role_type: { label: 'Role Type', type: 'text' }
|
||||
password: { label: 'Password', type: 'password' }
|
||||
},
|
||||
authorize: async (credentials, req) => {
|
||||
try {
|
||||
const data = {
|
||||
email: credentials.email,
|
||||
password: credentials.password,
|
||||
role_type: credentials.role_type
|
||||
password: credentials.password
|
||||
};
|
||||
|
||||
const user = await getJWT(data);
|
||||
@ -87,14 +85,15 @@ const options = {
|
||||
},
|
||||
async session({ session, token }) {
|
||||
if (token && token?.token) {
|
||||
const { user_id, email, roles } = jwt_decode.decode(token.token);
|
||||
const { user_id, email, roles, roleIndexLoginDefault } = jwt_decode.decode(token.token);
|
||||
session.user = {
|
||||
...session.user,
|
||||
token: token.token,
|
||||
refresh: token.refresh,
|
||||
user_id: user_id,
|
||||
email: email,
|
||||
roles: roles
|
||||
roles: roles,
|
||||
roleIndexLoginDefault : roleIndexLoginDefault
|
||||
};
|
||||
}
|
||||
return session;
|
||||
|
||||
@ -1,11 +0,0 @@
|
||||
|
||||
// Current Role Index
|
||||
// Cette fonction permet de stocker et l'index du rôle actuel dans le localStorage
|
||||
export function setCurrentRoleIndex(currentRoleIndex){
|
||||
localStorage.setItem('currentRoleIndex', currentRoleIndex);
|
||||
}
|
||||
|
||||
export function getCurrentRoleIndex(){
|
||||
const currentRoleIndex = localStorage.getItem('currentRoleIndex');
|
||||
return currentRoleIndex? currentRoleIndex : 0;
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
import { RIGHTS } from '@/utils/rights';
|
||||
export const BASE_URL = process.env.NEXT_PUBLIC_API_URL;
|
||||
|
||||
|
||||
|
||||
//URL-Back-End
|
||||
|
||||
// GESTION DocuSeal
|
||||
@ -105,4 +105,23 @@ export const FE_PARENTS_EDIT_INSCRIPTION_URL = `/parents/editInscription`
|
||||
// API DOCUSEAL
|
||||
export const FE_API_DOCUSEAL_GENERATE_TOKEN = `/api/docuseal/generateToken`
|
||||
export const FE_API_DOCUSEAL_CLONE_URL = `/api/docuseal/cloneTemplate`
|
||||
export const FE_API_DOCUSEAL_DOWNLOAD_URL = `/api/docuseal/downloadTemplate`
|
||||
export const FE_API_DOCUSEAL_DOWNLOAD_URL = `/api/docuseal/downloadTemplate`
|
||||
|
||||
|
||||
/**
|
||||
* Fonction pour obtenir l'URL de redirection en fonction du rôle
|
||||
* @param {RIGHTS} role
|
||||
* @returns {string|null} L'URL de redirection ou null si le rôle n'est pas géré
|
||||
*/
|
||||
export function getRedirectUrlFromRole(role) {
|
||||
switch (role) {
|
||||
case RIGHTS.ADMIN:
|
||||
return FE_ADMIN_SUBSCRIPTIONS_URL;
|
||||
case RIGHTS.TEACHER:
|
||||
return FE_ADMIN_SUBSCRIPTIONS_URL;
|
||||
case RIGHTS.PARENT:
|
||||
return FE_PARENTS_HOME_URL;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user