mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-01-28 15:33:22 +00:00
chore: application prettier
This commit is contained in:
3
Front-End/.prettierignore
Normal file
3
Front-End/.prettierignore
Normal file
@ -0,0 +1,3 @@
|
||||
node_modules/
|
||||
build/
|
||||
dist/
|
||||
6
Front-End/.prettierrc
Normal file
6
Front-End/.prettierrc
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
@ -1,4 +1,3 @@
|
||||
|
||||
{
|
||||
"responsable": "Guardian",
|
||||
"delete": "Delete",
|
||||
@ -10,4 +9,4 @@
|
||||
"profession": "Profession",
|
||||
"address": "Address",
|
||||
"add_responsible": "Add guardian"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
{
|
||||
"dashboard": "Dashboard",
|
||||
"totalStudents": "Total Students",
|
||||
"pendingRegistrations": "Pending Registration",
|
||||
"reInscriptionRate": "Re-enrollment Rate",
|
||||
"structureCapacity": "Structure Capacity",
|
||||
"capacityRate": "Capacity Rate",
|
||||
"inscriptionTrends": "Enrollment Trends",
|
||||
"upcomingEvents": "Upcoming Events"
|
||||
}
|
||||
"dashboard": "Dashboard",
|
||||
"totalStudents": "Total Students",
|
||||
"pendingRegistrations": "Pending Registration",
|
||||
"reInscriptionRate": "Re-enrollment Rate",
|
||||
"structureCapacity": "Structure Capacity",
|
||||
"capacityRate": "Capacity Rate",
|
||||
"inscriptionTrends": "Enrollment Trends",
|
||||
"upcomingEvents": "Upcoming Events"
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"welcomeParents": "Welcome Parents",
|
||||
"pleaseLogin": "Please login to access your account",
|
||||
"loginButton": "Go to login page"
|
||||
}
|
||||
"welcomeParents": "Welcome Parents",
|
||||
"pleaseLogin": "Please login to access your account",
|
||||
"loginButton": "Go to login page"
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"page": "Page",
|
||||
"of": "of",
|
||||
"previous": "Previous",
|
||||
"next": "Next"
|
||||
}
|
||||
"page": "Page",
|
||||
"of": "of",
|
||||
"previous": "Previous",
|
||||
"next": "Next"
|
||||
}
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
{
|
||||
"dashboard": "Dashboard",
|
||||
"subscriptions": "Subscriptions",
|
||||
"structure": "Structure",
|
||||
"directory": "Directory",
|
||||
"events": "Events",
|
||||
"grades": "Grades",
|
||||
"settings": "Settings",
|
||||
"schoolAdmin": "School Administration"
|
||||
}
|
||||
"dashboard": "Dashboard",
|
||||
"subscriptions": "Subscriptions",
|
||||
"structure": "Structure",
|
||||
"directory": "Directory",
|
||||
"events": "Events",
|
||||
"grades": "Grades",
|
||||
"settings": "Settings",
|
||||
"schoolAdmin": "School Administration"
|
||||
}
|
||||
|
||||
@ -1,33 +1,33 @@
|
||||
{
|
||||
"headerBarTitle": "Administration",
|
||||
"addStudent": "New",
|
||||
"allStudents": "All Students",
|
||||
"pending": "Pending Registrations",
|
||||
"subscribed": "Subscribed",
|
||||
"archived": "Archived",
|
||||
"name": "Name",
|
||||
"class": "Class",
|
||||
"status": "Status",
|
||||
"attendance": "Attendance",
|
||||
"lastEvaluation": "Last Evaluation",
|
||||
"active": "Active",
|
||||
"pendingStatus": "Pending",
|
||||
"goodAttendance": "Good",
|
||||
"averageAttendance": "Average",
|
||||
"lowAttendance": "Poor",
|
||||
"searchStudent": "Search for a student...",
|
||||
"title": "Registration",
|
||||
"information": "Information",
|
||||
"no_records": "There are currently no registration records.",
|
||||
"add_button": "Add",
|
||||
"create_first_record": "Please click the ADD button to create your first registration record.",
|
||||
"studentName":"Student name",
|
||||
"studentFistName":"Student first name",
|
||||
"mainContactMail":"Main contact email",
|
||||
"phone":"Phone",
|
||||
"lastUpdateDate":"Last update",
|
||||
"classe":"Class",
|
||||
"registrationFileStatus":"Registration file status",
|
||||
"files":"Files",
|
||||
"subscribeFiles":"Subscribe files"
|
||||
}
|
||||
"headerBarTitle": "Administration",
|
||||
"addStudent": "New",
|
||||
"allStudents": "All Students",
|
||||
"pending": "Pending Registrations",
|
||||
"subscribed": "Subscribed",
|
||||
"archived": "Archived",
|
||||
"name": "Name",
|
||||
"class": "Class",
|
||||
"status": "Status",
|
||||
"attendance": "Attendance",
|
||||
"lastEvaluation": "Last Evaluation",
|
||||
"active": "Active",
|
||||
"pendingStatus": "Pending",
|
||||
"goodAttendance": "Good",
|
||||
"averageAttendance": "Average",
|
||||
"lowAttendance": "Poor",
|
||||
"searchStudent": "Search for a student...",
|
||||
"title": "Registration",
|
||||
"information": "Information",
|
||||
"no_records": "There are currently no registration records.",
|
||||
"add_button": "Add",
|
||||
"create_first_record": "Please click the ADD button to create your first registration record.",
|
||||
"studentName": "Student name",
|
||||
"studentFistName": "Student first name",
|
||||
"mainContactMail": "Main contact email",
|
||||
"phone": "Phone",
|
||||
"lastUpdateDate": "Last update",
|
||||
"classe": "Class",
|
||||
"registrationFileStatus": "Registration file status",
|
||||
"files": "Files",
|
||||
"subscribeFiles": "Subscribe files"
|
||||
}
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
|
||||
{
|
||||
"responsable": "Responsable",
|
||||
"delete": "Supprimer",
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
{
|
||||
"dashboard": "Tableau de bord",
|
||||
"totalStudents": "Total des étudiants",
|
||||
"pendingRegistrations": "Inscriptions en attente",
|
||||
"reInscriptionRate": "Taux de réinscription",
|
||||
"structureCapacity": "Capacité de la structure",
|
||||
"capacityRate": "Remplissage de la structure",
|
||||
"inscriptionTrends": "Tendances d'inscription",
|
||||
"upcomingEvents": "Événements à venir"
|
||||
}
|
||||
"dashboard": "Tableau de bord",
|
||||
"totalStudents": "Total des étudiants",
|
||||
"pendingRegistrations": "Inscriptions en attente",
|
||||
"reInscriptionRate": "Taux de réinscription",
|
||||
"structureCapacity": "Capacité de la structure",
|
||||
"capacityRate": "Remplissage de la structure",
|
||||
"inscriptionTrends": "Tendances d'inscription",
|
||||
"upcomingEvents": "Événements à venir"
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"welcomeParents": "Bienvenue aux parents",
|
||||
"pleaseLogin": "Veuillez vous connecter pour accéder à votre compte",
|
||||
"loginButton": "Accéder à la page de login"
|
||||
}
|
||||
"welcomeParents": "Bienvenue aux parents",
|
||||
"pleaseLogin": "Veuillez vous connecter pour accéder à votre compte",
|
||||
"loginButton": "Accéder à la page de login"
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"page": "Page",
|
||||
"of": "sur",
|
||||
"previous": "Précédent",
|
||||
"next": "Suivant"
|
||||
}
|
||||
"page": "Page",
|
||||
"of": "sur",
|
||||
"previous": "Précédent",
|
||||
"next": "Suivant"
|
||||
}
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
{
|
||||
"dashboard": "Tableau de bord",
|
||||
"subscriptions": "Inscriptions",
|
||||
"structure": "Structure",
|
||||
"directory": "Annuaire",
|
||||
"events": "Evenements",
|
||||
"grades": "Notes",
|
||||
"settings": "Paramètres",
|
||||
"schoolAdmin": "Administration Scolaire"
|
||||
}
|
||||
{
|
||||
"dashboard": "Tableau de bord",
|
||||
"subscriptions": "Inscriptions",
|
||||
"structure": "Structure",
|
||||
"directory": "Annuaire",
|
||||
"events": "Evenements",
|
||||
"grades": "Notes",
|
||||
"settings": "Paramètres",
|
||||
"schoolAdmin": "Administration Scolaire"
|
||||
}
|
||||
|
||||
@ -1,33 +1,33 @@
|
||||
{
|
||||
"headerBarTitle":"Administration",
|
||||
"addStudent": "Nouveau",
|
||||
"allStudents": "Tous les élèves",
|
||||
"pending": "Inscriptions en attente",
|
||||
"subscribed": "Inscrits",
|
||||
"archived": "Archivés",
|
||||
"name": "Nom",
|
||||
"class": "Classe",
|
||||
"status": "Statut",
|
||||
"attendance": "Assiduité",
|
||||
"lastEvaluation": "Dernière évaluation",
|
||||
"active": "Actif",
|
||||
"pendingStatus": "En attente",
|
||||
"goodAttendance": "Bonne",
|
||||
"averageAttendance": "Moyenne",
|
||||
"lowAttendance": "Faible",
|
||||
"searchStudent": "Rechercher un élève...",
|
||||
"title": "Inscription",
|
||||
"information": "Information",
|
||||
"no_records": "Il n'y a actuellement aucun dossier d'inscription.",
|
||||
"add_button": "Ajouter",
|
||||
"create_first_record": "Veuillez cliquer sur le bouton AJOUTER pour créer votre premier dossier d'inscription.",
|
||||
"studentName":"Nom de l'élève",
|
||||
"studentFistName":"Prénom de l'élève",
|
||||
"mainContactMail":"Email de contact principal",
|
||||
"phone":"Téléphone",
|
||||
"lastUpdateDate":"Dernière mise à jour",
|
||||
"classe":"Classe",
|
||||
"registrationFileStatus":"État du dossier d'inscription",
|
||||
"files":"Fichiers",
|
||||
"subscribeFiles":"Fichiers d'inscription"
|
||||
}
|
||||
"headerBarTitle": "Administration",
|
||||
"addStudent": "Nouveau",
|
||||
"allStudents": "Tous les élèves",
|
||||
"pending": "Inscriptions en attente",
|
||||
"subscribed": "Inscrits",
|
||||
"archived": "Archivés",
|
||||
"name": "Nom",
|
||||
"class": "Classe",
|
||||
"status": "Statut",
|
||||
"attendance": "Assiduité",
|
||||
"lastEvaluation": "Dernière évaluation",
|
||||
"active": "Actif",
|
||||
"pendingStatus": "En attente",
|
||||
"goodAttendance": "Bonne",
|
||||
"averageAttendance": "Moyenne",
|
||||
"lowAttendance": "Faible",
|
||||
"searchStudent": "Rechercher un élève...",
|
||||
"title": "Inscription",
|
||||
"information": "Information",
|
||||
"no_records": "Il n'y a actuellement aucun dossier d'inscription.",
|
||||
"add_button": "Ajouter",
|
||||
"create_first_record": "Veuillez cliquer sur le bouton AJOUTER pour créer votre premier dossier d'inscription.",
|
||||
"studentName": "Nom de l'élève",
|
||||
"studentFistName": "Prénom de l'élève",
|
||||
"mainContactMail": "Email de contact principal",
|
||||
"phone": "Téléphone",
|
||||
"lastUpdateDate": "Dernière mise à jour",
|
||||
"classe": "Classe",
|
||||
"registrationFileStatus": "État du dossier d'inscription",
|
||||
"files": "Fichiers",
|
||||
"subscribeFiles": "Fichiers d'inscription"
|
||||
}
|
||||
|
||||
@ -1,14 +1,13 @@
|
||||
import createNextIntlPlugin from 'next-intl/plugin';
|
||||
import { createRequire } from "module";
|
||||
import { createRequire } from 'module';
|
||||
const require = createRequire(import.meta.url);
|
||||
const pkg = require("./package.json")
|
||||
|
||||
const pkg = require('./package.json');
|
||||
|
||||
const withNextIntl = createNextIntlPlugin();
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
output: "standalone",
|
||||
output: 'standalone',
|
||||
reactStrictMode: true,
|
||||
experimental: {
|
||||
instrumentationHook: true,
|
||||
@ -16,17 +15,18 @@ const nextConfig = {
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{
|
||||
protocol: "https",
|
||||
hostname: "www.gravatar.com",
|
||||
protocol: 'https',
|
||||
hostname: 'www.gravatar.com',
|
||||
},
|
||||
],
|
||||
},
|
||||
env: {
|
||||
NEXT_PUBLIC_APP_VERSION: pkg.version,
|
||||
NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL || "http://localhost:8080",
|
||||
NEXT_PUBLIC_API_URL:
|
||||
process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8080',
|
||||
NEXT_PUBLIC_USE_FAKE_DATA: process.env.NEXT_PUBLIC_USE_FAKE_DATA || 'false',
|
||||
AUTH_SECRET: process.env.AUTH_SECRET || 'false',
|
||||
NEXTAUTH_URL: process.env.NEXTAUTH_URL || "http://localhost:3000",
|
||||
NEXTAUTH_URL: process.env.NEXTAUTH_URL || 'http://localhost:3000',
|
||||
DOCUSEAL_API_KEY: process.env.DOCUSEAL_API_KEY,
|
||||
},
|
||||
async rewrites() {
|
||||
@ -40,7 +40,7 @@ const nextConfig = {
|
||||
destination: '/api/auth/:path*', // Exclure les routes NextAuth des réécritures de proxy
|
||||
},
|
||||
];
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default withNextIntl(nextConfig);
|
||||
export default withNextIntl(nextConfig);
|
||||
|
||||
@ -3,4 +3,4 @@ module.exports = {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
@ -29,7 +29,7 @@ const TAILWIND_PATTERNS = [
|
||||
// États
|
||||
/^(hover|focus|active|disabled|group|dark):/,
|
||||
// Couleurs
|
||||
/-(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-[0-9]+$/
|
||||
/-(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-[0-9]+$/,
|
||||
];
|
||||
|
||||
// Nouveaux patterns pour ignorer les logs et imports
|
||||
@ -58,7 +58,7 @@ function isHardcodedString(str) {
|
||||
|
||||
// Vérifier si la chaîne fait partie d'un console.log ou d'un import
|
||||
const context = str.trim();
|
||||
if (CODE_PATTERNS.some(pattern => pattern.test(context))) {
|
||||
if (CODE_PATTERNS.some((pattern) => pattern.test(context))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -68,19 +68,20 @@ function isHardcodedString(str) {
|
||||
}
|
||||
|
||||
// Vérifier si c'est une chaîne dans un import
|
||||
if (context.includes('from \'') || context.includes('from "')) {
|
||||
if (context.includes("from '") || context.includes('from "')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Vérifier si c'est une classe Tailwind
|
||||
const classes = str.split(' ');
|
||||
if (classes.some(cls =>
|
||||
TAILWIND_PATTERNS.some(pattern => pattern.test(cls))
|
||||
)) {
|
||||
if (
|
||||
classes.some((cls) =>
|
||||
TAILWIND_PATTERNS.some((pattern) => pattern.test(cls))
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Autres patterns à ignorer
|
||||
const IGNORE_PATTERNS = [
|
||||
/^[A-Z][A-Za-z]+$/, // Noms de composants
|
||||
@ -95,7 +96,7 @@ function isHardcodedString(str) {
|
||||
/^className=/, // className attributes
|
||||
];
|
||||
|
||||
return !IGNORE_PATTERNS.some(pattern => pattern.test(str));
|
||||
return !IGNORE_PATTERNS.some((pattern) => pattern.test(str));
|
||||
}
|
||||
|
||||
async function scanFile(filePath) {
|
||||
@ -106,7 +107,7 @@ async function scanFile(filePath) {
|
||||
const ast = babel.parse(content, {
|
||||
sourceType: 'module',
|
||||
plugins: ['jsx', 'typescript'],
|
||||
locations: true // Active le tracking des positions
|
||||
locations: true, // Active le tracking des positions
|
||||
});
|
||||
|
||||
traverse(ast, {
|
||||
@ -140,7 +141,11 @@ async function scanDirectory(dir) {
|
||||
const filePath = path.join(dir, file);
|
||||
const stat = fs.statSync(filePath);
|
||||
|
||||
if (stat.isDirectory() && !file.startsWith('.') && file !== 'node_modules') {
|
||||
if (
|
||||
stat.isDirectory() &&
|
||||
!file.startsWith('.') &&
|
||||
file !== 'node_modules'
|
||||
) {
|
||||
Object.assign(results, await scanDirectory(filePath));
|
||||
} else if (
|
||||
stat.isFile() &&
|
||||
@ -189,4 +194,4 @@ async function main() {
|
||||
await logStringsToFile(results);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
main().catch(console.error);
|
||||
|
||||
@ -1,15 +1,21 @@
|
||||
import Link from 'next/link'
|
||||
import Logo from '../components/Logo'
|
||||
import Link from 'next/link';
|
||||
import Logo from '../components/Logo';
|
||||
|
||||
export default function Custom500() {
|
||||
return (
|
||||
<div className='flex items-center justify-center min-h-screen bg-emerald-500'>
|
||||
<div className='text-center p-6 '>
|
||||
<div className="flex items-center justify-center min-h-screen bg-emerald-500">
|
||||
<div className="text-center p-6 ">
|
||||
<Logo className="w-32 h-32 mx-auto mb-4" />
|
||||
<h2 className='text-2xl font-bold text-emerald-900 mb-4'>500 | Erreur interne</h2>
|
||||
<p className='text-emerald-900 mb-4'>Une erreur interne est survenue.</p>
|
||||
<Link className="text-gray-900 hover:underline" href="/">Retour Accueil</Link>
|
||||
<h2 className="text-2xl font-bold text-emerald-900 mb-4">
|
||||
500 | Erreur interne
|
||||
</h2>
|
||||
<p className="text-emerald-900 mb-4">
|
||||
Une erreur interne est survenue.
|
||||
</p>
|
||||
<Link className="text-gray-900 hover:underline" href="/">
|
||||
Retour Accueil
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
'use client'
|
||||
'use client';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { fetchProfileRoles, updateProfileRoles, deleteProfileRoles } from '@/app/actions/authAction';
|
||||
import {
|
||||
fetchProfileRoles,
|
||||
updateProfileRoles,
|
||||
deleteProfileRoles,
|
||||
} from '@/app/actions/authAction';
|
||||
import { dissociateGuardian } from '@/app/actions/subscriptionAction';
|
||||
import logger from '@/utils/logger';
|
||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||
@ -24,21 +28,23 @@ export default function Page() {
|
||||
|
||||
const handleProfiles = () => {
|
||||
fetchProfileRoles(selectedEstablishmentId)
|
||||
.then(data => {
|
||||
.then((data) => {
|
||||
setProfileRoles(data);
|
||||
})
|
||||
.catch(error => logger.error('Error fetching profileRoles:', error));
|
||||
setReloadFetch(false);
|
||||
.catch((error) => logger.error('Error fetching profileRoles:', error));
|
||||
setReloadFetch(false);
|
||||
};
|
||||
|
||||
const handleEdit = (profileRole) => {
|
||||
const updatedData = { ...profileRole, is_active: !profileRole.is_active };
|
||||
return updateProfileRoles(profileRole.id, updatedData, csrfToken)
|
||||
.then(data => {
|
||||
setProfileRoles(prevState => prevState.map(item => item.id === profileRole.id ? data : item));
|
||||
.then((data) => {
|
||||
setProfileRoles((prevState) =>
|
||||
prevState.map((item) => (item.id === profileRole.id ? data : item))
|
||||
);
|
||||
return data;
|
||||
})
|
||||
.catch(error => {
|
||||
.catch((error) => {
|
||||
logger.error('Error editing data:', error);
|
||||
throw error;
|
||||
});
|
||||
@ -47,10 +53,12 @@ export default function Page() {
|
||||
const handleDelete = (id) => {
|
||||
return deleteProfileRoles(id, csrfToken)
|
||||
.then(() => {
|
||||
setProfileRoles(prevState => prevState.filter(item => item.id !== id));
|
||||
logger.debug("Profile deleted successfully:", id);
|
||||
setProfileRoles((prevState) =>
|
||||
prevState.filter((item) => item.id !== id)
|
||||
);
|
||||
logger.debug('Profile deleted successfully:', id);
|
||||
})
|
||||
.catch(error => {
|
||||
.catch((error) => {
|
||||
logger.error('Error deleting profile:', error);
|
||||
throw error;
|
||||
});
|
||||
@ -59,49 +67,58 @@ export default function Page() {
|
||||
const handleDissociate = (studentId, guardianId) => {
|
||||
return dissociateGuardian(studentId, guardianId)
|
||||
.then((response) => {
|
||||
logger.debug("Guardian dissociated successfully:", guardianId);
|
||||
|
||||
logger.debug('Guardian dissociated successfully:', guardianId);
|
||||
|
||||
// Vérifier si le Guardian a été supprimé
|
||||
const isGuardianDeleted = response?.isGuardianDeleted;
|
||||
|
||||
|
||||
// Mettre à jour le modèle profileRoles
|
||||
setProfileRoles(prevState =>
|
||||
prevState.map(profileRole => {
|
||||
if (profileRole.associated_person?.id === guardianId) {
|
||||
if (isGuardianDeleted) {
|
||||
// Si le Guardian est supprimé, retirer le profileRole
|
||||
return null;
|
||||
} else {
|
||||
// Si le Guardian n'est pas supprimé, mettre à jour les élèves associés
|
||||
const updatedStudents = profileRole.associated_person.students.filter(
|
||||
student => student.id !== studentId
|
||||
);
|
||||
return {
|
||||
...profileRole,
|
||||
associated_person: {
|
||||
...profileRole.associated_person,
|
||||
students: updatedStudents, // Mettre à jour les élèves associés
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
setReloadFetch(true);
|
||||
return profileRole; // Conserver les autres profileRoles
|
||||
}).filter(Boolean) // Supprimer les entrées nulles
|
||||
setProfileRoles(
|
||||
(prevState) =>
|
||||
prevState
|
||||
.map((profileRole) => {
|
||||
if (profileRole.associated_person?.id === guardianId) {
|
||||
if (isGuardianDeleted) {
|
||||
// Si le Guardian est supprimé, retirer le profileRole
|
||||
return null;
|
||||
} else {
|
||||
// Si le Guardian n'est pas supprimé, mettre à jour les élèves associés
|
||||
const updatedStudents =
|
||||
profileRole.associated_person.students.filter(
|
||||
(student) => student.id !== studentId
|
||||
);
|
||||
return {
|
||||
...profileRole,
|
||||
associated_person: {
|
||||
...profileRole.associated_person,
|
||||
students: updatedStudents, // Mettre à jour les élèves associés
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
setReloadFetch(true);
|
||||
return profileRole; // Conserver les autres profileRoles
|
||||
})
|
||||
.filter(Boolean) // Supprimer les entrées nulles
|
||||
);
|
||||
})
|
||||
.catch(error => {
|
||||
.catch((error) => {
|
||||
logger.error('Error dissociating guardian:', error);
|
||||
throw error;
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='p-8'>
|
||||
<div className="p-8">
|
||||
<DjangoCSRFToken csrfToken={csrfToken} />
|
||||
|
||||
<div className="w-full p-4">
|
||||
<ProfileDirectory profileRoles={profileRoles} handleActivateProfile={handleEdit} handleDeleteProfile={handleDelete} handleDissociateGuardian={handleDissociate} />
|
||||
<ProfileDirectory
|
||||
profileRoles={profileRoles}
|
||||
handleActivateProfile={handleEdit}
|
||||
handleDeleteProfile={handleDelete}
|
||||
handleDissociateGuardian={handleDissociate}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
'use client'
|
||||
'use client';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<div className='p-8'>
|
||||
<h1 className='heading-section'>Statistiques</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="p-8">
|
||||
<h1 className="heading-section">Statistiques</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
'use client'
|
||||
'use client';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import Sidebar from '@/components/Sidebar';
|
||||
import { usePathname } from 'next/navigation';
|
||||
@ -14,7 +14,7 @@ import {
|
||||
Settings,
|
||||
LogOut,
|
||||
Menu,
|
||||
X
|
||||
X,
|
||||
} from 'lucide-react';
|
||||
import DropdownMenu from '@/components/DropdownMenu';
|
||||
|
||||
@ -26,7 +26,7 @@ import {
|
||||
FE_ADMIN_DIRECTORY_URL,
|
||||
FE_ADMIN_GRADES_URL,
|
||||
FE_ADMIN_PLANNING_URL,
|
||||
FE_ADMIN_SETTINGS_URL
|
||||
FE_ADMIN_SETTINGS_URL,
|
||||
} from '@/utils/Url';
|
||||
|
||||
import { disconnect } from '@/app/actions/authAction';
|
||||
@ -38,25 +38,63 @@ import { getRightStr, RIGHTS } from '@/utils/rights';
|
||||
import logger from '@/utils/logger';
|
||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||
|
||||
export default function Layout({
|
||||
children,
|
||||
}) {
|
||||
export default function Layout({ children }) {
|
||||
const t = useTranslations('sidebar');
|
||||
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
||||
const { data: session } = useSession();
|
||||
const { selectedEstablishmentId, setSelectedEstablishmentId, profileRole, setProfileRole, establishments, user } = useEstablishment();
|
||||
|
||||
|
||||
const {
|
||||
selectedEstablishmentId,
|
||||
setSelectedEstablishmentId,
|
||||
profileRole,
|
||||
setProfileRole,
|
||||
establishments,
|
||||
user,
|
||||
} = useEstablishment();
|
||||
|
||||
// Déplacer le reste du code ici...
|
||||
const sidebarItems = {
|
||||
"admin": { "id": "admin", "name": t('dashboard'), "url": FE_ADMIN_HOME_URL, "icon": LayoutDashboard },
|
||||
"subscriptions": { "id": "subscriptions", "name": t('subscriptions'), "url": FE_ADMIN_SUBSCRIPTIONS_URL, "icon": FileText },
|
||||
"structure": { "id": "structure", "name": t('structure'), "url": FE_ADMIN_STRUCTURE_URL, "icon": School },
|
||||
"directory": { "id": "directory", "name": t('directory'), "url": FE_ADMIN_DIRECTORY_URL, "icon": Users },
|
||||
"grades": { "id": "grades", "name": t('grades'), "url": FE_ADMIN_GRADES_URL, "icon": Award },
|
||||
"planning": { "id": "planning", "name": t('events'), "url": FE_ADMIN_PLANNING_URL, "icon": Calendar },
|
||||
"settings": { "id": "settings", "name": t('settings'), "url": FE_ADMIN_SETTINGS_URL, "icon": Settings }
|
||||
admin: {
|
||||
id: 'admin',
|
||||
name: t('dashboard'),
|
||||
url: FE_ADMIN_HOME_URL,
|
||||
icon: LayoutDashboard,
|
||||
},
|
||||
subscriptions: {
|
||||
id: 'subscriptions',
|
||||
name: t('subscriptions'),
|
||||
url: FE_ADMIN_SUBSCRIPTIONS_URL,
|
||||
icon: FileText,
|
||||
},
|
||||
structure: {
|
||||
id: 'structure',
|
||||
name: t('structure'),
|
||||
url: FE_ADMIN_STRUCTURE_URL,
|
||||
icon: School,
|
||||
},
|
||||
directory: {
|
||||
id: 'directory',
|
||||
name: t('directory'),
|
||||
url: FE_ADMIN_DIRECTORY_URL,
|
||||
icon: Users,
|
||||
},
|
||||
grades: {
|
||||
id: 'grades',
|
||||
name: t('grades'),
|
||||
url: FE_ADMIN_GRADES_URL,
|
||||
icon: Award,
|
||||
},
|
||||
planning: {
|
||||
id: 'planning',
|
||||
name: t('events'),
|
||||
url: FE_ADMIN_PLANNING_URL,
|
||||
icon: Calendar,
|
||||
},
|
||||
settings: {
|
||||
id: 'settings',
|
||||
name: t('settings'),
|
||||
url: FE_ADMIN_SETTINGS_URL,
|
||||
icon: Settings,
|
||||
},
|
||||
};
|
||||
|
||||
const [isPopupVisible, setIsPopupVisible] = useState(false);
|
||||
@ -66,7 +104,7 @@ export default function Layout({
|
||||
|
||||
const headerTitle = sidebarItems[currentPage]?.name || t('dashboard');
|
||||
|
||||
const softwareName = "N3WT School";
|
||||
const softwareName = 'N3WT School';
|
||||
const softwareVersion = `${process.env.NEXT_PUBLIC_APP_VERSION}`;
|
||||
|
||||
const handleDisconnect = () => {
|
||||
@ -84,13 +122,15 @@ export default function Layout({
|
||||
content: (
|
||||
<div className="px-4 py-2">
|
||||
<div className="font-medium">{user?.email || 'Utilisateur'}</div>
|
||||
<div className="text-xs text-gray-400">{getRightStr(profileRole) || ''}</div>
|
||||
<div className="text-xs text-gray-400">
|
||||
{getRightStr(profileRole) || ''}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
),
|
||||
},
|
||||
{
|
||||
type: 'separator',
|
||||
content: <hr className="my-2 border-gray-200" />
|
||||
content: <hr className="my-2 border-gray-200" />,
|
||||
},
|
||||
{
|
||||
type: 'item',
|
||||
@ -126,7 +166,9 @@ export default function Layout({
|
||||
onEstablishmentChange={(establishmentId) => {
|
||||
const parsedEstablishmentId = parseInt(establishmentId, 10);
|
||||
setSelectedEstablishmentId(parsedEstablishmentId);
|
||||
let roleIndex = session.user.roles.findIndex(role => role.establishment__id === parsedEstablishmentId)
|
||||
let roleIndex = session.user.roles.findIndex(
|
||||
(role) => role.establishment__id === parsedEstablishmentId
|
||||
);
|
||||
if (roleIndex === -1) {
|
||||
roleIndex = 0;
|
||||
}
|
||||
@ -155,7 +197,9 @@ export default function Layout({
|
||||
>
|
||||
{isSidebarOpen ? <X size={24} /> : <Menu size={24} />}
|
||||
</button>
|
||||
<div className="text-lg md:text-xl font-semibold">{headerTitle}</div>
|
||||
<div className="text-lg md:text-xl font-semibold">
|
||||
{headerTitle}
|
||||
</div>
|
||||
</div>
|
||||
<DropdownMenu
|
||||
buttonContent={
|
||||
@ -175,11 +219,12 @@ export default function Layout({
|
||||
{/* Main Content */}
|
||||
<div className="flex-1 flex flex-col">
|
||||
{/* Content avec scroll si nécessaire */}
|
||||
<div className="flex-1 overflow-auto p-4 md:p-6">
|
||||
{children}
|
||||
</div>
|
||||
<div className="flex-1 overflow-auto p-4 md:p-6">{children}</div>
|
||||
{/* Footer responsive */}
|
||||
<Footer softwareName={softwareName} softwareVersion={softwareVersion} />
|
||||
<Footer
|
||||
softwareName={softwareName}
|
||||
softwareVersion={softwareVersion}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -192,5 +237,3 @@ export default function Layout({
|
||||
</ProtectedRoute>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
'use client';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { Users, Clock, CalendarCheck, School, TrendingUp, UserCheck } from 'lucide-react';
|
||||
import { Users, Clock, CalendarCheck, School, TrendingUp } from 'lucide-react';
|
||||
import Loader from '@/components/Loader';
|
||||
import ClasseDetails from '@/components/ClasseDetails';
|
||||
import { fetchClasses } from '@/app/actions/schoolAction';
|
||||
@ -11,7 +11,6 @@ import { fetchRegisterForms } from '@/app/actions/subscriptionAction';
|
||||
import { fetchUpcomingEvents } from '@/app/actions/planningAction';
|
||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||
|
||||
|
||||
// Composant EventCard pour afficher les événements
|
||||
const EventCard = ({ title, date, description, type }) => (
|
||||
<div className="bg-white p-4 rounded-lg shadow-sm border border-gray-100 mb-4">
|
||||
@ -35,10 +34,9 @@ export default function DashboardPage() {
|
||||
const [upcomingEvents, setUpcomingEvents] = useState([]);
|
||||
const [monthlyStats, setMonthlyStats] = useState({
|
||||
inscriptions: [],
|
||||
completionRate: 0
|
||||
completionRate: 0,
|
||||
});
|
||||
|
||||
|
||||
const [classes, setClasses] = useState([]);
|
||||
const { selectedEstablishmentId } = useEstablishment();
|
||||
|
||||
@ -49,35 +47,41 @@ export default function DashboardPage() {
|
||||
|
||||
// Fetch des classes
|
||||
fetchClasses(selectedEstablishmentId)
|
||||
.then(data => {
|
||||
.then((data) => {
|
||||
setClasses(data);
|
||||
logger.info('Classes fetched:', data);
|
||||
|
||||
const nbMaxStudents = data.reduce((acc, classe) => acc + classe.number_of_students, 0);
|
||||
const nbStudents = data.reduce((acc, classe) => acc + classe.students.length, 0);
|
||||
const nbMaxStudents = data.reduce(
|
||||
(acc, classe) => acc + classe.number_of_students,
|
||||
0
|
||||
);
|
||||
const nbStudents = data.reduce(
|
||||
(acc, classe) => acc + classe.students.length,
|
||||
0
|
||||
);
|
||||
setStructureCapacity(nbMaxStudents);
|
||||
setTotalStudents(nbStudents);
|
||||
})
|
||||
.catch(error => {
|
||||
.catch((error) => {
|
||||
logger.error('Error fetching classes:', error);
|
||||
});
|
||||
|
||||
// Fetch des formulaires d'inscription
|
||||
fetchRegisterForms()
|
||||
.then(data => {
|
||||
.then((data) => {
|
||||
logger.info('Pending registrations fetched:', data);
|
||||
setPendingRegistration(data.count);
|
||||
})
|
||||
.catch(error => {
|
||||
.catch((error) => {
|
||||
logger.error('Error fetching pending registrations:', error);
|
||||
});
|
||||
|
||||
// Fetch des événements à venir
|
||||
fetchUpcomingEvents()
|
||||
.then(data => {
|
||||
.then((data) => {
|
||||
setUpcomingEvents(data);
|
||||
})
|
||||
.catch(error => {
|
||||
.catch((error) => {
|
||||
logger.error('Error fetching upcoming events:', error);
|
||||
})
|
||||
.finally(() => {
|
||||
@ -110,9 +114,9 @@ export default function DashboardPage() {
|
||||
icon={<School className="text-green-500" size={24} />}
|
||||
color="emerald"
|
||||
/>
|
||||
<StatCard
|
||||
<StatCard
|
||||
title={t('capacityRate')}
|
||||
value={`${(totalStudents/structureCapacity * 100).toFixed(1)}%`}
|
||||
value={`${((totalStudents / structureCapacity) * 100).toFixed(1)}%`}
|
||||
icon={<School className="text-orange-500" size={24} />}
|
||||
color="orange"
|
||||
/>
|
||||
@ -122,7 +126,9 @@ export default function DashboardPage() {
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-6">
|
||||
{/* Graphique des inscriptions */}
|
||||
<div className="lg:col-span-2 bg-white p-6 rounded-lg shadow-sm border border-gray-100">
|
||||
<h2 className="text-lg font-semibold mb-4">{t('inscriptionTrends')}</h2>
|
||||
<h2 className="text-lg font-semibold mb-4">
|
||||
{t('inscriptionTrends')}
|
||||
</h2>
|
||||
{/* Insérer ici un composant de graphique */}
|
||||
<div className="h-64 bg-gray-50 rounded flex items-center justify-center">
|
||||
<TrendingUp size={48} className="text-gray-300" />
|
||||
@ -140,11 +146,14 @@ export default function DashboardPage() {
|
||||
|
||||
<div className="flex flex-wrap">
|
||||
{classes.map((classe) => (
|
||||
<div key={classe.id} className="lg:col-span-2 bg-white p-6 rounded-lg shadow-sm border border-gray-100 mr-4">
|
||||
<ClasseDetails classe={classe} />
|
||||
<div
|
||||
key={classe.id}
|
||||
className="lg:col-span-2 bg-white p-6 rounded-lg shadow-sm border border-gray-100 mr-4"
|
||||
>
|
||||
<ClasseDetails classe={classe} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
'use client'
|
||||
'use client';
|
||||
import { PlanningProvider } from '@/context/PlanningContext';
|
||||
import Calendar from '@/components/Calendar';
|
||||
import EventModal from '@/components/EventModal';
|
||||
@ -19,7 +19,7 @@ export default function Page() {
|
||||
recurrenceEnd: '',
|
||||
customInterval: 1,
|
||||
customUnit: 'days',
|
||||
viewType: 'week' // Ajouter la vue semaine par défaut
|
||||
viewType: 'week', // Ajouter la vue semaine par défaut
|
||||
});
|
||||
|
||||
const initializeNewEvent = (date = new Date()) => {
|
||||
@ -37,7 +37,7 @@ export default function Page() {
|
||||
selectedDays: [],
|
||||
recurrenceEnd: '',
|
||||
customInterval: 1,
|
||||
customUnit: 'days'
|
||||
customUnit: 'days',
|
||||
});
|
||||
setIsModalOpen(true);
|
||||
};
|
||||
@ -62,4 +62,4 @@ export default function Page() {
|
||||
</div>
|
||||
</PlanningProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
'use client'
|
||||
'use client';
|
||||
import React, { useState } from 'react';
|
||||
import Tab from '@/components/Tab';
|
||||
import TabContent from '@/components/TabContent';
|
||||
@ -85,18 +85,49 @@ export default function SettingsPage() {
|
||||
<div className="mt-4">
|
||||
<TabContent isActive={activeTab === 'structure'}>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<InputText label="Email" value={email} onChange={handleEmailChange} />
|
||||
<InputText label="Mot de passe" type="password" value={password} onChange={handlePasswordChange} />
|
||||
<InputText label="Confirmer le mot de passe" type="password" value={confirmPassword} onChange={handleConfirmPasswordChange} />
|
||||
<InputText
|
||||
label="Email"
|
||||
value={email}
|
||||
onChange={handleEmailChange}
|
||||
/>
|
||||
<InputText
|
||||
label="Mot de passe"
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={handlePasswordChange}
|
||||
/>
|
||||
<InputText
|
||||
label="Confirmer le mot de passe"
|
||||
type="password"
|
||||
value={confirmPassword}
|
||||
onChange={handleConfirmPasswordChange}
|
||||
/>
|
||||
<Button type="submit" primary text="Mettre à jour"></Button>
|
||||
</form>
|
||||
</TabContent>
|
||||
<TabContent isActive={activeTab === 'smtp'}>
|
||||
<form onSubmit={handleSmtpSubmit}>
|
||||
<InputText label="Serveur SMTP" value={smtpServer} onChange={handleSmtpServerChange} />
|
||||
<InputText label="Port SMTP" value={smtpPort} onChange={handleSmtpPortChange} />
|
||||
<InputText label="Utilisateur SMTP" value={smtpUser} onChange={handleSmtpUserChange} />
|
||||
<InputText label="Mot de passe SMTP" type="password" value={smtpPassword} onChange={handleSmtpPasswordChange} />
|
||||
<InputText
|
||||
label="Serveur SMTP"
|
||||
value={smtpServer}
|
||||
onChange={handleSmtpServerChange}
|
||||
/>
|
||||
<InputText
|
||||
label="Port SMTP"
|
||||
value={smtpPort}
|
||||
onChange={handleSmtpPortChange}
|
||||
/>
|
||||
<InputText
|
||||
label="Utilisateur SMTP"
|
||||
value={smtpUser}
|
||||
onChange={handleSmtpUserChange}
|
||||
/>
|
||||
<InputText
|
||||
label="Mot de passe SMTP"
|
||||
type="password"
|
||||
value={smtpPassword}
|
||||
onChange={handleSmtpPasswordChange}
|
||||
/>
|
||||
<Button type="submit" primary text="Mettre à jour"></Button>
|
||||
</form>
|
||||
</TabContent>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
'use client'
|
||||
'use client';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import StructureManagement from '@/components/Structure/Configuration/StructureManagement';
|
||||
import ScheduleManagement from '@/components/Structure/Planning/ScheduleManagement';
|
||||
@ -6,26 +6,27 @@ import FeesManagement from '@/components/Structure/Tarification/FeesManagement';
|
||||
import DjangoCSRFToken from '@/components/DjangoCSRFToken';
|
||||
import { useCsrfToken } from '@/context/CsrfContext';
|
||||
import { ClassesProvider } from '@/context/ClassesContext';
|
||||
import {
|
||||
createDatas,
|
||||
updateDatas,
|
||||
removeDatas,
|
||||
fetchSpecialities,
|
||||
fetchTeachers,
|
||||
fetchClasses,
|
||||
fetchSchedules,
|
||||
fetchRegistrationDiscounts,
|
||||
fetchTuitionDiscounts,
|
||||
fetchRegistrationFees,
|
||||
fetchTuitionFees,
|
||||
fetchRegistrationPaymentPlans,
|
||||
fetchTuitionPaymentPlans,
|
||||
fetchRegistrationPaymentModes,
|
||||
fetchTuitionPaymentModes } from '@/app/actions/schoolAction';
|
||||
import {
|
||||
createDatas,
|
||||
updateDatas,
|
||||
removeDatas,
|
||||
fetchSpecialities,
|
||||
fetchTeachers,
|
||||
fetchClasses,
|
||||
fetchSchedules,
|
||||
fetchRegistrationDiscounts,
|
||||
fetchTuitionDiscounts,
|
||||
fetchRegistrationFees,
|
||||
fetchTuitionFees,
|
||||
fetchRegistrationPaymentPlans,
|
||||
fetchTuitionPaymentPlans,
|
||||
fetchRegistrationPaymentModes,
|
||||
fetchTuitionPaymentModes,
|
||||
} from '@/app/actions/schoolAction';
|
||||
import { fetchProfileRoles, fetchProfiles } from '@/app/actions/authAction';
|
||||
import SidebarTabs from '@/components/SidebarTabs';
|
||||
import FilesGroupsManagement from '@/components/Structure/Files/FilesGroupsManagement';
|
||||
import { fetchRegistrationTemplateMaster } from "@/app/actions/registerFileGroupAction";
|
||||
import { fetchRegistrationTemplateMaster } from '@/app/actions/registerFileGroupAction';
|
||||
import logger from '@/utils/logger';
|
||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||
|
||||
@ -76,10 +77,10 @@ export default function Page() {
|
||||
|
||||
// Fetch data for registration file templates
|
||||
fetchRegistrationTemplateMaster()
|
||||
.then((data)=> {
|
||||
setFichiers(data)
|
||||
.then((data) => {
|
||||
setFichiers(data);
|
||||
})
|
||||
.catch(error => logger.error('Error fetching files:', error));
|
||||
.catch((error) => logger.error('Error fetching files:', error));
|
||||
|
||||
// Fetch data for registration payment plans
|
||||
handleRegistrationPaymentPlans();
|
||||
@ -94,145 +95,161 @@ export default function Page() {
|
||||
handleTuitionPaymentModes();
|
||||
|
||||
fetchProfiles()
|
||||
.then(data => {
|
||||
setProfiles(data);
|
||||
})
|
||||
.catch(error => {
|
||||
logger.error('Error fetching profileRoles:', error);
|
||||
})
|
||||
.then((data) => {
|
||||
setProfiles(data);
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error('Error fetching profileRoles:', error);
|
||||
});
|
||||
}
|
||||
}, [selectedEstablishmentId]);
|
||||
|
||||
const handleSpecialities = () => {
|
||||
fetchSpecialities(selectedEstablishmentId)
|
||||
.then(data => {
|
||||
.then((data) => {
|
||||
setSpecialities(data);
|
||||
})
|
||||
.catch(error => logger.error('Error fetching specialities:', error));
|
||||
.catch((error) => logger.error('Error fetching specialities:', error));
|
||||
};
|
||||
|
||||
const handleTeachers = () => {
|
||||
fetchTeachers(selectedEstablishmentId)
|
||||
.then(data => {
|
||||
.then((data) => {
|
||||
setTeachers(data);
|
||||
})
|
||||
.catch(error => logger.error('Error fetching teachers:', error));
|
||||
.catch((error) => logger.error('Error fetching teachers:', error));
|
||||
};
|
||||
|
||||
const handleClasses = () => {
|
||||
fetchClasses(selectedEstablishmentId)
|
||||
.then(data => {
|
||||
.then((data) => {
|
||||
setClasses(data);
|
||||
})
|
||||
.catch(error => logger.error('Error fetching classes:', error));
|
||||
.catch((error) => logger.error('Error fetching classes:', error));
|
||||
};
|
||||
|
||||
const handleSchedules = () => {
|
||||
fetchSchedules()
|
||||
.then(data => {
|
||||
.then((data) => {
|
||||
setSchedules(data);
|
||||
})
|
||||
.catch(error => logger.error('Error fetching schedules:', error));
|
||||
.catch((error) => logger.error('Error fetching schedules:', error));
|
||||
};
|
||||
|
||||
const handleRegistrationDiscounts = () => {
|
||||
fetchRegistrationDiscounts(selectedEstablishmentId)
|
||||
.then(data => {
|
||||
.then((data) => {
|
||||
setRegistrationDiscounts(data);
|
||||
})
|
||||
.catch(error => logger.error('Error fetching registration discounts:', error));
|
||||
.catch((error) =>
|
||||
logger.error('Error fetching registration discounts:', error)
|
||||
);
|
||||
};
|
||||
|
||||
const handleTuitionDiscounts = () => {
|
||||
fetchTuitionDiscounts(selectedEstablishmentId)
|
||||
.then(data => {
|
||||
.then((data) => {
|
||||
setTuitionDiscounts(data);
|
||||
})
|
||||
.catch(error => logger.error('Error fetching tuition discounts:', error));
|
||||
.catch((error) =>
|
||||
logger.error('Error fetching tuition discounts:', error)
|
||||
);
|
||||
};
|
||||
|
||||
const handleRegistrationFees = () => {
|
||||
fetchRegistrationFees(selectedEstablishmentId)
|
||||
.then(data => {
|
||||
.then((data) => {
|
||||
setRegistrationFees(data);
|
||||
})
|
||||
.catch(error => logger.error('Error fetching registration fees:', error));
|
||||
.catch((error) =>
|
||||
logger.error('Error fetching registration fees:', error)
|
||||
);
|
||||
};
|
||||
|
||||
const handleTuitionFees = () => {
|
||||
fetchTuitionFees(selectedEstablishmentId)
|
||||
.then(data => {
|
||||
.then((data) => {
|
||||
setTuitionFees(data);
|
||||
})
|
||||
.catch(error => logger.error('Error fetching tuition fees', error));
|
||||
.catch((error) => logger.error('Error fetching tuition fees', error));
|
||||
};
|
||||
|
||||
const handleRegistrationPaymentPlans = () => {
|
||||
fetchRegistrationPaymentPlans(selectedEstablishmentId)
|
||||
.then(data => {
|
||||
.then((data) => {
|
||||
setRegistrationPaymentPlans(data);
|
||||
})
|
||||
.catch(error => logger.error('Error fetching registration payment plans:', error));
|
||||
.catch((error) =>
|
||||
logger.error('Error fetching registration payment plans:', error)
|
||||
);
|
||||
};
|
||||
|
||||
const handleTuitionPaymentPlans = () => {
|
||||
fetchTuitionPaymentPlans(selectedEstablishmentId)
|
||||
.then(data => {
|
||||
.then((data) => {
|
||||
setTuitionPaymentPlans(data);
|
||||
})
|
||||
.catch(error => logger.error('Error fetching tuition payment plans:', error));
|
||||
.catch((error) =>
|
||||
logger.error('Error fetching tuition payment plans:', error)
|
||||
);
|
||||
};
|
||||
|
||||
const handleRegistrationPaymentModes = () => {
|
||||
fetchRegistrationPaymentModes(selectedEstablishmentId)
|
||||
.then(data => {
|
||||
.then((data) => {
|
||||
setRegistrationPaymentModes(data);
|
||||
})
|
||||
.catch(error => logger.error('Error fetching registration payment modes:', error));
|
||||
.catch((error) =>
|
||||
logger.error('Error fetching registration payment modes:', error)
|
||||
);
|
||||
};
|
||||
|
||||
const handleTuitionPaymentModes = () => {
|
||||
fetchTuitionPaymentModes(selectedEstablishmentId)
|
||||
.then(data => {
|
||||
.then((data) => {
|
||||
setTuitionPaymentModes(data);
|
||||
})
|
||||
.catch(error => logger.error('Error fetching tuition payment modes:', error));
|
||||
.catch((error) =>
|
||||
logger.error('Error fetching tuition payment modes:', error)
|
||||
);
|
||||
};
|
||||
|
||||
const handleCreate = (url, newData, setDatas) => {
|
||||
return createDatas(url, newData, csrfToken)
|
||||
.then(data => {
|
||||
setDatas(prevState => [...prevState, data]);
|
||||
return data;
|
||||
})
|
||||
.catch(error => {
|
||||
logger.error('Error creating data:', error);
|
||||
throw error;
|
||||
});
|
||||
.then((data) => {
|
||||
setDatas((prevState) => [...prevState, data]);
|
||||
return data;
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error('Error creating data:', error);
|
||||
throw error;
|
||||
});
|
||||
};
|
||||
|
||||
const handleEdit = (url, id, updatedData, setDatas) => {
|
||||
return updateDatas(url, id, updatedData, csrfToken)
|
||||
.then(data => {
|
||||
setDatas(prevState => prevState.map(item => item.id === id ? data : item));
|
||||
return data;
|
||||
})
|
||||
.catch(error => {
|
||||
logger.error('Error editing data:', error);
|
||||
throw error;
|
||||
});
|
||||
.then((data) => {
|
||||
setDatas((prevState) =>
|
||||
prevState.map((item) => (item.id === id ? data : item))
|
||||
);
|
||||
return data;
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error('Error editing data:', error);
|
||||
throw error;
|
||||
});
|
||||
};
|
||||
|
||||
const handleDelete = (url, id, setDatas) => {
|
||||
return removeDatas(url, id, csrfToken)
|
||||
.then(data => {
|
||||
setDatas(prevState => prevState.filter(item => item.id !== id));
|
||||
return data;
|
||||
})
|
||||
.catch(error => {
|
||||
logger.error('Error deleting data:', error);
|
||||
throw error;
|
||||
});
|
||||
.then((data) => {
|
||||
setDatas((prevState) => prevState.filter((item) => item.id !== id));
|
||||
return data;
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error('Error deleting data:', error);
|
||||
throw error;
|
||||
});
|
||||
};
|
||||
|
||||
const handleUpdatePlanning = (url, planningId, updatedData) => {
|
||||
@ -240,19 +257,19 @@ export default function Page() {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrfToken
|
||||
'X-CSRFToken': csrfToken,
|
||||
},
|
||||
body: JSON.stringify(updatedData),
|
||||
credentials: 'include'
|
||||
credentials: 'include',
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
logger.debug('Planning mis à jour avec succès :', data);
|
||||
//setDatas(data);
|
||||
})
|
||||
.catch(error => {
|
||||
logger.error('Erreur :', error);
|
||||
});
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
logger.debug('Planning mis à jour avec succès :', data);
|
||||
//setDatas(data);
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error('Erreur :', error);
|
||||
});
|
||||
};
|
||||
|
||||
const tabs = [
|
||||
@ -272,7 +289,7 @@ export default function Page() {
|
||||
handleEdit={handleEdit}
|
||||
handleDelete={handleDelete}
|
||||
/>
|
||||
)
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'Schedule',
|
||||
@ -284,7 +301,7 @@ export default function Page() {
|
||||
classes={classes}
|
||||
/>
|
||||
</ClassesProvider>
|
||||
)
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'Fees',
|
||||
@ -311,23 +328,27 @@ export default function Page() {
|
||||
handleEdit={handleEdit}
|
||||
handleDelete={handleDelete}
|
||||
/>
|
||||
)
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'Files',
|
||||
label: 'Documents d\'inscription',
|
||||
content: <FilesGroupsManagement csrfToken={csrfToken} selectedEstablishmentId={selectedEstablishmentId} />
|
||||
}
|
||||
label: "Documents d'inscription",
|
||||
content: (
|
||||
<FilesGroupsManagement
|
||||
csrfToken={csrfToken}
|
||||
selectedEstablishmentId={selectedEstablishmentId}
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className='p-8'>
|
||||
<div className="p-8">
|
||||
<DjangoCSRFToken csrfToken={csrfToken} />
|
||||
|
||||
<div className="w-full p-4">
|
||||
<SidebarTabs tabs={tabs} />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
'use client'
|
||||
'use client';
|
||||
import React, { useState } from 'react';
|
||||
import { useSearchParams, useRouter } from 'next/navigation';
|
||||
import InscriptionFormShared from '@/components/Inscription/InscriptionFormShared';
|
||||
@ -9,41 +9,37 @@ import { editRegisterForm } from '@/app/actions/subscriptionAction';
|
||||
import logger from '@/utils/logger';
|
||||
|
||||
export default function Page() {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const studentId = searchParams.get('studentId'); // Changé de codeDI à studentId
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const studentId = searchParams.get('studentId'); // Changé de codeDI à studentId
|
||||
|
||||
const [formErrors, setFormErrors] = useState({});
|
||||
const csrfToken = useCsrfToken();
|
||||
const { selectedEstablishmentId } = useEstablishment();
|
||||
const [formErrors, setFormErrors] = useState({});
|
||||
const csrfToken = useCsrfToken();
|
||||
const { selectedEstablishmentId } = useEstablishment();
|
||||
|
||||
const handleSubmit = (data) => {
|
||||
editRegisterForm(studentId, data, csrfToken)
|
||||
.then((result) => {
|
||||
logger.debug('Success:', result);
|
||||
router.push(FE_ADMIN_SUBSCRIPTIONS_URL);
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error('Error:', error.message);
|
||||
if (error.details) {
|
||||
logger.error('Form errors:', error.details);
|
||||
setFormErrors(error.details);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleSubmit = (data) => {
|
||||
|
||||
editRegisterForm(studentId, data, csrfToken)
|
||||
|
||||
.then((result) => {
|
||||
logger.debug('Success:', result);
|
||||
router.push(FE_ADMIN_SUBSCRIPTIONS_URL);
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error('Error:', error.message);
|
||||
if (error.details) {
|
||||
logger.error('Form errors:', error.details);
|
||||
setFormErrors(error.details);
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
return (
|
||||
<InscriptionFormShared
|
||||
studentId={studentId}
|
||||
csrfToken={csrfToken}
|
||||
selectedEstablishmentId={selectedEstablishmentId}
|
||||
onSubmit={handleSubmit}
|
||||
cancelUrl={FE_ADMIN_SUBSCRIPTIONS_URL}
|
||||
errors={formErrors}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<InscriptionFormShared
|
||||
studentId={studentId}
|
||||
csrfToken={csrfToken}
|
||||
selectedEstablishmentId={selectedEstablishmentId}
|
||||
onSubmit={handleSubmit}
|
||||
cancelUrl={FE_ADMIN_SUBSCRIPTIONS_URL}
|
||||
errors={formErrors}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,11 +1,11 @@
|
||||
'use client'
|
||||
'use client';
|
||||
import React from 'react';
|
||||
import { useSearchParams, useRouter } from 'next/navigation';
|
||||
import ValidateSubscription from '@/components/Inscription/ValidateSubscription';
|
||||
import { sendSEPARegisterForm } from "@/app/actions/subscriptionAction"
|
||||
import { sendSEPARegisterForm } from '@/app/actions/subscriptionAction';
|
||||
import { useCsrfToken } from '@/context/CsrfContext';
|
||||
import logger from '@/utils/logger';
|
||||
import { FE_ADMIN_SUBSCRIPTIONS_URL} from '@/utils/Url';
|
||||
import { FE_ADMIN_SUBSCRIPTIONS_URL } from '@/utils/Url';
|
||||
|
||||
export default function Page() {
|
||||
const searchParams = useSearchParams();
|
||||
@ -23,11 +23,11 @@ export default function Page() {
|
||||
const handleAcceptRF = (data) => {
|
||||
logger.debug('Mise à jour du RF avec les données:', data);
|
||||
|
||||
const {status, sepa_file} = data
|
||||
const { status, sepa_file } = data;
|
||||
const formData = new FormData();
|
||||
formData.append('status', status); // Ajoute le statut
|
||||
formData.append('sepa_file', sepa_file); // Ajoute le fichier SEPA
|
||||
|
||||
|
||||
// Appeler l'API pour mettre à jour le RF
|
||||
sendSEPARegisterForm(studentId, formData, csrfToken)
|
||||
.then((response) => {
|
||||
@ -39,7 +39,7 @@ export default function Page() {
|
||||
logger.error('Erreur lors de la mise à jour du RF:', error);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<ValidateSubscription
|
||||
studentId={studentId}
|
||||
@ -50,4 +50,4 @@ export default function Page() {
|
||||
onAccept={handleAcceptRF}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
'use client'
|
||||
import {useTranslations} from 'next-intl';
|
||||
'use client';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import React from 'react';
|
||||
import Button from '@/components/Button';
|
||||
import Logo from '@/components/Logo'; // Import du composant Logo
|
||||
|
||||
@ -1,38 +1,38 @@
|
||||
'use client'
|
||||
import React, { useState } from 'react';
|
||||
'use client';
|
||||
import React, { useState } from 'react';
|
||||
import InscriptionFormShared from '@/components/Inscription/InscriptionFormShared';
|
||||
import { useSearchParams, useRouter } from 'next/navigation';
|
||||
import { useCsrfToken } from '@/context/CsrfContext';
|
||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||
import { FE_PARENTS_HOME_URL} from '@/utils/Url';
|
||||
import { editRegisterForm} from '@/app/actions/subscriptionAction';
|
||||
import { FE_PARENTS_HOME_URL } from '@/utils/Url';
|
||||
import { editRegisterForm } from '@/app/actions/subscriptionAction';
|
||||
import logger from '@/utils/logger';
|
||||
|
||||
export default function Page() {
|
||||
const searchParams = useSearchParams();
|
||||
const idProfil = searchParams.get('id');
|
||||
const studentId = searchParams.get('studentId');
|
||||
const router = useRouter();
|
||||
const csrfToken = useCsrfToken();
|
||||
const { selectedEstablishmentId } = useEstablishment();
|
||||
const searchParams = useSearchParams();
|
||||
const idProfil = searchParams.get('id');
|
||||
const studentId = searchParams.get('studentId');
|
||||
const router = useRouter();
|
||||
const csrfToken = useCsrfToken();
|
||||
const { selectedEstablishmentId } = useEstablishment();
|
||||
|
||||
const handleSubmit = async (data) => {
|
||||
try {
|
||||
const result = await editRegisterForm(studentId, data, csrfToken);
|
||||
logger.debug('Success:', result);
|
||||
router.push(FE_PARENTS_HOME_URL);
|
||||
} catch (error) {
|
||||
logger.error('Error:', error);
|
||||
}
|
||||
};
|
||||
const handleSubmit = async (data) => {
|
||||
try {
|
||||
const result = await editRegisterForm(studentId, data, csrfToken);
|
||||
logger.debug('Success:', result);
|
||||
router.push(FE_PARENTS_HOME_URL);
|
||||
} catch (error) {
|
||||
logger.error('Error:', error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<InscriptionFormShared
|
||||
studentId={studentId}
|
||||
csrfToken={csrfToken}
|
||||
selectedEstablishmentId={selectedEstablishmentId}
|
||||
onSubmit={handleSubmit}
|
||||
cancelUrl={FE_PARENTS_HOME_URL}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<InscriptionFormShared
|
||||
studentId={studentId}
|
||||
csrfToken={csrfToken}
|
||||
selectedEstablishmentId={selectedEstablishmentId}
|
||||
onSubmit={handleSubmit}
|
||||
cancelUrl={FE_PARENTS_HOME_URL}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
'use client'
|
||||
'use client';
|
||||
// src/components/Layout.js
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import DropdownMenu from '@/components/DropdownMenu';
|
||||
@ -6,7 +6,11 @@ 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
|
||||
import { FE_PARENTS_HOME_URL,FE_PARENTS_MESSAGERIE_URL,FE_PARENTS_SETTINGS_URL } from '@/utils/Url'; // Ajout de l'importation de l'URL de la page d'accueil parent
|
||||
import {
|
||||
FE_PARENTS_HOME_URL,
|
||||
FE_PARENTS_MESSAGERIE_URL,
|
||||
FE_PARENTS_SETTINGS_URL,
|
||||
} from '@/utils/Url'; // Ajout de l'importation de l'URL de la page d'accueil parent
|
||||
import { fetchMessages } from '@/app/actions/messagerieAction';
|
||||
import ProtectedRoute from '@/components/ProtectedRoute';
|
||||
import { disconnect } from '@/app/actions/authAction';
|
||||
@ -18,19 +22,15 @@ import { useEstablishment } from '@/context/EstablishmentContext';
|
||||
import Image from 'next/image';
|
||||
import Footer from '@/components/Footer';
|
||||
|
||||
export default function Layout({
|
||||
children,
|
||||
}) {
|
||||
|
||||
export default function Layout({ children }) {
|
||||
const router = useRouter(); // Définition de router
|
||||
const [messages, setMessages] = useState([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [isPopupVisible, setIsPopupVisible] = useState(false);
|
||||
const { profileRole, user } = useEstablishment();
|
||||
const softwareName = "N3WT School";
|
||||
const softwareName = 'N3WT School';
|
||||
const softwareVersion = `${process.env.NEXT_PUBLIC_APP_VERSION}`;
|
||||
|
||||
|
||||
const handleDisconnect = () => {
|
||||
setIsPopupVisible(true);
|
||||
};
|
||||
@ -40,21 +40,29 @@ export default function Layout({
|
||||
disconnect();
|
||||
};
|
||||
|
||||
const dropdownItems = [
|
||||
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(profileRole) || ''}</div>
|
||||
<div className="text-xs text-gray-400">
|
||||
{getRightStr(profileRole) || ''}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
),
|
||||
},
|
||||
{
|
||||
type: 'separator',
|
||||
content: <hr className="my-2 border-gray-200" />
|
||||
content: <hr className="my-2 border-gray-200" />,
|
||||
},
|
||||
{
|
||||
label: 'Settings',
|
||||
icon: Settings,
|
||||
onClick: () => {
|
||||
router.push(FE_PARENTS_SETTINGS_URL);
|
||||
},
|
||||
},
|
||||
{ label: 'Settings', icon: Settings , onClick: () => { router.push(FE_PARENTS_SETTINGS_URL); } },
|
||||
{
|
||||
type: 'item',
|
||||
label: 'Déconnexion',
|
||||
@ -63,68 +71,72 @@ const dropdownItems = [
|
||||
},
|
||||
];
|
||||
return (
|
||||
<ProtectedRoute requiredRight={RIGHTS.PARENT}>
|
||||
<ProtectedRoute requiredRight={RIGHTS.PARENT}>
|
||||
<div className="flex flex-col min-h-screen bg-gray-50">
|
||||
{/* Entête */}
|
||||
<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">
|
||||
<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 className="flex items-center space-x-2">
|
||||
<div className="border-b border-gray-200 ">
|
||||
<ProfileSelector className="w-64 border-r" />
|
||||
</div>
|
||||
<div className="flex items-center space-x-2 md:space-x-4">
|
||||
<button
|
||||
className="p-1 md:p-2 rounded-full hover:bg-gray-200"
|
||||
onClick={() => { router.push(FE_PARENTS_HOME_URL); }} // Utilisation de router pour revenir à l'accueil parent
|
||||
>
|
||||
<Home className="h-5 w-5 md:h-6 md:w-6" />
|
||||
</button>
|
||||
<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
|
||||
className="p-1 md:p-2 rounded-full hover:bg-gray-200"
|
||||
onClick={() => {
|
||||
router.push(FE_PARENTS_HOME_URL);
|
||||
}} // Utilisation de router pour revenir à l'accueil parent
|
||||
>
|
||||
<Home className="h-5 w-5 md:h-6 md:w-6" />
|
||||
</button>
|
||||
|
||||
<div className="relative">
|
||||
<div className="relative">
|
||||
<button
|
||||
className="p-1 md:p-2 rounded-full hover:bg-gray-200"
|
||||
onClick={() => { router.push(FE_PARENTS_MESSAGERIE_URL); }} // Utilisation de router
|
||||
onClick={() => {
|
||||
router.push(FE_PARENTS_MESSAGERIE_URL);
|
||||
}} // Utilisation de router
|
||||
>
|
||||
<MessageSquare className="h-5 w-5 md:h-6 md:w-6" />
|
||||
</button>
|
||||
{messages.length > 0 && (
|
||||
<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)}
|
||||
alt="Profile"
|
||||
className="w-8 h-8 rounded-full cursor-pointer"
|
||||
width={32}
|
||||
height={32}
|
||||
/>}
|
||||
items={dropdownItems}
|
||||
buttonClassName=""
|
||||
menuClassName="absolute right-0 mt-2 w-64 bg-white border border-gray-200 rounded shadow-lg"
|
||||
/>
|
||||
{messages.length > 0 && (
|
||||
<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)}
|
||||
alt="Profile"
|
||||
className="w-8 h-8 rounded-full cursor-pointer"
|
||||
width={32}
|
||||
height={32}
|
||||
/>
|
||||
}
|
||||
items={dropdownItems}
|
||||
buttonClassName=""
|
||||
menuClassName="absolute right-0 mt-2 w-64 bg-white border border-gray-200 rounded shadow-lg"
|
||||
/>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Content */}
|
||||
<div className="pt-16 md:pt-20 p-4 md:p-8 flex-1"> {/* Ajout de flex-1 pour utiliser toute la hauteur disponible */}
|
||||
<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}
|
||||
message="Êtes-vous sûr(e) de vouloir vous déconnecter ?"
|
||||
onConfirm={confirmDisconnect}
|
||||
onCancel={() => setIsPopupVisible(false)}
|
||||
/>
|
||||
</ProtectedRoute>
|
||||
visible={isPopupVisible}
|
||||
message="Êtes-vous sûr(e) de vouloir vous déconnecter ?"
|
||||
onConfirm={confirmDisconnect}
|
||||
onCancel={() => setIsPopupVisible(false)}
|
||||
/>
|
||||
</ProtectedRoute>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -1,13 +1,25 @@
|
||||
'use client'
|
||||
'use client';
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import { SendHorizontal } from 'lucide-react';
|
||||
import Image from 'next/image';
|
||||
import { getGravatarUrl } from '@/utils/gravatar';
|
||||
|
||||
const contacts = [
|
||||
{ id: 1, name: 'Facturation', profilePic: getGravatarUrl('facturation@n3wtschool.com') },
|
||||
{ id: 2, name: 'Enseignant 1', profilePic: getGravatarUrl('enseignant@n3wtschool.com') },
|
||||
{ id: 3, name: 'Contact', profilePic: getGravatarUrl('contact@n3wtschool.com') },
|
||||
{
|
||||
id: 1,
|
||||
name: 'Facturation',
|
||||
profilePic: getGravatarUrl('facturation@n3wtschool.com'),
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Enseignant 1',
|
||||
profilePic: getGravatarUrl('enseignant@n3wtschool.com'),
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'Contact',
|
||||
profilePic: getGravatarUrl('contact@n3wtschool.com'),
|
||||
},
|
||||
];
|
||||
|
||||
export default function MessageriePage() {
|
||||
@ -29,7 +41,14 @@ export default function MessageriePage() {
|
||||
const contactMessages = messages[selectedContact.id] || [];
|
||||
setMessages({
|
||||
...messages,
|
||||
[selectedContact.id]: [...contactMessages, { id: contactMessages.length + 1, text: newMessage, date: new Date() }],
|
||||
[selectedContact.id]: [
|
||||
...contactMessages,
|
||||
{
|
||||
id: contactMessages.length + 1,
|
||||
text: newMessage,
|
||||
date: new Date(),
|
||||
},
|
||||
],
|
||||
});
|
||||
setNewMessage('');
|
||||
simulateContactResponse(selectedContact.id);
|
||||
@ -48,14 +67,24 @@ export default function MessageriePage() {
|
||||
const contactMessages = prevMessages[contactId] || [];
|
||||
return {
|
||||
...prevMessages,
|
||||
[contactId]: [...contactMessages, { id: contactMessages.length + 2, text: 'Réponse automatique', isResponse: true, date: new Date() }],
|
||||
[contactId]: [
|
||||
...contactMessages,
|
||||
{
|
||||
id: contactMessages.length + 2,
|
||||
text: 'Réponse automatique',
|
||||
isResponse: true,
|
||||
date: new Date(),
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
}, 2000);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex" style={{ height: 'calc(100vh - 128px )' }}> {/* Utilisation de calc pour soustraire la hauteur de l'entête */}
|
||||
<div className="flex" style={{ height: 'calc(100vh - 128px )' }}>
|
||||
{' '}
|
||||
{/* Utilisation de calc pour soustraire la hauteur de l'entête */}
|
||||
<div className="w-1/4 border-r border-gray-200 p-4 overflow-y-auto h-full ">
|
||||
{contacts.map((contact) => (
|
||||
<div
|
||||
@ -63,27 +92,49 @@ export default function MessageriePage() {
|
||||
className={`p-2 cursor-pointer ${selectedContact?.id === contact.id ? 'bg-gray-200' : ''}`}
|
||||
onClick={() => setSelectedContact(contact)}
|
||||
>
|
||||
<Image src={contact.profilePic} alt={`${contact.name}'s profile`} className="w-8 h-8 rounded-full inline-block mr-2" width={150} height={150}/>
|
||||
<Image
|
||||
src={contact.profilePic}
|
||||
alt={`${contact.name}'s profile`}
|
||||
className="w-8 h-8 rounded-full inline-block mr-2"
|
||||
width={150}
|
||||
height={150}
|
||||
/>
|
||||
{contact.name}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex-1 flex flex-col h-full">
|
||||
<div className="flex-1 overflow-y-auto p-4 h-full">
|
||||
{selectedContact && (messages[selectedContact.id] || []).map((message) => (
|
||||
<div
|
||||
key={message.id}
|
||||
className={`mb-2 p-2 rounded max-w-xs ${message.isResponse ? 'bg-gray-200 justify-self-end' : 'bg-emerald-200 justify-self-start'}`}
|
||||
style={{ borderRadius: message.isResponse ? '20px 20px 0 20px' : '20px 20px 20px 0', minWidth: '25%' }}
|
||||
>
|
||||
<div className="flex items-center mb-1">
|
||||
<img src={selectedContact.profilePic} alt={`${selectedContact.name}'s profile`} className="w-8 h-8 rounded-full inline-block mr-2" width={150} height={150} />
|
||||
<span className="text-xs text-gray-600">{selectedContact.name}</span>
|
||||
<span className="text-xs text-gray-400 ml-2">{new Date(message.date).toLocaleTimeString()}</span>
|
||||
{selectedContact &&
|
||||
(messages[selectedContact.id] || []).map((message) => (
|
||||
<div
|
||||
key={message.id}
|
||||
className={`mb-2 p-2 rounded max-w-xs ${message.isResponse ? 'bg-gray-200 justify-self-end' : 'bg-emerald-200 justify-self-start'}`}
|
||||
style={{
|
||||
borderRadius: message.isResponse
|
||||
? '20px 20px 0 20px'
|
||||
: '20px 20px 20px 0',
|
||||
minWidth: '25%',
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center mb-1">
|
||||
<img
|
||||
src={selectedContact.profilePic}
|
||||
alt={`${selectedContact.name}'s profile`}
|
||||
className="w-8 h-8 rounded-full inline-block mr-2"
|
||||
width={150}
|
||||
height={150}
|
||||
/>
|
||||
<span className="text-xs text-gray-600">
|
||||
{selectedContact.name}
|
||||
</span>
|
||||
<span className="text-xs text-gray-400 ml-2">
|
||||
{new Date(message.date).toLocaleTimeString()}
|
||||
</span>
|
||||
</div>
|
||||
{message.text}
|
||||
</div>
|
||||
{message.text}
|
||||
</div>
|
||||
))}
|
||||
))}
|
||||
<div ref={messagesEndRef} />
|
||||
</div>
|
||||
<div className="p-4 border-t border-gray-200 flex">
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
'use client'
|
||||
'use client';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import Table from '@/components/Table';
|
||||
@ -14,35 +14,42 @@ export default function ParentHomePage() {
|
||||
const [children, setChildren] = useState([]);
|
||||
const [userId, setUserId] = useState(null);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const { user, setProfileRole, selectedEstablishmentId, setSelectedEstablishmentId, establishments } = useEstablishment();
|
||||
const {
|
||||
user,
|
||||
setProfileRole,
|
||||
selectedEstablishmentId,
|
||||
setSelectedEstablishmentId,
|
||||
establishments,
|
||||
} = useEstablishment();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
const userIdFromSession = user.user_id;
|
||||
setUserId(userIdFromSession);
|
||||
console.log(selectedEstablishmentId)
|
||||
fetchChildren(userIdFromSession, selectedEstablishmentId).then(data => {
|
||||
setChildren(data);
|
||||
});
|
||||
|
||||
}, [ selectedEstablishmentId]);
|
||||
const userIdFromSession = user.user_id;
|
||||
setUserId(userIdFromSession);
|
||||
console.log(selectedEstablishmentId);
|
||||
fetchChildren(userIdFromSession, selectedEstablishmentId).then((data) => {
|
||||
setChildren(data);
|
||||
});
|
||||
}, [selectedEstablishmentId]);
|
||||
|
||||
const handleEstablishmentChange = (e) => {
|
||||
const establishmentId = parseInt(e.target.value, 10);
|
||||
setSelectedEstablishmentId(establishmentId);
|
||||
const role = establishments.find(est => est.id === establishmentId)?.role_type;
|
||||
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}`);
|
||||
router.push(`${FE_PARENTS_EDIT_INSCRIPTION_URL}?id=${userId}&studentId=${eleveId}`);
|
||||
router.push(
|
||||
`${FE_PARENTS_EDIT_INSCRIPTION_URL}?id=${userId}&studentId=${eleveId}`
|
||||
);
|
||||
}
|
||||
|
||||
const actionColumns = [
|
||||
{ name: 'Action', transform: (row) => row.action },
|
||||
];
|
||||
const actionColumns = [{ name: 'Action', transform: (row) => row.action }];
|
||||
|
||||
// Définir les colonnes du tableau
|
||||
const childrenColumns = [
|
||||
@ -52,9 +59,9 @@ export default function ParentHomePage() {
|
||||
name: 'Statut',
|
||||
transform: (row) => (
|
||||
<div className="flex justify-center items-center">
|
||||
<StatusLabel status={row.status} showDropdown={false} parent/>
|
||||
<StatusLabel status={row.status} showDropdown={false} parent />
|
||||
</div>
|
||||
)
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'Actions',
|
||||
@ -71,8 +78,8 @@ export default function ParentHomePage() {
|
||||
<Edit className="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const itemsPerPage = 5;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
'use client'
|
||||
'use client';
|
||||
import React, { useState } from 'react';
|
||||
import Button from '@/components/Button';
|
||||
import InputText from '@/components/InputText';
|
||||
@ -61,13 +61,7 @@ export default function SettingsPage() {
|
||||
required
|
||||
/>
|
||||
<div className="flex items-center justify-between">
|
||||
<Button
|
||||
type="submit"
|
||||
primary
|
||||
text={" Mettre à jour"}
|
||||
/>
|
||||
|
||||
|
||||
<Button type="submit" primary text={' Mettre à jour'} />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
"use client";
|
||||
'use client';
|
||||
|
||||
function ErrorBoundary({
|
||||
error
|
||||
}) {
|
||||
function ErrorBoundary({ error }) {
|
||||
return <>{error.message}</>;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,116 +1,159 @@
|
||||
'use client'
|
||||
import React, { useState } from 'react'
|
||||
import DjangoCSRFToken from '@/components/DjangoCSRFToken'
|
||||
'use client';
|
||||
import React, { useState } from 'react';
|
||||
import DjangoCSRFToken from '@/components/DjangoCSRFToken';
|
||||
import Logo from '@/components/Logo';
|
||||
import { useSearchParams, useRouter } from 'next/navigation'
|
||||
import { useSearchParams, useRouter } from 'next/navigation';
|
||||
import InputTextIcon from '@/components/InputTextIcon';
|
||||
import Loader from '@/components/Loader'; // Importez le composant Loader
|
||||
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,
|
||||
getRedirectUrlFromRole
|
||||
} from '@/utils/Url';
|
||||
import { FE_USERS_NEW_PASSWORD_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 { useEstablishment } from '@/context/EstablishmentContext';
|
||||
|
||||
|
||||
|
||||
export default function Page() {
|
||||
const searchParams = useSearchParams();
|
||||
const [errorMessage, setErrorMessage] = useState("");
|
||||
const [userFieldError, setUserFieldError] = useState("")
|
||||
const [passwordFieldError, setPasswordFieldError] = useState("")
|
||||
const { setUser } = useEstablishment();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const searchParams = useSearchParams();
|
||||
const [errorMessage, setErrorMessage] = useState('');
|
||||
const [userFieldError, setUserFieldError] = useState('');
|
||||
const [passwordFieldError, setPasswordFieldError] = useState('');
|
||||
const { setUser } = useEstablishment();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const router = useRouter();
|
||||
const csrfToken = useCsrfToken(); // Utilisez le hook useCsrfToken
|
||||
const router = useRouter();
|
||||
const csrfToken = useCsrfToken(); // Utilisez le hook useCsrfToken
|
||||
|
||||
function isOK(data) {
|
||||
return data.errorMessage === ""
|
||||
}
|
||||
function isOK(data) {
|
||||
return data.errorMessage === '';
|
||||
}
|
||||
|
||||
function handleFormLogin(formData) {
|
||||
setIsLoading(true);
|
||||
setErrorMessage('');
|
||||
|
||||
login({
|
||||
email: formData.get('login'),
|
||||
password: formData.get('password'),
|
||||
})
|
||||
.then((result) => {
|
||||
logger.debug('Sign In Result', result);
|
||||
|
||||
function handleFormLogin(formData) {
|
||||
setIsLoading(true);
|
||||
setErrorMessage("");
|
||||
|
||||
login({
|
||||
email: formData.get('login'),
|
||||
password: formData.get('password')
|
||||
}).then(result => {
|
||||
logger.debug('Sign In Result', result);
|
||||
|
||||
if (result.error) {
|
||||
setErrorMessage(result.error);
|
||||
if (result.error) {
|
||||
setErrorMessage(result.error);
|
||||
setIsLoading(false);
|
||||
} else {
|
||||
getSession()
|
||||
.then((session) => {
|
||||
if (!session || !session.user) {
|
||||
throw new Error('Session not found');
|
||||
}
|
||||
const user = session.user;
|
||||
logger.debug('User Session:', user);
|
||||
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é');
|
||||
}
|
||||
} else {
|
||||
setIsLoading(false);
|
||||
} else {
|
||||
getSession().then(session => {
|
||||
if (!session || !session.user) {
|
||||
throw new Error('Session not found');
|
||||
}
|
||||
const user = session.user;
|
||||
logger.debug('User Session:', user);
|
||||
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é');
|
||||
}
|
||||
} else {
|
||||
setIsLoading(false);
|
||||
setErrorMessage('Aucun rôle trouvé pour le profil sélectionné.');
|
||||
}
|
||||
}).catch(error => {
|
||||
logger.error('Erreur lors de la récupération de la session:', error);
|
||||
setIsLoading(false);
|
||||
setErrorMessage('Une erreur est survenue lors de la récupération de la session.');
|
||||
});
|
||||
}
|
||||
}).catch(error => {
|
||||
logger.error('Erreur lors de la connexion:', error);
|
||||
setIsLoading(false);
|
||||
setErrorMessage('Une erreur est survenue lors de la connexion.');
|
||||
});
|
||||
}
|
||||
setErrorMessage(
|
||||
'Aucun rôle trouvé pour le profil sélectionné.'
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error(
|
||||
'Erreur lors de la récupération de la session:',
|
||||
error
|
||||
);
|
||||
setIsLoading(false);
|
||||
setErrorMessage(
|
||||
'Une erreur est survenue lors de la récupération de la session.'
|
||||
);
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error('Erreur lors de la connexion:', error);
|
||||
setIsLoading(false);
|
||||
setErrorMessage('Une erreur est survenue lors de la connexion.');
|
||||
});
|
||||
}
|
||||
|
||||
if (isLoading === true) {
|
||||
return <Loader /> // Affichez le composant Loader
|
||||
} else {
|
||||
return <>
|
||||
<div className="container max mx-auto p-4">
|
||||
<div className="flex justify-center mb-4">
|
||||
<Logo className="h-150 w-150" />
|
||||
</div>
|
||||
<h1 className="text-2xl font-bold text-center mb-4">Authentification</h1>
|
||||
<form className="max-w-md mx-auto" onSubmit={(e) => { e.preventDefault(); handleFormLogin(new FormData(e.target)); }}>
|
||||
<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" />
|
||||
<div className="input-group mb-4">
|
||||
</div>
|
||||
<label className="text-red-500">{errorMessage}</label>
|
||||
<label><a className="float-right mb-4" href={`${FE_USERS_NEW_PASSWORD_URL}`}>Mot de passe oublié ?</a></label>
|
||||
<div className="form-group-submit mt-4">
|
||||
<Button text="Se Connecter" className="w-full" primary type="submit" name="connect" />
|
||||
</div>
|
||||
</form>
|
||||
if (isLoading === true) {
|
||||
return <Loader />; // Affichez le composant Loader
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
<div className="container max mx-auto p-4">
|
||||
<div className="flex justify-center mb-4">
|
||||
<Logo className="h-150 w-150" />
|
||||
</div>
|
||||
<h1 className="text-2xl font-bold text-center mb-4">
|
||||
Authentification
|
||||
</h1>
|
||||
<form
|
||||
className="max-w-md mx-auto"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
handleFormLogin(new FormData(e.target));
|
||||
}}
|
||||
>
|
||||
<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"
|
||||
/>
|
||||
<div className="input-group mb-4"></div>
|
||||
<label className="text-red-500">{errorMessage}</label>
|
||||
<label>
|
||||
<a
|
||||
className="float-right mb-4"
|
||||
href={`${FE_USERS_NEW_PASSWORD_URL}`}
|
||||
>
|
||||
Mot de passe oublié ?
|
||||
</a>
|
||||
</label>
|
||||
<div className="form-group-submit mt-4">
|
||||
<Button
|
||||
text="Se Connecter"
|
||||
className="w-full"
|
||||
primary
|
||||
type="submit"
|
||||
name="connect"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
};
|
||||
</form>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
'use client';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import DjangoCSRFToken from '@/components/DjangoCSRFToken'
|
||||
import DjangoCSRFToken from '@/components/DjangoCSRFToken';
|
||||
import Logo from '@/components/Logo';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
import InputTextIcon from '@/components/InputTextIcon';
|
||||
@ -17,80 +17,108 @@ import logger from '@/utils/logger';
|
||||
const useFakeData = process.env.NEXT_PUBLIC_USE_FAKE_DATA === 'true';
|
||||
|
||||
export default function Page() {
|
||||
const searchParams = useSearchParams();
|
||||
const [errorMessage, setErrorMessage] = useState("");
|
||||
const [userFieldError, setUserFieldError] = useState("");
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [popupVisible, setPopupVisible] = useState(false);
|
||||
const [popupMessage, setPopupMessage] = useState("");
|
||||
const [popupConfirmAction, setPopupConfirmAction] = useState(null);
|
||||
const csrfToken = useCsrfToken();
|
||||
const searchParams = useSearchParams();
|
||||
const [errorMessage, setErrorMessage] = useState('');
|
||||
const [userFieldError, setUserFieldError] = useState('');
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [popupVisible, setPopupVisible] = useState(false);
|
||||
const [popupMessage, setPopupMessage] = useState('');
|
||||
const [popupConfirmAction, setPopupConfirmAction] = useState(null);
|
||||
const csrfToken = useCsrfToken();
|
||||
|
||||
function validate(formData) {
|
||||
if (useFakeData) {
|
||||
setTimeout(() => {
|
||||
setUserFieldError("");
|
||||
setErrorMessage("");
|
||||
setPopupMessage("Mot de passe réinitialisé avec succès !");
|
||||
setPopupConfirmAction(() => () => setPopupVisible(false));
|
||||
setPopupVisible(true);
|
||||
}, 1000); // Simule un délai de traitement
|
||||
} else {
|
||||
const data = {email: formData.get('email')}
|
||||
sendNewPassword(data, csrfToken)
|
||||
.then(data => {
|
||||
logger.debug('Success:', data);
|
||||
setUserFieldError("");
|
||||
setErrorMessage("");
|
||||
if (data.errorMessage === "") {
|
||||
setPopupMessage(data.message);
|
||||
setPopupConfirmAction(() => () => setPopupVisible(false));
|
||||
setPopupVisible(true);
|
||||
} else {
|
||||
if (data.errorFields) {
|
||||
setUserFieldError(data.errorFields.email);
|
||||
}
|
||||
if (data.errorMessage) {
|
||||
setErrorMessage(data.errorMessage);
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
logger.error('Error fetching data:', error);
|
||||
error = error.errorMessage;
|
||||
logger.debug(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (isLoading === true) {
|
||||
return <Loader /> // Affichez le composant Loader
|
||||
function validate(formData) {
|
||||
if (useFakeData) {
|
||||
setTimeout(() => {
|
||||
setUserFieldError('');
|
||||
setErrorMessage('');
|
||||
setPopupMessage('Mot de passe réinitialisé avec succès !');
|
||||
setPopupConfirmAction(() => () => setPopupVisible(false));
|
||||
setPopupVisible(true);
|
||||
}, 1000); // Simule un délai de traitement
|
||||
} else {
|
||||
return <>
|
||||
<div className="container max mx-auto p-4">
|
||||
<div className="flex justify-center mb-4">
|
||||
<Logo className="h-150 w-150" />
|
||||
</div>
|
||||
<h1 className="text-2xl font-bold text-center mb-4">Nouveau Mot de passe</h1>
|
||||
<form className="max-w-md mx-auto" onSubmit={(e) => { e.preventDefault(); validate(new FormData(e.target)); }}>
|
||||
<DjangoCSRFToken csrfToken={csrfToken} />
|
||||
<InputTextIcon name="email" type="text" IconItem={User} label="Identifiant" placeholder="Identifiant" errorMsg={userFieldError} className="w-full" />
|
||||
<p className="text-red-500">{errorMessage}</p>
|
||||
<div className="form-group-submit mt-4">
|
||||
<Button text="Réinitialiser" className="w-full" primary type="submit" name="validate" />
|
||||
</div>
|
||||
</form>
|
||||
<br />
|
||||
<div className='flex justify-center mt-2 max-w-md mx-auto'>
|
||||
<Button text="Annuler" className="w-full" href={ `${FE_USERS_LOGIN_URL}`} />
|
||||
</div>
|
||||
</div>
|
||||
<Popup
|
||||
visible={popupVisible}
|
||||
message={popupMessage}
|
||||
onConfirm={popupConfirmAction}
|
||||
onCancel={() => setPopupVisible(false)}
|
||||
/>
|
||||
</>
|
||||
const data = { email: formData.get('email') };
|
||||
sendNewPassword(data, csrfToken)
|
||||
.then((data) => {
|
||||
logger.debug('Success:', data);
|
||||
setUserFieldError('');
|
||||
setErrorMessage('');
|
||||
if (data.errorMessage === '') {
|
||||
setPopupMessage(data.message);
|
||||
setPopupConfirmAction(() => () => setPopupVisible(false));
|
||||
setPopupVisible(true);
|
||||
} else {
|
||||
if (data.errorFields) {
|
||||
setUserFieldError(data.errorFields.email);
|
||||
}
|
||||
if (data.errorMessage) {
|
||||
setErrorMessage(data.errorMessage);
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error('Error fetching data:', error);
|
||||
error = error.errorMessage;
|
||||
logger.debug(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isLoading === true) {
|
||||
return <Loader />; // Affichez le composant Loader
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
<div className="container max mx-auto p-4">
|
||||
<div className="flex justify-center mb-4">
|
||||
<Logo className="h-150 w-150" />
|
||||
</div>
|
||||
<h1 className="text-2xl font-bold text-center mb-4">
|
||||
Nouveau Mot de passe
|
||||
</h1>
|
||||
<form
|
||||
className="max-w-md mx-auto"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
validate(new FormData(e.target));
|
||||
}}
|
||||
>
|
||||
<DjangoCSRFToken csrfToken={csrfToken} />
|
||||
<InputTextIcon
|
||||
name="email"
|
||||
type="text"
|
||||
IconItem={User}
|
||||
label="Identifiant"
|
||||
placeholder="Identifiant"
|
||||
errorMsg={userFieldError}
|
||||
className="w-full"
|
||||
/>
|
||||
<p className="text-red-500">{errorMessage}</p>
|
||||
<div className="form-group-submit mt-4">
|
||||
<Button
|
||||
text="Réinitialiser"
|
||||
className="w-full"
|
||||
primary
|
||||
type="submit"
|
||||
name="validate"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
<br />
|
||||
<div className="flex justify-center mt-2 max-w-md mx-auto">
|
||||
<Button
|
||||
text="Annuler"
|
||||
className="w-full"
|
||||
href={`${FE_USERS_LOGIN_URL}`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Popup
|
||||
visible={popupVisible}
|
||||
message={popupMessage}
|
||||
onConfirm={popupConfirmAction}
|
||||
onCancel={() => setPopupVisible(false)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
'use client'
|
||||
'use client';
|
||||
// src/app/pages/subscribe.js
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import DjangoCSRFToken from '@/components/DjangoCSRFToken'
|
||||
import DjangoCSRFToken from '@/components/DjangoCSRFToken';
|
||||
import Logo from '@/components/Logo';
|
||||
import { useSearchParams, useRouter } from 'next/navigation'
|
||||
import { useSearchParams, useRouter } from 'next/navigation';
|
||||
import InputTextIcon from '@/components/InputTextIcon';
|
||||
import Loader from '@/components/Loader'; // Importez le composant Loader
|
||||
import Button from '@/components/Button'; // Importez le composant Button
|
||||
@ -18,113 +18,149 @@ import logger from '@/utils/logger';
|
||||
const useFakeData = process.env.NEXT_PUBLIC_USE_FAKE_DATA === 'true';
|
||||
|
||||
export default function Page() {
|
||||
const searchParams = useSearchParams();
|
||||
const uuid = searchParams.get('uuid');
|
||||
const [errorMessage, setErrorMessage] = useState("");
|
||||
const [password1FieldError,setPassword1FieldError] = useState("")
|
||||
const [password2FieldError,setPassword2FieldError] = useState("")
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [popupVisible, setPopupVisible] = useState(false);
|
||||
const [popupMessage, setPopupMessage] = useState("");
|
||||
const searchParams = useSearchParams();
|
||||
const uuid = searchParams.get('uuid');
|
||||
const [errorMessage, setErrorMessage] = useState('');
|
||||
const [password1FieldError, setPassword1FieldError] = useState('');
|
||||
const [password2FieldError, setPassword2FieldError] = useState('');
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [popupVisible, setPopupVisible] = useState(false);
|
||||
const [popupMessage, setPopupMessage] = useState('');
|
||||
|
||||
const router = useRouter();
|
||||
const csrfToken = useCsrfToken();
|
||||
const router = useRouter();
|
||||
const csrfToken = useCsrfToken();
|
||||
|
||||
useEffect(() => {
|
||||
if (useFakeData) {
|
||||
setTimeout(() => {
|
||||
setIsLoading(false);
|
||||
}, 1000);
|
||||
} else {
|
||||
getResetPassword(uuid)
|
||||
.then(data => {
|
||||
logger.debug('Success:', data);
|
||||
setIsLoading(true);
|
||||
if(data.errorFields){
|
||||
setPassword1FieldError(data.errorFields.password1)
|
||||
setPassword2FieldError(data.errorFields.password2)
|
||||
}
|
||||
if(data.errorMessage){
|
||||
setErrorMessage(data.errorMessage)
|
||||
}
|
||||
setIsLoading(false);
|
||||
})
|
||||
.catch(error => {
|
||||
logger.error('Error fetching data:', error);
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
function validate(formData) {
|
||||
if (useFakeData) {
|
||||
setTimeout(() => {
|
||||
setPopupMessage("Mot de passe réinitialisé avec succès");
|
||||
setPopupVisible(true);
|
||||
}, 1000);
|
||||
} else {
|
||||
const data = {
|
||||
password1: formData.get('password1'),
|
||||
password2: formData.get('password2'),
|
||||
}
|
||||
resetPassword(uuid,data,csrfToken)
|
||||
.then(data => {
|
||||
logger.debug('Success:', data);
|
||||
setPassword1FieldError("")
|
||||
setPassword2FieldError("")
|
||||
setErrorMessage("")
|
||||
if(data.errorMessage === ""){
|
||||
setPopupMessage(data.message);
|
||||
setPopupVisible(true);
|
||||
} else {
|
||||
if(data.errorMessage){
|
||||
setErrorMessage(data.errorMessage);
|
||||
}
|
||||
if(data.errorFields){
|
||||
setPassword1FieldError(data.errorFields.password1)
|
||||
setPassword2FieldError(data.errorFields.password2)
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
logger.error('Error fetching data:', error);
|
||||
error = error.errorMessage;
|
||||
logger.debug(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (isLoading === true) {
|
||||
return <Loader /> // Affichez le composant Loader
|
||||
useEffect(() => {
|
||||
if (useFakeData) {
|
||||
setTimeout(() => {
|
||||
setIsLoading(false);
|
||||
}, 1000);
|
||||
} else {
|
||||
return <>
|
||||
<Popup
|
||||
visible={popupVisible}
|
||||
message={popupMessage}
|
||||
onConfirm={() => {
|
||||
setPopupVisible(false);
|
||||
router.push(`${FE_USERS_LOGIN_URL}`);
|
||||
}}
|
||||
onCancel={() => setPopupVisible(false)}
|
||||
/>
|
||||
<div className="container max mx-auto p-4">
|
||||
<div className="flex justify-center mb-4">
|
||||
<Logo className="h-150 w-150" />
|
||||
</div>
|
||||
<h1 className="text-2xl font-bold text-center mb-4">Réinitialisation du mot de passe</h1>
|
||||
<form className="max-w-md mx-auto" onSubmit={(e) => { e.preventDefault(); validate(new FormData(e.target)); }}>
|
||||
<DjangoCSRFToken csrfToken={csrfToken} />
|
||||
<InputTextIcon name="password1" type="password" IconItem={KeySquare} label="Mot de passe" placeholder="Mot de passe" errorMsg={password1FieldError} className="w-full mb-5" />
|
||||
<InputTextIcon name="password2" type="password" IconItem={KeySquare} label="Confirmation mot de passe" placeholder="Confirmation mot de passe" errorMsg={password2FieldError} className="w-full" />
|
||||
<label className="text-red-500">{errorMessage}</label>
|
||||
<div className="form-group-submit mt-4">
|
||||
<Button text="Enregistrer" className="w-full" primary type="submit" name="validate" />
|
||||
</div>
|
||||
</form>
|
||||
<br/>
|
||||
<div className="flex justify-center mt-2 max-w-md mx-auto">
|
||||
<Button text="Annuler" className="w-full" href={`${FE_USERS_LOGIN_URL}`} />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
getResetPassword(uuid)
|
||||
.then((data) => {
|
||||
logger.debug('Success:', data);
|
||||
setIsLoading(true);
|
||||
if (data.errorFields) {
|
||||
setPassword1FieldError(data.errorFields.password1);
|
||||
setPassword2FieldError(data.errorFields.password2);
|
||||
}
|
||||
if (data.errorMessage) {
|
||||
setErrorMessage(data.errorMessage);
|
||||
}
|
||||
setIsLoading(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error('Error fetching data:', error);
|
||||
});
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
function validate(formData) {
|
||||
if (useFakeData) {
|
||||
setTimeout(() => {
|
||||
setPopupMessage('Mot de passe réinitialisé avec succès');
|
||||
setPopupVisible(true);
|
||||
}, 1000);
|
||||
} else {
|
||||
const data = {
|
||||
password1: formData.get('password1'),
|
||||
password2: formData.get('password2'),
|
||||
};
|
||||
resetPassword(uuid, data, csrfToken)
|
||||
.then((data) => {
|
||||
logger.debug('Success:', data);
|
||||
setPassword1FieldError('');
|
||||
setPassword2FieldError('');
|
||||
setErrorMessage('');
|
||||
if (data.errorMessage === '') {
|
||||
setPopupMessage(data.message);
|
||||
setPopupVisible(true);
|
||||
} else {
|
||||
if (data.errorMessage) {
|
||||
setErrorMessage(data.errorMessage);
|
||||
}
|
||||
if (data.errorFields) {
|
||||
setPassword1FieldError(data.errorFields.password1);
|
||||
setPassword2FieldError(data.errorFields.password2);
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error('Error fetching data:', error);
|
||||
error = error.errorMessage;
|
||||
logger.debug(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (isLoading === true) {
|
||||
return <Loader />; // Affichez le composant Loader
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
<Popup
|
||||
visible={popupVisible}
|
||||
message={popupMessage}
|
||||
onConfirm={() => {
|
||||
setPopupVisible(false);
|
||||
router.push(`${FE_USERS_LOGIN_URL}`);
|
||||
}}
|
||||
onCancel={() => setPopupVisible(false)}
|
||||
/>
|
||||
<div className="container max mx-auto p-4">
|
||||
<div className="flex justify-center mb-4">
|
||||
<Logo className="h-150 w-150" />
|
||||
</div>
|
||||
<h1 className="text-2xl font-bold text-center mb-4">
|
||||
Réinitialisation du mot de passe
|
||||
</h1>
|
||||
<form
|
||||
className="max-w-md mx-auto"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
validate(new FormData(e.target));
|
||||
}}
|
||||
>
|
||||
<DjangoCSRFToken csrfToken={csrfToken} />
|
||||
<InputTextIcon
|
||||
name="password1"
|
||||
type="password"
|
||||
IconItem={KeySquare}
|
||||
label="Mot de passe"
|
||||
placeholder="Mot de passe"
|
||||
errorMsg={password1FieldError}
|
||||
className="w-full mb-5"
|
||||
/>
|
||||
<InputTextIcon
|
||||
name="password2"
|
||||
type="password"
|
||||
IconItem={KeySquare}
|
||||
label="Confirmation mot de passe"
|
||||
placeholder="Confirmation mot de passe"
|
||||
errorMsg={password2FieldError}
|
||||
className="w-full"
|
||||
/>
|
||||
<label className="text-red-500">{errorMessage}</label>
|
||||
<div className="form-group-submit mt-4">
|
||||
<Button
|
||||
text="Enregistrer"
|
||||
className="w-full"
|
||||
primary
|
||||
type="submit"
|
||||
name="validate"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
<br />
|
||||
<div className="flex justify-center mt-2 max-w-md mx-auto">
|
||||
<Button
|
||||
text="Annuler"
|
||||
className="w-full"
|
||||
href={`${FE_USERS_LOGIN_URL}`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,106 +1,155 @@
|
||||
'use client'
|
||||
'use client';
|
||||
// src/app/pages/subscribe.js
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import DjangoCSRFToken from '@/components/DjangoCSRFToken'
|
||||
import DjangoCSRFToken from '@/components/DjangoCSRFToken';
|
||||
import Logo from '@/components/Logo';
|
||||
import { useSearchParams, useRouter } from 'next/navigation'
|
||||
import { useSearchParams, useRouter } from 'next/navigation';
|
||||
import InputTextIcon from '@/components/InputTextIcon';
|
||||
import Loader from '@/components/Loader'; // Importez le composant Loader
|
||||
import Button from '@/components/Button'; // Importez le composant Button
|
||||
import Popup from '@/components/Popup'; // Importez le composant Popup
|
||||
import { User, KeySquare } from 'lucide-react'; // Importez directement les icônes nécessaires
|
||||
import { FE_USERS_LOGIN_URL } from '@/utils/Url';
|
||||
import { FE_USERS_LOGIN_URL } from '@/utils/Url';
|
||||
import { useCsrfToken } from '@/context/CsrfContext';
|
||||
import { subscribe } from '@/app/actions/authAction';
|
||||
import logger from '@/utils/logger';
|
||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||
|
||||
export default function Page() {
|
||||
const searchParams = useSearchParams();
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
const [errorMessage, setErrorMessage] = useState("");
|
||||
const [userFieldError,setUserFieldError] = useState("")
|
||||
const [password1FieldError,setPassword1FieldError] = useState("")
|
||||
const [password2FieldError,setPassword2FieldError] = useState("")
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [popupVisible, setPopupVisible] = useState(false);
|
||||
const [popupMessage, setPopupMessage] = useState("");
|
||||
const [errorMessage, setErrorMessage] = useState('');
|
||||
const [userFieldError, setUserFieldError] = useState('');
|
||||
const [password1FieldError, setPassword1FieldError] = useState('');
|
||||
const [password2FieldError, setPassword2FieldError] = useState('');
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [popupVisible, setPopupVisible] = useState(false);
|
||||
const [popupMessage, setPopupMessage] = useState('');
|
||||
|
||||
const router = useRouter();
|
||||
const csrfToken = useCsrfToken();
|
||||
|
||||
const establishment_id = searchParams.get('establishment_id');
|
||||
const router = useRouter();
|
||||
const csrfToken = useCsrfToken();
|
||||
|
||||
function isOK(data) {
|
||||
return data.errorMessage === ""
|
||||
}
|
||||
const establishment_id = searchParams.get('establishment_id');
|
||||
|
||||
function subscribeFormSubmit(formData) {
|
||||
const data ={
|
||||
email: formData.get('login'),
|
||||
password1: formData.get('password1'),
|
||||
password2: formData.get('password2'),
|
||||
establishment_id: establishment_id
|
||||
function isOK(data) {
|
||||
return data.errorMessage === '';
|
||||
}
|
||||
|
||||
function subscribeFormSubmit(formData) {
|
||||
const data = {
|
||||
email: formData.get('login'),
|
||||
password1: formData.get('password1'),
|
||||
password2: formData.get('password2'),
|
||||
establishment_id: establishment_id,
|
||||
};
|
||||
subscribe(data, csrfToken)
|
||||
.then((data) => {
|
||||
logger.debug('Success:', data);
|
||||
setUserFieldError('');
|
||||
setPassword1FieldError('');
|
||||
setPassword2FieldError('');
|
||||
setErrorMessage('');
|
||||
if (isOK(data)) {
|
||||
setPopupMessage(data.message);
|
||||
setPopupVisible(true);
|
||||
} else {
|
||||
if (data.errorMessage) {
|
||||
setErrorMessage(data.errorMessage);
|
||||
}
|
||||
if (data.errorFields) {
|
||||
setUserFieldError(data.errorFields.email);
|
||||
setPassword1FieldError(data.errorFields.password1);
|
||||
setPassword2FieldError(data.errorFields.password2);
|
||||
}
|
||||
}
|
||||
subscribe(data,csrfToken).then(data => {
|
||||
logger.debug('Success:', data);
|
||||
setUserFieldError("")
|
||||
setPassword1FieldError("")
|
||||
setPassword2FieldError("")
|
||||
setErrorMessage("")
|
||||
if(isOK(data)){
|
||||
setPopupMessage(data.message);
|
||||
setPopupVisible(true);
|
||||
} else {
|
||||
if(data.errorMessage){
|
||||
setErrorMessage(data.errorMessage);
|
||||
}
|
||||
if(data.errorFields){
|
||||
setUserFieldError(data.errorFields.email)
|
||||
setPassword1FieldError(data.errorFields.password1)
|
||||
setPassword2FieldError(data.errorFields.password2)
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
logger.error('Error fetching data:', error);
|
||||
error = error.errorMessage;
|
||||
logger.debug(error);
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error('Error fetching data:', error);
|
||||
error = error.errorMessage;
|
||||
logger.debug(error);
|
||||
});
|
||||
}
|
||||
|
||||
if (isLoading === true) {
|
||||
return <Loader /> // Affichez le composant Loader
|
||||
} else {
|
||||
return <>
|
||||
<div className="container max mx-auto p-4">
|
||||
<div className="flex justify-center mb-4">
|
||||
<Logo className="h-150 w-150" />
|
||||
</div>
|
||||
<h1 className="text-2xl font-bold text-center mb-4">Nouveau profil</h1>
|
||||
<form className="max-w-md mx-auto" onSubmit={(e) => { e.preventDefault(); subscribeFormSubmit(new FormData(e.target)); }}>
|
||||
<DjangoCSRFToken csrfToken={csrfToken} />
|
||||
<InputTextIcon name="login" type="text" IconItem={User} label="Identifiant" placeholder="Identifiant" errorMsg={userFieldError} className="w-full mb-5" />
|
||||
<InputTextIcon name="password1" type="password" IconItem={KeySquare} label="Mot de passe" placeholder="Mot de passe" errorMsg={password1FieldError} className="w-full mb-5" />
|
||||
<InputTextIcon name="password2" type="password" IconItem={KeySquare} label="Confirmation mot de passe" placeholder="Confirmation mot de passe" errorMsg={password2FieldError} className="w-full" />
|
||||
<p className="text-red-500">{errorMessage}</p>
|
||||
<div className="form-group-submit mt-4">
|
||||
<Button text="Enregistrer" className="w-full" primary type="submit" name="validate" />
|
||||
</div>
|
||||
</form>
|
||||
<br/>
|
||||
<div className='flex justify-center mt-2 max-w-md mx-auto'><Button text="Annuler" className="w-full" onClick={()=>{router.push(`${FE_USERS_LOGIN_URL}`)}} /></div>
|
||||
</div>
|
||||
<Popup
|
||||
visible={popupVisible}
|
||||
message={popupMessage}
|
||||
onConfirm={() => {
|
||||
setPopupVisible(false);
|
||||
router.push(`${FE_USERS_LOGIN_URL}`);
|
||||
}}
|
||||
onCancel={() => setPopupVisible(false)}
|
||||
if (isLoading === true) {
|
||||
return <Loader />; // Affichez le composant Loader
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
<div className="container max mx-auto p-4">
|
||||
<div className="flex justify-center mb-4">
|
||||
<Logo className="h-150 w-150" />
|
||||
</div>
|
||||
<h1 className="text-2xl font-bold text-center mb-4">
|
||||
Nouveau profil
|
||||
</h1>
|
||||
<form
|
||||
className="max-w-md mx-auto"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
subscribeFormSubmit(new FormData(e.target));
|
||||
}}
|
||||
>
|
||||
<DjangoCSRFToken csrfToken={csrfToken} />
|
||||
<InputTextIcon
|
||||
name="login"
|
||||
type="text"
|
||||
IconItem={User}
|
||||
label="Identifiant"
|
||||
placeholder="Identifiant"
|
||||
errorMsg={userFieldError}
|
||||
className="w-full mb-5"
|
||||
/>
|
||||
</>
|
||||
}
|
||||
}
|
||||
<InputTextIcon
|
||||
name="password1"
|
||||
type="password"
|
||||
IconItem={KeySquare}
|
||||
label="Mot de passe"
|
||||
placeholder="Mot de passe"
|
||||
errorMsg={password1FieldError}
|
||||
className="w-full mb-5"
|
||||
/>
|
||||
<InputTextIcon
|
||||
name="password2"
|
||||
type="password"
|
||||
IconItem={KeySquare}
|
||||
label="Confirmation mot de passe"
|
||||
placeholder="Confirmation mot de passe"
|
||||
errorMsg={password2FieldError}
|
||||
className="w-full"
|
||||
/>
|
||||
<p className="text-red-500">{errorMessage}</p>
|
||||
<div className="form-group-submit mt-4">
|
||||
<Button
|
||||
text="Enregistrer"
|
||||
className="w-full"
|
||||
primary
|
||||
type="submit"
|
||||
name="validate"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
<br />
|
||||
<div className="flex justify-center mt-2 max-w-md mx-auto">
|
||||
<Button
|
||||
text="Annuler"
|
||||
className="w-full"
|
||||
onClick={() => {
|
||||
router.push(`${FE_USERS_LOGIN_URL}`);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Popup
|
||||
visible={popupVisible}
|
||||
message={popupMessage}
|
||||
onConfirm={() => {
|
||||
setPopupVisible(false);
|
||||
router.push(`${FE_USERS_LOGIN_URL}`);
|
||||
}}
|
||||
onCancel={() => setPopupVisible(false)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,14 +11,13 @@ import {
|
||||
} from '@/utils/Url';
|
||||
import logger from '@/utils/logger';
|
||||
|
||||
|
||||
const requestResponseHandler = async (response) => {
|
||||
const body = await response.json();
|
||||
if (response.ok) {
|
||||
return body;
|
||||
}
|
||||
|
||||
const error = new Error(body?.errorMessage || "Une erreur est survenue");
|
||||
const error = new Error(body?.errorMessage || 'Une erreur est survenue');
|
||||
error.details = body;
|
||||
throw error;
|
||||
};
|
||||
@ -27,43 +26,39 @@ const requestResponseHandler = async (response) => {
|
||||
* Login action
|
||||
*/
|
||||
export const login = (data) => {
|
||||
return signIn('credentials', {
|
||||
redirect: false,
|
||||
email: data.email,
|
||||
password: data.password,
|
||||
role_type: data.role_type,
|
||||
})
|
||||
return signIn('credentials', {
|
||||
redirect: false,
|
||||
email: data.email,
|
||||
password: data.password,
|
||||
role_type: data.role_type,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Login user with API
|
||||
*/
|
||||
export const getJWT = (data) =>{
|
||||
const request = new Request(
|
||||
`${BE_AUTH_LOGIN_URL}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
credentials: 'include'
|
||||
}
|
||||
);
|
||||
return fetch(request).then(requestResponseHandler)
|
||||
}
|
||||
export const refreshJWT = (data) =>{
|
||||
const request = new Request(
|
||||
`${BE_AUTH_REFRESH_JWT_URL}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
credentials: 'include'
|
||||
}
|
||||
);
|
||||
return fetch(request).then(requestResponseHandler)
|
||||
}
|
||||
export const getJWT = (data) => {
|
||||
const request = new Request(`${BE_AUTH_LOGIN_URL}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
credentials: 'include',
|
||||
});
|
||||
return fetch(request).then(requestResponseHandler);
|
||||
};
|
||||
export const refreshJWT = (data) => {
|
||||
const request = new Request(`${BE_AUTH_REFRESH_JWT_URL}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
credentials: 'include',
|
||||
});
|
||||
return fetch(request).then(requestResponseHandler);
|
||||
};
|
||||
|
||||
/**
|
||||
* Disconnects the user after confirming the action.
|
||||
@ -75,140 +70,116 @@ export const refreshJWT = (data) =>{
|
||||
* @returns {void}
|
||||
*/
|
||||
export const disconnect = () => {
|
||||
signOut({ callbackUrl: FE_USERS_LOGIN_URL });
|
||||
signOut({ callbackUrl: FE_USERS_LOGIN_URL });
|
||||
};
|
||||
|
||||
export const fetchProfileRoles = (establishment) => {
|
||||
return fetch(`${BE_AUTH_PROFILES_ROLES_URL}?establishment_id=${establishment}`)
|
||||
.then(requestResponseHandler)
|
||||
return fetch(
|
||||
`${BE_AUTH_PROFILES_ROLES_URL}?establishment_id=${establishment}`
|
||||
).then(requestResponseHandler);
|
||||
};
|
||||
|
||||
export const updateProfileRoles = (id, data, csrfToken) => {
|
||||
const request = new Request(
|
||||
`${BE_AUTH_PROFILES_ROLES_URL}/${id}`,
|
||||
{
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrfToken
|
||||
},
|
||||
credentials: 'include',
|
||||
body: JSON.stringify(data),
|
||||
}
|
||||
);
|
||||
const request = new Request(`${BE_AUTH_PROFILES_ROLES_URL}/${id}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrfToken,
|
||||
},
|
||||
credentials: 'include',
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
return fetch(request).then(requestResponseHandler);
|
||||
};
|
||||
|
||||
export const deleteProfileRoles = (id, csrfToken) => {
|
||||
const request = new Request(
|
||||
`${BE_AUTH_PROFILES_ROLES_URL}/${id}`,
|
||||
{
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'X-CSRFToken': csrfToken
|
||||
},
|
||||
credentials: 'include'
|
||||
}
|
||||
);
|
||||
const request = new Request(`${BE_AUTH_PROFILES_ROLES_URL}/${id}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'X-CSRFToken': csrfToken,
|
||||
},
|
||||
credentials: 'include',
|
||||
});
|
||||
return fetch(request).then(requestResponseHandler);
|
||||
};
|
||||
|
||||
export const fetchProfiles = () => {
|
||||
return fetch(`${BE_AUTH_PROFILES_URL}`)
|
||||
.then(requestResponseHandler)
|
||||
return fetch(`${BE_AUTH_PROFILES_URL}`).then(requestResponseHandler);
|
||||
};
|
||||
|
||||
export const createProfile = (data, csrfToken) => {
|
||||
const request = new Request(
|
||||
`${BE_AUTH_PROFILES_URL}`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrfToken
|
||||
},
|
||||
credentials: 'include',
|
||||
body: JSON.stringify(data),
|
||||
}
|
||||
);
|
||||
const request = new Request(`${BE_AUTH_PROFILES_URL}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrfToken,
|
||||
},
|
||||
credentials: 'include',
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
return fetch(request).then(requestResponseHandler);
|
||||
};
|
||||
|
||||
export const deleteProfile = (id, csrfToken) => {
|
||||
const request = new Request(
|
||||
`${BE_AUTH_PROFILES_URL}/${id}`,
|
||||
{
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'X-CSRFToken': csrfToken
|
||||
},
|
||||
credentials: 'include'
|
||||
}
|
||||
);
|
||||
const request = new Request(`${BE_AUTH_PROFILES_URL}/${id}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'X-CSRFToken': csrfToken,
|
||||
},
|
||||
credentials: 'include',
|
||||
});
|
||||
return fetch(request).then(requestResponseHandler);
|
||||
};
|
||||
|
||||
export const updateProfile = (id, data, csrfToken) => {
|
||||
const request = new Request(
|
||||
`${BE_AUTH_PROFILES_URL}/${id}`,
|
||||
{
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrfToken
|
||||
},
|
||||
credentials: 'include',
|
||||
body: JSON.stringify(data),
|
||||
}
|
||||
);
|
||||
const request = new Request(`${BE_AUTH_PROFILES_URL}/${id}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrfToken,
|
||||
},
|
||||
credentials: 'include',
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
return fetch(request).then(requestResponseHandler);
|
||||
};
|
||||
|
||||
export const sendNewPassword = (data, csrfToken) => {
|
||||
const request = new Request(
|
||||
`${BE_AUTH_NEW_PASSWORD_URL}`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrfToken
|
||||
},
|
||||
credentials: 'include',
|
||||
body: JSON.stringify(data),
|
||||
}
|
||||
);
|
||||
const request = new Request(`${BE_AUTH_NEW_PASSWORD_URL}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrfToken,
|
||||
},
|
||||
credentials: 'include',
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
return fetch(request).then(requestResponseHandler);
|
||||
};
|
||||
|
||||
export const subscribe = (data, csrfToken) => {
|
||||
const request = new Request(
|
||||
`${BE_AUTH_REGISTER_URL}`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrfToken
|
||||
},
|
||||
credentials: 'include',
|
||||
body: JSON.stringify(data),
|
||||
}
|
||||
);
|
||||
const request = new Request(`${BE_AUTH_REGISTER_URL}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrfToken,
|
||||
},
|
||||
credentials: 'include',
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
return fetch(request).then(requestResponseHandler);
|
||||
};
|
||||
|
||||
export const resetPassword = (uuid, data, csrfToken) => {
|
||||
const request = new Request(
|
||||
`${BE_AUTH_RESET_PASSWORD_URL}/${uuid}`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrfToken
|
||||
},
|
||||
credentials: 'include',
|
||||
body: JSON.stringify(data),
|
||||
}
|
||||
);
|
||||
const request = new Request(`${BE_AUTH_RESET_PASSWORD_URL}/${uuid}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrfToken,
|
||||
},
|
||||
credentials: 'include',
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
return fetch(request).then(requestResponseHandler);
|
||||
};
|
||||
|
||||
|
||||
@ -1,24 +1,20 @@
|
||||
import {
|
||||
BE_GESTIONMESSAGERIE_MESSAGES_URL
|
||||
} from '@/utils/Url';
|
||||
import { BE_GESTIONMESSAGERIE_MESSAGES_URL } from '@/utils/Url';
|
||||
|
||||
const requestResponseHandler = async (response) => {
|
||||
|
||||
const body = await response.json();
|
||||
if (response.ok) {
|
||||
return body;
|
||||
}
|
||||
// Throw an error with the JSON body containing the form errors
|
||||
const error = new Error(body?.errorMessage || "Une erreur est survenue");
|
||||
// Throw an error with the JSON body containing the form errors
|
||||
const error = new Error(body?.errorMessage || 'Une erreur est survenue');
|
||||
error.details = body;
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export const fetchMessages = (id) =>{
|
||||
return fetch(`${BE_GESTIONMESSAGERIE_MESSAGES_URL}/${id}`, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}).then(requestResponseHandler)
|
||||
}
|
||||
export const fetchMessages = (id) => {
|
||||
return fetch(`${BE_GESTIONMESSAGERIE_MESSAGES_URL}/${id}`, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}).then(requestResponseHandler);
|
||||
};
|
||||
|
||||
@ -1,112 +1,96 @@
|
||||
import { BE_PLANNING_PLANNINGS_URL,
|
||||
BE_PLANNING_EVENTS_URL
|
||||
} from '@/utils/Url';
|
||||
|
||||
|
||||
import { BE_PLANNING_PLANNINGS_URL, BE_PLANNING_EVENTS_URL } from '@/utils/Url';
|
||||
|
||||
const requestResponseHandler = async (response) => {
|
||||
const body = response.status !== 204 ? await response?.json() : {};
|
||||
console.log(response)
|
||||
console.log(response);
|
||||
if (response.ok) {
|
||||
return body;
|
||||
}
|
||||
// Throw an error with the JSON body containing the form errors
|
||||
const error = new Error(body?.errorMessage || "Une erreur est survenue");
|
||||
// Throw an error with the JSON body containing the form errors
|
||||
const error = new Error(body?.errorMessage || 'Une erreur est survenue');
|
||||
error.details = body;
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const getData = (url) => {
|
||||
const getData = (url) => {
|
||||
return fetch(`${url}`).then(requestResponseHandler);
|
||||
}
|
||||
};
|
||||
|
||||
const createDatas = (url, newData, csrfToken) => {
|
||||
return fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrfToken
|
||||
'X-CSRFToken': csrfToken,
|
||||
},
|
||||
body: JSON.stringify(newData),
|
||||
credentials: 'include'
|
||||
})
|
||||
.then(requestResponseHandler)
|
||||
credentials: 'include',
|
||||
}).then(requestResponseHandler);
|
||||
};
|
||||
|
||||
const updateDatas = (url, updatedData, csrfToken) => {
|
||||
return fetch(`${url}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrfToken
|
||||
},
|
||||
body: JSON.stringify(updatedData),
|
||||
credentials: 'include'
|
||||
})
|
||||
.then(requestResponseHandler)
|
||||
};
|
||||
|
||||
const removeDatas = (url, csrfToken) => {
|
||||
return fetch(`${url}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrfToken
|
||||
},
|
||||
credentials: 'include'
|
||||
})
|
||||
.then(requestResponseHandler)
|
||||
return fetch(`${url}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrfToken,
|
||||
},
|
||||
body: JSON.stringify(updatedData),
|
||||
credentials: 'include',
|
||||
}).then(requestResponseHandler);
|
||||
};
|
||||
|
||||
|
||||
|
||||
const removeDatas = (url, csrfToken) => {
|
||||
return fetch(`${url}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrfToken,
|
||||
},
|
||||
credentials: 'include',
|
||||
}).then(requestResponseHandler);
|
||||
};
|
||||
|
||||
export const fetchPlannings = () => {
|
||||
return getData(`${BE_PLANNING_PLANNINGS_URL}`)
|
||||
return getData(`${BE_PLANNING_PLANNINGS_URL}`);
|
||||
};
|
||||
|
||||
|
||||
export const getPlanning = (id) => {
|
||||
return getData(`${BE_PLANNING_PLANNINGS_URL}/${id}`)
|
||||
return getData(`${BE_PLANNING_PLANNINGS_URL}/${id}`);
|
||||
};
|
||||
|
||||
export const createPlanning = (newData, csrfToken) => {
|
||||
return createDatas(`${BE_PLANNING_PLANNINGS_URL}`, newData, csrfToken)
|
||||
}
|
||||
|
||||
export const updatePlanning = (id,newData, csrfToken) => {
|
||||
return updateDatas(`${BE_PLANNING_PLANNINGS_URL}/${id}`, newData, csrfToken)
|
||||
}
|
||||
|
||||
export const deletePlanning = (id, csrfToken) => {
|
||||
return removeDatas(`${BE_PLANNING_PLANNINGS_URL}/${id}`, csrfToken)
|
||||
}
|
||||
|
||||
|
||||
export const fetchEvents = () => {
|
||||
return getData(`${BE_PLANNING_EVENTS_URL}`)
|
||||
return createDatas(`${BE_PLANNING_PLANNINGS_URL}`, newData, csrfToken);
|
||||
};
|
||||
|
||||
export const updatePlanning = (id, newData, csrfToken) => {
|
||||
return updateDatas(`${BE_PLANNING_PLANNINGS_URL}/${id}`, newData, csrfToken);
|
||||
};
|
||||
|
||||
export const deletePlanning = (id, csrfToken) => {
|
||||
return removeDatas(`${BE_PLANNING_PLANNINGS_URL}/${id}`, csrfToken);
|
||||
};
|
||||
|
||||
export const fetchEvents = () => {
|
||||
return getData(`${BE_PLANNING_EVENTS_URL}`);
|
||||
};
|
||||
|
||||
export const getEvent = (id) => {
|
||||
return getData(`${BE_PLANNING_EVENTS_URL}/${id}`)
|
||||
return getData(`${BE_PLANNING_EVENTS_URL}/${id}`);
|
||||
};
|
||||
|
||||
export const createEvent = (newData, csrfToken) => {
|
||||
return createDatas(`${BE_PLANNING_EVENTS_URL}`, newData, csrfToken)
|
||||
}
|
||||
return createDatas(`${BE_PLANNING_EVENTS_URL}`, newData, csrfToken);
|
||||
};
|
||||
|
||||
export const updateEvent = (id,newData, csrfToken) => {
|
||||
return updateDatas(`${BE_PLANNING_EVENTS_URL}/${id}`, newData, csrfToken)
|
||||
}
|
||||
export const updateEvent = (id, newData, csrfToken) => {
|
||||
return updateDatas(`${BE_PLANNING_EVENTS_URL}/${id}`, newData, csrfToken);
|
||||
};
|
||||
|
||||
export const deleteEvent = (id, csrfToken) => {
|
||||
return removeDatas(`${BE_PLANNING_EVENTS_URL}/${id}`, csrfToken)
|
||||
}
|
||||
return removeDatas(`${BE_PLANNING_EVENTS_URL}/${id}`, csrfToken);
|
||||
};
|
||||
|
||||
export const fetchUpcomingEvents = () => {
|
||||
return getData(`${BE_PLANNING_EVENTS_URL}/upcoming`);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
@ -1,30 +1,33 @@
|
||||
import { BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL,
|
||||
BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL,
|
||||
BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL,
|
||||
FE_API_DOCUSEAL_CLONE_URL,
|
||||
FE_API_DOCUSEAL_DOWNLOAD_URL,
|
||||
FE_API_DOCUSEAL_GENERATE_TOKEN
|
||||
import {
|
||||
BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL,
|
||||
BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL,
|
||||
BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL,
|
||||
FE_API_DOCUSEAL_CLONE_URL,
|
||||
FE_API_DOCUSEAL_DOWNLOAD_URL,
|
||||
FE_API_DOCUSEAL_GENERATE_TOKEN,
|
||||
} from '@/utils/Url';
|
||||
|
||||
const requestResponseHandler = async (response) => {
|
||||
|
||||
const body = await response.json();
|
||||
if (response.ok) {
|
||||
return body;
|
||||
}
|
||||
// Throw an error with the JSON body containing the form errors
|
||||
const error = new Error(body?.errorMessage || "Une erreur est survenue");
|
||||
// Throw an error with the JSON body containing the form errors
|
||||
const error = new Error(body?.errorMessage || 'Une erreur est survenue');
|
||||
error.details = body;
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export async function fetchRegistrationFileGroups(establishment) {
|
||||
const response = await fetch(`${BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL}?establishment_id=${establishment}`, {
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
const response = await fetch(
|
||||
`${BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL}?establishment_id=${establishment}`,
|
||||
{
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
},
|
||||
}
|
||||
});
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch file groups');
|
||||
}
|
||||
@ -32,15 +35,18 @@ export async function fetchRegistrationFileGroups(establishment) {
|
||||
}
|
||||
|
||||
export async function createRegistrationFileGroup(groupData, csrfToken) {
|
||||
const response = await fetch(`${BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrfToken,
|
||||
},
|
||||
body: JSON.stringify(groupData),
|
||||
credentials: 'include'
|
||||
});
|
||||
const response = await fetch(
|
||||
`${BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL}`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrfToken,
|
||||
},
|
||||
body: JSON.stringify(groupData),
|
||||
credentials: 'include',
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to create file group');
|
||||
@ -50,26 +56,36 @@ export async function createRegistrationFileGroup(groupData, csrfToken) {
|
||||
}
|
||||
|
||||
export async function deleteRegistrationFileGroup(groupId, csrfToken) {
|
||||
const response = await fetch(`${BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL}/${groupId}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'X-CSRFToken': csrfToken,
|
||||
},
|
||||
credentials: 'include'
|
||||
});
|
||||
const response = await fetch(
|
||||
`${BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL}/${groupId}`,
|
||||
{
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'X-CSRFToken': csrfToken,
|
||||
},
|
||||
credentials: 'include',
|
||||
}
|
||||
);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
export const editRegistrationFileGroup = async (groupId, groupData, csrfToken) => {
|
||||
const response = await fetch(`${BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL}/${groupId}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrfToken,
|
||||
},
|
||||
body: JSON.stringify(groupData),
|
||||
});
|
||||
export const editRegistrationFileGroup = async (
|
||||
groupId,
|
||||
groupData,
|
||||
csrfToken
|
||||
) => {
|
||||
const response = await fetch(
|
||||
`${BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL}/${groupId}`,
|
||||
{
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrfToken,
|
||||
},
|
||||
body: JSON.stringify(groupData),
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Erreur lors de la modification du groupe');
|
||||
@ -79,33 +95,35 @@ export const editRegistrationFileGroup = async (groupId, groupData, csrfToken) =
|
||||
};
|
||||
|
||||
export const fetchRegistrationFileFromGroup = async (groupId) => {
|
||||
const response = await fetch(`${BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL}/${groupId}/templates`, {
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
const response = await fetch(
|
||||
`${BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL}/${groupId}/templates`,
|
||||
{
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
},
|
||||
}
|
||||
});
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new Error('Erreur lors de la récupération des fichiers associés au groupe');
|
||||
throw new Error(
|
||||
'Erreur lors de la récupération des fichiers associés au groupe'
|
||||
);
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchRegistrationTemplates = (id = null) => {
|
||||
let url = `${BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL}`
|
||||
export const fetchRegistrationTemplates = (id = null) => {
|
||||
let url = `${BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL}`;
|
||||
if (id) {
|
||||
url = `${BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL}/${id}`;
|
||||
}
|
||||
const request = new Request(
|
||||
`${url}`,
|
||||
{
|
||||
method:'GET',
|
||||
headers: {
|
||||
'Content-Type':'application/json'
|
||||
},
|
||||
}
|
||||
);
|
||||
return fetch(request).then(requestResponseHandler)
|
||||
const request = new Request(`${url}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
return fetch(request).then(requestResponseHandler);
|
||||
};
|
||||
|
||||
export const editRegistrationTemplates = (fileId, data, csrfToken) => {
|
||||
@ -116,12 +134,10 @@ export const editRegistrationTemplates = (fileId, data, csrfToken) => {
|
||||
'X-CSRFToken': csrfToken,
|
||||
},
|
||||
credentials: 'include',
|
||||
})
|
||||
.then(requestResponseHandler)
|
||||
}
|
||||
|
||||
export const createRegistrationTemplates = (data,csrfToken) => {
|
||||
}).then(requestResponseHandler);
|
||||
};
|
||||
|
||||
export const createRegistrationTemplates = (data, csrfToken) => {
|
||||
return fetch(`${BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL}`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
@ -130,73 +146,72 @@ export const createRegistrationTemplates = (data,csrfToken) => {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
credentials: 'include',
|
||||
})
|
||||
.then(requestResponseHandler)
|
||||
}
|
||||
}).then(requestResponseHandler);
|
||||
};
|
||||
|
||||
export const deleteRegistrationTemplates = (fileId,csrfToken) => {
|
||||
export const deleteRegistrationTemplates = (fileId, csrfToken) => {
|
||||
return fetch(`${BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL}/${fileId}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'X-CSRFToken': csrfToken,
|
||||
},
|
||||
credentials: 'include',
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const fetchRegistrationTemplateMaster = (id = null) => {
|
||||
let url = `${BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL}`;
|
||||
if(id){
|
||||
if (id) {
|
||||
url = `${BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL}/${id}`;
|
||||
}
|
||||
const request = new Request(
|
||||
`${url}`,
|
||||
{
|
||||
method:'GET',
|
||||
headers: {
|
||||
'Content-Type':'application/json'
|
||||
},
|
||||
}
|
||||
);
|
||||
return fetch(request).then(requestResponseHandler)
|
||||
const request = new Request(`${url}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
return fetch(request).then(requestResponseHandler);
|
||||
};
|
||||
|
||||
export const createRegistrationTemplateMaster = (data,csrfToken) => {
|
||||
|
||||
export const createRegistrationTemplateMaster = (data, csrfToken) => {
|
||||
return fetch(`${BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL}`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
headers: {
|
||||
'X-CSRFToken': csrfToken,
|
||||
'Content-Type':'application/json'
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
credentials: 'include',
|
||||
})
|
||||
.then(requestResponseHandler)
|
||||
}
|
||||
}).then(requestResponseHandler);
|
||||
};
|
||||
|
||||
export const deleteRegistrationTemplateMaster = (fileId,csrfToken) => {
|
||||
return fetch(`${BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL}/${fileId}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'X-CSRFToken': csrfToken,
|
||||
},
|
||||
credentials: 'include',
|
||||
})
|
||||
}
|
||||
export const deleteRegistrationTemplateMaster = (fileId, csrfToken) => {
|
||||
return fetch(
|
||||
`${BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL}/${fileId}`,
|
||||
{
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'X-CSRFToken': csrfToken,
|
||||
},
|
||||
credentials: 'include',
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export const editRegistrationTemplateMaster = (fileId, data, csrfToken) => {
|
||||
return fetch(`${BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL}/${fileId}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(data),
|
||||
headers: {
|
||||
'X-CSRFToken': csrfToken,
|
||||
'Content-Type':'application/json'
|
||||
},
|
||||
credentials: 'include',
|
||||
})
|
||||
.then(requestResponseHandler)
|
||||
}
|
||||
return fetch(
|
||||
`${BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL}/${fileId}`,
|
||||
{
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(data),
|
||||
headers: {
|
||||
'X-CSRFToken': csrfToken,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
credentials: 'include',
|
||||
}
|
||||
).then(requestResponseHandler);
|
||||
};
|
||||
|
||||
export const cloneTemplate = (templateId, email, is_required) => {
|
||||
return fetch(`${FE_API_DOCUSEAL_CLONE_URL}`, {
|
||||
@ -207,21 +222,19 @@ export const cloneTemplate = (templateId, email, is_required) => {
|
||||
body: JSON.stringify({
|
||||
templateId,
|
||||
email,
|
||||
is_required
|
||||
})
|
||||
})
|
||||
.then(requestResponseHandler)
|
||||
}
|
||||
is_required,
|
||||
}),
|
||||
}).then(requestResponseHandler);
|
||||
};
|
||||
|
||||
export const downloadTemplate = (slug) => {
|
||||
return fetch(`${FE_API_DOCUSEAL_DOWNLOAD_URL}/${slug}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
})
|
||||
.then(requestResponseHandler)
|
||||
}
|
||||
},
|
||||
}).then(requestResponseHandler);
|
||||
};
|
||||
|
||||
export const generateToken = (email, id = null) => {
|
||||
return fetch(`${FE_API_DOCUSEAL_GENERATE_TOKEN}`, {
|
||||
@ -231,4 +244,4 @@ export const generateToken = (email, id = null) => {
|
||||
},
|
||||
body: JSON.stringify({ user_email: email, id }),
|
||||
}).then(requestResponseHandler);
|
||||
};
|
||||
};
|
||||
|
||||
@ -1,127 +1,133 @@
|
||||
import {
|
||||
BE_SCHOOL_SPECIALITIES_URL,
|
||||
BE_SCHOOL_TEACHERS_URL,
|
||||
BE_SCHOOL_SCHOOLCLASSES_URL,
|
||||
BE_SCHOOL_PLANNINGS_URL,
|
||||
BE_SCHOOL_FEES_URL,
|
||||
BE_SCHOOL_DISCOUNTS_URL,
|
||||
BE_SCHOOL_PAYMENT_PLANS_URL,
|
||||
BE_SCHOOL_PAYMENT_MODES_URL,
|
||||
BE_SCHOOL_ESTABLISHMENT_URL
|
||||
BE_SCHOOL_SPECIALITIES_URL,
|
||||
BE_SCHOOL_TEACHERS_URL,
|
||||
BE_SCHOOL_SCHOOLCLASSES_URL,
|
||||
BE_SCHOOL_PLANNINGS_URL,
|
||||
BE_SCHOOL_FEES_URL,
|
||||
BE_SCHOOL_DISCOUNTS_URL,
|
||||
BE_SCHOOL_PAYMENT_PLANS_URL,
|
||||
BE_SCHOOL_PAYMENT_MODES_URL,
|
||||
BE_SCHOOL_ESTABLISHMENT_URL,
|
||||
} from '@/utils/Url';
|
||||
|
||||
const requestResponseHandler = async (response) => {
|
||||
|
||||
const body = await response.json();
|
||||
if (response.ok) {
|
||||
return body;
|
||||
}
|
||||
// Throw an error with the JSON body containing the form errors
|
||||
const error = new Error(body?.errorMessage || "Une erreur est survenue");
|
||||
// Throw an error with the JSON body containing the form errors
|
||||
const error = new Error(body?.errorMessage || 'Une erreur est survenue');
|
||||
error.details = body;
|
||||
throw error;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
export const fetchSpecialities = (establishment) => {
|
||||
return fetch(`${BE_SCHOOL_SPECIALITIES_URL}?establishment_id=${establishment}`)
|
||||
.then(requestResponseHandler)
|
||||
return fetch(
|
||||
`${BE_SCHOOL_SPECIALITIES_URL}?establishment_id=${establishment}`
|
||||
).then(requestResponseHandler);
|
||||
};
|
||||
|
||||
export const fetchTeachers = (establishment) => {
|
||||
return fetch(`${BE_SCHOOL_TEACHERS_URL}?establishment_id=${establishment}`)
|
||||
.then(requestResponseHandler)
|
||||
return fetch(
|
||||
`${BE_SCHOOL_TEACHERS_URL}?establishment_id=${establishment}`
|
||||
).then(requestResponseHandler);
|
||||
};
|
||||
|
||||
export const fetchClasses = (establishment) => {
|
||||
return fetch(`${BE_SCHOOL_SCHOOLCLASSES_URL}?establishment_id=${establishment}`)
|
||||
.then(requestResponseHandler)
|
||||
return fetch(
|
||||
`${BE_SCHOOL_SCHOOLCLASSES_URL}?establishment_id=${establishment}`
|
||||
).then(requestResponseHandler);
|
||||
};
|
||||
|
||||
export const fetchSchedules = () => {
|
||||
return fetch(`${BE_SCHOOL_PLANNINGS_URL}`)
|
||||
.then(requestResponseHandler)
|
||||
return fetch(`${BE_SCHOOL_PLANNINGS_URL}`).then(requestResponseHandler);
|
||||
};
|
||||
|
||||
export const fetchRegistrationDiscounts = (establishment) => {
|
||||
return fetch(`${BE_SCHOOL_DISCOUNTS_URL}?filter=registration&establishment_id=${establishment}`)
|
||||
.then(requestResponseHandler)
|
||||
return fetch(
|
||||
`${BE_SCHOOL_DISCOUNTS_URL}?filter=registration&establishment_id=${establishment}`
|
||||
).then(requestResponseHandler);
|
||||
};
|
||||
|
||||
export const fetchTuitionDiscounts = (establishment) => {
|
||||
return fetch(`${BE_SCHOOL_DISCOUNTS_URL}?filter=tuition&establishment_id=${establishment}`)
|
||||
.then(requestResponseHandler)
|
||||
return fetch(
|
||||
`${BE_SCHOOL_DISCOUNTS_URL}?filter=tuition&establishment_id=${establishment}`
|
||||
).then(requestResponseHandler);
|
||||
};
|
||||
|
||||
export const fetchRegistrationFees = (establishment) => {
|
||||
return fetch(`${BE_SCHOOL_FEES_URL}?filter=registration&establishment_id=${establishment}`)
|
||||
.then(requestResponseHandler)
|
||||
return fetch(
|
||||
`${BE_SCHOOL_FEES_URL}?filter=registration&establishment_id=${establishment}`
|
||||
).then(requestResponseHandler);
|
||||
};
|
||||
|
||||
export const fetchTuitionFees = (establishment) => {
|
||||
return fetch(`${BE_SCHOOL_FEES_URL}?filter=tuition&establishment_id=${establishment}`)
|
||||
.then(requestResponseHandler)
|
||||
return fetch(
|
||||
`${BE_SCHOOL_FEES_URL}?filter=tuition&establishment_id=${establishment}`
|
||||
).then(requestResponseHandler);
|
||||
};
|
||||
|
||||
export const fetchRegistrationPaymentPlans = (establishment) => {
|
||||
return fetch(`${BE_SCHOOL_PAYMENT_PLANS_URL}?filter=registration&establishment_id=${establishment}`)
|
||||
.then(requestResponseHandler)
|
||||
}
|
||||
return fetch(
|
||||
`${BE_SCHOOL_PAYMENT_PLANS_URL}?filter=registration&establishment_id=${establishment}`
|
||||
).then(requestResponseHandler);
|
||||
};
|
||||
|
||||
export const fetchTuitionPaymentPlans = (establishment) => {
|
||||
return fetch(`${BE_SCHOOL_PAYMENT_PLANS_URL}?filter=tuition&establishment_id=${establishment}`)
|
||||
.then(requestResponseHandler)
|
||||
}
|
||||
return fetch(
|
||||
`${BE_SCHOOL_PAYMENT_PLANS_URL}?filter=tuition&establishment_id=${establishment}`
|
||||
).then(requestResponseHandler);
|
||||
};
|
||||
|
||||
export const fetchRegistrationPaymentModes = (establishment) => {
|
||||
return fetch(`${BE_SCHOOL_PAYMENT_MODES_URL}?filter=registration&establishment_id=${establishment}`)
|
||||
.then(requestResponseHandler)
|
||||
}
|
||||
return fetch(
|
||||
`${BE_SCHOOL_PAYMENT_MODES_URL}?filter=registration&establishment_id=${establishment}`
|
||||
).then(requestResponseHandler);
|
||||
};
|
||||
|
||||
export const fetchTuitionPaymentModes = (establishment) => {
|
||||
return fetch(`${BE_SCHOOL_PAYMENT_MODES_URL}?filter=tuition&establishment_id=${establishment}`)
|
||||
.then(requestResponseHandler)
|
||||
}
|
||||
return fetch(
|
||||
`${BE_SCHOOL_PAYMENT_MODES_URL}?filter=tuition&establishment_id=${establishment}`
|
||||
).then(requestResponseHandler);
|
||||
};
|
||||
|
||||
export const fetchEstablishment = (establishment) => {
|
||||
return fetch(`${BE_SCHOOL_ESTABLISHMENT_URL}/${establishment}`)
|
||||
.then(requestResponseHandler)
|
||||
}
|
||||
return fetch(`${BE_SCHOOL_ESTABLISHMENT_URL}/${establishment}`).then(
|
||||
requestResponseHandler
|
||||
);
|
||||
};
|
||||
|
||||
export const createDatas = (url, newData, csrfToken) => {
|
||||
return fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrfToken
|
||||
'X-CSRFToken': csrfToken,
|
||||
},
|
||||
body: JSON.stringify(newData),
|
||||
credentials: 'include'
|
||||
})
|
||||
.then(requestResponseHandler)
|
||||
credentials: 'include',
|
||||
}).then(requestResponseHandler);
|
||||
};
|
||||
|
||||
export const updateDatas = (url, id, updatedData, csrfToken) => {
|
||||
return fetch(`${url}/${id}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrfToken
|
||||
},
|
||||
body: JSON.stringify(updatedData),
|
||||
credentials: 'include'
|
||||
})
|
||||
.then(requestResponseHandler)
|
||||
};
|
||||
return fetch(`${url}/${id}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrfToken,
|
||||
},
|
||||
body: JSON.stringify(updatedData),
|
||||
credentials: 'include',
|
||||
}).then(requestResponseHandler);
|
||||
};
|
||||
|
||||
export const removeDatas = (url, id, csrfToken) => {
|
||||
return fetch(`${url}/${id}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrfToken
|
||||
},
|
||||
credentials: 'include'
|
||||
})
|
||||
.then(requestResponseHandler)
|
||||
};
|
||||
return fetch(`${url}/${id}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrfToken,
|
||||
},
|
||||
credentials: 'include',
|
||||
}).then(requestResponseHandler);
|
||||
};
|
||||
|
||||
@ -1,150 +1,152 @@
|
||||
import {
|
||||
BE_SUBSCRIPTION_STUDENTS_URL,
|
||||
BE_SUBSCRIPTION_CHILDRENS_URL,
|
||||
BE_SUBSCRIPTION_REGISTERFORMS_URL,
|
||||
BE_SUBSCRIPTION_LAST_GUARDIAN_ID_URL
|
||||
BE_SUBSCRIPTION_STUDENTS_URL,
|
||||
BE_SUBSCRIPTION_CHILDRENS_URL,
|
||||
BE_SUBSCRIPTION_REGISTERFORMS_URL,
|
||||
BE_SUBSCRIPTION_LAST_GUARDIAN_ID_URL,
|
||||
} from '@/utils/Url';
|
||||
|
||||
export const PENDING = 'pending';
|
||||
export const SUBSCRIBED = 'subscribed';
|
||||
export const ARCHIVED = 'archived';
|
||||
|
||||
|
||||
const requestResponseHandler = async (response) => {
|
||||
|
||||
const body = await response.json();
|
||||
if (response.ok) {
|
||||
return body;
|
||||
}
|
||||
// Throw an error with the JSON body containing the form errors
|
||||
const error = new Error(body?.errorMessage || "Une erreur est survenue");
|
||||
// Throw an error with the JSON body containing the form errors
|
||||
const error = new Error(body?.errorMessage || 'Une erreur est survenue');
|
||||
error.details = body;
|
||||
throw error;
|
||||
}
|
||||
|
||||
export const fetchRegisterForms = (establishment, filter=PENDING, page='', pageSize='', search = '') => {
|
||||
let url = `${BE_SUBSCRIPTION_REGISTERFORMS_URL}?filter=${filter}&establishment_id=${establishment}`;
|
||||
if (page !== '' && pageSize !== '') {
|
||||
url = `${BE_SUBSCRIPTION_REGISTERFORMS_URL}?filter=${filter}&establishment_id=${establishment}&page=${page}&search=${search}`;
|
||||
}
|
||||
return fetch(url, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}).then(requestResponseHandler)
|
||||
};
|
||||
|
||||
export const fetchRegisterForm = (id) =>{
|
||||
return fetch(`${BE_SUBSCRIPTION_REGISTERFORMS_URL}/${id}`) // Utilisation de studentId au lieu de codeDI
|
||||
.then(requestResponseHandler)
|
||||
}
|
||||
export const fetchLastGuardian = () =>{
|
||||
return fetch(`${BE_SUBSCRIPTION_LAST_GUARDIAN_ID_URL}`)
|
||||
.then(requestResponseHandler)
|
||||
}
|
||||
|
||||
export const editRegisterForm=(id, data, csrfToken)=>{
|
||||
return fetch(`${BE_SUBSCRIPTION_REGISTERFORMS_URL}/${id}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrfToken
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
credentials: 'include'
|
||||
})
|
||||
.then(requestResponseHandler)
|
||||
};
|
||||
|
||||
export const sendSEPARegisterForm=(id, data, csrfToken)=>{
|
||||
export const fetchRegisterForms = (
|
||||
establishment,
|
||||
filter = PENDING,
|
||||
page = '',
|
||||
pageSize = '',
|
||||
search = ''
|
||||
) => {
|
||||
let url = `${BE_SUBSCRIPTION_REGISTERFORMS_URL}?filter=${filter}&establishment_id=${establishment}`;
|
||||
if (page !== '' && pageSize !== '') {
|
||||
url = `${BE_SUBSCRIPTION_REGISTERFORMS_URL}?filter=${filter}&establishment_id=${establishment}&page=${page}&search=${search}`;
|
||||
}
|
||||
return fetch(url, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}).then(requestResponseHandler);
|
||||
};
|
||||
|
||||
export const fetchRegisterForm = (id) => {
|
||||
return fetch(`${BE_SUBSCRIPTION_REGISTERFORMS_URL}/${id}`) // Utilisation de studentId au lieu de codeDI
|
||||
.then(requestResponseHandler);
|
||||
};
|
||||
export const fetchLastGuardian = () => {
|
||||
return fetch(`${BE_SUBSCRIPTION_LAST_GUARDIAN_ID_URL}`).then(
|
||||
requestResponseHandler
|
||||
);
|
||||
};
|
||||
|
||||
export const editRegisterForm = (id, data, csrfToken) => {
|
||||
return fetch(`${BE_SUBSCRIPTION_REGISTERFORMS_URL}/${id}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'X-CSRFToken': csrfToken
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrfToken,
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
credentials: 'include',
|
||||
}).then(requestResponseHandler);
|
||||
};
|
||||
|
||||
export const sendSEPARegisterForm = (id, data, csrfToken) => {
|
||||
return fetch(`${BE_SUBSCRIPTION_REGISTERFORMS_URL}/${id}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'X-CSRFToken': csrfToken,
|
||||
},
|
||||
body: data,
|
||||
credentials: 'include'
|
||||
})
|
||||
.then(requestResponseHandler)
|
||||
credentials: 'include',
|
||||
}).then(requestResponseHandler);
|
||||
};
|
||||
|
||||
export const createRegisterForm=(data, csrfToken)=>{
|
||||
const url = `${BE_SUBSCRIPTION_REGISTERFORMS_URL}`;
|
||||
return fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrfToken
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
credentials: 'include'
|
||||
})
|
||||
.then(requestResponseHandler)
|
||||
}
|
||||
export const createRegisterForm = (data, csrfToken) => {
|
||||
const url = `${BE_SUBSCRIPTION_REGISTERFORMS_URL}`;
|
||||
return fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrfToken,
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
credentials: 'include',
|
||||
}).then(requestResponseHandler);
|
||||
};
|
||||
|
||||
export const sendRegisterForm = (id) => {
|
||||
const url = `${BE_SUBSCRIPTION_REGISTERFORMS_URL}/${id}/send`;
|
||||
return fetch(url, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}).then(requestResponseHandler)
|
||||
}
|
||||
|
||||
export const resendRegisterForm = (id) => {
|
||||
const url = `${BE_SUBSCRIPTION_REGISTERFORMS_URL}/${id}/resend`;
|
||||
return fetch(url, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}).then(requestResponseHandler)
|
||||
|
||||
}
|
||||
export const archiveRegisterForm = (id) => {
|
||||
const url = `${BE_SUBSCRIPTION_REGISTERFORMS_URL}/${id}/archive`;
|
||||
return fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}).then(requestResponseHandler)
|
||||
}
|
||||
|
||||
export const fetchStudents = (establishment, id=null) => {
|
||||
const url = (id)?`${BE_SUBSCRIPTION_STUDENTS_URL}/${id}`:`${BE_SUBSCRIPTION_STUDENTS_URL}?establishment_id=${establishment}`;
|
||||
const request = new Request(
|
||||
url,
|
||||
{
|
||||
method:'GET',
|
||||
headers: {
|
||||
'Content-Type':'application/json'
|
||||
},
|
||||
}
|
||||
);
|
||||
return fetch(request).then(requestResponseHandler)
|
||||
|
||||
const url = `${BE_SUBSCRIPTION_REGISTERFORMS_URL}/${id}/send`;
|
||||
return fetch(url, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}).then(requestResponseHandler);
|
||||
};
|
||||
|
||||
export const fetchChildren = (id, establishment) =>{
|
||||
const request = new Request(
|
||||
`${BE_SUBSCRIPTION_CHILDRENS_URL}/${id}?establishment_id=${establishment}`,
|
||||
{
|
||||
method:'GET',
|
||||
headers: {
|
||||
'Content-Type':'application/json'
|
||||
},
|
||||
}
|
||||
);
|
||||
return fetch(request).then(requestResponseHandler)
|
||||
}
|
||||
export const resendRegisterForm = (id) => {
|
||||
const url = `${BE_SUBSCRIPTION_REGISTERFORMS_URL}/${id}/resend`;
|
||||
return fetch(url, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}).then(requestResponseHandler);
|
||||
};
|
||||
export const archiveRegisterForm = (id) => {
|
||||
const url = `${BE_SUBSCRIPTION_REGISTERFORMS_URL}/${id}/archive`;
|
||||
return fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}).then(requestResponseHandler);
|
||||
};
|
||||
|
||||
export const fetchStudents = (establishment, id = null) => {
|
||||
const url = id
|
||||
? `${BE_SUBSCRIPTION_STUDENTS_URL}/${id}`
|
||||
: `${BE_SUBSCRIPTION_STUDENTS_URL}?establishment_id=${establishment}`;
|
||||
const request = new Request(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
return fetch(request).then(requestResponseHandler);
|
||||
};
|
||||
|
||||
export const fetchChildren = (id, establishment) => {
|
||||
const request = new Request(
|
||||
`${BE_SUBSCRIPTION_CHILDRENS_URL}/${id}?establishment_id=${establishment}`,
|
||||
{
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}
|
||||
);
|
||||
return fetch(request).then(requestResponseHandler);
|
||||
};
|
||||
|
||||
export async function getRegisterFormFileTemplate(fileId) {
|
||||
const response = await fetch(`${BE_SUBSCRIPTION_REGISTERFORM_FILE_TEMPLATE_URL}/${fileId}`, {
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
const response = await fetch(
|
||||
`${BE_SUBSCRIPTION_REGISTERFORM_FILE_TEMPLATE_URL}/${fileId}`,
|
||||
{
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
},
|
||||
}
|
||||
});
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch file template');
|
||||
}
|
||||
@ -152,28 +154,36 @@ export async function getRegisterFormFileTemplate(fileId) {
|
||||
}
|
||||
|
||||
export const fetchTemplatesFromRegistrationFiles = async (id) => {
|
||||
const response = await fetch(`${BE_SUBSCRIPTION_REGISTERFORMS_URL}/${id}/templates`, {
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
const response = await fetch(
|
||||
`${BE_SUBSCRIPTION_REGISTERFORMS_URL}/${id}/templates`,
|
||||
{
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
},
|
||||
}
|
||||
});
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new Error('Erreur lors de la récupération des fichiers associés au groupe');
|
||||
throw new Error(
|
||||
'Erreur lors de la récupération des fichiers associés au groupe'
|
||||
);
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
};
|
||||
|
||||
export const dissociateGuardian = async (studentId, guardianId) => {
|
||||
const response = await fetch(`${BE_SUBSCRIPTION_STUDENTS_URL}/${studentId}/guardians/${guardianId}/dissociate`, {
|
||||
const response = await fetch(
|
||||
`${BE_SUBSCRIPTION_STUDENTS_URL}/${studentId}/guardians/${guardianId}/dissociate`,
|
||||
{
|
||||
credentials: 'include',
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
Accept: 'application/json',
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new Error('Erreur lors de la dissociation.');
|
||||
throw new Error('Erreur lors de la dissociation.');
|
||||
}
|
||||
return response.json();
|
||||
};
|
||||
};
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import React from 'react';
|
||||
import { getMessages } from 'next-intl/server';
|
||||
import Providers from '@/components/Providers'
|
||||
import "@/css/tailwind.css";
|
||||
import Providers from '@/components/Providers';
|
||||
import '@/css/tailwind.css';
|
||||
import { headers } from 'next/headers';
|
||||
|
||||
export const metadata = {
|
||||
title: "N3WT-SCHOOL",
|
||||
title: 'N3WT-SCHOOL',
|
||||
description: "Gestion de l'école",
|
||||
icons: {
|
||||
icon: [
|
||||
@ -14,7 +14,7 @@ export const metadata = {
|
||||
type: 'image/svg+xml',
|
||||
},
|
||||
{
|
||||
url: '/favicon.ico', // Fallback pour les anciens navigateurs
|
||||
url: '/favicon.ico', // Fallback pour les anciens navigateurs
|
||||
sizes: 'any',
|
||||
},
|
||||
],
|
||||
|
||||
@ -1,15 +1,21 @@
|
||||
import Link from 'next/link'
|
||||
import Logo from '../components/Logo'
|
||||
import Link from 'next/link';
|
||||
import Logo from '../components/Logo';
|
||||
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<div className='flex items-center justify-center min-h-screen bg-emerald-500'>
|
||||
<div className='text-center p-6 '>
|
||||
<div className="flex items-center justify-center min-h-screen bg-emerald-500">
|
||||
<div className="text-center p-6 ">
|
||||
<Logo className="w-32 h-32 mx-auto mb-4" />
|
||||
<h2 className='text-2xl font-bold text-emerald-900 mb-4'>404 | Page non trouvée</h2>
|
||||
<p className='text-emerald-900 mb-4'>La ressource que vous souhaitez consulter n'existe pas ou plus.</p>
|
||||
<Link className="text-gray-900 hover:underline" href="/">Retour Accueil</Link>
|
||||
<h2 className="text-2xl font-bold text-emerald-900 mb-4">
|
||||
404 | Page non trouvée
|
||||
</h2>
|
||||
<p className="text-emerald-900 mb-4">
|
||||
La ressource que vous souhaitez consulter n'existe pas ou plus.
|
||||
</p>
|
||||
<Link className="text-gray-900 hover:underline" href="/">
|
||||
Retour Accueil
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
const AffectationClasseForm = ({ eleve = {}, onSubmit, classes }) => {
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
classeAssocie_id: eleve?.classeAssocie_id || null,
|
||||
});
|
||||
@ -17,10 +16,10 @@ const AffectationClasseForm = ({ eleve = {}, onSubmit, classes }) => {
|
||||
const handleSubmit = () => {
|
||||
onSubmit({
|
||||
eleve: {
|
||||
...formData
|
||||
...formData,
|
||||
},
|
||||
etat:5
|
||||
});
|
||||
etat: 5,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
@ -30,18 +29,21 @@ const AffectationClasseForm = ({ eleve = {}, onSubmit, classes }) => {
|
||||
Classes
|
||||
</label>
|
||||
<div className="mt-2 grid grid-cols-1 gap-4">
|
||||
{classes.map(classe => (
|
||||
{classes.map((classe) => (
|
||||
<div key={classe.id} className="flex items-center">
|
||||
<input
|
||||
type="radio"
|
||||
id={`classe-${classe.id}`}
|
||||
name="classeAssocie_id"
|
||||
value={classe.id}
|
||||
checked={formData.classeAssocie_id === classe.id}
|
||||
onChange={handleChange}
|
||||
className="form-radio h-3 w-3 text-emerald-600 focus:ring-emerald-500 hover:ring-emerald-400 checked:bg-emerald-600 checked:h-3 checked:w-3"
|
||||
type="radio"
|
||||
id={`classe-${classe.id}`}
|
||||
name="classeAssocie_id"
|
||||
value={classe.id}
|
||||
checked={formData.classeAssocie_id === classe.id}
|
||||
onChange={handleChange}
|
||||
className="form-radio h-3 w-3 text-emerald-600 focus:ring-emerald-500 hover:ring-emerald-400 checked:bg-emerald-600 checked:h-3 checked:w-3"
|
||||
/>
|
||||
<label htmlFor={`classe-${classe.id}`} className="ml-2 block text-sm text-gray-900 flex items-center">
|
||||
<label
|
||||
htmlFor={`classe-${classe.id}`}
|
||||
className="ml-2 block text-sm text-gray-900 flex items-center"
|
||||
>
|
||||
{classe.atmosphere_name}
|
||||
</label>
|
||||
</div>
|
||||
@ -50,15 +52,15 @@ const AffectationClasseForm = ({ eleve = {}, onSubmit, classes }) => {
|
||||
</div>
|
||||
<div className="flex justify-end mt-4 space-x-4">
|
||||
<button
|
||||
onClick={handleSubmit}
|
||||
className={`px-4 py-2 rounded-md shadow-sm focus:outline-none ${
|
||||
(!formData.classeAssocie_id )
|
||||
? "bg-gray-300 text-gray-700 cursor-not-allowed"
|
||||
: "bg-emerald-500 text-white hover:bg-emerald-600"
|
||||
}`}
|
||||
disabled={(!formData.classeAssocie_id)}
|
||||
onClick={handleSubmit}
|
||||
className={`px-4 py-2 rounded-md shadow-sm focus:outline-none ${
|
||||
!formData.classeAssocie_id
|
||||
? 'bg-gray-300 text-gray-700 cursor-not-allowed'
|
||||
: 'bg-emerald-500 text-white hover:bg-emerald-600'
|
||||
}`}
|
||||
disabled={!formData.classeAssocie_id}
|
||||
>
|
||||
Associer
|
||||
Associer
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@ -2,11 +2,17 @@ import React from 'react';
|
||||
|
||||
const AlertMessage = ({ title, message, buttonText, buttonLink }) => {
|
||||
return (
|
||||
<div className="alert centered bg-yellow-100 border-l-4 border-yellow-500 text-yellow-700 p-4" role="alert">
|
||||
<div
|
||||
className="alert centered bg-yellow-100 border-l-4 border-yellow-500 text-yellow-700 p-4"
|
||||
role="alert"
|
||||
>
|
||||
<h3 className="font-bold">{title}</h3>
|
||||
<p className="mt-2">{message}</p>
|
||||
<div className="alert-actions mt-4">
|
||||
<a className="btn primary bg-emerald-500 text-white rounded-md px-4 py-2 hover:bg-emerald-600" href={buttonLink}>
|
||||
<a
|
||||
className="btn primary bg-emerald-500 text-white rounded-md px-4 py-2 hover:bg-emerald-600"
|
||||
href={buttonLink}
|
||||
>
|
||||
{buttonText} <i className="icon profile-add"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@ -2,28 +2,31 @@ import React, { useState } from 'react';
|
||||
import Modal from '@/components/Modal';
|
||||
import { UserPlus } from 'lucide-react';
|
||||
|
||||
const AlertWithModal = ({ title, message, buttonText}) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const AlertWithModal = ({ title, message, buttonText }) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const openModal = () => {
|
||||
setIsOpen(true);
|
||||
};
|
||||
const openModal = () => {
|
||||
setIsOpen(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="alert centered bg-yellow-100 border-l-4 border-yellow-500 text-yellow-700 p-4" role="alert">
|
||||
<h3 className="font-bold">{title}</h3>
|
||||
<p className="mt-2">{message}</p>
|
||||
<div className="alert-actions mt-4">
|
||||
<button
|
||||
className="btn primary bg-emerald-500 text-white rounded-md px-4 py-2 hover:bg-emerald-600 flex items-center"
|
||||
onClick={openModal}
|
||||
>
|
||||
{buttonText} <UserPlus size={20} className="ml-2" />
|
||||
</button>
|
||||
</div>
|
||||
<Modal isOpen={isOpen} setIsOpen={setIsOpen} />
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div
|
||||
className="alert centered bg-yellow-100 border-l-4 border-yellow-500 text-yellow-700 p-4"
|
||||
role="alert"
|
||||
>
|
||||
<h3 className="font-bold">{title}</h3>
|
||||
<p className="mt-2">{message}</p>
|
||||
<div className="alert-actions mt-4">
|
||||
<button
|
||||
className="btn primary bg-emerald-500 text-white rounded-md px-4 py-2 hover:bg-emerald-600 flex items-center"
|
||||
onClick={openModal}
|
||||
>
|
||||
{buttonText} <UserPlus size={20} className="ml-2" />
|
||||
</button>
|
||||
</div>
|
||||
<Modal isOpen={isOpen} setIsOpen={setIsOpen} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AlertWithModal;
|
||||
export default AlertWithModal;
|
||||
|
||||
@ -1,37 +1,39 @@
|
||||
|
||||
import React, { useState } from 'react';
|
||||
|
||||
|
||||
const AlphabetPaginationNumber = ({ letter, active , onClick}) => (
|
||||
<button className={`w-8 h-8 flex items-center justify-center rounded ${
|
||||
active ? 'bg-emerald-500 text-white' : 'text-gray-600 bg-gray-200 hover:bg-gray-50'
|
||||
}`} onClick={onClick}>
|
||||
const AlphabetPaginationNumber = ({ letter, active, onClick }) => (
|
||||
<button
|
||||
className={`w-8 h-8 flex items-center justify-center rounded ${
|
||||
active
|
||||
? 'bg-emerald-500 text-white'
|
||||
: 'text-gray-600 bg-gray-200 hover:bg-gray-50'
|
||||
}`}
|
||||
onClick={onClick}
|
||||
>
|
||||
{letter}
|
||||
</button>
|
||||
);
|
||||
|
||||
|
||||
const AlphabetLinks = ({filter, onLetterClick }) => {
|
||||
const AlphabetLinks = ({ filter, onLetterClick }) => {
|
||||
const [currentLetter, setCurrentLetter] = useState(filter);
|
||||
const alphabet = "*ABCDEFGHIJKLMNOPQRSTUVWXYZ".split('');
|
||||
|
||||
const alphabet = '*ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
|
||||
|
||||
return (
|
||||
<div className="px-6 py-4 border-t border-gray-200 flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
{alphabet.map((letter) => (
|
||||
<AlphabetPaginationNumber
|
||||
key={letter}
|
||||
letter={letter}
|
||||
active={currentLetter === letter }
|
||||
onClick={() => {setCurrentLetter(letter);onLetterClick(letter)}}
|
||||
/>
|
||||
|
||||
|
||||
))}
|
||||
{alphabet.map((letter) => (
|
||||
<AlphabetPaginationNumber
|
||||
key={letter}
|
||||
letter={letter}
|
||||
active={currentLetter === letter}
|
||||
onClick={() => {
|
||||
setCurrentLetter(letter);
|
||||
onLetterClick(letter);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AlphabetLinks;
|
||||
export default AlphabetLinks;
|
||||
|
||||
@ -1,9 +1,18 @@
|
||||
import React from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
const Button = ({ text, onClick, href, className, primary, icon, disabled}) => {
|
||||
const Button = ({
|
||||
text,
|
||||
onClick,
|
||||
href,
|
||||
className,
|
||||
primary,
|
||||
icon,
|
||||
disabled,
|
||||
}) => {
|
||||
const router = useRouter();
|
||||
const baseClass = 'px-4 py-2 rounded-md text-white h-8 flex items-center justify-center';
|
||||
const baseClass =
|
||||
'px-4 py-2 rounded-md text-white h-8 flex items-center justify-center';
|
||||
const primaryClass = 'bg-emerald-500 hover:bg-emerald-600';
|
||||
const secondaryClass = 'bg-gray-300 hover:bg-gray-400 text-black';
|
||||
const buttonClass = `${baseClass} ${primary && !disabled ? primaryClass : secondaryClass} ${className}`;
|
||||
|
||||
@ -6,25 +6,43 @@ import YearView from '@/components/Calendar/YearView';
|
||||
import PlanningView from '@/components/Calendar/PlanningView';
|
||||
import ToggleView from '@/components/ToggleView';
|
||||
import { ChevronLeft, ChevronRight, Plus, ChevronDown } from 'lucide-react';
|
||||
import { format, addWeeks, addMonths, addYears, subWeeks, subMonths, subYears, getWeek, setMonth, setYear } from 'date-fns';
|
||||
import {
|
||||
format,
|
||||
addWeeks,
|
||||
addMonths,
|
||||
addYears,
|
||||
subWeeks,
|
||||
subMonths,
|
||||
subYears,
|
||||
getWeek,
|
||||
setMonth,
|
||||
setYear,
|
||||
} from 'date-fns';
|
||||
import { fr } from 'date-fns/locale';
|
||||
import { AnimatePresence, motion } from 'framer-motion'; // Ajouter cet import
|
||||
import logger from '@/utils/logger';
|
||||
|
||||
const Calendar = ({ onDateClick, onEventClick }) => {
|
||||
const { currentDate, setCurrentDate, viewType, setViewType, events, hiddenSchedules } = usePlanning();
|
||||
const {
|
||||
currentDate,
|
||||
setCurrentDate,
|
||||
viewType,
|
||||
setViewType,
|
||||
events,
|
||||
hiddenSchedules,
|
||||
} = usePlanning();
|
||||
const [visibleEvents, setVisibleEvents] = useState([]);
|
||||
const [showDatePicker, setShowDatePicker] = useState(false);
|
||||
|
||||
// Ajouter ces fonctions pour la gestion des mois et années
|
||||
const months = Array.from({ length: 12 }, (_, i) => ({
|
||||
value: i,
|
||||
label: format(new Date(2024, i, 1), 'MMMM', { locale: fr })
|
||||
label: format(new Date(2024, i, 1), 'MMMM', { locale: fr }),
|
||||
}));
|
||||
|
||||
const years = Array.from({ length: 10 }, (_, i) => ({
|
||||
value: new Date().getFullYear() - 5 + i,
|
||||
label: new Date().getFullYear() - 5 + i
|
||||
label: new Date().getFullYear() - 5 + i,
|
||||
}));
|
||||
|
||||
const handleMonthSelect = (monthIndex) => {
|
||||
@ -39,7 +57,9 @@ const Calendar = ({ onDateClick, onEventClick }) => {
|
||||
|
||||
useEffect(() => {
|
||||
// S'assurer que le filtrage est fait au niveau parent
|
||||
const filtered = events?.filter(event => !hiddenSchedules.includes(event.planning));
|
||||
const filtered = events?.filter(
|
||||
(event) => !hiddenSchedules.includes(event.planning)
|
||||
);
|
||||
setVisibleEvents(filtered);
|
||||
logger.debug('Events filtrés:', filtered); // Debug
|
||||
}, [events, hiddenSchedules]);
|
||||
@ -78,7 +98,10 @@ const Calendar = ({ onDateClick, onEventClick }) => {
|
||||
>
|
||||
Aujourd'hui
|
||||
</button>
|
||||
<button onClick={() => navigateDate('prev')} className="p-2 hover:bg-gray-100 rounded-full">
|
||||
<button
|
||||
onClick={() => navigateDate('prev')}
|
||||
className="p-2 hover:bg-gray-100 rounded-full"
|
||||
>
|
||||
<ChevronLeft className="w-5 h-5" />
|
||||
</button>
|
||||
|
||||
@ -89,7 +112,11 @@ const Calendar = ({ onDateClick, onEventClick }) => {
|
||||
className="flex items-center gap-1 px-2 py-1 hover:bg-gray-100 rounded-md"
|
||||
>
|
||||
<h2 className="text-xl font-semibold">
|
||||
{format(currentDate, viewType === 'year' ? 'yyyy' : 'MMMM yyyy', { locale: fr })}
|
||||
{format(
|
||||
currentDate,
|
||||
viewType === 'year' ? 'yyyy' : 'MMMM yyyy',
|
||||
{ locale: fr }
|
||||
)}
|
||||
</h2>
|
||||
<ChevronDown className="w-4 h-4" />
|
||||
</button>
|
||||
@ -129,7 +156,10 @@ const Calendar = ({ onDateClick, onEventClick }) => {
|
||||
)}
|
||||
</div>
|
||||
|
||||
<button onClick={() => navigateDate('next')} className="p-2 hover:bg-gray-100 rounded-full">
|
||||
<button
|
||||
onClick={() => navigateDate('next')}
|
||||
className="p-2 hover:bg-gray-100 rounded-full"
|
||||
>
|
||||
<ChevronRight className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
@ -168,7 +198,11 @@ const Calendar = ({ onDateClick, onEventClick }) => {
|
||||
transition={{ duration: 0.2 }}
|
||||
className="h-full flex flex-col"
|
||||
>
|
||||
<WeekView onDateClick={onDateClick} onEventClick={onEventClick} events={visibleEvents} />
|
||||
<WeekView
|
||||
onDateClick={onDateClick}
|
||||
onEventClick={onEventClick}
|
||||
events={visibleEvents}
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
{viewType === 'month' && (
|
||||
@ -179,7 +213,11 @@ const Calendar = ({ onDateClick, onEventClick }) => {
|
||||
exit={{ opacity: 0, y: -20 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
<MonthView onDateClick={onDateClick} onEventClick={onEventClick} events={visibleEvents} />
|
||||
<MonthView
|
||||
onDateClick={onDateClick}
|
||||
onEventClick={onEventClick}
|
||||
events={visibleEvents}
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
{viewType === 'year' && (
|
||||
@ -201,7 +239,10 @@ const Calendar = ({ onDateClick, onEventClick }) => {
|
||||
exit={{ opacity: 0, y: -20 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
<PlanningView onEventClick={onEventClick} events={visibleEvents} />
|
||||
<PlanningView
|
||||
onEventClick={onEventClick}
|
||||
events={visibleEvents}
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
@ -210,4 +251,4 @@ const Calendar = ({ onDateClick, onEventClick }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default Calendar;
|
||||
export default Calendar;
|
||||
|
||||
@ -1,6 +1,15 @@
|
||||
import React from 'react';
|
||||
import { usePlanning } from '@/context/PlanningContext';
|
||||
import { format, startOfWeek, endOfWeek, eachDayOfInterval, startOfMonth, endOfMonth, isSameMonth, isToday } from 'date-fns';
|
||||
import {
|
||||
format,
|
||||
startOfWeek,
|
||||
endOfWeek,
|
||||
eachDayOfInterval,
|
||||
startOfMonth,
|
||||
endOfMonth,
|
||||
isSameMonth,
|
||||
isToday,
|
||||
} from 'date-fns';
|
||||
import { fr } from 'date-fns/locale';
|
||||
import { getEventsForDate } from '@/utils/events';
|
||||
|
||||
@ -35,7 +44,8 @@ const MonthView = ({ onDateClick, onEventClick }) => {
|
||||
onClick={() => handleDayClick(day)}
|
||||
>
|
||||
<div className="flex justify-between items-center mb-1">
|
||||
<span className={`text-sm font-medium rounded-full w-7 h-7 flex items-center justify-center
|
||||
<span
|
||||
className={`text-sm font-medium rounded-full w-7 h-7 flex items-center justify-center
|
||||
${isCurrentDay ? 'bg-emerald-500 text-white' : ''}
|
||||
${!isCurrentMonth ? 'text-gray-400' : ''}`}
|
||||
>
|
||||
@ -50,7 +60,7 @@ const MonthView = ({ onDateClick, onEventClick }) => {
|
||||
style={{
|
||||
backgroundColor: `${event.color}15`,
|
||||
color: event.color,
|
||||
borderLeft: `2px solid ${event.color}`
|
||||
borderLeft: `2px solid ${event.color}`,
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
@ -69,8 +79,11 @@ const MonthView = ({ onDateClick, onEventClick }) => {
|
||||
<div className="h-full flex flex-col border border-gray-200 rounded-lg bg-white">
|
||||
{/* En-tête des jours de la semaine */}
|
||||
<div className="grid grid-cols-7 border-b">
|
||||
{['Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam', 'Dim'].map(day => (
|
||||
<div key={day} className="p-2 text-center text-sm font-medium text-gray-500">
|
||||
{['Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam', 'Dim'].map((day) => (
|
||||
<div
|
||||
key={day}
|
||||
className="p-2 text-center text-sm font-medium text-gray-500"
|
||||
>
|
||||
{day}
|
||||
</div>
|
||||
))}
|
||||
@ -83,4 +96,4 @@ const MonthView = ({ onDateClick, onEventClick }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default MonthView;
|
||||
export default MonthView;
|
||||
|
||||
@ -15,17 +15,20 @@ const PlanningView = ({ events, onEventClick }) => {
|
||||
|
||||
// Sinon, créer une entrée pour chaque jour
|
||||
const days = eachDayOfInterval({ start, end });
|
||||
return days.map(day => ({
|
||||
return days.map((day) => ({
|
||||
...event,
|
||||
displayDate: day,
|
||||
isMultiDay: true
|
||||
isMultiDay: true,
|
||||
}));
|
||||
};
|
||||
|
||||
// Aplatir tous les événements en incluant les événements sur plusieurs jours
|
||||
const flattenedEvents = events
|
||||
.flatMap(splitEventByDays)
|
||||
.sort((a, b) => new Date(a.displayDate || a.start) - new Date(b.displayDate || b.start));
|
||||
.sort(
|
||||
(a, b) =>
|
||||
new Date(a.displayDate || a.start) - new Date(b.displayDate || b.start)
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="bg-white h-full overflow-auto">
|
||||
@ -58,26 +61,29 @@ const PlanningView = ({ events, onEventClick }) => {
|
||||
<td className="py-3 px-4 text-sm text-gray-900 whitespace-nowrap">
|
||||
<div className="flex items-center gap-1">
|
||||
<span className="font-extrabold">{format(start, 'd')}</span>
|
||||
<span className="font-semibold">{format(start, 'MMM', { locale: fr }).toLowerCase()}</span>
|
||||
<span className="font-semibold">{format(start, 'EEE', { locale: fr })}</span>
|
||||
<span className="font-semibold">
|
||||
{format(start, 'MMM', { locale: fr }).toLowerCase()}
|
||||
</span>
|
||||
<span className="font-semibold">
|
||||
{format(start, 'EEE', { locale: fr })}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="py-3 px-4 text-sm text-gray-900 whitespace-nowrap">
|
||||
<div className="flex items-center">
|
||||
<div
|
||||
<div className="flex items-center">
|
||||
<div
|
||||
className="w-2 h-2 rounded-full mr-2"
|
||||
style={{ backgroundColor: event.color }}
|
||||
/>
|
||||
{isMultiDay
|
||||
? (isSameDay(start, new Date(event.start))
|
||||
? "À partir de "
|
||||
: isSameDay(start, end)
|
||||
? "Jusqu'à "
|
||||
: "Toute la journée")
|
||||
: ""
|
||||
}
|
||||
{format(new Date(event.start), 'HH:mm')}
|
||||
{!isMultiDay && ` - ${format(end, 'HH:mm')}`}
|
||||
{isMultiDay
|
||||
? isSameDay(start, new Date(event.start))
|
||||
? 'À partir de '
|
||||
: isSameDay(start, end)
|
||||
? "Jusqu'à "
|
||||
: 'Toute la journée'
|
||||
: ''}
|
||||
{format(new Date(event.start), 'HH:mm')}
|
||||
{!isMultiDay && ` - ${format(end, 'HH:mm')}`}
|
||||
</div>
|
||||
</td>
|
||||
<td className="py-3 px-4">
|
||||
@ -108,4 +114,4 @@ const PlanningView = ({ events, onEventClick }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default PlanningView;
|
||||
export default PlanningView;
|
||||
|
||||
@ -1,6 +1,12 @@
|
||||
import React, { useEffect, useState, useRef } from 'react';
|
||||
import { usePlanning } from '@/context/PlanningContext';
|
||||
import { format, startOfWeek, addDays, differenceInMinutes, isSameDay } from 'date-fns';
|
||||
import {
|
||||
format,
|
||||
startOfWeek,
|
||||
addDays,
|
||||
differenceInMinutes,
|
||||
isSameDay,
|
||||
} from 'date-fns';
|
||||
import { fr } from 'date-fns/locale';
|
||||
import { getWeekEvents } from '@/utils/events';
|
||||
import { isToday } from 'date-fns';
|
||||
@ -16,7 +22,7 @@ const WeekView = ({ onDateClick, onEventClick, events }) => {
|
||||
const weekDays = Array.from({ length: 7 }, (_, i) => addDays(weekStart, i));
|
||||
|
||||
// Maintenant on peut utiliser weekDays
|
||||
const isCurrentWeek = weekDays.some(day => isSameDay(day, new Date()));
|
||||
const isCurrentWeek = weekDays.some((day) => isSameDay(day, new Date()));
|
||||
|
||||
// Mettre à jour la position de la ligne toutes les minutes
|
||||
useEffect(() => {
|
||||
@ -61,7 +67,7 @@ const WeekView = ({ onDateClick, onEventClick, events }) => {
|
||||
const eventStart = new Date(event.start);
|
||||
const eventEnd = new Date(event.end);
|
||||
|
||||
return dayEvents.filter(otherEvent => {
|
||||
return dayEvents.filter((otherEvent) => {
|
||||
if (otherEvent.id === event.id) return false;
|
||||
const otherStart = new Date(otherEvent.start);
|
||||
const otherEnd = new Date(otherEvent.end);
|
||||
@ -77,7 +83,7 @@ const WeekView = ({ onDateClick, onEventClick, events }) => {
|
||||
|
||||
// Trouver les événements qui se chevauchent
|
||||
const overlappingEvents = findOverlappingEvents(event, dayEvents);
|
||||
const eventIndex = overlappingEvents.findIndex(e => e.id > event.id) + 1;
|
||||
const eventIndex = overlappingEvents.findIndex((e) => e.id > event.id) + 1;
|
||||
const totalOverlapping = overlappingEvents.length + 1;
|
||||
|
||||
// Calculer la largeur et la position horizontale
|
||||
@ -93,7 +99,7 @@ const WeekView = ({ onDateClick, onEventClick, events }) => {
|
||||
borderLeft: `3px solid ${event.color}`,
|
||||
borderRadius: '0.25rem',
|
||||
zIndex: 1,
|
||||
transform: `translateY(${startMinutes}rem)`
|
||||
transform: `translateY(${startMinutes}rem)`,
|
||||
};
|
||||
};
|
||||
|
||||
@ -111,14 +117,24 @@ const WeekView = ({ onDateClick, onEventClick, events }) => {
|
||||
}}
|
||||
>
|
||||
<div className="p-1">
|
||||
<div className="font-semibold text-xs truncate" style={{ color: event.color }}>
|
||||
<div
|
||||
className="font-semibold text-xs truncate"
|
||||
style={{ color: event.color }}
|
||||
>
|
||||
{event.title}
|
||||
</div>
|
||||
<div className="text-xs" style={{ color: event.color, opacity: 0.75 }}>
|
||||
{format(new Date(event.start), 'HH:mm')} - {format(new Date(event.end), 'HH:mm')}
|
||||
<div
|
||||
className="text-xs"
|
||||
style={{ color: event.color, opacity: 0.75 }}
|
||||
>
|
||||
{format(new Date(event.start), 'HH:mm')} -{' '}
|
||||
{format(new Date(event.end), 'HH:mm')}
|
||||
</div>
|
||||
{event.location && (
|
||||
<div className="text-xs truncate" style={{ color: event.color, opacity: 0.75 }}>
|
||||
<div
|
||||
className="text-xs truncate"
|
||||
style={{ color: event.color, opacity: 0.75 }}
|
||||
>
|
||||
{event.location}
|
||||
</div>
|
||||
)}
|
||||
@ -130,7 +146,10 @@ const WeekView = ({ onDateClick, onEventClick, events }) => {
|
||||
return (
|
||||
<div className="flex flex-col h-full overflow-hidden">
|
||||
{/* En-tête des jours */}
|
||||
<div className="grid gap-[1px] bg-gray-100 pr-[17px]" style={{ gridTemplateColumns: "2.5rem repeat(7, 1fr)" }}>
|
||||
<div
|
||||
className="grid gap-[1px] bg-gray-100 pr-[17px]"
|
||||
style={{ gridTemplateColumns: '2.5rem repeat(7, 1fr)' }}
|
||||
>
|
||||
<div className="bg-white h-14"></div>
|
||||
{weekDays.map((day) => (
|
||||
<div
|
||||
@ -142,8 +161,10 @@ const WeekView = ({ onDateClick, onEventClick, events }) => {
|
||||
<div className="text-xs font-medium text-gray-500">
|
||||
{format(day, 'EEEE', { locale: fr })}
|
||||
</div>
|
||||
<div className={`text-sm font-semibold inline-block rounded-full w-7 h-7 leading-7
|
||||
${isToday(day) ? 'bg-emerald-500 text-white' : ''}`}>
|
||||
<div
|
||||
className={`text-sm font-semibold inline-block rounded-full w-7 h-7 leading-7
|
||||
${isToday(day) ? 'bg-emerald-500 text-white' : ''}`}
|
||||
>
|
||||
{format(day, 'd', { locale: fr })}
|
||||
</div>
|
||||
</div>
|
||||
@ -158,17 +179,17 @@ const WeekView = ({ onDateClick, onEventClick, events }) => {
|
||||
className="absolute left-0 right-0 z-10 border-emerald-500 border pointer-events-none"
|
||||
style={{
|
||||
top: getCurrentTimePosition(),
|
||||
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="absolute -left-2 -top-1 w-2 h-2 rounded-full bg-emerald-500"
|
||||
/>
|
||||
<div className="absolute -left-2 -top-1 w-2 h-2 rounded-full bg-emerald-500" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid gap-[1px] bg-gray-100" style={{ gridTemplateColumns: "2.5rem repeat(7, 1fr)" }}>
|
||||
{timeSlots.map(hour => (
|
||||
<div
|
||||
className="grid gap-[1px] bg-gray-100"
|
||||
style={{ gridTemplateColumns: '2.5rem repeat(7, 1fr)' }}
|
||||
>
|
||||
{timeSlots.map((hour) => (
|
||||
<React.Fragment key={hour}>
|
||||
<div className="h-20 p-1 text-right text-sm text-gray-500 bg-gray-100 font-medium">
|
||||
{`${hour.toString().padStart(2, '0')}:00`}
|
||||
@ -188,11 +209,15 @@ const WeekView = ({ onDateClick, onEventClick, events }) => {
|
||||
onDateClick(date);
|
||||
}}
|
||||
>
|
||||
<div className="flex gap-1"> {/* Ajout de gap-1 */}
|
||||
{dayEvents.filter(event => {
|
||||
const eventStart = new Date(event.start);
|
||||
return eventStart.getHours() === hour;
|
||||
}).map(event => renderEventInCell(event, dayEvents))}
|
||||
<div className="flex gap-1">
|
||||
{' '}
|
||||
{/* Ajout de gap-1 */}
|
||||
{dayEvents
|
||||
.filter((event) => {
|
||||
const eventStart = new Date(event.start);
|
||||
return eventStart.getHours() === hour;
|
||||
})
|
||||
.map((event) => renderEventInCell(event, dayEvents))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@ -205,4 +230,4 @@ const WeekView = ({ onDateClick, onEventClick, events }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default WeekView;
|
||||
export default WeekView;
|
||||
|
||||
@ -37,7 +37,7 @@ const YearView = ({ onDateClick }) => {
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-4 gap-4 p-4">
|
||||
{months.map(month => (
|
||||
{months.map((month) => (
|
||||
<MonthCard
|
||||
key={month.getTime()}
|
||||
month={month}
|
||||
@ -49,4 +49,4 @@ const YearView = ({ onDateClick }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default YearView;
|
||||
export default YearView;
|
||||
|
||||
@ -1,13 +1,24 @@
|
||||
import React from 'react';
|
||||
|
||||
const CheckBox = ({ item, formData, handleChange, fieldName, itemLabelFunc = () => null, labelAttenuated = () => false, horizontal }) => {
|
||||
const CheckBox = ({
|
||||
item,
|
||||
formData,
|
||||
handleChange,
|
||||
fieldName,
|
||||
itemLabelFunc = () => null,
|
||||
labelAttenuated = () => false,
|
||||
horizontal,
|
||||
}) => {
|
||||
const isChecked = formData[fieldName].includes(parseInt(item.id));
|
||||
const isAttenuated = labelAttenuated(item) && !isChecked;
|
||||
return (
|
||||
<div key={item.id} className={`flex ${horizontal ? 'flex-col items-center' : 'flex-row items-center'}`}>
|
||||
<div
|
||||
key={item.id}
|
||||
className={`flex ${horizontal ? 'flex-col items-center' : 'flex-row items-center'}`}
|
||||
>
|
||||
{horizontal && (
|
||||
<label
|
||||
htmlFor={`${fieldName}-${item.id}`}
|
||||
<label
|
||||
htmlFor={`${fieldName}-${item.id}`}
|
||||
className={`block text-sm text-center mb-1 ${isAttenuated ? 'text-gray-300' : 'font-bold text-emerald-600'}`}
|
||||
>
|
||||
{itemLabelFunc(item)}
|
||||
@ -24,8 +35,8 @@ const CheckBox = ({ item, formData, handleChange, fieldName, itemLabelFunc = ()
|
||||
style={{ borderRadius: '6px', outline: 'none', boxShadow: 'none' }}
|
||||
/>
|
||||
{!horizontal && (
|
||||
<label
|
||||
htmlFor={`${fieldName}-${item.id}`}
|
||||
<label
|
||||
htmlFor={`${fieldName}-${item.id}`}
|
||||
className={`block text-sm ${isAttenuated ? 'text-gray-300' : 'font-bold text-emerald-600'}`}
|
||||
>
|
||||
{itemLabelFunc(item)}
|
||||
@ -35,4 +46,4 @@ const CheckBox = ({ item, formData, handleChange, fieldName, itemLabelFunc = ()
|
||||
);
|
||||
};
|
||||
|
||||
export default CheckBox;
|
||||
export default CheckBox;
|
||||
|
||||
@ -11,7 +11,7 @@ const CheckBoxList = ({
|
||||
className,
|
||||
itemLabelFunc = (item) => item.name,
|
||||
labelAttenuated = () => false,
|
||||
horizontal = false // Ajouter l'option horizontal
|
||||
horizontal = false, // Ajouter l'option horizontal
|
||||
}) => {
|
||||
return (
|
||||
<div className={`mb-4 w-full ${className}`}>
|
||||
@ -19,8 +19,10 @@ const CheckBoxList = ({
|
||||
{Icon && <Icon className="w-5 h-5 mr-2" />}
|
||||
{label}
|
||||
</label>
|
||||
<div className={`mt-2 grid ${horizontal ? 'grid-cols-6 gap-2' : 'grid-cols-1 gap-4'}`}>
|
||||
{items.map(item => (
|
||||
<div
|
||||
className={`mt-2 grid ${horizontal ? 'grid-cols-6 gap-2' : 'grid-cols-1 gap-4'}`}
|
||||
>
|
||||
{items.map((item) => (
|
||||
<CheckBox
|
||||
key={`${fieldName}-${item.id}`}
|
||||
item={item}
|
||||
|
||||
@ -5,7 +5,7 @@ import { GraduationCap } from 'lucide-react';
|
||||
const ClasseDetails = ({ classe }) => {
|
||||
if (!classe) return null;
|
||||
|
||||
const nombreElevesInscrits = classe?.eleves?.length||0;
|
||||
const nombreElevesInscrits = classe?.eleves?.length || 0;
|
||||
const capaciteTotale = classe.number_of_students;
|
||||
const pourcentage = Math.round((nombreElevesInscrits / capaciteTotale) * 100);
|
||||
|
||||
@ -42,14 +42,12 @@ const ClasseDetails = ({ classe }) => {
|
||||
{nombreElevesInscrits}/{capaciteTotale}
|
||||
</span>
|
||||
<div className="w-32 bg-gray-200 rounded-full h-6 shadow-inner">
|
||||
<div
|
||||
<div
|
||||
className={`h-full rounded-full ${getColor(pourcentage)}`}
|
||||
style={{ width: `${pourcentage}%` }}
|
||||
></div>
|
||||
</div>
|
||||
<span className="ml-4 font-bold text-gray-700">
|
||||
{pourcentage}%
|
||||
</span>
|
||||
<span className="ml-4 font-bold text-gray-700">{pourcentage}%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -60,7 +58,7 @@ const ClasseDetails = ({ classe }) => {
|
||||
columns={[
|
||||
{ name: 'NOM', transform: (row) => row.name },
|
||||
{ name: 'PRENOM', transform: (row) => row.first_name },
|
||||
{ name: 'AGE', transform: (row) => `${row.age}` }
|
||||
{ name: 'AGE', transform: (row) => `${row.age}` },
|
||||
]}
|
||||
data={classe.students}
|
||||
/>
|
||||
|
||||
@ -2,8 +2,8 @@ import React from 'react';
|
||||
|
||||
const LevelLabel = ({ label, index }) => {
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
<div
|
||||
key={index}
|
||||
className={`ml-2 px-3 py-1 rounded-md shadow-sm ${
|
||||
index % 2 === 0 ? 'bg-white' : 'bg-gray-100'
|
||||
} border border-gray-200 text-gray-700`}
|
||||
|
||||
@ -2,13 +2,15 @@ import React from 'react';
|
||||
|
||||
const TeacherLabel = ({ nom, prenom, index }) => {
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
<div
|
||||
key={index}
|
||||
className={`ml-2 px-3 py-1 rounded-md shadow-sm ${
|
||||
index % 2 === 0 ? 'bg-white' : 'bg-gray-100'
|
||||
} border border-gray-200 text-gray-700`}
|
||||
>
|
||||
<span className="font-bold">{nom} {prenom}</span>
|
||||
<span className="font-bold">
|
||||
{nom} {prenom}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -2,9 +2,17 @@ import React, { useState, useEffect } from 'react';
|
||||
import { Check } from 'lucide-react';
|
||||
import Popup from '@/components/Popup';
|
||||
|
||||
const DateTab = ({ dates, activeTab, handleDateChange, handleEdit, type, paymentPlanId, resetModifiedDates }) => {
|
||||
const DateTab = ({
|
||||
dates,
|
||||
activeTab,
|
||||
handleDateChange,
|
||||
handleEdit,
|
||||
type,
|
||||
paymentPlanId,
|
||||
resetModifiedDates,
|
||||
}) => {
|
||||
const [popupVisible, setPopupVisible] = useState(false);
|
||||
const [popupMessage, setPopupMessage] = useState("");
|
||||
const [popupMessage, setPopupMessage] = useState('');
|
||||
const [modifiedDates, setModifiedDates] = useState({});
|
||||
|
||||
useEffect(() => {
|
||||
@ -16,22 +24,24 @@ const DateTab = ({ dates, activeTab, handleDateChange, handleEdit, type, payment
|
||||
const submit = (updatedData) => {
|
||||
const dataWithType = {
|
||||
...updatedData,
|
||||
type: type
|
||||
type: type,
|
||||
};
|
||||
handleEdit(paymentPlanId, dataWithType)
|
||||
.then(() => {
|
||||
setPopupMessage(`Mise à jour de la date d'échéance effectuée avec succès`);
|
||||
setPopupMessage(
|
||||
`Mise à jour de la date d'échéance effectuée avec succès`
|
||||
);
|
||||
setPopupVisible(true);
|
||||
setModifiedDates({});
|
||||
})
|
||||
.catch(error => {
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
});
|
||||
};
|
||||
|
||||
const handleDateChangeWithModification = (tab, index, value) => {
|
||||
handleDateChange(tab, index, value);
|
||||
setModifiedDates(prev => ({ ...prev, [`${tab}-${index}`]: true }));
|
||||
setModifiedDates((prev) => ({ ...prev, [`${tab}-${index}`]: true }));
|
||||
};
|
||||
|
||||
return (
|
||||
@ -39,17 +49,30 @@ const DateTab = ({ dates, activeTab, handleDateChange, handleEdit, type, payment
|
||||
<div className="flex flex-col space-y-3">
|
||||
{dates[activeTab]?.map((date, index) => (
|
||||
<div key={index} className="flex items-center space-x-3">
|
||||
<span className="text-emerald-700 font-semibold">Échéance {index + 1}</span>
|
||||
<span className="text-emerald-700 font-semibold">
|
||||
Échéance {index + 1}
|
||||
</span>
|
||||
<input
|
||||
type="date"
|
||||
value={date}
|
||||
onChange={(e) => handleDateChangeWithModification(activeTab, index, e.target.value)}
|
||||
onChange={(e) =>
|
||||
handleDateChangeWithModification(
|
||||
activeTab,
|
||||
index,
|
||||
e.target.value
|
||||
)
|
||||
}
|
||||
className="p-2 border border-emerald-300 rounded focus:outline-none focus:ring-2 focus:ring-emerald-500 cursor-pointer"
|
||||
/>
|
||||
{modifiedDates[`${activeTab}-${index}`] && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => submit({ frequency: dates[activeTab].length, due_dates: dates[activeTab] })}
|
||||
onClick={() =>
|
||||
submit({
|
||||
frequency: dates[activeTab].length,
|
||||
due_dates: dates[activeTab],
|
||||
})
|
||||
}
|
||||
className="text-emerald-500 hover:text-emerald-800"
|
||||
>
|
||||
<Check className="w-5 h-5" />
|
||||
@ -71,4 +94,4 @@ const DateTab = ({ dates, activeTab, handleDateChange, handleEdit, type, payment
|
||||
);
|
||||
};
|
||||
|
||||
export default DateTab;
|
||||
export default DateTab;
|
||||
|
||||
@ -2,15 +2,15 @@ import React, { useEffect } from 'react';
|
||||
import { useCookies } from 'react-cookie';
|
||||
|
||||
export default function DjangoCSRFToken({ csrfToken }) {
|
||||
const [cookies, setCookie] = useCookies(['csrftoken']);
|
||||
const [cookies, setCookie] = useCookies(['csrftoken']);
|
||||
|
||||
useEffect(() => {
|
||||
if (csrfToken && csrfToken !== cookies.csrftoken) {
|
||||
setCookie('csrftoken', csrfToken, { path: '/' });
|
||||
}
|
||||
}, [csrfToken, cookies.csrftoken, setCookie]);
|
||||
useEffect(() => {
|
||||
if (csrfToken && csrfToken !== cookies.csrftoken) {
|
||||
setCookie('csrftoken', csrfToken, { path: '/' });
|
||||
}
|
||||
}, [csrfToken, cookies.csrftoken, setCookie]);
|
||||
|
||||
return (
|
||||
<input type="hidden" value={cookies.csrftoken} name="csrfmiddlewaretoken" />
|
||||
);
|
||||
return (
|
||||
<input type="hidden" value={cookies.csrftoken} name="csrfmiddlewaretoken" />
|
||||
);
|
||||
}
|
||||
|
||||
@ -14,4 +14,4 @@ const DocusealBuilder = ({ onSave, onSend, ...props }) => {
|
||||
return <OriginalDocusealBuilder {...props} />;
|
||||
};
|
||||
|
||||
export default DocusealBuilder;
|
||||
export default DocusealBuilder;
|
||||
|
||||
@ -4,7 +4,6 @@ import { Upload } from 'lucide-react';
|
||||
export default function DraggableFileUpload({ fileName, onFileSelect }) {
|
||||
const [dragActive, setDragActive] = useState(false);
|
||||
|
||||
|
||||
const handleDragOver = (event) => {
|
||||
event.preventDefault();
|
||||
setDragActive(true);
|
||||
@ -39,12 +38,23 @@ export default function DraggableFileUpload({ fileName, onFileSelect }) {
|
||||
className={`border-2 border-dashed p-8 rounded-md ${dragActive ? 'border-blue-500' : 'border-gray-300'} flex flex-col items-center justify-center`}
|
||||
style={{ height: '200px' }}
|
||||
>
|
||||
<input type="file" onChange={handleFileChange} className="hidden" id="fileInput" />
|
||||
<label htmlFor="fileInput" className="cursor-pointer flex flex-col items-center">
|
||||
<input
|
||||
type="file"
|
||||
onChange={handleFileChange}
|
||||
className="hidden"
|
||||
id="fileInput"
|
||||
/>
|
||||
<label
|
||||
htmlFor="fileInput"
|
||||
className="cursor-pointer flex flex-col items-center"
|
||||
>
|
||||
<Upload size={48} className="text-gray-400 mb-2" />
|
||||
<p className="text-center">{fileName || 'Glissez et déposez un fichier ici ou cliquez ici pour sélectionner un fichier'}</p>
|
||||
<p className="text-center">
|
||||
{fileName ||
|
||||
'Glissez et déposez un fichier ici ou cliquez ici pour sélectionner un fichier'}
|
||||
</p>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,13 +2,23 @@
|
||||
import { useRouter } from 'next/navigation';
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
|
||||
const DropdownMenu = ({ buttonContent, items, buttonClassName, menuClassName, dropdownOpen: propDropdownOpen, setDropdownOpen: propSetDropdownOpen }) => {
|
||||
const DropdownMenu = ({
|
||||
buttonContent,
|
||||
items,
|
||||
buttonClassName,
|
||||
menuClassName,
|
||||
dropdownOpen: propDropdownOpen,
|
||||
setDropdownOpen: propSetDropdownOpen,
|
||||
}) => {
|
||||
const [dropdownOpen, setDropdownOpen] = useState(false);
|
||||
const menuRef = useRef(null);
|
||||
const router = useRouter();
|
||||
const isControlled = propDropdownOpen !== undefined && propSetDropdownOpen !== undefined;
|
||||
const isControlled =
|
||||
propDropdownOpen !== undefined && propSetDropdownOpen !== undefined;
|
||||
const actualDropdownOpen = isControlled ? propDropdownOpen : dropdownOpen;
|
||||
const actualSetDropdownOpen = isControlled ? propSetDropdownOpen : setDropdownOpen;
|
||||
const actualSetDropdownOpen = isControlled
|
||||
? propSetDropdownOpen
|
||||
: setDropdownOpen;
|
||||
|
||||
const handleClickOutside = (event) => {
|
||||
if (menuRef.current && !menuRef.current.contains(event.target)) {
|
||||
@ -52,7 +62,10 @@ const DropdownMenu = ({ buttonContent, items, buttonClassName, menuClassName, dr
|
||||
|
||||
return (
|
||||
<div className="relative" ref={menuRef}>
|
||||
<button className={buttonClassName} onClick={() => actualSetDropdownOpen(!actualDropdownOpen)}>
|
||||
<button
|
||||
className={buttonClassName}
|
||||
onClick={() => actualSetDropdownOpen(!actualDropdownOpen)}
|
||||
>
|
||||
{buttonContent}
|
||||
</button>
|
||||
{actualDropdownOpen && (
|
||||
@ -64,4 +77,4 @@ const DropdownMenu = ({ buttonContent, items, buttonClassName, menuClassName, dr
|
||||
);
|
||||
};
|
||||
|
||||
export default DropdownMenu;
|
||||
export default DropdownMenu;
|
||||
|
||||
@ -2,16 +2,22 @@ import { usePlanning } from '@/context/PlanningContext';
|
||||
import { format } from 'date-fns';
|
||||
import React from 'react';
|
||||
|
||||
export default function EventModal({ isOpen, onClose, eventData, setEventData }) {
|
||||
const { addEvent, handleUpdateEvent, handleDeleteEvent, schedules } = usePlanning();
|
||||
export default function EventModal({
|
||||
isOpen,
|
||||
onClose,
|
||||
eventData,
|
||||
setEventData,
|
||||
}) {
|
||||
const { addEvent, handleUpdateEvent, handleDeleteEvent, schedules } =
|
||||
usePlanning();
|
||||
|
||||
// S'assurer que planning est défini lors du premier rendu
|
||||
React.useEffect(() => {
|
||||
if (!eventData.planning && schedules.length > 0) {
|
||||
setEventData(prev => ({
|
||||
setEventData((prev) => ({
|
||||
...prev,
|
||||
planning: schedules[0].id,
|
||||
color: schedules[0].color
|
||||
color: schedules[0].color,
|
||||
}));
|
||||
}
|
||||
}, [schedules, eventData.planning]);
|
||||
@ -23,7 +29,7 @@ export default function EventModal({ isOpen, onClose, eventData, setEventData })
|
||||
{ value: 'daily', label: 'Quotidienne' },
|
||||
{ value: 'weekly', label: 'Hebdomadaire' },
|
||||
{ value: 'monthly', label: 'Mensuelle' },
|
||||
{ value: 'custom', label: 'Personnalisée' } // Nouvelle option
|
||||
{ value: 'custom', label: 'Personnalisée' }, // Nouvelle option
|
||||
];
|
||||
|
||||
const daysOfWeek = [
|
||||
@ -33,7 +39,7 @@ export default function EventModal({ isOpen, onClose, eventData, setEventData })
|
||||
{ value: 4, label: 'Jeu' },
|
||||
{ value: 5, label: 'Ven' },
|
||||
{ value: 6, label: 'Sam' },
|
||||
{ value: 0, label: 'Dim' }
|
||||
{ value: 0, label: 'Dim' },
|
||||
];
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
@ -44,27 +50,30 @@ export default function EventModal({ isOpen, onClose, eventData, setEventData })
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedSchedule = schedules.find(s => s.id === eventData.planning);
|
||||
const selectedSchedule = schedules.find((s) => s.id === eventData.planning);
|
||||
|
||||
if (eventData.id) {
|
||||
handleUpdateEvent(eventData.id, {
|
||||
...eventData,
|
||||
planning: eventData.planning, // S'assurer que planning est bien défini
|
||||
color: eventData.color || selectedSchedule?.color
|
||||
color: eventData.color || selectedSchedule?.color,
|
||||
});
|
||||
} else {
|
||||
addEvent({
|
||||
...eventData,
|
||||
id: `event-${Date.now()}`,
|
||||
planning: eventData.planning, // S'assurer que planning est bien défini
|
||||
color: eventData.color || selectedSchedule?.color
|
||||
color: eventData.color || selectedSchedule?.color,
|
||||
});
|
||||
}
|
||||
onClose();
|
||||
};
|
||||
|
||||
const handleDelete = () => {
|
||||
if (eventData.id && confirm('Êtes-vous sûr de vouloir supprimer cet événement ?')) {
|
||||
if (
|
||||
eventData.id &&
|
||||
confirm('Êtes-vous sûr de vouloir supprimer cet événement ?')
|
||||
) {
|
||||
handleDeleteEvent(eventData.id);
|
||||
onClose();
|
||||
}
|
||||
@ -74,7 +83,7 @@ export default function EventModal({ isOpen, onClose, eventData, setEventData })
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||
<div className="bg-white p-6 rounded-lg w-full max-w-md">
|
||||
<h2 className="text-xl font-semibold mb-4">
|
||||
{eventData.id ? 'Modifier l\'événement' : 'Nouvel événement'}
|
||||
{eventData.id ? "Modifier l'événement" : 'Nouvel événement'}
|
||||
</h2>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
@ -86,7 +95,9 @@ export default function EventModal({ isOpen, onClose, eventData, setEventData })
|
||||
<input
|
||||
type="text"
|
||||
value={eventData.title || ''}
|
||||
onChange={(e) => setEventData({ ...eventData, title: e.target.value })}
|
||||
onChange={(e) =>
|
||||
setEventData({ ...eventData, title: e.target.value })
|
||||
}
|
||||
className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-emerald-500"
|
||||
required
|
||||
/>
|
||||
@ -99,7 +110,9 @@ export default function EventModal({ isOpen, onClose, eventData, setEventData })
|
||||
</label>
|
||||
<textarea
|
||||
value={eventData.description || ''}
|
||||
onChange={(e) => setEventData({ ...eventData, description: e.target.value })}
|
||||
onChange={(e) =>
|
||||
setEventData({ ...eventData, description: e.target.value })
|
||||
}
|
||||
className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-emerald-500"
|
||||
rows="3"
|
||||
/>
|
||||
@ -113,17 +126,19 @@ export default function EventModal({ isOpen, onClose, eventData, setEventData })
|
||||
<select
|
||||
value={eventData.planning || schedules[0]?.id}
|
||||
onChange={(e) => {
|
||||
const selectedSchedule = schedules.find(s => s.id === e.target.value);
|
||||
const selectedSchedule = schedules.find(
|
||||
(s) => s.id === e.target.value
|
||||
);
|
||||
setEventData({
|
||||
...eventData,
|
||||
planning: e.target.value,
|
||||
color: selectedSchedule?.color || '#10b981'
|
||||
color: selectedSchedule?.color || '#10b981',
|
||||
});
|
||||
}}
|
||||
className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-emerald-500"
|
||||
required
|
||||
>
|
||||
{schedules.map(schedule => (
|
||||
{schedules.map((schedule) => (
|
||||
<option key={schedule.id} value={schedule.id}>
|
||||
{schedule.name}
|
||||
</option>
|
||||
@ -138,8 +153,14 @@ export default function EventModal({ isOpen, onClose, eventData, setEventData })
|
||||
</label>
|
||||
<input
|
||||
type="color"
|
||||
value={eventData.color || schedules.find(s => s.id === eventData.planning)?.color || '#10b981'}
|
||||
onChange={(e) => setEventData({ ...eventData, color: e.target.value })}
|
||||
value={
|
||||
eventData.color ||
|
||||
schedules.find((s) => s.id === eventData.planning)?.color ||
|
||||
'#10b981'
|
||||
}
|
||||
onChange={(e) =>
|
||||
setEventData({ ...eventData, color: e.target.value })
|
||||
}
|
||||
className="w-full h-10 p-1 rounded border"
|
||||
/>
|
||||
</div>
|
||||
@ -151,10 +172,12 @@ export default function EventModal({ isOpen, onClose, eventData, setEventData })
|
||||
</label>
|
||||
<select
|
||||
value={eventData.recurrence || 'none'}
|
||||
onChange={(e) => setEventData({ ...eventData, recurrence: e.target.value })}
|
||||
onChange={(e) =>
|
||||
setEventData({ ...eventData, recurrence: e.target.value })
|
||||
}
|
||||
className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-emerald-500"
|
||||
>
|
||||
{recurrenceOptions.map(option => (
|
||||
{recurrenceOptions.map((option) => (
|
||||
<option key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</option>
|
||||
@ -174,18 +197,22 @@ export default function EventModal({ isOpen, onClose, eventData, setEventData })
|
||||
type="number"
|
||||
min="1"
|
||||
value={eventData.customInterval || 1}
|
||||
onChange={(e) => setEventData({
|
||||
...eventData,
|
||||
customInterval: parseInt(e.target.value) || 1
|
||||
})}
|
||||
onChange={(e) =>
|
||||
setEventData({
|
||||
...eventData,
|
||||
customInterval: parseInt(e.target.value) || 1,
|
||||
})
|
||||
}
|
||||
className="w-20 p-2 border rounded focus:outline-none focus:ring-2 focus:ring-emerald-500"
|
||||
/>
|
||||
<select
|
||||
value={eventData.customUnit || 'days'}
|
||||
onChange={(e) => setEventData({
|
||||
...eventData,
|
||||
customUnit: e.target.value
|
||||
})}
|
||||
onChange={(e) =>
|
||||
setEventData({
|
||||
...eventData,
|
||||
customUnit: e.target.value,
|
||||
})
|
||||
}
|
||||
className="flex-1 p-2 border rounded focus:outline-none focus:ring-2 focus:ring-emerald-500"
|
||||
>
|
||||
<option value="days">Jours</option>
|
||||
@ -204,14 +231,14 @@ export default function EventModal({ isOpen, onClose, eventData, setEventData })
|
||||
Jours de répétition
|
||||
</label>
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
{daysOfWeek.map(day => (
|
||||
{daysOfWeek.map((day) => (
|
||||
<button
|
||||
key={day.value}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
const days = eventData.selectedDays || [];
|
||||
const newDays = days.includes(day.value)
|
||||
? days.filter(d => d !== day.value)
|
||||
? days.filter((d) => d !== day.value)
|
||||
: [...days, day.value];
|
||||
setEventData({ ...eventData, selectedDays: newDays });
|
||||
}}
|
||||
@ -237,7 +264,9 @@ export default function EventModal({ isOpen, onClose, eventData, setEventData })
|
||||
<input
|
||||
type="date"
|
||||
value={eventData.recurrenceEnd || ''}
|
||||
onChange={(e) => setEventData({ ...eventData, recurrenceEnd: e.target.value })}
|
||||
onChange={(e) =>
|
||||
setEventData({ ...eventData, recurrenceEnd: e.target.value })
|
||||
}
|
||||
className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-emerald-500"
|
||||
/>
|
||||
</div>
|
||||
@ -252,7 +281,12 @@ export default function EventModal({ isOpen, onClose, eventData, setEventData })
|
||||
<input
|
||||
type="datetime-local"
|
||||
value={format(new Date(eventData.start), "yyyy-MM-dd'T'HH:mm")}
|
||||
onChange={(e) => setEventData({ ...eventData, start: new Date(e.target.value).toISOString() })}
|
||||
onChange={(e) =>
|
||||
setEventData({
|
||||
...eventData,
|
||||
start: new Date(e.target.value).toISOString(),
|
||||
})
|
||||
}
|
||||
className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-emerald-500"
|
||||
required
|
||||
/>
|
||||
@ -264,7 +298,12 @@ export default function EventModal({ isOpen, onClose, eventData, setEventData })
|
||||
<input
|
||||
type="datetime-local"
|
||||
value={format(new Date(eventData.end), "yyyy-MM-dd'T'HH:mm")}
|
||||
onChange={(e) => setEventData({ ...eventData, end: new Date(e.target.value).toISOString() })}
|
||||
onChange={(e) =>
|
||||
setEventData({
|
||||
...eventData,
|
||||
end: new Date(e.target.value).toISOString(),
|
||||
})
|
||||
}
|
||||
className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-emerald-500"
|
||||
required
|
||||
/>
|
||||
@ -279,7 +318,9 @@ export default function EventModal({ isOpen, onClose, eventData, setEventData })
|
||||
<input
|
||||
type="text"
|
||||
value={eventData.location || ''}
|
||||
onChange={(e) => setEventData({ ...eventData, location: e.target.value })}
|
||||
onChange={(e) =>
|
||||
setEventData({ ...eventData, location: e.target.value })
|
||||
}
|
||||
className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-emerald-500"
|
||||
/>
|
||||
</div>
|
||||
@ -317,4 +358,4 @@ export default function EventModal({ isOpen, onClose, eventData, setEventData })
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,14 +8,14 @@ const FileStatusLabel = ({ status }) => {
|
||||
return {
|
||||
label: 'En attente',
|
||||
className: 'bg-green-50 text-green-600',
|
||||
icon: <Check size={16} className="text-green-600" />
|
||||
icon: <Check size={16} className="text-green-600" />,
|
||||
};
|
||||
case 'pending':
|
||||
default:
|
||||
return {
|
||||
label: 'En attente',
|
||||
className: 'bg-orange-50 text-orange-600',
|
||||
icon: <Clock size={16} className="text-orange-600" />
|
||||
icon: <Clock size={16} className="text-orange-600" />,
|
||||
};
|
||||
}
|
||||
};
|
||||
@ -23,7 +23,9 @@ const FileStatusLabel = ({ status }) => {
|
||||
const { label, className, icon } = getStatusConfig();
|
||||
|
||||
return (
|
||||
<div className={`flex items-center justify-center gap-2 px-3 py-1 rounded-md text-sm font-medium ${className}`}>
|
||||
<div
|
||||
className={`flex items-center justify-center gap-2 px-3 py-1 rounded-md text-sm font-medium ${className}`}
|
||||
>
|
||||
{icon}
|
||||
<span>{label}</span>
|
||||
</div>
|
||||
|
||||
@ -15,7 +15,9 @@ export default function FileUpload({ onFileUpload, fileToEdit = null }) {
|
||||
const { selectedEstablishmentId } = useEstablishment();
|
||||
|
||||
useEffect(() => {
|
||||
fetchRegistrationFileGroups(selectedEstablishmentId).then(data => setGroups(data));
|
||||
fetchRegistrationFileGroups(selectedEstablishmentId).then((data) =>
|
||||
setGroups(data)
|
||||
);
|
||||
|
||||
if (fileToEdit) {
|
||||
setFileName(fileToEdit.name || '');
|
||||
@ -35,7 +37,7 @@ export default function FileUpload({ onFileUpload, fileToEdit = null }) {
|
||||
name: fileName,
|
||||
is_required: isRequired,
|
||||
order: parseInt(order, 10),
|
||||
groupId: selectedGroup || null
|
||||
groupId: selectedGroup || null,
|
||||
});
|
||||
setFile(null);
|
||||
setFileName('');
|
||||
@ -50,7 +52,7 @@ export default function FileUpload({ onFileUpload, fileToEdit = null }) {
|
||||
fileName={fileName}
|
||||
onFileSelect={(selectedFile) => {
|
||||
setFile(selectedFile);
|
||||
setFileName(selectedFile.name.replace(/\.[^/.]+$/, ""));
|
||||
setFileName(selectedFile.name.replace(/\.[^/.]+$/, ''));
|
||||
}}
|
||||
/>
|
||||
<div className="flex mt-2">
|
||||
@ -70,8 +72,8 @@ export default function FileUpload({ onFileUpload, fileToEdit = null }) {
|
||||
/>
|
||||
<button
|
||||
onClick={handleUpload}
|
||||
className={`p-2 rounded-md shadow transition duration-200 ml-2 ${fileName !== "" ? 'bg-emerald-600 text-white hover:bg-emerald-900' : 'bg-gray-300 text-gray-500 cursor-not-allowed'}`}
|
||||
disabled={fileName === ""}
|
||||
className={`p-2 rounded-md shadow transition duration-200 ml-2 ${fileName !== '' ? 'bg-emerald-600 text-white hover:bg-emerald-900' : 'bg-gray-300 text-gray-500 cursor-not-allowed'}`}
|
||||
disabled={fileName === ''}
|
||||
>
|
||||
Ajouter
|
||||
</button>
|
||||
@ -91,11 +93,13 @@ export default function FileUpload({ onFileUpload, fileToEdit = null }) {
|
||||
className="w-full border rounded p-2"
|
||||
>
|
||||
<option value="">Aucun groupe</option>
|
||||
{groups.map(group => (
|
||||
<option key={group.id} value={group.id}>{group.name}</option>
|
||||
{groups.map((group) => (
|
||||
<option key={group.id} value={group.id}>
|
||||
{group.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,17 +1,19 @@
|
||||
import Logo from '@/components/Logo';
|
||||
|
||||
export default function Footer ({softwareName, softwareVersion}) {
|
||||
|
||||
return (
|
||||
export default function Footer({ softwareName, softwareVersion }) {
|
||||
return (
|
||||
<footer className="h-16 bg-white border-t border-gray-200 px-8 py-4 flex items-center justify-between">
|
||||
<div className="text-sm font-light">
|
||||
<span>© {new Date().getFullYear()} N3WT-INNOV Tous droits réservés.</span>
|
||||
</div>
|
||||
<div className="text-sm font-light flex items-center justify-between">
|
||||
<div className="text-sm font-light mr-4">{softwareName} - {softwareVersion}</div>
|
||||
<Logo className="w-8 h-8" />
|
||||
<div className="text-sm font-light">
|
||||
<span>
|
||||
© {new Date().getFullYear()} N3WT-INNOV Tous droits réservés.
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-sm font-light flex items-center justify-between">
|
||||
<div className="text-sm font-light mr-4">
|
||||
{softwareName} - {softwareVersion}
|
||||
</div>
|
||||
<Logo className="w-8 h-8" />
|
||||
</div>
|
||||
</footer>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -2,31 +2,37 @@ import React from 'react';
|
||||
import { PhoneInput } from 'react-international-phone';
|
||||
import 'react-international-phone/style.css';
|
||||
|
||||
export default function InputPhone({ name, label, value, onChange, errorMsg, className, required }) {
|
||||
return (
|
||||
<div className={`${className}`}>
|
||||
<label htmlFor={name} className="block text-sm font-medium text-gray-700">
|
||||
{label}
|
||||
{required && <span className="text-red-500 ml-1">*</span>}
|
||||
</label>
|
||||
export default function InputPhone({
|
||||
name,
|
||||
label,
|
||||
value,
|
||||
onChange,
|
||||
errorMsg,
|
||||
className,
|
||||
required,
|
||||
}) {
|
||||
return (
|
||||
<div className={`${className}`}>
|
||||
<label htmlFor={name} className="block text-sm font-medium text-gray-700">
|
||||
{label}
|
||||
{required && <span className="text-red-500 ml-1">*</span>}
|
||||
</label>
|
||||
|
||||
<PhoneInput
|
||||
defaultCountry="fr"
|
||||
value={value}
|
||||
onChange={(phone) => onChange(phone)}
|
||||
inputProps={{
|
||||
name: name,
|
||||
required: required,
|
||||
}}
|
||||
className="!w-full mt-1 !h-[38px]"
|
||||
containerClassName="!w-full !h-[36px] !flex !items-center !rounded-md"
|
||||
inputClassName={`flex-1 px-3 py-2 block w-full sm:text-sm border-none focus:ring-0 outline-none !rounded-r-md !outline-none items-center !border !border-gray-200 rounded-md ${errorMsg ? 'border-red-500' : ''} hover:border-gray-400 focus-within:border-gray-500` }
|
||||
buttonClassName="!h-[38px] !flex !items-center !justify-center !rounded-l-md !border border-gray-200 !border-r-0"
|
||||
/>
|
||||
<PhoneInput
|
||||
defaultCountry="fr"
|
||||
value={value}
|
||||
onChange={(phone) => onChange(phone)}
|
||||
inputProps={{
|
||||
name: name,
|
||||
required: required,
|
||||
}}
|
||||
className="!w-full mt-1 !h-[38px]"
|
||||
containerClassName="!w-full !h-[36px] !flex !items-center !rounded-md"
|
||||
inputClassName={`flex-1 px-3 py-2 block w-full sm:text-sm border-none focus:ring-0 outline-none !rounded-r-md !outline-none items-center !border !border-gray-200 rounded-md ${errorMsg ? 'border-red-500' : ''} hover:border-gray-400 focus-within:border-gray-500`}
|
||||
buttonClassName="!h-[38px] !flex !items-center !justify-center !rounded-l-md !border border-gray-200 !border-r-0"
|
||||
/>
|
||||
|
||||
{errorMsg && (
|
||||
<p className="mt-2 text-sm text-red-600">{errorMsg}</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
{errorMsg && <p className="mt-2 text-sm text-red-600">{errorMsg}</p>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,25 +1,40 @@
|
||||
export default function InputText({name, type, label, value, onChange, errorMsg, placeholder, className, required}) {
|
||||
return (
|
||||
<>
|
||||
<div className={`${className}`}>
|
||||
<label htmlFor={name} className="block text-sm font-medium text-gray-700">
|
||||
{label}
|
||||
{required && <span className="text-red-500 ml-1">*</span>}
|
||||
</label>
|
||||
<div className={`mt-1 flex items-center border border-gray-200 rounded-md ${errorMsg ? 'border-red-500' : ''} hover:border-gray-400 focus-within:border-gray-500`}>
|
||||
<input
|
||||
type={type}
|
||||
id={name}
|
||||
placeholder={placeholder}
|
||||
name={name}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
className="flex-1 px-3 py-2 block w-full sm:text-sm border-none focus:ring-0 outline-none rounded-md"
|
||||
required={required}
|
||||
/>
|
||||
</div>
|
||||
{errorMsg && <p className="mt-2 text-sm text-red-600">{errorMsg}</p>}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
export default function InputText({
|
||||
name,
|
||||
type,
|
||||
label,
|
||||
value,
|
||||
onChange,
|
||||
errorMsg,
|
||||
placeholder,
|
||||
className,
|
||||
required,
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<div className={`${className}`}>
|
||||
<label
|
||||
htmlFor={name}
|
||||
className="block text-sm font-medium text-gray-700"
|
||||
>
|
||||
{label}
|
||||
{required && <span className="text-red-500 ml-1">*</span>}
|
||||
</label>
|
||||
<div
|
||||
className={`mt-1 flex items-center border border-gray-200 rounded-md ${errorMsg ? 'border-red-500' : ''} hover:border-gray-400 focus-within:border-gray-500`}
|
||||
>
|
||||
<input
|
||||
type={type}
|
||||
id={name}
|
||||
placeholder={placeholder}
|
||||
name={name}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
className="flex-1 px-3 py-2 block w-full sm:text-sm border-none focus:ring-0 outline-none rounded-md"
|
||||
required={required}
|
||||
/>
|
||||
</div>
|
||||
{errorMsg && <p className="mt-2 text-sm text-red-600">{errorMsg}</p>}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,24 +1,41 @@
|
||||
export default function InputTextIcon({name, type, IconItem, label, value, onChange, errorMsg, placeholder, className}) {
|
||||
return (
|
||||
<>
|
||||
<div className={`${className}`}>
|
||||
<label htmlFor={name} className="block text-sm font-medium text-gray-700">{label}</label>
|
||||
<div className={`mt-1 flex items-stretch border border-gray-200 rounded-md ${errorMsg ? 'border-red-500' : ''} hover:border-gray-400 focus-within:border-gray-500`}>
|
||||
<span className="inline-flex items-center px-3 rounded-l-md bg-gray-50 text-gray-500 text-sm">
|
||||
{IconItem && <IconItem />}
|
||||
</span>
|
||||
<input
|
||||
type={type}
|
||||
id={name}
|
||||
placeholder={placeholder}
|
||||
name={name}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
className="flex-1 px-3 py-2 block w-full rounded-r-md sm:text-sm border-none focus:ring-0 outline-none"
|
||||
/>
|
||||
</div>
|
||||
{errorMsg && <p className="mt-2 text-sm text-red-600">{errorMsg}</p>}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
export default function InputTextIcon({
|
||||
name,
|
||||
type,
|
||||
IconItem,
|
||||
label,
|
||||
value,
|
||||
onChange,
|
||||
errorMsg,
|
||||
placeholder,
|
||||
className,
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<div className={`${className}`}>
|
||||
<label
|
||||
htmlFor={name}
|
||||
className="block text-sm font-medium text-gray-700"
|
||||
>
|
||||
{label}
|
||||
</label>
|
||||
<div
|
||||
className={`mt-1 flex items-stretch border border-gray-200 rounded-md ${errorMsg ? 'border-red-500' : ''} hover:border-gray-400 focus-within:border-gray-500`}
|
||||
>
|
||||
<span className="inline-flex items-center px-3 rounded-l-md bg-gray-50 text-gray-500 text-sm">
|
||||
{IconItem && <IconItem />}
|
||||
</span>
|
||||
<input
|
||||
type={type}
|
||||
id={name}
|
||||
placeholder={placeholder}
|
||||
name={name}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
className="flex-1 px-3 py-2 block w-full rounded-r-md sm:text-sm border-none focus:ring-0 outline-none"
|
||||
/>
|
||||
</div>
|
||||
{errorMsg && <p className="mt-2 text-sm text-red-600">{errorMsg}</p>}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,7 +1,15 @@
|
||||
import React from 'react';
|
||||
import { Palette } from 'lucide-react';
|
||||
|
||||
const InputTextWithColorIcon = ({ name, textValue, colorValue, onTextChange, onColorChange, placeholder, errorMsg }) => {
|
||||
const InputTextWithColorIcon = ({
|
||||
name,
|
||||
textValue,
|
||||
colorValue,
|
||||
onTextChange,
|
||||
onColorChange,
|
||||
placeholder,
|
||||
errorMsg,
|
||||
}) => {
|
||||
return (
|
||||
<div className="flex items-center space-x-2">
|
||||
<input
|
||||
@ -31,4 +39,4 @@ const InputTextWithColorIcon = ({ name, textValue, colorValue, onTextChange, onC
|
||||
);
|
||||
};
|
||||
|
||||
export default InputTextWithColorIcon;
|
||||
export default InputTextWithColorIcon;
|
||||
|
||||
@ -2,17 +2,19 @@ import React from 'react';
|
||||
import Table from '@/components/Table';
|
||||
|
||||
export default function FilesToSign({ fileTemplates, columns }) {
|
||||
return (
|
||||
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
|
||||
<h2 className="text-xl font-bold mb-4 text-gray-800">Fichiers à remplir</h2>
|
||||
<Table
|
||||
data={fileTemplates}
|
||||
columns={columns}
|
||||
itemsPerPage={5}
|
||||
currentPage={1}
|
||||
totalPages={1}
|
||||
onPageChange={() => {}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
|
||||
<h2 className="text-xl font-bold mb-4 text-gray-800">
|
||||
Fichiers à remplir
|
||||
</h2>
|
||||
<Table
|
||||
data={fileTemplates}
|
||||
columns={columns}
|
||||
itemsPerPage={5}
|
||||
currentPage={1}
|
||||
totalPages={1}
|
||||
onPageChange={() => {}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -2,17 +2,19 @@ import React from 'react';
|
||||
import Table from '@/components/Table';
|
||||
|
||||
export default function FilesToUpload({ fileTemplates, columns }) {
|
||||
return (
|
||||
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
|
||||
<h2 className="text-xl font-bold mb-4 text-gray-800">Fichiers à uploader</h2>
|
||||
<Table
|
||||
data={fileTemplates}
|
||||
columns={columns}
|
||||
itemsPerPage={5}
|
||||
currentPage={1}
|
||||
totalPages={1}
|
||||
onPageChange={() => {}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
|
||||
<h2 className="text-xl font-bold mb-4 text-gray-800">
|
||||
Fichiers à uploader
|
||||
</h2>
|
||||
<Table
|
||||
data={fileTemplates}
|
||||
columns={columns}
|
||||
itemsPerPage={5}
|
||||
currentPage={1}
|
||||
totalPages={1}
|
||||
onPageChange={() => {}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -3,22 +3,29 @@ import React, { useState, useEffect } from 'react';
|
||||
import Loader from '@/components/Loader';
|
||||
import Button from '@/components/Button';
|
||||
import DjangoCSRFToken from '@/components/DjangoCSRFToken';
|
||||
import { fetchRegisterForm, fetchTemplatesFromRegistrationFiles } from '@/app/actions/subscriptionAction';
|
||||
import { downloadTemplate,
|
||||
createRegistrationTemplates,
|
||||
editRegistrationTemplates,
|
||||
deleteRegistrationTemplates
|
||||
import {
|
||||
fetchRegisterForm,
|
||||
fetchTemplatesFromRegistrationFiles,
|
||||
} from '@/app/actions/subscriptionAction';
|
||||
import {
|
||||
downloadTemplate,
|
||||
createRegistrationTemplates,
|
||||
editRegistrationTemplates,
|
||||
deleteRegistrationTemplates,
|
||||
} from '@/app/actions/registerFileGroupAction';
|
||||
import {
|
||||
fetchRegistrationPaymentModes,
|
||||
fetchTuitionPaymentModes } from '@/app/actions/schoolAction';
|
||||
fetchRegistrationPaymentModes,
|
||||
fetchTuitionPaymentModes,
|
||||
} from '@/app/actions/schoolAction';
|
||||
import { Download, Upload, Trash2, Eye } from 'lucide-react';
|
||||
import { BASE_URL } from '@/utils/Url';
|
||||
import DraggableFileUpload from '@/components/DraggableFileUpload';
|
||||
import Modal from '@/components/Modal';
|
||||
import FileStatusLabel from '@/components/FileStatusLabel';
|
||||
import logger from '@/utils/logger';
|
||||
import StudentInfoForm, { validateStudentInfo } from '@/components/Inscription/StudentInfoForm';
|
||||
import StudentInfoForm, {
|
||||
validateStudentInfo,
|
||||
} from '@/components/Inscription/StudentInfoForm';
|
||||
import FilesToUpload from '@/components/Inscription/FilesToUpload';
|
||||
import { DocusealForm } from '@docuseal/react';
|
||||
|
||||
@ -31,440 +38,498 @@ import { DocusealForm } from '@docuseal/react';
|
||||
* @param {object} errors - Erreurs de validation du formulaire
|
||||
*/
|
||||
export default function InscriptionFormShared({
|
||||
studentId,
|
||||
csrfToken,
|
||||
selectedEstablishmentId,
|
||||
onSubmit,
|
||||
cancelUrl,
|
||||
errors = {} // Nouvelle prop pour les erreurs
|
||||
studentId,
|
||||
csrfToken,
|
||||
selectedEstablishmentId,
|
||||
onSubmit,
|
||||
cancelUrl,
|
||||
errors = {}, // Nouvelle prop pour les erreurs
|
||||
}) {
|
||||
// États pour gérer les données du formulaire
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [formData, setFormData] = useState({
|
||||
id: '',
|
||||
last_name: '',
|
||||
first_name: '',
|
||||
address: '',
|
||||
birth_date: '',
|
||||
birth_place: '',
|
||||
birth_postal_code: '',
|
||||
nationality: '',
|
||||
attending_physician: '',
|
||||
level: '',
|
||||
registration_payment: '',
|
||||
tuition_payment: ''
|
||||
// États pour gérer les données du formulaire
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [formData, setFormData] = useState({
|
||||
id: '',
|
||||
last_name: '',
|
||||
first_name: '',
|
||||
address: '',
|
||||
birth_date: '',
|
||||
birth_place: '',
|
||||
birth_postal_code: '',
|
||||
nationality: '',
|
||||
attending_physician: '',
|
||||
level: '',
|
||||
registration_payment: '',
|
||||
tuition_payment: '',
|
||||
});
|
||||
|
||||
const [guardians, setGuardians] = useState([]);
|
||||
|
||||
const [registrationPaymentModes, setRegistrationPaymentModes] = useState([]);
|
||||
const [tuitionPaymentModes, setTuitionPaymentModes] = useState([]);
|
||||
|
||||
// États pour la gestion des fichiers
|
||||
const [uploadedFiles, setUploadedFiles] = useState([]);
|
||||
const [fileTemplates, setFileTemplates] = useState([]);
|
||||
const [fileGroup, setFileGroup] = useState(null);
|
||||
const [fileName, setFileName] = useState('');
|
||||
const [file, setFile] = useState('');
|
||||
const [showUploadModal, setShowUploadModal] = useState(false);
|
||||
const [currentTemplateId, setCurrentTemplateId] = useState(null);
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
|
||||
const isCurrentPageValid = () => {
|
||||
if (currentPage === 1) {
|
||||
const isValid = validateStudentInfo(formData);
|
||||
return isValid;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
// Chargement initial des données
|
||||
// Mettre à jour les données quand initialData change
|
||||
useEffect(() => {
|
||||
if (studentId) {
|
||||
fetchRegisterForm(studentId).then((data) => {
|
||||
logger.debug(data);
|
||||
|
||||
setFormData({
|
||||
id: data?.student?.id || '',
|
||||
last_name: data?.student?.last_name || '',
|
||||
first_name: data?.student?.first_name || '',
|
||||
address: data?.student?.address || '',
|
||||
birth_date: data?.student?.birth_date || '',
|
||||
birth_place: data?.student?.birth_place || '',
|
||||
birth_postal_code: data?.student?.birth_postal_code || '',
|
||||
nationality: data?.student?.nationality || '',
|
||||
attending_physician: data?.student?.attending_physician || '',
|
||||
level: data?.student?.level || '',
|
||||
registration_payment: data?.registration_payment || '',
|
||||
tuition_payment: data?.tuition_payment || '',
|
||||
totalRegistrationFees: data?.totalRegistrationFees,
|
||||
totalTuitionFees: data?.totalTuitionFees,
|
||||
});
|
||||
setGuardians(data?.student?.guardians || []);
|
||||
setUploadedFiles(data.registration_files || []);
|
||||
});
|
||||
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [studentId]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchTemplatesFromRegistrationFiles(studentId).then((data) => {
|
||||
setFileTemplates(data);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const [guardians, setGuardians] = useState([]);
|
||||
useEffect(() => {
|
||||
if (selectedEstablishmentId) {
|
||||
// Fetch data for registration payment modes
|
||||
handleRegistrationPaymentModes();
|
||||
|
||||
const [registrationPaymentModes, setRegistrationPaymentModes] = useState([]);
|
||||
const [tuitionPaymentModes, setTuitionPaymentModes] = useState([]);
|
||||
// Fetch data for tuition payment modes
|
||||
handleTuitionPaymentModes();
|
||||
}
|
||||
}, [selectedEstablishmentId]);
|
||||
|
||||
// États pour la gestion des fichiers
|
||||
const [uploadedFiles, setUploadedFiles] = useState([]);
|
||||
const [fileTemplates, setFileTemplates] = useState([]);
|
||||
const [fileGroup, setFileGroup] = useState(null);
|
||||
const [fileName, setFileName] = useState("");
|
||||
const [file, setFile] = useState("");
|
||||
const [showUploadModal, setShowUploadModal] = useState(false);
|
||||
const [currentTemplateId, setCurrentTemplateId] = useState(null);
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
|
||||
const isCurrentPageValid = () => {
|
||||
if (currentPage === 1) {
|
||||
const isValid = validateStudentInfo(formData);
|
||||
return isValid;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
// Chargement initial des données
|
||||
// Mettre à jour les données quand initialData change
|
||||
useEffect(() => {
|
||||
if (studentId) {
|
||||
fetchRegisterForm(studentId).then((data) => {
|
||||
logger.debug(data);
|
||||
|
||||
setFormData({
|
||||
id: data?.student?.id || '',
|
||||
last_name: data?.student?.last_name || '',
|
||||
first_name: data?.student?.first_name || '',
|
||||
address: data?.student?.address || '',
|
||||
birth_date: data?.student?.birth_date || '',
|
||||
birth_place: data?.student?.birth_place || '',
|
||||
birth_postal_code: data?.student?.birth_postal_code || '',
|
||||
nationality: data?.student?.nationality || '',
|
||||
attending_physician: data?.student?.attending_physician || '',
|
||||
level: data?.student?.level || '',
|
||||
registration_payment: data?.registration_payment || '',
|
||||
tuition_payment: data?.tuition_payment || '',
|
||||
totalRegistrationFees: data?.totalRegistrationFees,
|
||||
totalTuitionFees: data?.totalTuitionFees,
|
||||
});
|
||||
setGuardians(data?.student?.guardians || []);
|
||||
setUploadedFiles(data.registration_files || []);
|
||||
});
|
||||
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [studentId]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchTemplatesFromRegistrationFiles(studentId).then((data) => {
|
||||
setFileTemplates(data);
|
||||
})
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedEstablishmentId) {
|
||||
// Fetch data for registration payment modes
|
||||
handleRegistrationPaymentModes();
|
||||
|
||||
// Fetch data for tuition payment modes
|
||||
handleTuitionPaymentModes();
|
||||
}
|
||||
}, [selectedEstablishmentId]);
|
||||
|
||||
const handleRegistrationPaymentModes = () => {
|
||||
fetchRegistrationPaymentModes(selectedEstablishmentId)
|
||||
.then(data => {
|
||||
const activePaymentModes = data.filter(mode => mode.is_active === true);
|
||||
setRegistrationPaymentModes(activePaymentModes);
|
||||
})
|
||||
.catch(error => logger.error('Error fetching registration payment modes:', error));
|
||||
};
|
||||
|
||||
const handleTuitionPaymentModes = () => {
|
||||
fetchTuitionPaymentModes(selectedEstablishmentId)
|
||||
.then(data => {
|
||||
const activePaymentModes = data.filter(mode => mode.is_active === true);
|
||||
setTuitionPaymentModes(activePaymentModes);
|
||||
})
|
||||
.catch(error => logger.error('Error fetching tuition payment modes:', error));
|
||||
};
|
||||
|
||||
// Fonctions de gestion du formulaire et des fichiers
|
||||
const updateFormField = (field, value) => {
|
||||
setFormData(prev => ({...prev, [field]: value}));
|
||||
};
|
||||
|
||||
// Gestion du téléversement de fichiers
|
||||
const handleFileUpload = async (file, fileName) => {
|
||||
if (!file || !currentTemplateId || !formData.id) {
|
||||
logger.error('Missing required data for upload');
|
||||
return;
|
||||
}
|
||||
|
||||
const data = new FormData();
|
||||
data.append('file', file);
|
||||
data.append('name', fileName);
|
||||
data.append('template', currentTemplateId);
|
||||
data.append('register_form', formData.id);
|
||||
|
||||
try {
|
||||
const response = await createRegistrationTemplates(data, csrfToken);
|
||||
if (response) {
|
||||
setUploadedFiles(prev => {
|
||||
const newFiles = prev.filter(f => parseInt(f.template) !== currentTemplateId);
|
||||
return [...newFiles, {
|
||||
name: fileName,
|
||||
template: currentTemplateId,
|
||||
file: response.file
|
||||
}];
|
||||
});
|
||||
|
||||
// Rafraîchir les données du formulaire pour avoir les fichiers à jour
|
||||
if (studentId) {
|
||||
fetchRegisterForm(studentId).then((data) => {
|
||||
setUploadedFiles(data.registration_files || []);
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error uploading file:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// Vérification si un fichier est déjà uploadé
|
||||
const isFileUploaded = (templateId) => {
|
||||
return uploadedFiles.find(template =>
|
||||
template.template === templateId
|
||||
const handleRegistrationPaymentModes = () => {
|
||||
fetchRegistrationPaymentModes(selectedEstablishmentId)
|
||||
.then((data) => {
|
||||
const activePaymentModes = data.filter(
|
||||
(mode) => mode.is_active === true
|
||||
);
|
||||
};
|
||||
setRegistrationPaymentModes(activePaymentModes);
|
||||
})
|
||||
.catch((error) =>
|
||||
logger.error('Error fetching registration payment modes:', error)
|
||||
);
|
||||
};
|
||||
|
||||
// Récupération d'un fichier uploadé
|
||||
const getUploadedFile = (templateId) => {
|
||||
return uploadedFiles.find(file => parseInt(file.template) === templateId);
|
||||
};
|
||||
const handleTuitionPaymentModes = () => {
|
||||
fetchTuitionPaymentModes(selectedEstablishmentId)
|
||||
.then((data) => {
|
||||
const activePaymentModes = data.filter(
|
||||
(mode) => mode.is_active === true
|
||||
);
|
||||
setTuitionPaymentModes(activePaymentModes);
|
||||
})
|
||||
.catch((error) =>
|
||||
logger.error('Error fetching tuition payment modes:', error)
|
||||
);
|
||||
};
|
||||
|
||||
// Suppression d'un fichier
|
||||
const handleDeleteFile = async (templateId) => {
|
||||
const fileToDelete = getUploadedFile(templateId);
|
||||
if (!fileToDelete) return;
|
||||
// Fonctions de gestion du formulaire et des fichiers
|
||||
const updateFormField = (field, value) => {
|
||||
setFormData((prev) => ({ ...prev, [field]: value }));
|
||||
};
|
||||
|
||||
try {
|
||||
await deleteRegistrationTemplates(fileToDelete.id, csrfToken);
|
||||
setUploadedFiles(prev => prev.filter(f => parseInt(f.template) !== templateId));
|
||||
} catch (error) {
|
||||
logger.error('Error deleting file:', error);
|
||||
}
|
||||
};
|
||||
// Gestion du téléversement de fichiers
|
||||
const handleFileUpload = async (file, fileName) => {
|
||||
if (!file || !currentTemplateId || !formData.id) {
|
||||
logger.error('Missing required data for upload');
|
||||
return;
|
||||
}
|
||||
|
||||
// Soumission du formulaire
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
const data ={
|
||||
student: {
|
||||
...formData,
|
||||
guardians
|
||||
const data = new FormData();
|
||||
data.append('file', file);
|
||||
data.append('name', fileName);
|
||||
data.append('template', currentTemplateId);
|
||||
data.append('register_form', formData.id);
|
||||
|
||||
try {
|
||||
const response = await createRegistrationTemplates(data, csrfToken);
|
||||
if (response) {
|
||||
setUploadedFiles((prev) => {
|
||||
const newFiles = prev.filter(
|
||||
(f) => parseInt(f.template) !== currentTemplateId
|
||||
);
|
||||
return [
|
||||
...newFiles,
|
||||
{
|
||||
name: fileName,
|
||||
template: currentTemplateId,
|
||||
file: response.file,
|
||||
},
|
||||
establishment: selectedEstablishmentId,
|
||||
status:3,
|
||||
tuition_payment:formData.tuition_payment,
|
||||
registration_payment:formData.registration_payment
|
||||
];
|
||||
});
|
||||
|
||||
// Rafraîchir les données du formulaire pour avoir les fichiers à jour
|
||||
if (studentId) {
|
||||
fetchRegisterForm(studentId).then((data) => {
|
||||
setUploadedFiles(data.registration_files || []);
|
||||
});
|
||||
}
|
||||
onSubmit(data);
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error uploading file:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// Soumission du formulaire
|
||||
const handleSave = (e) => {
|
||||
e.preventDefault();
|
||||
const data ={
|
||||
student: {
|
||||
...formData,
|
||||
guardians
|
||||
},
|
||||
establishment: selectedEstablishmentId
|
||||
// Vérification si un fichier est déjà uploadé
|
||||
const isFileUploaded = (templateId) => {
|
||||
return uploadedFiles.find((template) => template.template === templateId);
|
||||
};
|
||||
|
||||
// Récupération d'un fichier uploadé
|
||||
const getUploadedFile = (templateId) => {
|
||||
return uploadedFiles.find((file) => parseInt(file.template) === templateId);
|
||||
};
|
||||
|
||||
// Suppression d'un fichier
|
||||
const handleDeleteFile = async (templateId) => {
|
||||
const fileToDelete = getUploadedFile(templateId);
|
||||
if (!fileToDelete) return;
|
||||
|
||||
try {
|
||||
await deleteRegistrationTemplates(fileToDelete.id, csrfToken);
|
||||
setUploadedFiles((prev) =>
|
||||
prev.filter((f) => parseInt(f.template) !== templateId)
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error('Error deleting file:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// Soumission du formulaire
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
const data = {
|
||||
student: {
|
||||
...formData,
|
||||
guardians,
|
||||
},
|
||||
establishment: selectedEstablishmentId,
|
||||
status: 3,
|
||||
tuition_payment: formData.tuition_payment,
|
||||
registration_payment: formData.registration_payment,
|
||||
};
|
||||
onSubmit(data);
|
||||
};
|
||||
|
||||
// Soumission du formulaire
|
||||
const handleSave = (e) => {
|
||||
e.preventDefault();
|
||||
const data = {
|
||||
student: {
|
||||
...formData,
|
||||
guardians,
|
||||
},
|
||||
establishment: selectedEstablishmentId,
|
||||
};
|
||||
onSubmit(data);
|
||||
};
|
||||
|
||||
const handleNextPage = () => {
|
||||
setCurrentPage(currentPage + 1);
|
||||
};
|
||||
|
||||
const handlePreviousPage = () => {
|
||||
setCurrentPage(currentPage - 1);
|
||||
};
|
||||
|
||||
const requiredFileTemplates = fileTemplates;
|
||||
|
||||
// Configuration des colonnes pour le tableau des fichiers
|
||||
const columns = [
|
||||
{ name: 'Nom du fichier', transform: (row) => row.name },
|
||||
{
|
||||
name: 'Fichier à Remplir',
|
||||
transform: (row) => (row.is_required ? 'Oui' : 'Non'),
|
||||
},
|
||||
{
|
||||
name: 'Fichier de référence',
|
||||
transform: (row) =>
|
||||
row.file && (
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
{' '}
|
||||
<a
|
||||
href={`${BASE_URL}${row.file}`}
|
||||
target="_blank"
|
||||
className="text-blue-500 hover:text-blue-700"
|
||||
>
|
||||
<Download size={16} />
|
||||
</a>{' '}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'Statut',
|
||||
transform: (row) =>
|
||||
row.is_required && (
|
||||
<FileStatusLabel
|
||||
status={isFileUploaded(row.id) ? 'sent' : 'pending'}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'Actions',
|
||||
transform: (row) => {
|
||||
if (!row.is_required) return null;
|
||||
|
||||
const uploadedFile = getUploadedFile(row.id);
|
||||
|
||||
if (uploadedFile) {
|
||||
return (
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<a
|
||||
href={`${BASE_URL}${uploadedFile.file}`}
|
||||
target="_blank"
|
||||
className="text-blue-500 hover:text-blue-700"
|
||||
>
|
||||
<Eye size={16} />
|
||||
</a>
|
||||
<button
|
||||
className="text-red-500 hover:text-red-700"
|
||||
onClick={() => handleDeleteFile(row.id)}
|
||||
type="button"
|
||||
>
|
||||
<Trash2 size={16} />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
onSubmit(data);
|
||||
};
|
||||
|
||||
const handleNextPage = () => {
|
||||
setCurrentPage(currentPage + 1);
|
||||
};
|
||||
|
||||
const handlePreviousPage = () => {
|
||||
setCurrentPage(currentPage - 1);
|
||||
};
|
||||
return (
|
||||
<button
|
||||
className="text-emerald-500 hover:text-emerald-700"
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setCurrentTemplateId(row.id);
|
||||
setShowUploadModal(true);
|
||||
}}
|
||||
>
|
||||
<Upload size={16} />
|
||||
</button>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const requiredFileTemplates = fileTemplates;
|
||||
// Affichage du loader pendant le chargement
|
||||
if (isLoading) return <Loader />;
|
||||
|
||||
// Configuration des colonnes pour le tableau des fichiers
|
||||
const columns = [
|
||||
{ name: 'Nom du fichier', transform: (row) => row.name },
|
||||
{ name: 'Fichier à Remplir', transform: (row) => row.is_required ? 'Oui' : 'Non' },
|
||||
{ name: 'Fichier de référence', transform: (row) => row.file && <div className="flex items-center justify-center gap-2"> <a href={`${BASE_URL}${row.file}`} target='_blank' className="text-blue-500 hover:text-blue-700">
|
||||
<Download size={16} />
|
||||
</a> </div>},
|
||||
{ name: 'Statut', transform: (row) =>
|
||||
row.is_required && (
|
||||
<FileStatusLabel
|
||||
status={isFileUploaded(row.id) ? 'sent' : 'pending'}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{ name: 'Actions', transform: (row) => {
|
||||
if (!row.is_required) return null;
|
||||
// Rendu du composant
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto p-6">
|
||||
<form onSubmit={handleSubmit} className="space-y-8">
|
||||
<DjangoCSRFToken csrfToken={csrfToken} />
|
||||
{/* Page 1 : Informations de l'élève et Responsables */}
|
||||
{currentPage === 1 && (
|
||||
<StudentInfoForm
|
||||
formData={formData}
|
||||
updateFormField={updateFormField}
|
||||
guardians={guardians}
|
||||
setGuardians={setGuardians}
|
||||
registrationPaymentModes={registrationPaymentModes}
|
||||
tuitionPaymentModes={tuitionPaymentModes}
|
||||
errors={errors}
|
||||
/>
|
||||
)}
|
||||
|
||||
const uploadedFile = getUploadedFile(row.id);
|
||||
{/* Pages suivantes : Section Fichiers d'inscription */}
|
||||
{currentPage > 1 && currentPage <= requiredFileTemplates.length + 1 && (
|
||||
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
|
||||
{/* Titre du document */}
|
||||
<div className="mb-4">
|
||||
<h2 className="text-lg font-semibold text-gray-800">
|
||||
{requiredFileTemplates[currentPage - 2].name ||
|
||||
'Document sans nom'}
|
||||
</h2>
|
||||
<p className="text-sm text-gray-500">
|
||||
{requiredFileTemplates[currentPage - 2].description ||
|
||||
'Aucune description disponible pour ce document.'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
if (uploadedFile) {
|
||||
return (
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<a
|
||||
href={`${BASE_URL}${uploadedFile.file}`}
|
||||
target="_blank"
|
||||
className="text-blue-500 hover:text-blue-700"
|
||||
>
|
||||
<Eye size={16} />
|
||||
</a>
|
||||
<button
|
||||
className="text-red-500 hover:text-red-700"
|
||||
onClick={() => handleDeleteFile(row.id)}
|
||||
type="button"
|
||||
>
|
||||
<Trash2 size={16} />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
{/* Affichage du formulaire ou du document */}
|
||||
{requiredFileTemplates[currentPage - 2].file === '' ? (
|
||||
<DocusealForm
|
||||
id="docusealForm"
|
||||
src={
|
||||
'https://docuseal.com/s/' +
|
||||
requiredFileTemplates[currentPage - 2].slug
|
||||
}
|
||||
withDownloadButton={false}
|
||||
onComplete={() => {
|
||||
downloadTemplate(requiredFileTemplates[currentPage - 2].slug)
|
||||
.then((data) => fetch(data))
|
||||
.then((response) => response.blob())
|
||||
.then((blob) => {
|
||||
const file = new File(
|
||||
[blob],
|
||||
`${requiredFileTemplates[currentPage - 2].name}.pdf`,
|
||||
{ type: blob.type }
|
||||
);
|
||||
const updateData = new FormData();
|
||||
updateData.append('file', file);
|
||||
|
||||
return (
|
||||
<button
|
||||
className="text-emerald-500 hover:text-emerald-700"
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setCurrentTemplateId(row.id);
|
||||
setShowUploadModal(true);
|
||||
}}
|
||||
>
|
||||
<Upload size={16} />
|
||||
</button>
|
||||
);
|
||||
}},
|
||||
];
|
||||
|
||||
// Affichage du loader pendant le chargement
|
||||
if (isLoading) return <Loader />;
|
||||
|
||||
// Rendu du composant
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto p-6">
|
||||
<form onSubmit={handleSubmit} className="space-y-8">
|
||||
<DjangoCSRFToken csrfToken={csrfToken}/>
|
||||
{/* Page 1 : Informations de l'élève et Responsables */}
|
||||
{currentPage === 1 && (
|
||||
<StudentInfoForm
|
||||
formData={formData}
|
||||
updateFormField={updateFormField}
|
||||
guardians={guardians}
|
||||
setGuardians={setGuardians}
|
||||
registrationPaymentModes={registrationPaymentModes}
|
||||
tuitionPaymentModes={tuitionPaymentModes}
|
||||
errors={errors}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Pages suivantes : Section Fichiers d'inscription */}
|
||||
{currentPage > 1 && currentPage <= requiredFileTemplates.length + 1 && (
|
||||
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
|
||||
{/* Titre du document */}
|
||||
<div className="mb-4">
|
||||
<h2 className="text-lg font-semibold text-gray-800">
|
||||
{requiredFileTemplates[currentPage - 2].name || "Document sans nom"}
|
||||
</h2>
|
||||
<p className="text-sm text-gray-500">
|
||||
{requiredFileTemplates[currentPage - 2].description || "Aucune description disponible pour ce document."}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Affichage du formulaire ou du document */}
|
||||
{requiredFileTemplates[currentPage - 2].file === "" ? (
|
||||
<DocusealForm
|
||||
id="docusealForm"
|
||||
src={"https://docuseal.com/s/" + requiredFileTemplates[currentPage - 2].slug}
|
||||
withDownloadButton={false}
|
||||
onComplete={() => {
|
||||
downloadTemplate(requiredFileTemplates[currentPage - 2].slug)
|
||||
.then((data) => fetch(data))
|
||||
.then((response) => response.blob())
|
||||
.then((blob) => {
|
||||
const file = new File([blob], `${requiredFileTemplates[currentPage - 2].name}.pdf`, { type: blob.type });
|
||||
const updateData = new FormData();
|
||||
updateData.append('file', file);
|
||||
|
||||
return editRegistrationTemplates(requiredFileTemplates[currentPage - 2].id, updateData, csrfToken);
|
||||
})
|
||||
.then((data) => {
|
||||
logger.debug("EDIT TEMPLATE : ", data);
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error("error editing template : ", error);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<iframe
|
||||
src={`${BASE_URL}/${requiredFileTemplates[currentPage - 2].file}`}
|
||||
title="Document Viewer"
|
||||
className="w-full"
|
||||
style={{
|
||||
height: '75vh', // Ajuster la hauteur à 75% de la fenêtre
|
||||
border: 'none',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Dernière page : Section Fichiers parents */}
|
||||
{currentPage === requiredFileTemplates.length + 2 && (
|
||||
<>
|
||||
<FilesToUpload
|
||||
fileTemplates={fileTemplates.filter(template => !template.is_required)}
|
||||
columns={columns}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Boutons de contrôle */}
|
||||
<div className="flex justify-end space-x-4">
|
||||
<Button
|
||||
text="Sauvegarder"
|
||||
onClick={handleSave}
|
||||
className="px-4 py-2 rounded-md shadow-sm focus:outline-none bg-orange-500 text-white hover:bg-orange-600"
|
||||
primary
|
||||
name="Save"
|
||||
/>
|
||||
{currentPage > 1 && (
|
||||
<Button text="Précédent" onClick={(e) => { e.preventDefault(); handlePreviousPage(); }} />
|
||||
)}
|
||||
{currentPage < requiredFileTemplates.length + 2 && (
|
||||
<Button
|
||||
text="Suivant"
|
||||
onClick={(e) => { e.preventDefault(); handleNextPage(); }}
|
||||
className={`px-4 py-2 rounded-md shadow-sm focus:outline-none ${
|
||||
!isCurrentPageValid()
|
||||
? "bg-gray-300 text-gray-700 cursor-not-allowed"
|
||||
: "bg-emerald-500 text-white hover:bg-emerald-600"
|
||||
}`}
|
||||
disabled={!isCurrentPageValid()}
|
||||
primary
|
||||
name="Next"
|
||||
/>
|
||||
)}
|
||||
{currentPage === requiredFileTemplates.length + 2 && (
|
||||
<Button type="submit" text="Valider" primary />
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
{fileTemplates.length > 0 && (
|
||||
<Modal
|
||||
isOpen={showUploadModal}
|
||||
setIsOpen={setShowUploadModal}
|
||||
title="Téléverser un fichier"
|
||||
ContentComponent={() => (
|
||||
<>
|
||||
<DraggableFileUpload
|
||||
className="w-full"
|
||||
fileName={fileName}
|
||||
onFileSelect={(selectedFile) => {
|
||||
if (selectedFile) {
|
||||
setFile(selectedFile);
|
||||
setFileName(selectedFile.name);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<div className="mt-4 flex justify-center space-x-4">
|
||||
<Button
|
||||
text="Annuler"
|
||||
onClick={() => {
|
||||
setShowUploadModal(false);
|
||||
setCurrentTemplateId(null);
|
||||
setFile(null);
|
||||
setFileName("");
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
text="Valider"
|
||||
onClick={() => {
|
||||
if (file && fileName) {
|
||||
handleFileUpload(file, fileName);
|
||||
setShowUploadModal(false);
|
||||
setCurrentTemplateId(null);
|
||||
setFile(null);
|
||||
setFileName("");
|
||||
}
|
||||
}}
|
||||
primary={true}
|
||||
disabled={!file || !fileName}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
return editRegistrationTemplates(
|
||||
requiredFileTemplates[currentPage - 2].id,
|
||||
updateData,
|
||||
csrfToken
|
||||
);
|
||||
})
|
||||
.then((data) => {
|
||||
logger.debug('EDIT TEMPLATE : ', data);
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error('error editing template : ', error);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<iframe
|
||||
src={`${BASE_URL}/${requiredFileTemplates[currentPage - 2].file}`}
|
||||
title="Document Viewer"
|
||||
className="w-full"
|
||||
style={{
|
||||
height: '75vh', // Ajuster la hauteur à 75% de la fenêtre
|
||||
border: 'none',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Dernière page : Section Fichiers parents */}
|
||||
{currentPage === requiredFileTemplates.length + 2 && (
|
||||
<>
|
||||
<FilesToUpload
|
||||
fileTemplates={fileTemplates.filter(
|
||||
(template) => !template.is_required
|
||||
)}
|
||||
columns={columns}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Boutons de contrôle */}
|
||||
<div className="flex justify-end space-x-4">
|
||||
<Button
|
||||
text="Sauvegarder"
|
||||
onClick={handleSave}
|
||||
className="px-4 py-2 rounded-md shadow-sm focus:outline-none bg-orange-500 text-white hover:bg-orange-600"
|
||||
primary
|
||||
name="Save"
|
||||
/>
|
||||
{currentPage > 1 && (
|
||||
<Button
|
||||
text="Précédent"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
handlePreviousPage();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{currentPage < requiredFileTemplates.length + 2 && (
|
||||
<Button
|
||||
text="Suivant"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
handleNextPage();
|
||||
}}
|
||||
className={`px-4 py-2 rounded-md shadow-sm focus:outline-none ${
|
||||
!isCurrentPageValid()
|
||||
? 'bg-gray-300 text-gray-700 cursor-not-allowed'
|
||||
: 'bg-emerald-500 text-white hover:bg-emerald-600'
|
||||
}`}
|
||||
disabled={!isCurrentPageValid()}
|
||||
primary
|
||||
name="Next"
|
||||
/>
|
||||
)}
|
||||
{currentPage === requiredFileTemplates.length + 2 && (
|
||||
<Button type="submit" text="Valider" primary />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
</form>
|
||||
{fileTemplates.length > 0 && (
|
||||
<Modal
|
||||
isOpen={showUploadModal}
|
||||
setIsOpen={setShowUploadModal}
|
||||
title="Téléverser un fichier"
|
||||
ContentComponent={() => (
|
||||
<>
|
||||
<DraggableFileUpload
|
||||
className="w-full"
|
||||
fileName={fileName}
|
||||
onFileSelect={(selectedFile) => {
|
||||
if (selectedFile) {
|
||||
setFile(selectedFile);
|
||||
setFileName(selectedFile.name);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<div className="mt-4 flex justify-center space-x-4">
|
||||
<Button
|
||||
text="Annuler"
|
||||
onClick={() => {
|
||||
setShowUploadModal(false);
|
||||
setCurrentTemplateId(null);
|
||||
setFile(null);
|
||||
setFileName('');
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
text="Valider"
|
||||
onClick={() => {
|
||||
if (file && fileName) {
|
||||
handleFileUpload(file, fileName);
|
||||
setShowUploadModal(false);
|
||||
setCurrentTemplateId(null);
|
||||
setFile(null);
|
||||
setFileName('');
|
||||
}
|
||||
}}
|
||||
primary={true}
|
||||
disabled={!file || !fileName}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,34 +1,48 @@
|
||||
import React from 'react';
|
||||
import SelectChoice from '@/components/SelectChoice';
|
||||
|
||||
export default function PaymentMethodSelector({ formData, title, name, updateFormField, selected, paymentModes, paymentModesOptions, amount, getError }) {
|
||||
//console.log(paymentModes)
|
||||
//console.log(selected)
|
||||
return (
|
||||
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
|
||||
{/* Titre */}
|
||||
<h2 className="text-2xl font-semibold mb-6 text-gray-800 border-b pb-2">{title}</h2>
|
||||
export default function PaymentMethodSelector({
|
||||
formData,
|
||||
title,
|
||||
name,
|
||||
updateFormField,
|
||||
selected,
|
||||
paymentModes,
|
||||
paymentModesOptions,
|
||||
amount,
|
||||
getError,
|
||||
}) {
|
||||
//console.log(paymentModes)
|
||||
//console.log(selected)
|
||||
return (
|
||||
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
|
||||
{/* Titre */}
|
||||
<h2 className="text-2xl font-semibold mb-6 text-gray-800 border-b pb-2">
|
||||
{title}
|
||||
</h2>
|
||||
|
||||
{/* Section d'information */}
|
||||
<div className="mb-6 bg-gray-50 p-4 rounded-lg border border-gray-100">
|
||||
<p className="text-gray-700 text-sm mb-2">
|
||||
<strong className="text-gray-900">Montant :</strong> {amount} €
|
||||
</p>
|
||||
</div>
|
||||
{/* Section d'information */}
|
||||
<div className="mb-6 bg-gray-50 p-4 rounded-lg border border-gray-100">
|
||||
<p className="text-gray-700 text-sm mb-2">
|
||||
<strong className="text-gray-900">Montant :</strong> {amount} €
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<SelectChoice
|
||||
name={name}
|
||||
label="Mode de Paiement"
|
||||
placeHolder="Sélectionner un mode de paiement"
|
||||
selected={selected || ''}
|
||||
callback={(e) => updateFormField(name, e.target.value)}
|
||||
choices={paymentModes.map((mode) => ({
|
||||
value: mode.mode,
|
||||
label: paymentModesOptions.find(option => option.id === mode.mode)?.name || 'Mode inconnu'
|
||||
}))}
|
||||
required
|
||||
errorMsg={getError('payment_method')}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
<SelectChoice
|
||||
name={name}
|
||||
label="Mode de Paiement"
|
||||
placeHolder="Sélectionner un mode de paiement"
|
||||
selected={selected || ''}
|
||||
callback={(e) => updateFormField(name, e.target.value)}
|
||||
choices={paymentModes.map((mode) => ({
|
||||
value: mode.mode,
|
||||
label:
|
||||
paymentModesOptions.find((option) => option.id === mode.mode)
|
||||
?.name || 'Mode inconnu',
|
||||
}))}
|
||||
required
|
||||
errorMsg={getError('payment_method')}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -5,112 +5,137 @@ import React from 'react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { Trash2, Plus } from 'lucide-react';
|
||||
|
||||
export default function ResponsableInputFields({guardians, onGuardiansChange, addGuardian, deleteGuardian, errors = []}) {
|
||||
const t = useTranslations('ResponsableInputFields');
|
||||
export default function ResponsableInputFields({
|
||||
guardians,
|
||||
onGuardiansChange,
|
||||
addGuardian,
|
||||
deleteGuardian,
|
||||
errors = [],
|
||||
}) {
|
||||
const t = useTranslations('ResponsableInputFields');
|
||||
|
||||
const getError = (index, field) => {
|
||||
return errors[index]?.[field]?.[0];
|
||||
};
|
||||
const getError = (index, field) => {
|
||||
return errors[index]?.[field]?.[0];
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
{guardians.map((item, index) => (
|
||||
<div className="p-6 bg-gray-50 rounded-lg shadow-sm" key={index}>
|
||||
<div className='flex justify-between items-center mb-4'>
|
||||
<h3 className='text-xl font-bold'>{t('responsable')} {index+1}</h3>
|
||||
{guardians.length > 1 && (
|
||||
<Trash2
|
||||
className="w-5 h-5 text-red-500 cursor-pointer hover:text-red-700 transition-colors"
|
||||
onClick={() => deleteGuardian(index)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
{guardians.map((item, index) => (
|
||||
<div className="p-6 bg-gray-50 rounded-lg shadow-sm" key={index}>
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h3 className="text-xl font-bold">
|
||||
{t('responsable')} {index + 1}
|
||||
</h3>
|
||||
{guardians.length > 1 && (
|
||||
<Trash2
|
||||
className="w-5 h-5 text-red-500 cursor-pointer hover:text-red-700 transition-colors"
|
||||
onClick={() => deleteGuardian(index)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="idResponsable" value={item.id} />
|
||||
<input type="hidden" name="idResponsable" value={item.id} />
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||
<InputText
|
||||
name="nomResponsable"
|
||||
type="text"
|
||||
label={t('lastname')}
|
||||
value={item.last_name}
|
||||
onChange={(event) => {
|
||||
onGuardiansChange(item.id, 'last_name', event.target.value);
|
||||
}}
|
||||
errorMsg={getError(index, 'last_name')}
|
||||
required
|
||||
/>
|
||||
<InputText
|
||||
name="prenomResponsable"
|
||||
type="text"
|
||||
label={t('firstname')}
|
||||
value={item.first_name}
|
||||
onChange={(event) => {
|
||||
onGuardiansChange(item.id, 'first_name', event.target.value);
|
||||
}}
|
||||
errorMsg={getError(index, 'first_name')}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='grid grid-cols-1 md:grid-cols-2 gap-4 mb-4'>
|
||||
<InputText
|
||||
name="nomResponsable"
|
||||
type="text"
|
||||
label={t('lastname')}
|
||||
value={item.last_name}
|
||||
onChange={(event) => {onGuardiansChange(item.id, "last_name", event.target.value)}}
|
||||
errorMsg={getError(index, 'last_name')}
|
||||
required
|
||||
/>
|
||||
<InputText
|
||||
name="prenomResponsable"
|
||||
type="text"
|
||||
label={t('firstname')}
|
||||
value={item.first_name}
|
||||
onChange={(event) => {onGuardiansChange(item.id, "first_name", event.target.value)}}
|
||||
errorMsg={getError(index, 'first_name')}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||
<InputText
|
||||
name="mailResponsable"
|
||||
type="email"
|
||||
label={t('email')}
|
||||
value={item.associated_profile_email}
|
||||
onChange={(event) => {
|
||||
onGuardiansChange(
|
||||
item.id,
|
||||
'associated_profile_email',
|
||||
event.target.value
|
||||
);
|
||||
}}
|
||||
required
|
||||
errorMsg={getError(index, 'email')}
|
||||
/>
|
||||
<InputPhone
|
||||
name="telephoneResponsable"
|
||||
label={t('phone')}
|
||||
value={item.phone}
|
||||
onChange={(event) => {
|
||||
onGuardiansChange(item.id, 'phone', event);
|
||||
}}
|
||||
required
|
||||
errorMsg={getError(index, 'phone')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='grid grid-cols-1 md:grid-cols-2 gap-4 mb-4'>
|
||||
<InputText
|
||||
name="mailResponsable"
|
||||
type="email"
|
||||
label={t('email')}
|
||||
value={item.associated_profile_email}
|
||||
onChange={(event) => {onGuardiansChange(item.id, "associated_profile_email", event.target.value)}}
|
||||
required
|
||||
errorMsg={getError(index, 'email')}
|
||||
/>
|
||||
<InputPhone
|
||||
name="telephoneResponsable"
|
||||
label={t('phone')}
|
||||
value={item.phone}
|
||||
onChange={(event) => {onGuardiansChange(item.id, "phone", event)}}
|
||||
required
|
||||
errorMsg={getError(index, 'phone')}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||
<InputText
|
||||
name="dateNaissanceResponsable"
|
||||
type="date"
|
||||
label={t('birthdate')}
|
||||
value={item.birth_date}
|
||||
onChange={(event) => {
|
||||
onGuardiansChange(item.id, 'birth_date', event.target.value);
|
||||
}}
|
||||
required
|
||||
errorMsg={getError(index, 'birth_date')}
|
||||
/>
|
||||
<InputText
|
||||
name="professionResponsable"
|
||||
type="text"
|
||||
label={t('profession')}
|
||||
value={item.profession}
|
||||
onChange={(event) => {
|
||||
onGuardiansChange(item.id, 'profession', event.target.value);
|
||||
}}
|
||||
required
|
||||
errorMsg={getError(index, 'profession')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='grid grid-cols-1 md:grid-cols-2 gap-4 mb-4'>
|
||||
<InputText
|
||||
name="dateNaissanceResponsable"
|
||||
type="date"
|
||||
label={t('birthdate')}
|
||||
value={item.birth_date}
|
||||
onChange={(event) => {onGuardiansChange(item.id, "birth_date", event.target.value)}}
|
||||
required
|
||||
errorMsg={getError(index, 'birth_date')}
|
||||
/>
|
||||
<InputText
|
||||
name="professionResponsable"
|
||||
type="text"
|
||||
label={t('profession')}
|
||||
value={item.profession}
|
||||
onChange={(event) => {onGuardiansChange(item.id, "profession", event.target.value)}}
|
||||
required
|
||||
errorMsg={getError(index, 'profession')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='grid grid-cols-1 gap-4'>
|
||||
<InputText
|
||||
name="adresseResponsable"
|
||||
type="text"
|
||||
label={t('address')}
|
||||
value={item.address}
|
||||
onChange={(event) => {onGuardiansChange(item.id, "address", event.target.value)}}
|
||||
required
|
||||
errorMsg={getError(index, 'address')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<div className="flex justify-center">
|
||||
<Plus
|
||||
className="w-8 h-8 text-green-500 cursor-pointer hover:text-green-700 transition-colors border-2 border-green-500 hover:border-green-700 rounded-full p-1"
|
||||
onClick={(e) => addGuardian(e)}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
<InputText
|
||||
name="adresseResponsable"
|
||||
type="text"
|
||||
label={t('address')}
|
||||
value={item.address}
|
||||
onChange={(event) => {
|
||||
onGuardiansChange(item.id, 'address', event.target.value);
|
||||
}}
|
||||
required
|
||||
errorMsg={getError(index, 'address')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
))}
|
||||
|
||||
<div className="flex justify-center">
|
||||
<Plus
|
||||
className="w-8 h-8 text-green-500 cursor-pointer hover:text-green-700 transition-colors border-2 border-green-500 hover:border-green-700 rounded-full p-1"
|
||||
onClick={(e) => addGuardian(e)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -5,176 +5,190 @@ import ResponsableInputFields from '@/components/Inscription/ResponsableInputFie
|
||||
import PaymentMethodSelector from '@/components/Inscription/PaymentMethodSelector';
|
||||
|
||||
const levels = [
|
||||
{ value:'1', label: 'TPS - Très Petite Section'},
|
||||
{ value:'2', label: 'PS - Petite Section'},
|
||||
{ value:'3', label: 'MS - Moyenne Section'},
|
||||
{ value:'4', label: 'GS - Grande Section'},
|
||||
{ value: '1', label: 'TPS - Très Petite Section' },
|
||||
{ value: '2', label: 'PS - Petite Section' },
|
||||
{ value: '3', label: 'MS - Moyenne Section' },
|
||||
{ value: '4', label: 'GS - Grande Section' },
|
||||
];
|
||||
|
||||
const paymentModesOptions = [
|
||||
{ id: 1, name: 'Prélèvement SEPA' },
|
||||
{ id: 2, name: 'Virement' },
|
||||
{ id: 3, name: 'Chèque' },
|
||||
{ id: 4, name: 'Espèce' },
|
||||
];
|
||||
{ id: 1, name: 'Prélèvement SEPA' },
|
||||
{ id: 2, name: 'Virement' },
|
||||
{ id: 3, name: 'Chèque' },
|
||||
{ id: 4, name: 'Espèce' },
|
||||
];
|
||||
|
||||
// Fonction de validation pour vérifier les champs requis
|
||||
export function validateStudentInfo(formData) {
|
||||
const requiredFields = [
|
||||
'last_name',
|
||||
'first_name',
|
||||
'nationality',
|
||||
'birth_date',
|
||||
'birth_place',
|
||||
'birth_postal_code',
|
||||
'address',
|
||||
'attending_physician',
|
||||
'level',
|
||||
];
|
||||
const requiredFields = [
|
||||
'last_name',
|
||||
'first_name',
|
||||
'nationality',
|
||||
'birth_date',
|
||||
'birth_place',
|
||||
'birth_postal_code',
|
||||
'address',
|
||||
'attending_physician',
|
||||
'level',
|
||||
];
|
||||
|
||||
const isValid = requiredFields.every((field) => {
|
||||
const value = formData[field];
|
||||
return typeof value === 'string' ? value.trim() !== '' : Boolean(value);
|
||||
});
|
||||
const isValid = requiredFields.every((field) => {
|
||||
const value = formData[field];
|
||||
return typeof value === 'string' ? value.trim() !== '' : Boolean(value);
|
||||
});
|
||||
|
||||
return isValid;
|
||||
return isValid;
|
||||
}
|
||||
|
||||
export default function StudentInfoForm({ formData, updateFormField, guardians, setGuardians, registrationPaymentModes, tuitionPaymentModes, errors }) {
|
||||
const getError = (field) => {
|
||||
return errors?.student?.[field]?.[0];
|
||||
};
|
||||
export default function StudentInfoForm({
|
||||
formData,
|
||||
updateFormField,
|
||||
guardians,
|
||||
setGuardians,
|
||||
registrationPaymentModes,
|
||||
tuitionPaymentModes,
|
||||
errors,
|
||||
}) {
|
||||
const getError = (field) => {
|
||||
return errors?.student?.[field]?.[0];
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
|
||||
<h2 className="text-xl font-bold mb-4 text-gray-800">Informations de l'élève</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<InputText
|
||||
name="last_name"
|
||||
label="Nom"
|
||||
value={formData.last_name}
|
||||
onChange={(e) => updateFormField('last_name', e.target.value)}
|
||||
required
|
||||
errorMsg={getError('last_name')}
|
||||
/>
|
||||
<InputText
|
||||
name="first_name"
|
||||
label="Prénom"
|
||||
value={formData.first_name}
|
||||
onChange={(e) => updateFormField('first_name', e.target.value)}
|
||||
errorMsg={getError('first_name')}
|
||||
required
|
||||
/>
|
||||
<InputText
|
||||
name="nationality"
|
||||
label="Nationalité"
|
||||
value={formData.nationality}
|
||||
required
|
||||
onChange={(e) => updateFormField('nationality', e.target.value)}
|
||||
/>
|
||||
<InputText
|
||||
name="birth_date"
|
||||
type="date"
|
||||
label="Date de Naissance"
|
||||
value={formData.birth_date}
|
||||
onChange={(e) => updateFormField('birth_date', e.target.value)}
|
||||
required
|
||||
errorMsg={getError('birth_date')}
|
||||
/>
|
||||
<InputText
|
||||
name="birth_place"
|
||||
label="Lieu de Naissance"
|
||||
value={formData.birth_place}
|
||||
onChange={(e) => updateFormField('birth_place', e.target.value)}
|
||||
required
|
||||
errorMsg={getError('birth_place')}
|
||||
/>
|
||||
<InputText
|
||||
name="birth_postal_code"
|
||||
label="Code Postal de Naissance"
|
||||
value={formData.birth_postal_code}
|
||||
onChange={(e) => updateFormField('birth_postal_code', e.target.value)}
|
||||
required
|
||||
errorMsg={getError('birth_postal_code')}
|
||||
/>
|
||||
<div className="md:col-span-2">
|
||||
<InputText
|
||||
name="address"
|
||||
label="Adresse"
|
||||
value={formData.address}
|
||||
onChange={(e) => updateFormField('address', e.target.value)}
|
||||
required
|
||||
errorMsg={getError('address')}
|
||||
/>
|
||||
</div>
|
||||
<InputText
|
||||
name="attending_physician"
|
||||
label="Médecin Traitant"
|
||||
value={formData.attending_physician}
|
||||
onChange={(e) => updateFormField('attending_physician', e.target.value)}
|
||||
required
|
||||
errorMsg={getError('attending_physician')}
|
||||
/>
|
||||
<SelectChoice
|
||||
name="level"
|
||||
label="Niveau"
|
||||
placeHolder="Sélectionner un niveau"
|
||||
selected={formData.level}
|
||||
callback={(e) => updateFormField('level', e.target.value)}
|
||||
choices={levels}
|
||||
required
|
||||
errorMsg={getError('level')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
|
||||
<h2 className="text-xl font-bold mb-4 text-gray-800">Responsables</h2>
|
||||
<ResponsableInputFields
|
||||
guardians={guardians}
|
||||
onGuardiansChange={(id, field, value) => {
|
||||
const updatedGuardians = guardians.map(resp =>
|
||||
resp.id === id ? { ...resp, [field]: value } : resp
|
||||
);
|
||||
setGuardians(updatedGuardians);
|
||||
}}
|
||||
addGuardian={(e) => {
|
||||
e.preventDefault();
|
||||
setGuardians([...guardians, { id: Date.now() }]);
|
||||
}}
|
||||
deleteGuardian={(index) => {
|
||||
const newArray = [...guardians];
|
||||
newArray.splice(index, 1);
|
||||
setGuardians(newArray);
|
||||
}}
|
||||
errors={errors?.student?.guardians || []}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<PaymentMethodSelector
|
||||
formData={formData}
|
||||
title="Frais d'inscription"
|
||||
name="registration_payment"
|
||||
updateFormField={updateFormField}
|
||||
selected={formData.registration_payment}
|
||||
paymentModes={registrationPaymentModes}
|
||||
paymentModesOptions={paymentModesOptions}
|
||||
amount={formData.totalRegistrationFees}
|
||||
getError={getError}
|
||||
return (
|
||||
<>
|
||||
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
|
||||
<h2 className="text-xl font-bold mb-4 text-gray-800">
|
||||
Informations de l'élève
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<InputText
|
||||
name="last_name"
|
||||
label="Nom"
|
||||
value={formData.last_name}
|
||||
onChange={(e) => updateFormField('last_name', e.target.value)}
|
||||
required
|
||||
errorMsg={getError('last_name')}
|
||||
/>
|
||||
<InputText
|
||||
name="first_name"
|
||||
label="Prénom"
|
||||
value={formData.first_name}
|
||||
onChange={(e) => updateFormField('first_name', e.target.value)}
|
||||
errorMsg={getError('first_name')}
|
||||
required
|
||||
/>
|
||||
<InputText
|
||||
name="nationality"
|
||||
label="Nationalité"
|
||||
value={formData.nationality}
|
||||
required
|
||||
onChange={(e) => updateFormField('nationality', e.target.value)}
|
||||
/>
|
||||
<InputText
|
||||
name="birth_date"
|
||||
type="date"
|
||||
label="Date de Naissance"
|
||||
value={formData.birth_date}
|
||||
onChange={(e) => updateFormField('birth_date', e.target.value)}
|
||||
required
|
||||
errorMsg={getError('birth_date')}
|
||||
/>
|
||||
<InputText
|
||||
name="birth_place"
|
||||
label="Lieu de Naissance"
|
||||
value={formData.birth_place}
|
||||
onChange={(e) => updateFormField('birth_place', e.target.value)}
|
||||
required
|
||||
errorMsg={getError('birth_place')}
|
||||
/>
|
||||
<InputText
|
||||
name="birth_postal_code"
|
||||
label="Code Postal de Naissance"
|
||||
value={formData.birth_postal_code}
|
||||
onChange={(e) =>
|
||||
updateFormField('birth_postal_code', e.target.value)
|
||||
}
|
||||
required
|
||||
errorMsg={getError('birth_postal_code')}
|
||||
/>
|
||||
<div className="md:col-span-2">
|
||||
<InputText
|
||||
name="address"
|
||||
label="Adresse"
|
||||
value={formData.address}
|
||||
onChange={(e) => updateFormField('address', e.target.value)}
|
||||
required
|
||||
errorMsg={getError('address')}
|
||||
/>
|
||||
</div>
|
||||
<InputText
|
||||
name="attending_physician"
|
||||
label="Médecin Traitant"
|
||||
value={formData.attending_physician}
|
||||
onChange={(e) =>
|
||||
updateFormField('attending_physician', e.target.value)
|
||||
}
|
||||
required
|
||||
errorMsg={getError('attending_physician')}
|
||||
/>
|
||||
<SelectChoice
|
||||
name="level"
|
||||
label="Niveau"
|
||||
placeHolder="Sélectionner un niveau"
|
||||
selected={formData.level}
|
||||
callback={(e) => updateFormField('level', e.target.value)}
|
||||
choices={levels}
|
||||
required
|
||||
errorMsg={getError('level')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<PaymentMethodSelector
|
||||
formData={formData}
|
||||
title="Frais de scolarité"
|
||||
name="tuition_payment"
|
||||
updateFormField={updateFormField}
|
||||
selected={formData.tuition_payment}
|
||||
paymentModes={tuitionPaymentModes}
|
||||
paymentModesOptions={paymentModesOptions}
|
||||
amount={formData.totalTuitionFees}
|
||||
getError={getError}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
|
||||
<h2 className="text-xl font-bold mb-4 text-gray-800">Responsables</h2>
|
||||
<ResponsableInputFields
|
||||
guardians={guardians}
|
||||
onGuardiansChange={(id, field, value) => {
|
||||
const updatedGuardians = guardians.map((resp) =>
|
||||
resp.id === id ? { ...resp, [field]: value } : resp
|
||||
);
|
||||
setGuardians(updatedGuardians);
|
||||
}}
|
||||
addGuardian={(e) => {
|
||||
e.preventDefault();
|
||||
setGuardians([...guardians, { id: Date.now() }]);
|
||||
}}
|
||||
deleteGuardian={(index) => {
|
||||
const newArray = [...guardians];
|
||||
newArray.splice(index, 1);
|
||||
setGuardians(newArray);
|
||||
}}
|
||||
errors={errors?.student?.guardians || []}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<PaymentMethodSelector
|
||||
formData={formData}
|
||||
title="Frais d'inscription"
|
||||
name="registration_payment"
|
||||
updateFormField={updateFormField}
|
||||
selected={formData.registration_payment}
|
||||
paymentModes={registrationPaymentModes}
|
||||
paymentModesOptions={paymentModesOptions}
|
||||
amount={formData.totalRegistrationFees}
|
||||
getError={getError}
|
||||
/>
|
||||
|
||||
<PaymentMethodSelector
|
||||
formData={formData}
|
||||
title="Frais de scolarité"
|
||||
name="tuition_payment"
|
||||
updateFormField={updateFormField}
|
||||
selected={formData.tuition_payment}
|
||||
paymentModes={tuitionPaymentModes}
|
||||
paymentModesOptions={paymentModesOptions}
|
||||
amount={formData.totalTuitionFees}
|
||||
getError={getError}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
'use client'
|
||||
'use client';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { DocusealBuilder } from '@docuseal/react';
|
||||
import Button from '@/components/Button';
|
||||
@ -7,7 +7,14 @@ import { generateToken } from '@/app/actions/registerFileGroupAction';
|
||||
import logger from '@/utils/logger';
|
||||
import { GraduationCap, CloudUpload } from 'lucide-react';
|
||||
|
||||
export default function ValidateSubscription({ studentId, firstName, lastName, paymentMode, file, onAccept }) {
|
||||
export default function ValidateSubscription({
|
||||
studentId,
|
||||
firstName,
|
||||
lastName,
|
||||
paymentMode,
|
||||
file,
|
||||
onAccept,
|
||||
}) {
|
||||
const [token, setToken] = useState(null);
|
||||
const [uploadedFileName, setUploadedFileName] = useState('');
|
||||
const [pdfUrl, setPdfUrl] = useState(`${BASE_URL}/${file}`);
|
||||
@ -20,7 +27,9 @@ export default function ValidateSubscription({ studentId, firstName, lastName, p
|
||||
.then((data) => {
|
||||
setToken(data.token);
|
||||
})
|
||||
.catch((error) => logger.error('Erreur lors de la génération du token:', error));
|
||||
.catch((error) =>
|
||||
logger.error('Erreur lors de la génération du token:', error)
|
||||
);
|
||||
}
|
||||
}, [isSepa]);
|
||||
|
||||
@ -32,23 +41,23 @@ export default function ValidateSubscription({ studentId, firstName, lastName, p
|
||||
const handleAccept = () => {
|
||||
const fileInput = document.getElementById('fileInput'); // Récupère l'élément input
|
||||
const file = fileInput?.files[0]; // Récupère le fichier sélectionné
|
||||
|
||||
|
||||
if (!file) {
|
||||
logger.error('Aucun fichier sélectionné pour le champ SEPA.');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const data = {
|
||||
status: 7,
|
||||
status: 7,
|
||||
sepa_file: file,
|
||||
};
|
||||
|
||||
|
||||
// Appeler la fonction passée par le parent pour mettre à jour le RF
|
||||
onAccept(data);
|
||||
};
|
||||
|
||||
const handleRefuse = () => {
|
||||
logger.debug('Dossier refusé pour l\'étudiant:', studentId);
|
||||
logger.debug("Dossier refusé pour l'étudiant:", studentId);
|
||||
// Logique pour refuser l'inscription
|
||||
};
|
||||
|
||||
@ -75,10 +84,14 @@ export default function ValidateSubscription({ studentId, firstName, lastName, p
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-gray-800">
|
||||
Dossier scolaire de <span className="text-emerald-600">{firstName} {lastName}</span>
|
||||
Dossier scolaire de{' '}
|
||||
<span className="text-emerald-600">
|
||||
{firstName} {lastName}
|
||||
</span>
|
||||
</h1>
|
||||
<p className="text-sm text-gray-500 italic">
|
||||
Année scolaire {new Date().getFullYear()}-{new Date().getFullYear() + 1}
|
||||
Année scolaire {new Date().getFullYear()}-
|
||||
{new Date().getFullYear() + 1}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -101,45 +114,54 @@ export default function ValidateSubscription({ studentId, firstName, lastName, p
|
||||
|
||||
{currentPage === 2 && isSepa && (
|
||||
<div className="border p-4 rounded-md shadow-md">
|
||||
<h3 className="text-lg font-semibold mb-4">Sélection du mandat de pélèvement SEPA</h3>
|
||||
<h3 className="text-lg font-semibold mb-4">
|
||||
Sélection du mandat de pélèvement SEPA
|
||||
</h3>
|
||||
<div
|
||||
className="border-2 border-dashed border-gray-500 p-6 rounded-lg flex flex-col items-center justify-center cursor-pointer hover:border-emerald-500"
|
||||
onClick={() => document.getElementById('fileInput').click()} // Ouvre l'explorateur de fichiers au clic
|
||||
onDragOver={(e) => e.preventDefault()}
|
||||
onDrop={(e) => {
|
||||
e.preventDefault();
|
||||
const file = e.dataTransfer.files[0];
|
||||
if (file) {
|
||||
setUploadedFileName(file.name); // Stocke uniquement le nom du fichier
|
||||
logger.debug('Fichier déposé:', file.name);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<CloudUpload className="w-12 h-12 text-emerald-500 mb-4" /> {/* Icône de cloud */}
|
||||
<input
|
||||
type="file"
|
||||
accept=".pdf"
|
||||
onChange={(e) => {
|
||||
const file = e.target.files[0];
|
||||
if (file) {
|
||||
setUploadedFileName(file.name); // Stocke uniquement le nom du fichier
|
||||
logger.debug('Fichier sélectionné:', file.name);
|
||||
}
|
||||
}}
|
||||
className="hidden"
|
||||
id="fileInput"
|
||||
/>
|
||||
<label htmlFor="fileInput" className="text-center text-gray-500">
|
||||
<p className="text-lg font-semibold text-gray-800">Déposez votre fichier ici</p>
|
||||
<p className="text-sm text-gray-500 mt-2">ou cliquez pour sélectionner un fichier PDF</p>
|
||||
</label>
|
||||
</div>
|
||||
className="border-2 border-dashed border-gray-500 p-6 rounded-lg flex flex-col items-center justify-center cursor-pointer hover:border-emerald-500"
|
||||
onClick={() => document.getElementById('fileInput').click()} // Ouvre l'explorateur de fichiers au clic
|
||||
onDragOver={(e) => e.preventDefault()}
|
||||
onDrop={(e) => {
|
||||
e.preventDefault();
|
||||
const file = e.dataTransfer.files[0];
|
||||
if (file) {
|
||||
setUploadedFileName(file.name); // Stocke uniquement le nom du fichier
|
||||
logger.debug('Fichier déposé:', file.name);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<CloudUpload className="w-12 h-12 text-emerald-500 mb-4" />{' '}
|
||||
{/* Icône de cloud */}
|
||||
<input
|
||||
type="file"
|
||||
accept=".pdf"
|
||||
onChange={(e) => {
|
||||
const file = e.target.files[0];
|
||||
if (file) {
|
||||
setUploadedFileName(file.name); // Stocke uniquement le nom du fichier
|
||||
logger.debug('Fichier sélectionné:', file.name);
|
||||
}
|
||||
}}
|
||||
className="hidden"
|
||||
id="fileInput"
|
||||
/>
|
||||
<label htmlFor="fileInput" className="text-center text-gray-500">
|
||||
<p className="text-lg font-semibold text-gray-800">
|
||||
Déposez votre fichier ici
|
||||
</p>
|
||||
<p className="text-sm text-gray-500 mt-2">
|
||||
ou cliquez pour sélectionner un fichier PDF
|
||||
</p>
|
||||
</label>
|
||||
</div>
|
||||
{uploadedFileName && (
|
||||
<div className="mt-4 flex items-center space-x-4 bg-gray-100 p-3 rounded-md shadow-sm">
|
||||
<CloudUpload className="w-6 h-6 text-emerald-500" />
|
||||
<p className="text-sm font-medium text-gray-800"><span className="font-semibold">{uploadedFileName}</span></p>
|
||||
</div>
|
||||
)}
|
||||
<div className="mt-4 flex items-center space-x-4 bg-gray-100 p-3 rounded-md shadow-sm">
|
||||
<CloudUpload className="w-6 h-6 text-emerald-500" />
|
||||
<p className="text-sm font-medium text-gray-800">
|
||||
<span className="font-semibold">{uploadedFileName}</span>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -172,4 +194,4 @@ export default function ValidateSubscription({ studentId, firstName, lastName, p
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,7 +5,12 @@ import logoImage from '@/img/logo_min.svg'; // Assurez-vous que le chemin vers l
|
||||
const Logo = ({ className }) => {
|
||||
return (
|
||||
<div className={`max-w-[150px] ${className}`}>
|
||||
<Image src={logoImage} alt="Logo" style={{ width: 'auto', height: 'auto'}} priority />
|
||||
<Image
|
||||
src={logoImage}
|
||||
alt="Logo"
|
||||
style={{ width: 'auto', height: 'auto' }}
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,36 +1,55 @@
|
||||
import * as Dialog from '@radix-ui/react-dialog';
|
||||
|
||||
const Modal = ({ isOpen, setIsOpen, title, ContentComponent, modalClassName }) => {
|
||||
return (
|
||||
<Dialog.Root open={isOpen} onOpenChange={setIsOpen}>
|
||||
<Dialog.Portal>
|
||||
<Dialog.Overlay className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
|
||||
<Dialog.Content className="fixed inset-0 flex items-center justify-center p-4">
|
||||
<div className={`inline-block bg-white rounded-lg px-6 py-5 text-left shadow-xl transform transition-all sm:my-8 ${modalClassName ? modalClassName : 'min-w-[500px] m-12 w-max max-h-[80vh] h-full'}`}>
|
||||
<div className="flex justify-between items-start mb-4">
|
||||
<Dialog.Title className="text-xl font-medium text-gray-900">
|
||||
{title}
|
||||
</Dialog.Title>
|
||||
<Dialog.Close asChild>
|
||||
<button
|
||||
onClick={() => setIsOpen(false)}
|
||||
className="text-gray-400 hover:text-gray-500 ml-4 focus:outline-none"
|
||||
>
|
||||
<span className="sr-only">Fermer</span>
|
||||
<svg className="h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</Dialog.Close>
|
||||
</div>
|
||||
<div className="w-full h-full">
|
||||
<ContentComponent />
|
||||
</div>
|
||||
</div>
|
||||
</Dialog.Content>
|
||||
</Dialog.Portal>
|
||||
</Dialog.Root>
|
||||
);
|
||||
const Modal = ({
|
||||
isOpen,
|
||||
setIsOpen,
|
||||
title,
|
||||
ContentComponent,
|
||||
modalClassName,
|
||||
}) => {
|
||||
return (
|
||||
<Dialog.Root open={isOpen} onOpenChange={setIsOpen}>
|
||||
<Dialog.Portal>
|
||||
<Dialog.Overlay className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
|
||||
<Dialog.Content className="fixed inset-0 flex items-center justify-center p-4">
|
||||
<div
|
||||
className={`inline-block bg-white rounded-lg px-6 py-5 text-left shadow-xl transform transition-all sm:my-8 ${modalClassName ? modalClassName : 'min-w-[500px] m-12 w-max max-h-[80vh] h-full'}`}
|
||||
>
|
||||
<div className="flex justify-between items-start mb-4">
|
||||
<Dialog.Title className="text-xl font-medium text-gray-900">
|
||||
{title}
|
||||
</Dialog.Title>
|
||||
<Dialog.Close asChild>
|
||||
<button
|
||||
onClick={() => setIsOpen(false)}
|
||||
className="text-gray-400 hover:text-gray-500 ml-4 focus:outline-none"
|
||||
>
|
||||
<span className="sr-only">Fermer</span>
|
||||
<svg
|
||||
className="h-6 w-6"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</Dialog.Close>
|
||||
</div>
|
||||
<div className="w-full h-full">
|
||||
<ContentComponent />
|
||||
</div>
|
||||
</div>
|
||||
</Dialog.Content>
|
||||
</Dialog.Portal>
|
||||
</Dialog.Root>
|
||||
);
|
||||
};
|
||||
|
||||
export default Modal;
|
||||
|
||||
@ -1,15 +1,26 @@
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { Check, ChevronDown } from 'lucide-react';
|
||||
|
||||
const MultiSelect = ({ name, label, options, selectedOptions, onChange, errorMsg }) => {
|
||||
const MultiSelect = ({
|
||||
name,
|
||||
label,
|
||||
options,
|
||||
selectedOptions,
|
||||
onChange,
|
||||
errorMsg,
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const containerRef = useRef(null);
|
||||
|
||||
const handleSelect = (option) => {
|
||||
const isSelected = selectedOptions.some(selected => selected.id === option.id);
|
||||
const isSelected = selectedOptions.some(
|
||||
(selected) => selected.id === option.id
|
||||
);
|
||||
let newSelectedOptions;
|
||||
if (isSelected) {
|
||||
newSelectedOptions = selectedOptions.filter(selected => selected.id !== option.id);
|
||||
newSelectedOptions = selectedOptions.filter(
|
||||
(selected) => selected.id !== option.id
|
||||
);
|
||||
} else {
|
||||
newSelectedOptions = [...selectedOptions, option];
|
||||
}
|
||||
@ -39,8 +50,11 @@ const MultiSelect = ({ name, label, options, selectedOptions, onChange, errorMsg
|
||||
>
|
||||
{selectedOptions.length > 0 ? (
|
||||
<div className="flex flex-wrap gap-1 justify-center items-center">
|
||||
{selectedOptions.map(option => (
|
||||
<span key={option.id} className="bg-emerald-100 text-emerald-700 px-2 py-1 rounded-md text-sm">
|
||||
{selectedOptions.map((option) => (
|
||||
<span
|
||||
key={option.id}
|
||||
className="bg-emerald-100 text-emerald-700 px-2 py-1 rounded-md text-sm"
|
||||
>
|
||||
{option.name}
|
||||
</span>
|
||||
))}
|
||||
@ -52,18 +66,24 @@ const MultiSelect = ({ name, label, options, selectedOptions, onChange, errorMsg
|
||||
</button>
|
||||
{isOpen && (
|
||||
<ul className="absolute z-10 mt-1 w-full bg-white shadow-lg max-h-60 rounded-md py-1 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm">
|
||||
{options.map(option => (
|
||||
{options.map((option) => (
|
||||
<li
|
||||
key={option.id}
|
||||
className={`cursor-pointer select-none relative py-2 pl-3 pr-9 ${
|
||||
selectedOptions.some(selected => selected.id === option.id) ? 'text-white bg-emerald-600' : 'text-gray-900 hover:bg-emerald-100 hover:text-emerald-900'
|
||||
selectedOptions.some((selected) => selected.id === option.id)
|
||||
? 'text-white bg-emerald-600'
|
||||
: 'text-gray-900 hover:bg-emerald-100 hover:text-emerald-900'
|
||||
}`}
|
||||
onClick={() => handleSelect(option)}
|
||||
>
|
||||
<span className={`block truncate ${selectedOptions.some(selected => selected.id === option.id) ? 'font-semibold' : 'font-normal'}`}>
|
||||
<span
|
||||
className={`block truncate ${selectedOptions.some((selected) => selected.id === option.id) ? 'font-semibold' : 'font-normal'}`}
|
||||
>
|
||||
{option.name}
|
||||
</span>
|
||||
{selectedOptions.some(selected => selected.id === option.id) && (
|
||||
{selectedOptions.some(
|
||||
(selected) => selected.id === option.id
|
||||
) && (
|
||||
<span className="absolute inset-y-0 right-0 flex items-center pr-4 text-white">
|
||||
<Check className="h-5 w-5" />
|
||||
</span>
|
||||
@ -78,4 +98,4 @@ const MultiSelect = ({ name, label, options, selectedOptions, onChange, errorMsg
|
||||
);
|
||||
};
|
||||
|
||||
export default MultiSelect;
|
||||
export default MultiSelect;
|
||||
|
||||
@ -1,45 +1,57 @@
|
||||
import React from 'react';
|
||||
import {useTranslations} from 'next-intl';
|
||||
import { useTranslations } from 'next-intl';
|
||||
|
||||
const Pagination = ({ currentPage, totalPages, onPageChange }) => {
|
||||
const t = useTranslations('pagination');
|
||||
const pages = Array.from({ length: totalPages }, (_, i) => i + 1);
|
||||
|
||||
return (
|
||||
|
||||
<div className="px-6 py-4 border-t border-gray-200 flex items-center justify-between">
|
||||
<div className="text-sm text-gray-600">{t('page')} {currentPage} {t('of')} {pages.length}</div>
|
||||
<div className="text-sm text-gray-600">
|
||||
{t('page')} {currentPage} {t('of')} {pages.length}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{currentPage > 1 && (
|
||||
<PaginationButton text={t('previous')} onClick={() => onPageChange(currentPage - 1)}/>
|
||||
)}
|
||||
{pages.map((page) => (
|
||||
<PaginationNumber
|
||||
key={page}
|
||||
number={page}
|
||||
active={page === currentPage}
|
||||
onClick={() => onPageChange(page)}
|
||||
/>
|
||||
|
||||
))}
|
||||
{currentPage < totalPages && (
|
||||
<PaginationButton text={t('next')} onClick={() => onPageChange(currentPage + 1)} />
|
||||
)}
|
||||
{currentPage > 1 && (
|
||||
<PaginationButton
|
||||
text={t('previous')}
|
||||
onClick={() => onPageChange(currentPage - 1)}
|
||||
/>
|
||||
)}
|
||||
{pages.map((page) => (
|
||||
<PaginationNumber
|
||||
key={page}
|
||||
number={page}
|
||||
active={page === currentPage}
|
||||
onClick={() => onPageChange(page)}
|
||||
/>
|
||||
))}
|
||||
{currentPage < totalPages && (
|
||||
<PaginationButton
|
||||
text={t('next')}
|
||||
onClick={() => onPageChange(currentPage + 1)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const PaginationButton = ({ text , onClick}) => (
|
||||
<button className="px-3 py-1 text-sm text-gray-600 hover:bg-gray-50 rounded" onClick={onClick}>
|
||||
const PaginationButton = ({ text, onClick }) => (
|
||||
<button
|
||||
className="px-3 py-1 text-sm text-gray-600 hover:bg-gray-50 rounded"
|
||||
onClick={onClick}
|
||||
>
|
||||
{text}
|
||||
</button>
|
||||
);
|
||||
|
||||
const PaginationNumber = ({ number, active , onClick}) => (
|
||||
<button className={`w-8 h-8 flex items-center justify-center rounded ${
|
||||
active ? 'bg-emerald-500 text-white' : 'text-gray-600 hover:bg-gray-50'
|
||||
}`} onClick={onClick}>
|
||||
const PaginationNumber = ({ number, active, onClick }) => (
|
||||
<button
|
||||
className={`w-8 h-8 flex items-center justify-center rounded ${
|
||||
active ? 'bg-emerald-500 text-white' : 'text-gray-600 hover:bg-gray-50'
|
||||
}`}
|
||||
onClick={onClick}
|
||||
>
|
||||
{number}
|
||||
</button>
|
||||
);
|
||||
|
||||
@ -8,12 +8,19 @@ const paymentModesOptions = [
|
||||
{ id: 4, name: 'Espèce' },
|
||||
];
|
||||
|
||||
const PaymentModeSelector = ({ paymentModes, setPaymentModes, handleEdit, type }) => {
|
||||
const PaymentModeSelector = ({
|
||||
paymentModes,
|
||||
setPaymentModes,
|
||||
handleEdit,
|
||||
type,
|
||||
}) => {
|
||||
const [activePaymentModes, setActivePaymentModes] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
// Initialiser activePaymentModes avec les modes dont is_active est à true
|
||||
const activeModes = paymentModes.filter(mode => mode.is_active).map(mode => mode.mode);
|
||||
const activeModes = paymentModes
|
||||
.filter((mode) => mode.is_active)
|
||||
.map((mode) => mode.mode);
|
||||
setActivePaymentModes(activeModes);
|
||||
}, [paymentModes]);
|
||||
|
||||
@ -24,9 +31,12 @@ const PaymentModeSelector = ({ paymentModes, setPaymentModes, handleEdit, type }
|
||||
: [...prevActiveModes, modeId];
|
||||
|
||||
// Mettre à jour le mode de paiement dans le backend
|
||||
const updatedMode = paymentModes.find(mode => mode.mode === modeId);
|
||||
const updatedMode = paymentModes.find((mode) => mode.mode === modeId);
|
||||
if (updatedMode) {
|
||||
handleEdit(updatedMode.id, { ...updatedMode, is_active: !updatedMode.is_active });
|
||||
handleEdit(updatedMode.id, {
|
||||
...updatedMode,
|
||||
is_active: !updatedMode.is_active,
|
||||
});
|
||||
}
|
||||
|
||||
return newActiveModes;
|
||||
@ -59,4 +69,4 @@ const PaymentModeSelector = ({ paymentModes, setPaymentModes, handleEdit, type }
|
||||
);
|
||||
};
|
||||
|
||||
export default PaymentModeSelector;
|
||||
export default PaymentModeSelector;
|
||||
|
||||
@ -12,24 +12,33 @@ const paymentPlansOptions = [
|
||||
{ id: 3, name: '12 fois', frequency: 12 },
|
||||
];
|
||||
|
||||
const PaymentPlanSelector = ({ paymentPlans, setPaymentPlans, handleEdit, type }) => {
|
||||
const PaymentPlanSelector = ({
|
||||
paymentPlans,
|
||||
setPaymentPlans,
|
||||
handleEdit,
|
||||
type,
|
||||
}) => {
|
||||
const [dates, setDates] = useState({});
|
||||
const [selectedFrequency, setSelectedFrequency] = useState(null);
|
||||
const [activeFrequencies, setActiveFrequencies] = useState([]);
|
||||
const [defaultDay, setDefaultDay] = useState('-');
|
||||
const [isDefaultDayModified, setIsDefaultDayModified] = useState(false);
|
||||
const [popupVisible, setPopupVisible] = useState(false);
|
||||
const [popupMessage, setPopupMessage] = useState("");
|
||||
const [popupMessage, setPopupMessage] = useState('');
|
||||
const [errorMsg, setErrorMsg] = useState('');
|
||||
const [resetModifiedDates, setResetModifiedDates] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (paymentPlans && paymentPlans.length > 0) {
|
||||
const activePlans = paymentPlans.filter(plan => plan.is_active);
|
||||
const frequencies = activePlans.map(plan => {
|
||||
const paymentPlanOption = paymentPlansOptions.find(p => p.frequency === plan.frequency);
|
||||
return paymentPlanOption ? paymentPlanOption.id : null;
|
||||
}).filter(id => id !== null);
|
||||
const activePlans = paymentPlans.filter((plan) => plan.is_active);
|
||||
const frequencies = activePlans
|
||||
.map((plan) => {
|
||||
const paymentPlanOption = paymentPlansOptions.find(
|
||||
(p) => p.frequency === plan.frequency
|
||||
);
|
||||
return paymentPlanOption ? paymentPlanOption.id : null;
|
||||
})
|
||||
.filter((id) => id !== null);
|
||||
setActiveFrequencies(frequencies);
|
||||
|
||||
if (activePlans.length > 0) {
|
||||
@ -38,8 +47,10 @@ const PaymentPlanSelector = ({ paymentPlans, setPaymentPlans, handleEdit, type }
|
||||
}
|
||||
|
||||
const initialDates = {};
|
||||
paymentPlans.forEach(plan => {
|
||||
const paymentPlanOption = paymentPlansOptions.find(p => p.frequency === plan.frequency);
|
||||
paymentPlans.forEach((plan) => {
|
||||
const paymentPlanOption = paymentPlansOptions.find(
|
||||
(p) => p.frequency === plan.frequency
|
||||
);
|
||||
if (paymentPlanOption) {
|
||||
initialDates[paymentPlanOption.id] = plan.due_dates;
|
||||
}
|
||||
@ -55,8 +66,8 @@ const PaymentPlanSelector = ({ paymentPlans, setPaymentPlans, handleEdit, type }
|
||||
const updateDefaultDay = () => {
|
||||
const currentDates = dates[selectedFrequency];
|
||||
if (currentDates && currentDates.length > 0) {
|
||||
const days = currentDates.map(date => new Date(date).getDate());
|
||||
const allSameDay = days.every(day => day === days[0]);
|
||||
const days = currentDates.map((date) => new Date(date).getDate());
|
||||
const allSameDay = days.every((day) => day === days[0]);
|
||||
if (allSameDay) {
|
||||
setDefaultDay(days[0]);
|
||||
} else {
|
||||
@ -69,32 +80,44 @@ const PaymentPlanSelector = ({ paymentPlans, setPaymentPlans, handleEdit, type }
|
||||
};
|
||||
|
||||
const handleActivationChange = (value) => {
|
||||
const selectedPlan = paymentPlans.find(plan => plan.frequency === paymentPlansOptions.find(p => p.id === value)?.frequency);
|
||||
const selectedPlan = paymentPlans.find(
|
||||
(plan) =>
|
||||
plan.frequency ===
|
||||
paymentPlansOptions.find((p) => p.id === value)?.frequency
|
||||
);
|
||||
if (!selectedPlan) return;
|
||||
|
||||
const updatedData = {
|
||||
...selectedPlan,
|
||||
is_active: !selectedPlan.is_active
|
||||
is_active: !selectedPlan.is_active,
|
||||
};
|
||||
|
||||
handleEdit(selectedPlan.id, updatedData)
|
||||
.then(() => {
|
||||
setPaymentPlans(prevPlans => prevPlans.map(plan =>
|
||||
plan.id === selectedPlan.id ? { ...plan, is_active: updatedData.is_active } : plan
|
||||
));
|
||||
setActiveFrequencies(prevFrequencies => {
|
||||
setPaymentPlans((prevPlans) =>
|
||||
prevPlans.map((plan) =>
|
||||
plan.id === selectedPlan.id
|
||||
? { ...plan, is_active: updatedData.is_active }
|
||||
: plan
|
||||
)
|
||||
);
|
||||
setActiveFrequencies((prevFrequencies) => {
|
||||
if (updatedData.is_active) {
|
||||
setPopupMessage(`L'option de paiement en ${paymentPlansOptions.find(p => p.id === value).name} a été activée.`);
|
||||
setPopupMessage(
|
||||
`L'option de paiement en ${paymentPlansOptions.find((p) => p.id === value).name} a été activée.`
|
||||
);
|
||||
setPopupVisible(true);
|
||||
return [...prevFrequencies, value];
|
||||
} else {
|
||||
setPopupMessage(`L'option de paiement en ${paymentPlansOptions.find(p => p.id === value).name} a été désactivée.`);
|
||||
setPopupMessage(
|
||||
`L'option de paiement en ${paymentPlansOptions.find((p) => p.id === value).name} a été désactivée.`
|
||||
);
|
||||
setPopupVisible(true);
|
||||
return prevFrequencies.filter(item => item !== value);
|
||||
return prevFrequencies.filter((item) => item !== value);
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
});
|
||||
};
|
||||
@ -106,18 +129,21 @@ const PaymentPlanSelector = ({ paymentPlans, setPaymentPlans, handleEdit, type }
|
||||
} else {
|
||||
setSelectedFrequency(value);
|
||||
if (!dates[value]) {
|
||||
const frequencyValue = paymentPlansOptions.find(plan => plan.id === value)?.frequency || 1;
|
||||
const newDates = Array(frequencyValue).fill('').map((_, index) => {
|
||||
const newDate = new Date();
|
||||
newDate.setDate(defaultDay);
|
||||
if (value === 1) {
|
||||
newDate.setMonth(newDate.getMonth() + index * 4); // Espacer de 4 mois pour le paiement en 3 fois
|
||||
} else {
|
||||
newDate.setMonth(newDate.getMonth() + index);
|
||||
}
|
||||
return newDate.toISOString().split('T')[0];
|
||||
});
|
||||
setDates(prevDates => ({ ...prevDates, [value]: newDates }));
|
||||
const frequencyValue =
|
||||
paymentPlansOptions.find((plan) => plan.id === value)?.frequency || 1;
|
||||
const newDates = Array(frequencyValue)
|
||||
.fill('')
|
||||
.map((_, index) => {
|
||||
const newDate = new Date();
|
||||
newDate.setDate(defaultDay);
|
||||
if (value === 1) {
|
||||
newDate.setMonth(newDate.getMonth() + index * 4); // Espacer de 4 mois pour le paiement en 3 fois
|
||||
} else {
|
||||
newDate.setMonth(newDate.getMonth() + index);
|
||||
}
|
||||
return newDate.toISOString().split('T')[0];
|
||||
});
|
||||
setDates((prevDates) => ({ ...prevDates, [value]: newDates }));
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -154,30 +180,39 @@ const PaymentPlanSelector = ({ paymentPlans, setPaymentPlans, handleEdit, type }
|
||||
setTimeout(() => setResetModifiedDates(false), 0);
|
||||
|
||||
// Mettre à jour les dates d'échéance en fonction du jour sélectionné
|
||||
const updatedDates = dates[selectedFrequency].map(date => {
|
||||
const updatedDates = dates[selectedFrequency].map((date) => {
|
||||
const newDate = new Date(date);
|
||||
newDate.setDate(day);
|
||||
return newDate.toISOString().split('T')[0];
|
||||
});
|
||||
setDates(prevDates => ({ ...prevDates, [selectedFrequency]: updatedDates }));
|
||||
setDates((prevDates) => ({
|
||||
...prevDates,
|
||||
[selectedFrequency]: updatedDates,
|
||||
}));
|
||||
};
|
||||
|
||||
const handleSubmitDefaultDay = () => {
|
||||
const selectedPlan = paymentPlans.find(plan => plan.frequency === paymentPlansOptions.find(p => p.id === selectedFrequency)?.frequency);
|
||||
const selectedPlan = paymentPlans.find(
|
||||
(plan) =>
|
||||
plan.frequency ===
|
||||
paymentPlansOptions.find((p) => p.id === selectedFrequency)?.frequency
|
||||
);
|
||||
if (!selectedPlan) return;
|
||||
|
||||
const updatedData = {
|
||||
...selectedPlan,
|
||||
due_dates: dates[selectedFrequency]
|
||||
due_dates: dates[selectedFrequency],
|
||||
};
|
||||
|
||||
handleEdit(selectedPlan.id, updatedData)
|
||||
.then(() => {
|
||||
setPopupMessage(`Mise à jour des dates d'échéances effectuée avec succès`);
|
||||
.then(() => {
|
||||
setPopupMessage(
|
||||
`Mise à jour des dates d'échéances effectuée avec succès`
|
||||
);
|
||||
setPopupVisible(true);
|
||||
setIsDefaultDayModified(false);
|
||||
})
|
||||
.catch(error => {
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
});
|
||||
};
|
||||
@ -190,7 +225,9 @@ const PaymentPlanSelector = ({ paymentPlans, setPaymentPlans, handleEdit, type }
|
||||
const renderCell = (row, column) => {
|
||||
switch (column) {
|
||||
case 'OPTIONS':
|
||||
return <span className="text-sm font-medium text-gray-900">{row.name}</span>;
|
||||
return (
|
||||
<span className="text-sm font-medium text-gray-900">{row.name}</span>
|
||||
);
|
||||
case 'ACTIONS':
|
||||
return (
|
||||
<button
|
||||
@ -199,9 +236,17 @@ const PaymentPlanSelector = ({ paymentPlans, setPaymentPlans, handleEdit, type }
|
||||
e.stopPropagation();
|
||||
handleActivationChange(row.id);
|
||||
}}
|
||||
className={activeFrequencies.includes(row.id) ? 'text-emerald-500 hover:text-emerald-700' : 'text-orange-500 hover:text-orange-700'}
|
||||
className={
|
||||
activeFrequencies.includes(row.id)
|
||||
? 'text-emerald-500 hover:text-emerald-700'
|
||||
: 'text-orange-500 hover:text-orange-700'
|
||||
}
|
||||
>
|
||||
{activeFrequencies.includes(row.id) ? <Eye className="w-5 h-5" /> : <EyeOff className="w-5 h-5" />}
|
||||
{activeFrequencies.includes(row.id) ? (
|
||||
<Eye className="w-5 h-5" />
|
||||
) : (
|
||||
<EyeOff className="w-5 h-5" />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
default:
|
||||
@ -209,7 +254,11 @@ const PaymentPlanSelector = ({ paymentPlans, setPaymentPlans, handleEdit, type }
|
||||
}
|
||||
};
|
||||
|
||||
const selectedPaymentPlan = paymentPlans.find(plan => plan.frequency === paymentPlansOptions.find(p => p.id === selectedFrequency)?.frequency);
|
||||
const selectedPaymentPlan = paymentPlans.find(
|
||||
(plan) =>
|
||||
plan.frequency ===
|
||||
paymentPlansOptions.find((p) => p.id === selectedFrequency)?.frequency
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
@ -275,4 +324,4 @@ const PaymentPlanSelector = ({ paymentPlans, setPaymentPlans, handleEdit, type }
|
||||
);
|
||||
};
|
||||
|
||||
export default PaymentPlanSelector;
|
||||
export default PaymentPlanSelector;
|
||||
|
||||
@ -1,7 +1,12 @@
|
||||
import { formatPhoneNumber } from "@/utils/Telephone";
|
||||
import { formatPhoneNumber } from '@/utils/Telephone';
|
||||
|
||||
export function PhoneLabel({phoneNumber}){
|
||||
return (
|
||||
<a className="text-sm font-semibold text-gray-800" href={"tel:"+phoneNumber}>{formatPhoneNumber(phoneNumber)}</a>
|
||||
);
|
||||
}
|
||||
export function PhoneLabel({ phoneNumber }) {
|
||||
return (
|
||||
<a
|
||||
className="text-sm font-semibold text-gray-800"
|
||||
href={'tel:' + phoneNumber}
|
||||
>
|
||||
{formatPhoneNumber(phoneNumber)}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,7 +1,13 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
const Popup = ({ visible, message, onConfirm, onCancel, uniqueConfirmButton = false }) => {
|
||||
const Popup = ({
|
||||
visible,
|
||||
message,
|
||||
onConfirm,
|
||||
onCancel,
|
||||
uniqueConfirmButton = false,
|
||||
}) => {
|
||||
if (!visible) return null;
|
||||
|
||||
// Vérifier si le message est une chaîne de caractères
|
||||
@ -13,17 +19,15 @@ const Popup = ({ visible, message, onConfirm, onCancel, uniqueConfirmButton = fa
|
||||
<div className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50">
|
||||
<div className="bg-white p-6 rounded-lg shadow-xl max-w-md w-full">
|
||||
<div className="mb-4">
|
||||
{isStringMessage ? (
|
||||
// Afficher le message sous forme de lignes si c'est une chaîne
|
||||
messageLines.map((line, index) => (
|
||||
<p key={index} className="text-gray-700">
|
||||
{line}
|
||||
</p>
|
||||
))
|
||||
) : (
|
||||
// Sinon, afficher directement le contenu React
|
||||
message
|
||||
)}
|
||||
{isStringMessage
|
||||
? // Afficher le message sous forme de lignes si c'est une chaîne
|
||||
messageLines.map((line, index) => (
|
||||
<p key={index} className="text-gray-700">
|
||||
{line}
|
||||
</p>
|
||||
))
|
||||
: // Sinon, afficher directement le contenu React
|
||||
message}
|
||||
</div>
|
||||
<div className="flex justify-end space-x-2">
|
||||
{!uniqueConfirmButton && (
|
||||
|
||||
@ -1,5 +1,13 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Trash2, Eye, EyeOff, ToggleLeft, ToggleRight, Info, XCircle } from 'lucide-react';
|
||||
import {
|
||||
Trash2,
|
||||
Eye,
|
||||
EyeOff,
|
||||
ToggleLeft,
|
||||
ToggleRight,
|
||||
Info,
|
||||
XCircle,
|
||||
} from 'lucide-react';
|
||||
import Table from '@/components/Table';
|
||||
import Popup from '@/components/Popup';
|
||||
import StatusLabel from '@/components/StatusLabel';
|
||||
@ -32,14 +40,23 @@ const roleTypeToBadgeClass = (roleType) => {
|
||||
}
|
||||
};
|
||||
|
||||
const ProfileDirectory = ({ profileRoles, handleActivateProfile, handleDeleteProfile, handleDissociateGuardian }) => {
|
||||
const parentProfiles = profileRoles.filter(profileRole => profileRole.role_type === 2);
|
||||
const schoolAdminProfiles = profileRoles.filter(profileRole => profileRole.role_type !== 2);
|
||||
const ProfileDirectory = ({
|
||||
profileRoles,
|
||||
handleActivateProfile,
|
||||
handleDeleteProfile,
|
||||
handleDissociateGuardian,
|
||||
}) => {
|
||||
const parentProfiles = profileRoles.filter(
|
||||
(profileRole) => profileRole.role_type === 2
|
||||
);
|
||||
const schoolAdminProfiles = profileRoles.filter(
|
||||
(profileRole) => profileRole.role_type !== 2
|
||||
);
|
||||
|
||||
const [popupVisible, setPopupVisible] = useState(false);
|
||||
const [popupMessage, setPopupMessage] = useState("");
|
||||
const [popupMessage, setPopupMessage] = useState('');
|
||||
const [confirmPopupVisible, setConfirmPopupVisible] = useState(false);
|
||||
const [confirmPopupMessage, setConfirmPopupMessage] = useState("");
|
||||
const [confirmPopupMessage, setConfirmPopupMessage] = useState('');
|
||||
const [confirmPopupOnConfirm, setConfirmPopupOnConfirm] = useState(() => {});
|
||||
const [visibleTooltipId, setVisibleTooltipId] = useState(null);
|
||||
|
||||
@ -49,18 +66,24 @@ const ProfileDirectory = ({ profileRoles, handleActivateProfile, handleDeletePro
|
||||
|
||||
const handleTooltipHide = () => {
|
||||
setVisibleTooltipId(null); // Cacher toutes les tooltips
|
||||
}
|
||||
};
|
||||
|
||||
const handleConfirmActivateProfile = (profileRole) => {
|
||||
setConfirmPopupMessage(`Êtes-vous sûr de vouloir ${profileRole.is_active ? 'désactiver' : 'activer'} ce profil ?`);
|
||||
setConfirmPopupMessage(
|
||||
`Êtes-vous sûr de vouloir ${profileRole.is_active ? 'désactiver' : 'activer'} ce profil ?`
|
||||
);
|
||||
setConfirmPopupOnConfirm(() => () => {
|
||||
handleActivateProfile(profileRole)
|
||||
.then(() => {
|
||||
setPopupMessage(`Le profil a été ${profileRole.is_active ? 'désactivé' : 'activé'} avec succès.`);
|
||||
setPopupMessage(
|
||||
`Le profil a été ${profileRole.is_active ? 'désactivé' : 'activé'} avec succès.`
|
||||
);
|
||||
setPopupVisible(true);
|
||||
})
|
||||
.catch(error => {
|
||||
setPopupMessage(`Erreur lors de la ${profileRole.is_active ? 'désactivation' : 'activation'} du profil.`);
|
||||
.catch((error) => {
|
||||
setPopupMessage(
|
||||
`Erreur lors de la ${profileRole.is_active ? 'désactivation' : 'activation'} du profil.`
|
||||
);
|
||||
setPopupVisible(true);
|
||||
});
|
||||
setConfirmPopupVisible(false);
|
||||
@ -69,15 +92,15 @@ const ProfileDirectory = ({ profileRoles, handleActivateProfile, handleDeletePro
|
||||
};
|
||||
|
||||
const handleConfirmDeleteProfile = (id) => {
|
||||
setConfirmPopupMessage("Êtes-vous sûr de vouloir supprimer ce profil ?");
|
||||
setConfirmPopupMessage('Êtes-vous sûr de vouloir supprimer ce profil ?');
|
||||
setConfirmPopupOnConfirm(() => () => {
|
||||
handleDeleteProfile(id)
|
||||
.then(() => {
|
||||
setPopupMessage("Le profil a été supprimé avec succès.");
|
||||
setPopupMessage('Le profil a été supprimé avec succès.');
|
||||
setPopupVisible(true);
|
||||
})
|
||||
.catch(error => {
|
||||
setPopupMessage("Erreur lors de la suppression du profil.");
|
||||
.catch((error) => {
|
||||
setPopupMessage('Erreur lors de la suppression du profil.');
|
||||
setPopupVisible(true);
|
||||
});
|
||||
setConfirmPopupVisible(false);
|
||||
@ -93,11 +116,11 @@ const ProfileDirectory = ({ profileRoles, handleActivateProfile, handleDeletePro
|
||||
setConfirmPopupOnConfirm(() => () => {
|
||||
handleDissociateGuardian(student.id, profileRole.associated_person?.id)
|
||||
.then(() => {
|
||||
setPopupMessage("Le responsable a été dissocié avec succès.");
|
||||
setPopupMessage('Le responsable a été dissocié avec succès.');
|
||||
setPopupVisible(true);
|
||||
})
|
||||
.catch(error => {
|
||||
setPopupMessage("Erreur lors de la dissociation du responsable.");
|
||||
.catch((error) => {
|
||||
setPopupMessage('Erreur lors de la dissociation du responsable.');
|
||||
setPopupVisible(true);
|
||||
});
|
||||
setConfirmPopupVisible(false);
|
||||
@ -108,11 +131,15 @@ const ProfileDirectory = ({ profileRoles, handleActivateProfile, handleDeletePro
|
||||
const parentColumns = [
|
||||
{ name: 'Identifiant', transform: (row) => row.associated_profile_email },
|
||||
{ name: 'Mise à jour', transform: (row) => row.updated_date_formatted },
|
||||
{ name: 'Rôle', transform: (row) => (
|
||||
<span className={`px-2 py-1 rounded-full font-bold ${roleTypeToBadgeClass(row.role_type)}`}>
|
||||
{
|
||||
name: 'Rôle',
|
||||
transform: (row) => (
|
||||
<span
|
||||
className={`px-2 py-1 rounded-full font-bold ${roleTypeToBadgeClass(row.role_type)}`}
|
||||
>
|
||||
{roleTypeToLabel(row.role_type)}
|
||||
</span>
|
||||
)
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'Utilisateur',
|
||||
@ -121,47 +148,53 @@ const ProfileDirectory = ({ profileRoles, handleActivateProfile, handleDeletePro
|
||||
<span>{row.associated_person?.guardian_name}</span>
|
||||
{row.associated_person && (
|
||||
<div
|
||||
className="relative group"
|
||||
onMouseEnter={() => handleTooltipVisibility(row.id)} // Afficher la tooltip pour cette ligne
|
||||
onMouseLeave={handleTooltipHide} // Cacher la tooltip
|
||||
>
|
||||
<button className="relative text-blue-500 hover:text-blue-700 flex items-center justify-center">
|
||||
<div className="w-6 h-6 bg-blue-100 text-blue-700 rounded-full flex items-center justify-center font-bold">
|
||||
{row.associated_person?.students?.length || 0}
|
||||
</div>
|
||||
</button>
|
||||
{visibleTooltipId === row.id && ( // Afficher uniquement si l'ID correspond
|
||||
<div
|
||||
className="fixed z-50 w-96 p-4 bg-white border border-gray-200 rounded shadow-lg -translate-x-1/2"
|
||||
>
|
||||
<div className="mb-2">
|
||||
<strong>Elève(s) associé(s):</strong>
|
||||
<div className="flex flex-col justify-center space-y-2 mt-4">
|
||||
{row.associated_person?.students?.map(student => (
|
||||
<div key={student.student_name} className="flex justify-between items-center">
|
||||
<span className="px-2 py-1 rounded-full text-gray-800 whitespace-nowrap inline-block min-w-0 max-w-fit">
|
||||
{student.student_name}
|
||||
</span>
|
||||
<div className="flex items-center space-x-2">
|
||||
<StatusLabel status={student.registration_status} showDropdown={false} />
|
||||
<button
|
||||
className="text-red-500 hover:text-red-700 flex items-center space-x-1"
|
||||
onClick={() => handleConfirmDissociateGuardian(row, student)}
|
||||
>
|
||||
<XCircle className="w-5 h-5" />
|
||||
<span className="text-sm">Dissocier</span>
|
||||
</button>
|
||||
className="relative group"
|
||||
onMouseEnter={() => handleTooltipVisibility(row.id)} // Afficher la tooltip pour cette ligne
|
||||
onMouseLeave={handleTooltipHide} // Cacher la tooltip
|
||||
>
|
||||
<button className="relative text-blue-500 hover:text-blue-700 flex items-center justify-center">
|
||||
<div className="w-6 h-6 bg-blue-100 text-blue-700 rounded-full flex items-center justify-center font-bold">
|
||||
{row.associated_person?.students?.length || 0}
|
||||
</div>
|
||||
</button>
|
||||
{visibleTooltipId === row.id && ( // Afficher uniquement si l'ID correspond
|
||||
<div className="fixed z-50 w-96 p-4 bg-white border border-gray-200 rounded shadow-lg -translate-x-1/2">
|
||||
<div className="mb-2">
|
||||
<strong>Elève(s) associé(s):</strong>
|
||||
<div className="flex flex-col justify-center space-y-2 mt-4">
|
||||
{row.associated_person?.students?.map((student) => (
|
||||
<div
|
||||
key={student.student_name}
|
||||
className="flex justify-between items-center"
|
||||
>
|
||||
<span className="px-2 py-1 rounded-full text-gray-800 whitespace-nowrap inline-block min-w-0 max-w-fit">
|
||||
{student.student_name}
|
||||
</span>
|
||||
<div className="flex items-center space-x-2">
|
||||
<StatusLabel
|
||||
status={student.registration_status}
|
||||
showDropdown={false}
|
||||
/>
|
||||
<button
|
||||
className="text-red-500 hover:text-red-700 flex items-center space-x-1"
|
||||
onClick={() =>
|
||||
handleConfirmDissociateGuardian(row, student)
|
||||
}
|
||||
>
|
||||
<XCircle className="w-5 h-5" />
|
||||
<span className="text-sm">Dissocier</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'Actions',
|
||||
@ -169,10 +202,18 @@ const ProfileDirectory = ({ profileRoles, handleActivateProfile, handleDeletePro
|
||||
<div className="flex justify-center space-x-2">
|
||||
<button
|
||||
type="button"
|
||||
className={row.is_active ? 'text-emerald-500 hover:text-emerald-700' : 'text-orange-500 hover:text-orange-700'}
|
||||
className={
|
||||
row.is_active
|
||||
? 'text-emerald-500 hover:text-emerald-700'
|
||||
: 'text-orange-500 hover:text-orange-700'
|
||||
}
|
||||
onClick={() => handleConfirmActivateProfile(row)}
|
||||
>
|
||||
{row.is_active ? <ToggleRight className="w-5 h-5 " /> : <ToggleLeft className="w-5 h-5" />}
|
||||
{row.is_active ? (
|
||||
<ToggleRight className="w-5 h-5 " />
|
||||
) : (
|
||||
<ToggleLeft className="w-5 h-5" />
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@ -182,20 +223,26 @@ const ProfileDirectory = ({ profileRoles, handleActivateProfile, handleDeletePro
|
||||
<Trash2 className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const schoolAdminColumns = [
|
||||
{ name: 'Identifiant', transform: (row) => row.associated_profile_email },
|
||||
{ name: 'Mise à jour', transform: (row) => row.updated_date_formatted },
|
||||
{ name: 'Rôle', transform: (row) => (
|
||||
<span className={`px-2 py-1 rounded-full font-bold ${roleTypeToBadgeClass(row.role_type)}`}>
|
||||
{
|
||||
name: 'Rôle',
|
||||
transform: (row) => (
|
||||
<span
|
||||
className={`px-2 py-1 rounded-full font-bold ${roleTypeToBadgeClass(row.role_type)}`}
|
||||
>
|
||||
{roleTypeToLabel(row.role_type)}
|
||||
</span>
|
||||
)
|
||||
),
|
||||
},
|
||||
{ name: 'Utilisateur', transform: (row) => (
|
||||
{
|
||||
name: 'Utilisateur',
|
||||
transform: (row) => (
|
||||
<div className="flex items-center justify-center space-x-2 relative">
|
||||
<span>{row.associated_person?.teacher_name}</span>
|
||||
{row.associated_person && (
|
||||
@ -210,14 +257,15 @@ const ProfileDirectory = ({ profileRoles, handleActivateProfile, handleDeletePro
|
||||
</div>
|
||||
</button>
|
||||
{visibleTooltipId === row.id && ( // Afficher uniquement si l'ID correspond
|
||||
<div
|
||||
className="fixed z-50 w-96 p-4 bg-white border border-gray-200 rounded shadow-lg -translate-x-1/2"
|
||||
>
|
||||
<div className="fixed z-50 w-96 p-4 bg-white border border-gray-200 rounded shadow-lg -translate-x-1/2">
|
||||
<div className="mb-2">
|
||||
<strong>Classes associées:</strong>
|
||||
<div className="flex flex-wrap justify-center space-x-2 mt-4">
|
||||
{row.associated_person?.classes?.map(classe => (
|
||||
<span key={classe.id} className="px-2 py-1 rounded-full bg-gray-200 text-gray-800">
|
||||
{row.associated_person?.classes?.map((classe) => (
|
||||
<span
|
||||
key={classe.id}
|
||||
className="px-2 py-1 rounded-full bg-gray-200 text-gray-800"
|
||||
>
|
||||
{classe.name}
|
||||
</span>
|
||||
))}
|
||||
@ -226,9 +274,15 @@ const ProfileDirectory = ({ profileRoles, handleActivateProfile, handleDeletePro
|
||||
<div>
|
||||
<strong>Spécialités:</strong>
|
||||
<div className="flex flex-wrap justify-center space-x-2 mt-4">
|
||||
{row.associated_person?.specialities?.map(speciality => (
|
||||
<SpecialityItem key={speciality.name} speciality={speciality} isDraggable={false} />
|
||||
))}
|
||||
{row.associated_person?.specialities?.map(
|
||||
(speciality) => (
|
||||
<SpecialityItem
|
||||
key={speciality.name}
|
||||
speciality={speciality}
|
||||
isDraggable={false}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -236,7 +290,7 @@ const ProfileDirectory = ({ profileRoles, handleActivateProfile, handleDeletePro
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'Actions',
|
||||
@ -244,10 +298,18 @@ const ProfileDirectory = ({ profileRoles, handleActivateProfile, handleDeletePro
|
||||
<div className="flex justify-center space-x-2">
|
||||
<button
|
||||
type="button"
|
||||
className={row.is_active ? 'text-emerald-500 hover:text-emerald-700' : 'text-orange-500 hover:text-orange-700'}
|
||||
className={
|
||||
row.is_active
|
||||
? 'text-emerald-500 hover:text-emerald-700'
|
||||
: 'text-orange-500 hover:text-orange-700'
|
||||
}
|
||||
onClick={() => handleConfirmActivateProfile(row)}
|
||||
>
|
||||
{row.is_active ? <ToggleRight className="w-5 h-5 " /> : <ToggleLeft className="w-5 h-5" />}
|
||||
{row.is_active ? (
|
||||
<ToggleRight className="w-5 h-5 " />
|
||||
) : (
|
||||
<ToggleLeft className="w-5 h-5" />
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@ -257,8 +319,8 @@ const ProfileDirectory = ({ profileRoles, handleActivateProfile, handleDeletePro
|
||||
<Trash2 className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
@ -268,20 +330,14 @@ const ProfileDirectory = ({ profileRoles, handleActivateProfile, handleDeletePro
|
||||
{parentProfiles.length === 0 ? (
|
||||
<div>Aucun profil trouvé</div>
|
||||
) : (
|
||||
<Table
|
||||
data={parentProfiles}
|
||||
columns={parentColumns}
|
||||
/>
|
||||
<Table data={parentProfiles} columns={parentColumns} />
|
||||
)}
|
||||
</div>
|
||||
<div className="max-h-128 overflow-y-auto border rounded p-4">
|
||||
{schoolAdminProfiles.length === 0 ? (
|
||||
<div>Aucun profil trouvé</div>
|
||||
) : (
|
||||
<Table
|
||||
data={schoolAdminProfiles}
|
||||
columns={schoolAdminColumns}
|
||||
/>
|
||||
<Table data={schoolAdminProfiles} columns={schoolAdminColumns} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@ -301,4 +357,4 @@ const ProfileDirectory = ({ profileRoles, handleActivateProfile, handleDeletePro
|
||||
);
|
||||
};
|
||||
|
||||
export default ProfileDirectory;
|
||||
export default ProfileDirectory;
|
||||
|
||||
@ -5,12 +5,19 @@ 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 {
|
||||
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;
|
||||
const role = establishments.find(
|
||||
(est) => est.id === establishmentId
|
||||
)?.role_type;
|
||||
setProfileRole(role);
|
||||
|
||||
if (onEstablishmentChange) {
|
||||
@ -20,11 +27,17 @@ const ProfileSelector = ({ onEstablishmentChange, className = '' }) => {
|
||||
};
|
||||
|
||||
// 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) {
|
||||
if (
|
||||
!establishments ||
|
||||
establishments.length === 0 ||
|
||||
establishments.length === 1
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const selectedEstablishment = establishments.find(est => est.id === selectedEstablishmentId);
|
||||
const selectedEstablishment = establishments.find(
|
||||
(est) => est.id === selectedEstablishmentId
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={`relative ${className}`}>
|
||||
@ -32,7 +45,9 @@ const ProfileSelector = ({ onEstablishmentChange, className = '' }) => {
|
||||
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="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>
|
||||
@ -45,11 +60,13 @@ const ProfileSelector = ({ onEstablishmentChange, className = '' }) => {
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
items={establishments.map(establishment => ({
|
||||
items={establishments.map((establishment) => ({
|
||||
type: 'item',
|
||||
label: (
|
||||
<div className="text-left">
|
||||
<div className="font-bold">{getRightStr(establishment.role_type)}</div>
|
||||
<div className="font-bold">
|
||||
{getRightStr(establishment.role_type)}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500">{establishment.name}</div>
|
||||
</div>
|
||||
),
|
||||
@ -64,4 +81,4 @@ const ProfileSelector = ({ onEstablishmentChange, className = '' }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default ProfileSelector;
|
||||
export default ProfileSelector;
|
||||
|
||||
@ -1,157 +1,186 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
|
||||
const Step = ({ number, title, isActive, isValid, isCompleted, onClick }) => {
|
||||
return (
|
||||
<div className="flex-shrink-0 flex justify-center relative mx-4">
|
||||
<div className={`
|
||||
return (
|
||||
<div className="flex-shrink-0 flex justify-center relative mx-4">
|
||||
<div
|
||||
className={`
|
||||
w-8 h-8 rounded-full
|
||||
flex items-center justify-center
|
||||
text-sm font-semibold
|
||||
${isCompleted
|
||||
${
|
||||
isCompleted
|
||||
? 'bg-emerald-600 text-white'
|
||||
: isActive
|
||||
? 'bg-emerald-600 text-white'
|
||||
: 'bg-gray-200 text-gray-600'
|
||||
? 'bg-emerald-600 text-white'
|
||||
: 'bg-gray-200 text-gray-600'
|
||||
}
|
||||
`}>
|
||||
{isCompleted ? (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
|
||||
</svg>
|
||||
) : (
|
||||
number
|
||||
)}
|
||||
</div>
|
||||
<div className="absolute top-12 left-1/2 -translate-x-1/2">
|
||||
<span className={`
|
||||
`}
|
||||
>
|
||||
{isCompleted ? (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="h-5 w-5"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
) : (
|
||||
number
|
||||
)}
|
||||
</div>
|
||||
<div className="absolute top-12 left-1/2 -translate-x-1/2">
|
||||
<span
|
||||
className={`
|
||||
text-xs font-medium w-20 text-center block break-words
|
||||
${isActive ? 'text-emerald-600' : 'text-gray-500'}
|
||||
`}>
|
||||
{title}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
`}
|
||||
>
|
||||
{title}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const SpacerStep = ({ isCompleted }) => {
|
||||
return (
|
||||
<div className={`flex-1 h-0.5 ${isCompleted ? 'bg-emerald-600' : 'bg-gray-200'}`} />
|
||||
);
|
||||
return (
|
||||
<div
|
||||
className={`flex-1 h-0.5 ${isCompleted ? 'bg-emerald-600' : 'bg-gray-200'}`}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const Dots = () => {
|
||||
return (
|
||||
<div className="text-gray-500 relative flex items-center mx-4">
|
||||
<span>...</span>
|
||||
<div className="absolute top-8 left-1/2 -translate-x-1/2">
|
||||
<span className="text-xs font-medium w-20 text-center block">...</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="text-gray-500 relative flex items-center mx-4">
|
||||
<span>...</span>
|
||||
<div className="absolute top-8 left-1/2 -translate-x-1/2">
|
||||
<span className="text-xs font-medium w-20 text-center block">...</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ProgressStep = ({ steps, stepTitles, currentStep, setStep, isStepValid }) => {
|
||||
const [windowWidth, setWindowWidth] = useState(window.innerWidth);
|
||||
const [visibleSteps, setVisibleSteps] = useState(steps);
|
||||
const ProgressStep = ({
|
||||
steps,
|
||||
stepTitles,
|
||||
currentStep,
|
||||
setStep,
|
||||
isStepValid,
|
||||
}) => {
|
||||
const [windowWidth, setWindowWidth] = useState(window.innerWidth);
|
||||
const [visibleSteps, setVisibleSteps] = useState(steps);
|
||||
|
||||
useEffect(() => {
|
||||
const handleResize = () => setWindowWidth(window.innerWidth);
|
||||
window.addEventListener('resize', handleResize);
|
||||
return () => window.removeEventListener('resize', handleResize);
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
const handleResize = () => setWindowWidth(window.innerWidth);
|
||||
window.addEventListener('resize', handleResize);
|
||||
return () => window.removeEventListener('resize', handleResize);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const calculateVisibleSteps = () => {
|
||||
const minWidth = 150; // Largeur minimale estimée par étape
|
||||
const maxVisibleSteps = Math.floor(windowWidth / minWidth);
|
||||
useEffect(() => {
|
||||
const calculateVisibleSteps = () => {
|
||||
const minWidth = 150; // Largeur minimale estimée par étape
|
||||
const maxVisibleSteps = Math.floor(windowWidth / minWidth);
|
||||
|
||||
if (maxVisibleSteps >= steps.length) {
|
||||
setVisibleSteps(steps);
|
||||
return;
|
||||
}
|
||||
if (maxVisibleSteps >= steps.length) {
|
||||
setVisibleSteps(steps);
|
||||
return;
|
||||
}
|
||||
|
||||
if (maxVisibleSteps < 4) {
|
||||
// Garder seulement première, dernière et courante
|
||||
let filtered = [steps[0]];
|
||||
if (currentStep > 1 && currentStep < steps.length) {
|
||||
filtered.push('...');
|
||||
filtered.push(steps[currentStep - 1]);
|
||||
}
|
||||
if (currentStep < steps.length) {
|
||||
filtered.push('...');
|
||||
}
|
||||
filtered.push(steps[steps.length - 1]);
|
||||
setVisibleSteps(filtered);
|
||||
} else {
|
||||
// Garder première, dernière, courante et quelques étapes adjacentes
|
||||
let filtered = [steps[0]];
|
||||
if (currentStep > 2) filtered.push('...');
|
||||
if (currentStep > 1 && currentStep < steps.length) {
|
||||
filtered.push(steps[currentStep - 1]);
|
||||
}
|
||||
if (currentStep < steps.length - 1) filtered.push('...');
|
||||
filtered.push(steps[steps.length - 1]);
|
||||
setVisibleSteps(filtered);
|
||||
}
|
||||
};
|
||||
|
||||
calculateVisibleSteps();
|
||||
}, [windowWidth, currentStep, steps]);
|
||||
|
||||
const handleStepClick = (stepIndex) => {
|
||||
// Vérifie si on peut naviguer vers l'étape (toutes les étapes précédentes doivent être valides)
|
||||
const canNavigate = Array.from({ length: stepIndex }, (_, i) => i + 1)
|
||||
.every(step => isStepValid(step));
|
||||
|
||||
if (canNavigate) {
|
||||
setStep(stepIndex + 1);
|
||||
if (maxVisibleSteps < 4) {
|
||||
// Garder seulement première, dernière et courante
|
||||
let filtered = [steps[0]];
|
||||
if (currentStep > 1 && currentStep < steps.length) {
|
||||
filtered.push('...');
|
||||
filtered.push(steps[currentStep - 1]);
|
||||
}
|
||||
if (currentStep < steps.length) {
|
||||
filtered.push('...');
|
||||
}
|
||||
filtered.push(steps[steps.length - 1]);
|
||||
setVisibleSteps(filtered);
|
||||
} else {
|
||||
// Garder première, dernière, courante et quelques étapes adjacentes
|
||||
let filtered = [steps[0]];
|
||||
if (currentStep > 2) filtered.push('...');
|
||||
if (currentStep > 1 && currentStep < steps.length) {
|
||||
filtered.push(steps[currentStep - 1]);
|
||||
}
|
||||
if (currentStep < steps.length - 1) filtered.push('...');
|
||||
filtered.push(steps[steps.length - 1]);
|
||||
setVisibleSteps(filtered);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full py-6">
|
||||
<div className="flex items-center min-h-[100px]">
|
||||
{visibleSteps.map((step, index) => {
|
||||
if (step === '...') {
|
||||
return (
|
||||
<div key={`dots-${index}`} className="flex-1 flex items-center justify-center">
|
||||
<Dots />
|
||||
{index !== visibleSteps.length - 1 && <SpacerStep isCompleted={false} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
calculateVisibleSteps();
|
||||
}, [windowWidth, currentStep, steps]);
|
||||
|
||||
const originalIndex = steps.indexOf(step);
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className={`
|
||||
const handleStepClick = (stepIndex) => {
|
||||
// Vérifie si on peut naviguer vers l'étape (toutes les étapes précédentes doivent être valides)
|
||||
const canNavigate = Array.from(
|
||||
{ length: stepIndex },
|
||||
(_, i) => i + 1
|
||||
).every((step) => isStepValid(step));
|
||||
|
||||
if (canNavigate) {
|
||||
setStep(stepIndex + 1);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full py-6">
|
||||
<div className="flex items-center min-h-[100px]">
|
||||
{visibleSteps.map((step, index) => {
|
||||
if (step === '...') {
|
||||
return (
|
||||
<div
|
||||
key={`dots-${index}`}
|
||||
className="flex-1 flex items-center justify-center"
|
||||
>
|
||||
<Dots />
|
||||
{index !== visibleSteps.length - 1 && (
|
||||
<SpacerStep isCompleted={false} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const originalIndex = steps.indexOf(step);
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className={`
|
||||
flex-1 relative
|
||||
${Array.from({ length: originalIndex + 1 }, (_, i) => i + 1).every(s => isStepValid(s)) ? 'cursor-pointer' : 'cursor-not-allowed'}
|
||||
${Array.from({ length: originalIndex + 1 }, (_, i) => i + 1).every((s) => isStepValid(s)) ? 'cursor-pointer' : 'cursor-not-allowed'}
|
||||
`}
|
||||
onClick={() => handleStepClick(originalIndex)}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<div className="w-full flex items-center">
|
||||
<Step
|
||||
number={originalIndex + 1}
|
||||
title={stepTitles ? stepTitles[originalIndex + 1] : step}
|
||||
isActive={currentStep === originalIndex + 1}
|
||||
isCompleted={currentStep > originalIndex + 1}
|
||||
isValid={isStepValid(originalIndex + 1)}
|
||||
/>
|
||||
{index !== visibleSteps.length - 1 && (
|
||||
<SpacerStep isCompleted={currentStep > originalIndex + 1} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
onClick={() => handleStepClick(originalIndex)}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<div className="w-full flex items-center">
|
||||
<Step
|
||||
number={originalIndex + 1}
|
||||
title={stepTitles ? stepTitles[originalIndex + 1] : step}
|
||||
isActive={currentStep === originalIndex + 1}
|
||||
isCompleted={currentStep > originalIndex + 1}
|
||||
isValid={isStepValid(originalIndex + 1)}
|
||||
/>
|
||||
{index !== visibleSteps.length - 1 && (
|
||||
<SpacerStep isCompleted={currentStep > originalIndex + 1} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProgressStep;
|
||||
|
||||
@ -4,37 +4,33 @@ import { useEstablishment } from '@/context/EstablishmentContext';
|
||||
import { FE_USERS_LOGIN_URL, getRedirectUrlFromRole } from '@/utils/Url';
|
||||
|
||||
const ProtectedRoute = ({ children, requiredRight }) => {
|
||||
|
||||
const { user, profileRole } = useEstablishment();
|
||||
const router = useRouter();
|
||||
let hasRequiredRight = false;
|
||||
|
||||
if(requiredRight && Array.isArray(requiredRight) ){
|
||||
if (requiredRight && Array.isArray(requiredRight)) {
|
||||
// Vérifier si l'utilisateur a le droit requis
|
||||
hasRequiredRight = requiredRight.some((right) => profileRole === right);
|
||||
}else{
|
||||
hasRequiredRight = (profileRole === requiredRight);
|
||||
} else {
|
||||
hasRequiredRight = profileRole === requiredRight;
|
||||
}
|
||||
|
||||
// Vérifier si l'utilisateur a au moins un rôle correspondant au requiredRight
|
||||
useEffect(() => {
|
||||
if(user){
|
||||
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{
|
||||
} else {
|
||||
// User non authentifié
|
||||
router.push(`${FE_USERS_LOGIN_URL}`);
|
||||
}
|
||||
}, [profileRole]);
|
||||
|
||||
|
||||
|
||||
// Autoriser l'affichage si authentifié et rôle correct
|
||||
return hasRequiredRight ? children : null;
|
||||
};
|
||||
|
||||
export default ProtectedRoute;
|
||||
export default ProtectedRoute;
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
'use client'
|
||||
'use client';
|
||||
|
||||
import { SessionProvider } from "next-auth/react"
|
||||
import { CsrfProvider } from '@/context/CsrfContext'
|
||||
import { NextIntlClientProvider } from 'next-intl'
|
||||
import { SessionProvider } from 'next-auth/react';
|
||||
import { CsrfProvider } from '@/context/CsrfContext';
|
||||
import { NextIntlClientProvider } from 'next-intl';
|
||||
import { EstablishmentProvider } from '@/context/EstablishmentContext';
|
||||
import { DndProvider } from 'react-dnd';
|
||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||
|
||||
|
||||
export default function Providers({ children, messages, locale, session }) {
|
||||
if (!locale) {
|
||||
console.error('Locale non définie dans Providers');
|
||||
@ -25,5 +24,5 @@ export default function Providers({ children, messages, locale, session }) {
|
||||
</CsrfProvider>
|
||||
</DndProvider>
|
||||
</SessionProvider>
|
||||
)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,10 +1,17 @@
|
||||
import React from 'react';
|
||||
|
||||
const RadioList = ({ items, formData, handleChange, fieldName, icon: Icon, className }) => {
|
||||
const RadioList = ({
|
||||
items,
|
||||
formData,
|
||||
handleChange,
|
||||
fieldName,
|
||||
icon: Icon,
|
||||
className,
|
||||
}) => {
|
||||
return (
|
||||
<div className={`mb-4 ${className}`}>
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
{items.map(item => (
|
||||
{items.map((item) => (
|
||||
<div key={item.id} className="flex items-center">
|
||||
<input
|
||||
key={`${item.id}-${Math.random()}`}
|
||||
|
||||
@ -3,12 +3,23 @@ import { usePlanning } from '@/context/PlanningContext';
|
||||
import { Plus, Edit2, Eye, EyeOff, Check, X } from 'lucide-react';
|
||||
|
||||
export default function ScheduleNavigation() {
|
||||
const { schedules, selectedSchedule, setSelectedSchedule, hiddenSchedules, toggleScheduleVisibility, addSchedule, updateSchedule } = usePlanning();
|
||||
const {
|
||||
schedules,
|
||||
selectedSchedule,
|
||||
setSelectedSchedule,
|
||||
hiddenSchedules,
|
||||
toggleScheduleVisibility,
|
||||
addSchedule,
|
||||
updateSchedule,
|
||||
} = usePlanning();
|
||||
const [editingId, setEditingId] = useState(null);
|
||||
const [editedName, setEditedName] = useState('');
|
||||
const [editedColor, setEditedColor] = useState('');
|
||||
const [isAddingNew, setIsAddingNew] = useState(false);
|
||||
const [newSchedule, setNewSchedule] = useState({ name: '', color: '#10b981' });
|
||||
const [newSchedule, setNewSchedule] = useState({
|
||||
name: '',
|
||||
color: '#10b981',
|
||||
});
|
||||
|
||||
const handleEdit = (schedule) => {
|
||||
setEditingId(schedule.id);
|
||||
@ -19,9 +30,9 @@ export default function ScheduleNavigation() {
|
||||
const handleSave = () => {
|
||||
if (editingId) {
|
||||
updateSchedule(editingId, {
|
||||
...schedules.find(s => s.id === editingId),
|
||||
...schedules.find((s) => s.id === editingId),
|
||||
name: editedName,
|
||||
color: editedColor
|
||||
color: editedColor,
|
||||
});
|
||||
setEditingId(null);
|
||||
}
|
||||
@ -31,7 +42,7 @@ export default function ScheduleNavigation() {
|
||||
if (newSchedule.name) {
|
||||
addSchedule({
|
||||
id: `schedule-${Date.now()}`,
|
||||
...newSchedule
|
||||
...newSchedule,
|
||||
});
|
||||
setIsAddingNew(false);
|
||||
setNewSchedule({ name: '', color: '#10b981' });
|
||||
@ -55,7 +66,9 @@ export default function ScheduleNavigation() {
|
||||
<input
|
||||
type="text"
|
||||
value={newSchedule.name}
|
||||
onChange={(e) => setNewSchedule(prev => ({ ...prev, name: e.target.value }))}
|
||||
onChange={(e) =>
|
||||
setNewSchedule((prev) => ({ ...prev, name: e.target.value }))
|
||||
}
|
||||
className="w-full p-1 mb-2 border rounded"
|
||||
placeholder="Nom du planning"
|
||||
/>
|
||||
@ -64,7 +77,9 @@ export default function ScheduleNavigation() {
|
||||
<input
|
||||
type="color"
|
||||
value={newSchedule.color}
|
||||
onChange={(e) => setNewSchedule(prev => ({ ...prev, color: e.target.value }))}
|
||||
onChange={(e) =>
|
||||
setNewSchedule((prev) => ({ ...prev, color: e.target.value }))
|
||||
}
|
||||
className="w-8 h-8"
|
||||
/>
|
||||
</div>
|
||||
@ -86,11 +101,13 @@ export default function ScheduleNavigation() {
|
||||
)}
|
||||
|
||||
<ul className="space-y-2">
|
||||
{schedules.map(schedule => (
|
||||
{schedules.map((schedule) => (
|
||||
<li
|
||||
key={schedule.id}
|
||||
className={`p-2 rounded ${
|
||||
selectedSchedule === schedule.id ? 'bg-gray-100' : 'hover:bg-gray-50'
|
||||
selectedSchedule === schedule.id
|
||||
? 'bg-gray-100'
|
||||
: 'hover:bg-gray-50'
|
||||
}`}
|
||||
>
|
||||
{editingId === schedule.id ? (
|
||||
@ -135,7 +152,13 @@ export default function ScheduleNavigation() {
|
||||
className="w-3 h-3 rounded-full"
|
||||
style={{ backgroundColor: schedule.color }}
|
||||
/>
|
||||
<span className={hiddenSchedules.includes(schedule.id) ? 'text-gray-400' : ''}>
|
||||
<span
|
||||
className={
|
||||
hiddenSchedules.includes(schedule.id)
|
||||
? 'text-gray-400'
|
||||
: ''
|
||||
}
|
||||
>
|
||||
{schedule.name}
|
||||
</span>
|
||||
</div>
|
||||
@ -167,4 +190,4 @@ export default function ScheduleNavigation() {
|
||||
</ul>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,4 +13,4 @@ const SectionTitle = ({ title }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default SectionTitle;
|
||||
export default SectionTitle;
|
||||
|
||||
@ -1,17 +1,34 @@
|
||||
export default function SelectChoice({ type, name, label, required, placeHolder, choices, callback, selected, errorMsg, IconItem, disabled = false }) {
|
||||
export default function SelectChoice({
|
||||
type,
|
||||
name,
|
||||
label,
|
||||
required,
|
||||
placeHolder,
|
||||
choices,
|
||||
callback,
|
||||
selected,
|
||||
errorMsg,
|
||||
IconItem,
|
||||
disabled = false,
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<label htmlFor={name} className="block text-sm font-medium text-gray-700">
|
||||
<label
|
||||
htmlFor={name}
|
||||
className="block text-sm font-medium text-gray-700"
|
||||
>
|
||||
{label}
|
||||
{required && <span className="text-red-500 ml-1">*</span>}
|
||||
</label>
|
||||
<div className={`mt-1 flex items-center border border-gray-200 rounded-md ${errorMsg ? 'border-red-500' : ''} ${disabled ? '' : 'hover:border-gray-400 focus-within:border-gray-500'}`}>
|
||||
{IconItem &&
|
||||
<div
|
||||
className={`mt-1 flex items-center border border-gray-200 rounded-md ${errorMsg ? 'border-red-500' : ''} ${disabled ? '' : 'hover:border-gray-400 focus-within:border-gray-500'}`}
|
||||
>
|
||||
{IconItem && (
|
||||
<span className="inline-flex items-center px-3 text-gray-500 text-sm">
|
||||
{<IconItem />}
|
||||
</span>
|
||||
}
|
||||
)}
|
||||
<select
|
||||
className={`flex-1 px-3 py-2 block w-full sm:text-sm border-none focus:ring-0 outline-none rounded-md ${disabled ? 'bg-gray-100' : ''}`}
|
||||
type={type}
|
||||
@ -33,4 +50,4 @@ export default function SelectChoice({ type, name, label, required, placeHolder,
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user