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",
|
"responsable": "Guardian",
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
@ -10,4 +9,4 @@
|
|||||||
"profession": "Profession",
|
"profession": "Profession",
|
||||||
"address": "Address",
|
"address": "Address",
|
||||||
"add_responsible": "Add guardian"
|
"add_responsible": "Add guardian"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"dashboard": "Dashboard",
|
"dashboard": "Dashboard",
|
||||||
"totalStudents": "Total Students",
|
"totalStudents": "Total Students",
|
||||||
"pendingRegistrations": "Pending Registration",
|
"pendingRegistrations": "Pending Registration",
|
||||||
"reInscriptionRate": "Re-enrollment Rate",
|
"reInscriptionRate": "Re-enrollment Rate",
|
||||||
"structureCapacity": "Structure Capacity",
|
"structureCapacity": "Structure Capacity",
|
||||||
"capacityRate": "Capacity Rate",
|
"capacityRate": "Capacity Rate",
|
||||||
"inscriptionTrends": "Enrollment Trends",
|
"inscriptionTrends": "Enrollment Trends",
|
||||||
"upcomingEvents": "Upcoming Events"
|
"upcomingEvents": "Upcoming Events"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"welcomeParents": "Welcome Parents",
|
"welcomeParents": "Welcome Parents",
|
||||||
"pleaseLogin": "Please login to access your account",
|
"pleaseLogin": "Please login to access your account",
|
||||||
"loginButton": "Go to login page"
|
"loginButton": "Go to login page"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"page": "Page",
|
"page": "Page",
|
||||||
"of": "of",
|
"of": "of",
|
||||||
"previous": "Previous",
|
"previous": "Previous",
|
||||||
"next": "Next"
|
"next": "Next"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"dashboard": "Dashboard",
|
"dashboard": "Dashboard",
|
||||||
"subscriptions": "Subscriptions",
|
"subscriptions": "Subscriptions",
|
||||||
"structure": "Structure",
|
"structure": "Structure",
|
||||||
"directory": "Directory",
|
"directory": "Directory",
|
||||||
"events": "Events",
|
"events": "Events",
|
||||||
"grades": "Grades",
|
"grades": "Grades",
|
||||||
"settings": "Settings",
|
"settings": "Settings",
|
||||||
"schoolAdmin": "School Administration"
|
"schoolAdmin": "School Administration"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,33 +1,33 @@
|
|||||||
{
|
{
|
||||||
"headerBarTitle": "Administration",
|
"headerBarTitle": "Administration",
|
||||||
"addStudent": "New",
|
"addStudent": "New",
|
||||||
"allStudents": "All Students",
|
"allStudents": "All Students",
|
||||||
"pending": "Pending Registrations",
|
"pending": "Pending Registrations",
|
||||||
"subscribed": "Subscribed",
|
"subscribed": "Subscribed",
|
||||||
"archived": "Archived",
|
"archived": "Archived",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"class": "Class",
|
"class": "Class",
|
||||||
"status": "Status",
|
"status": "Status",
|
||||||
"attendance": "Attendance",
|
"attendance": "Attendance",
|
||||||
"lastEvaluation": "Last Evaluation",
|
"lastEvaluation": "Last Evaluation",
|
||||||
"active": "Active",
|
"active": "Active",
|
||||||
"pendingStatus": "Pending",
|
"pendingStatus": "Pending",
|
||||||
"goodAttendance": "Good",
|
"goodAttendance": "Good",
|
||||||
"averageAttendance": "Average",
|
"averageAttendance": "Average",
|
||||||
"lowAttendance": "Poor",
|
"lowAttendance": "Poor",
|
||||||
"searchStudent": "Search for a student...",
|
"searchStudent": "Search for a student...",
|
||||||
"title": "Registration",
|
"title": "Registration",
|
||||||
"information": "Information",
|
"information": "Information",
|
||||||
"no_records": "There are currently no registration records.",
|
"no_records": "There are currently no registration records.",
|
||||||
"add_button": "Add",
|
"add_button": "Add",
|
||||||
"create_first_record": "Please click the ADD button to create your first registration record.",
|
"create_first_record": "Please click the ADD button to create your first registration record.",
|
||||||
"studentName":"Student name",
|
"studentName": "Student name",
|
||||||
"studentFistName":"Student first name",
|
"studentFistName": "Student first name",
|
||||||
"mainContactMail":"Main contact email",
|
"mainContactMail": "Main contact email",
|
||||||
"phone":"Phone",
|
"phone": "Phone",
|
||||||
"lastUpdateDate":"Last update",
|
"lastUpdateDate": "Last update",
|
||||||
"classe":"Class",
|
"classe": "Class",
|
||||||
"registrationFileStatus":"Registration file status",
|
"registrationFileStatus": "Registration file status",
|
||||||
"files":"Files",
|
"files": "Files",
|
||||||
"subscribeFiles":"Subscribe files"
|
"subscribeFiles": "Subscribe files"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
{
|
{
|
||||||
"responsable": "Responsable",
|
"responsable": "Responsable",
|
||||||
"delete": "Supprimer",
|
"delete": "Supprimer",
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"dashboard": "Tableau de bord",
|
"dashboard": "Tableau de bord",
|
||||||
"totalStudents": "Total des étudiants",
|
"totalStudents": "Total des étudiants",
|
||||||
"pendingRegistrations": "Inscriptions en attente",
|
"pendingRegistrations": "Inscriptions en attente",
|
||||||
"reInscriptionRate": "Taux de réinscription",
|
"reInscriptionRate": "Taux de réinscription",
|
||||||
"structureCapacity": "Capacité de la structure",
|
"structureCapacity": "Capacité de la structure",
|
||||||
"capacityRate": "Remplissage de la structure",
|
"capacityRate": "Remplissage de la structure",
|
||||||
"inscriptionTrends": "Tendances d'inscription",
|
"inscriptionTrends": "Tendances d'inscription",
|
||||||
"upcomingEvents": "Événements à venir"
|
"upcomingEvents": "Événements à venir"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"welcomeParents": "Bienvenue aux parents",
|
"welcomeParents": "Bienvenue aux parents",
|
||||||
"pleaseLogin": "Veuillez vous connecter pour accéder à votre compte",
|
"pleaseLogin": "Veuillez vous connecter pour accéder à votre compte",
|
||||||
"loginButton": "Accéder à la page de login"
|
"loginButton": "Accéder à la page de login"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"page": "Page",
|
"page": "Page",
|
||||||
"of": "sur",
|
"of": "sur",
|
||||||
"previous": "Précédent",
|
"previous": "Précédent",
|
||||||
"next": "Suivant"
|
"next": "Suivant"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"dashboard": "Tableau de bord",
|
"dashboard": "Tableau de bord",
|
||||||
"subscriptions": "Inscriptions",
|
"subscriptions": "Inscriptions",
|
||||||
"structure": "Structure",
|
"structure": "Structure",
|
||||||
"directory": "Annuaire",
|
"directory": "Annuaire",
|
||||||
"events": "Evenements",
|
"events": "Evenements",
|
||||||
"grades": "Notes",
|
"grades": "Notes",
|
||||||
"settings": "Paramètres",
|
"settings": "Paramètres",
|
||||||
"schoolAdmin": "Administration Scolaire"
|
"schoolAdmin": "Administration Scolaire"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,33 +1,33 @@
|
|||||||
{
|
{
|
||||||
"headerBarTitle":"Administration",
|
"headerBarTitle": "Administration",
|
||||||
"addStudent": "Nouveau",
|
"addStudent": "Nouveau",
|
||||||
"allStudents": "Tous les élèves",
|
"allStudents": "Tous les élèves",
|
||||||
"pending": "Inscriptions en attente",
|
"pending": "Inscriptions en attente",
|
||||||
"subscribed": "Inscrits",
|
"subscribed": "Inscrits",
|
||||||
"archived": "Archivés",
|
"archived": "Archivés",
|
||||||
"name": "Nom",
|
"name": "Nom",
|
||||||
"class": "Classe",
|
"class": "Classe",
|
||||||
"status": "Statut",
|
"status": "Statut",
|
||||||
"attendance": "Assiduité",
|
"attendance": "Assiduité",
|
||||||
"lastEvaluation": "Dernière évaluation",
|
"lastEvaluation": "Dernière évaluation",
|
||||||
"active": "Actif",
|
"active": "Actif",
|
||||||
"pendingStatus": "En attente",
|
"pendingStatus": "En attente",
|
||||||
"goodAttendance": "Bonne",
|
"goodAttendance": "Bonne",
|
||||||
"averageAttendance": "Moyenne",
|
"averageAttendance": "Moyenne",
|
||||||
"lowAttendance": "Faible",
|
"lowAttendance": "Faible",
|
||||||
"searchStudent": "Rechercher un élève...",
|
"searchStudent": "Rechercher un élève...",
|
||||||
"title": "Inscription",
|
"title": "Inscription",
|
||||||
"information": "Information",
|
"information": "Information",
|
||||||
"no_records": "Il n'y a actuellement aucun dossier d'inscription.",
|
"no_records": "Il n'y a actuellement aucun dossier d'inscription.",
|
||||||
"add_button": "Ajouter",
|
"add_button": "Ajouter",
|
||||||
"create_first_record": "Veuillez cliquer sur le bouton AJOUTER pour créer votre premier dossier d'inscription.",
|
"create_first_record": "Veuillez cliquer sur le bouton AJOUTER pour créer votre premier dossier d'inscription.",
|
||||||
"studentName":"Nom de l'élève",
|
"studentName": "Nom de l'élève",
|
||||||
"studentFistName":"Prénom de l'élève",
|
"studentFistName": "Prénom de l'élève",
|
||||||
"mainContactMail":"Email de contact principal",
|
"mainContactMail": "Email de contact principal",
|
||||||
"phone":"Téléphone",
|
"phone": "Téléphone",
|
||||||
"lastUpdateDate":"Dernière mise à jour",
|
"lastUpdateDate": "Dernière mise à jour",
|
||||||
"classe":"Classe",
|
"classe": "Classe",
|
||||||
"registrationFileStatus":"État du dossier d'inscription",
|
"registrationFileStatus": "État du dossier d'inscription",
|
||||||
"files":"Fichiers",
|
"files": "Fichiers",
|
||||||
"subscribeFiles":"Fichiers d'inscription"
|
"subscribeFiles": "Fichiers d'inscription"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +1,13 @@
|
|||||||
import createNextIntlPlugin from 'next-intl/plugin';
|
import createNextIntlPlugin from 'next-intl/plugin';
|
||||||
import { createRequire } from "module";
|
import { createRequire } from 'module';
|
||||||
const require = createRequire(import.meta.url);
|
const require = createRequire(import.meta.url);
|
||||||
const pkg = require("./package.json")
|
const pkg = require('./package.json');
|
||||||
|
|
||||||
|
|
||||||
const withNextIntl = createNextIntlPlugin();
|
const withNextIntl = createNextIntlPlugin();
|
||||||
|
|
||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
output: "standalone",
|
output: 'standalone',
|
||||||
reactStrictMode: true,
|
reactStrictMode: true,
|
||||||
experimental: {
|
experimental: {
|
||||||
instrumentationHook: true,
|
instrumentationHook: true,
|
||||||
@ -16,17 +15,18 @@ const nextConfig = {
|
|||||||
images: {
|
images: {
|
||||||
remotePatterns: [
|
remotePatterns: [
|
||||||
{
|
{
|
||||||
protocol: "https",
|
protocol: 'https',
|
||||||
hostname: "www.gravatar.com",
|
hostname: 'www.gravatar.com',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
env: {
|
env: {
|
||||||
NEXT_PUBLIC_APP_VERSION: pkg.version,
|
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',
|
NEXT_PUBLIC_USE_FAKE_DATA: process.env.NEXT_PUBLIC_USE_FAKE_DATA || 'false',
|
||||||
AUTH_SECRET: process.env.AUTH_SECRET || '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,
|
DOCUSEAL_API_KEY: process.env.DOCUSEAL_API_KEY,
|
||||||
},
|
},
|
||||||
async rewrites() {
|
async rewrites() {
|
||||||
@ -40,7 +40,7 @@ const nextConfig = {
|
|||||||
destination: '/api/auth/:path*', // Exclure les routes NextAuth des réécritures de proxy
|
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: {},
|
tailwindcss: {},
|
||||||
autoprefixer: {},
|
autoprefixer: {},
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
|
|||||||
@ -29,7 +29,7 @@ const TAILWIND_PATTERNS = [
|
|||||||
// États
|
// États
|
||||||
/^(hover|focus|active|disabled|group|dark):/,
|
/^(hover|focus|active|disabled|group|dark):/,
|
||||||
// Couleurs
|
// 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
|
// 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
|
// Vérifier si la chaîne fait partie d'un console.log ou d'un import
|
||||||
const context = str.trim();
|
const context = str.trim();
|
||||||
if (CODE_PATTERNS.some(pattern => pattern.test(context))) {
|
if (CODE_PATTERNS.some((pattern) => pattern.test(context))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,19 +68,20 @@ function isHardcodedString(str) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Vérifier si c'est une chaîne dans un import
|
// 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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vérifier si c'est une classe Tailwind
|
// Vérifier si c'est une classe Tailwind
|
||||||
const classes = str.split(' ');
|
const classes = str.split(' ');
|
||||||
if (classes.some(cls =>
|
if (
|
||||||
TAILWIND_PATTERNS.some(pattern => pattern.test(cls))
|
classes.some((cls) =>
|
||||||
)) {
|
TAILWIND_PATTERNS.some((pattern) => pattern.test(cls))
|
||||||
|
)
|
||||||
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Autres patterns à ignorer
|
// Autres patterns à ignorer
|
||||||
const IGNORE_PATTERNS = [
|
const IGNORE_PATTERNS = [
|
||||||
/^[A-Z][A-Za-z]+$/, // Noms de composants
|
/^[A-Z][A-Za-z]+$/, // Noms de composants
|
||||||
@ -95,7 +96,7 @@ function isHardcodedString(str) {
|
|||||||
/^className=/, // className attributes
|
/^className=/, // className attributes
|
||||||
];
|
];
|
||||||
|
|
||||||
return !IGNORE_PATTERNS.some(pattern => pattern.test(str));
|
return !IGNORE_PATTERNS.some((pattern) => pattern.test(str));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function scanFile(filePath) {
|
async function scanFile(filePath) {
|
||||||
@ -106,7 +107,7 @@ async function scanFile(filePath) {
|
|||||||
const ast = babel.parse(content, {
|
const ast = babel.parse(content, {
|
||||||
sourceType: 'module',
|
sourceType: 'module',
|
||||||
plugins: ['jsx', 'typescript'],
|
plugins: ['jsx', 'typescript'],
|
||||||
locations: true // Active le tracking des positions
|
locations: true, // Active le tracking des positions
|
||||||
});
|
});
|
||||||
|
|
||||||
traverse(ast, {
|
traverse(ast, {
|
||||||
@ -140,7 +141,11 @@ async function scanDirectory(dir) {
|
|||||||
const filePath = path.join(dir, file);
|
const filePath = path.join(dir, file);
|
||||||
const stat = fs.statSync(filePath);
|
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));
|
Object.assign(results, await scanDirectory(filePath));
|
||||||
} else if (
|
} else if (
|
||||||
stat.isFile() &&
|
stat.isFile() &&
|
||||||
@ -189,4 +194,4 @@ async function main() {
|
|||||||
await logStringsToFile(results);
|
await logStringsToFile(results);
|
||||||
}
|
}
|
||||||
|
|
||||||
main().catch(console.error);
|
main().catch(console.error);
|
||||||
|
|||||||
@ -1,15 +1,21 @@
|
|||||||
import Link from 'next/link'
|
import Link from 'next/link';
|
||||||
import Logo from '../components/Logo'
|
import Logo from '../components/Logo';
|
||||||
|
|
||||||
export default function Custom500() {
|
export default function Custom500() {
|
||||||
return (
|
return (
|
||||||
<div className='flex items-center justify-center min-h-screen bg-emerald-500'>
|
<div className="flex items-center justify-center min-h-screen bg-emerald-500">
|
||||||
<div className='text-center p-6 '>
|
<div className="text-center p-6 ">
|
||||||
<Logo className="w-32 h-32 mx-auto mb-4" />
|
<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>
|
<h2 className="text-2xl font-bold text-emerald-900 mb-4">
|
||||||
<p className='text-emerald-900 mb-4'>Une erreur interne est survenue.</p>
|
500 | Erreur interne
|
||||||
<Link className="text-gray-900 hover:underline" href="/">Retour Accueil</Link>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,10 @@
|
|||||||
'use client'
|
'use client';
|
||||||
import React, { useState, useEffect } from 'react';
|
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 { dissociateGuardian } from '@/app/actions/subscriptionAction';
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||||
@ -24,21 +28,23 @@ export default function Page() {
|
|||||||
|
|
||||||
const handleProfiles = () => {
|
const handleProfiles = () => {
|
||||||
fetchProfileRoles(selectedEstablishmentId)
|
fetchProfileRoles(selectedEstablishmentId)
|
||||||
.then(data => {
|
.then((data) => {
|
||||||
setProfileRoles(data);
|
setProfileRoles(data);
|
||||||
})
|
})
|
||||||
.catch(error => logger.error('Error fetching profileRoles:', error));
|
.catch((error) => logger.error('Error fetching profileRoles:', error));
|
||||||
setReloadFetch(false);
|
setReloadFetch(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEdit = (profileRole) => {
|
const handleEdit = (profileRole) => {
|
||||||
const updatedData = { ...profileRole, is_active: !profileRole.is_active };
|
const updatedData = { ...profileRole, is_active: !profileRole.is_active };
|
||||||
return updateProfileRoles(profileRole.id, updatedData, csrfToken)
|
return updateProfileRoles(profileRole.id, updatedData, csrfToken)
|
||||||
.then(data => {
|
.then((data) => {
|
||||||
setProfileRoles(prevState => prevState.map(item => item.id === profileRole.id ? data : item));
|
setProfileRoles((prevState) =>
|
||||||
|
prevState.map((item) => (item.id === profileRole.id ? data : item))
|
||||||
|
);
|
||||||
return data;
|
return data;
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch((error) => {
|
||||||
logger.error('Error editing data:', error);
|
logger.error('Error editing data:', error);
|
||||||
throw error;
|
throw error;
|
||||||
});
|
});
|
||||||
@ -47,10 +53,12 @@ export default function Page() {
|
|||||||
const handleDelete = (id) => {
|
const handleDelete = (id) => {
|
||||||
return deleteProfileRoles(id, csrfToken)
|
return deleteProfileRoles(id, csrfToken)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
setProfileRoles(prevState => prevState.filter(item => item.id !== id));
|
setProfileRoles((prevState) =>
|
||||||
logger.debug("Profile deleted successfully:", id);
|
prevState.filter((item) => item.id !== id)
|
||||||
|
);
|
||||||
|
logger.debug('Profile deleted successfully:', id);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch((error) => {
|
||||||
logger.error('Error deleting profile:', error);
|
logger.error('Error deleting profile:', error);
|
||||||
throw error;
|
throw error;
|
||||||
});
|
});
|
||||||
@ -59,49 +67,58 @@ export default function Page() {
|
|||||||
const handleDissociate = (studentId, guardianId) => {
|
const handleDissociate = (studentId, guardianId) => {
|
||||||
return dissociateGuardian(studentId, guardianId)
|
return dissociateGuardian(studentId, guardianId)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
logger.debug("Guardian dissociated successfully:", guardianId);
|
logger.debug('Guardian dissociated successfully:', guardianId);
|
||||||
|
|
||||||
// Vérifier si le Guardian a été supprimé
|
// Vérifier si le Guardian a été supprimé
|
||||||
const isGuardianDeleted = response?.isGuardianDeleted;
|
const isGuardianDeleted = response?.isGuardianDeleted;
|
||||||
|
|
||||||
// Mettre à jour le modèle profileRoles
|
// Mettre à jour le modèle profileRoles
|
||||||
setProfileRoles(prevState =>
|
setProfileRoles(
|
||||||
prevState.map(profileRole => {
|
(prevState) =>
|
||||||
if (profileRole.associated_person?.id === guardianId) {
|
prevState
|
||||||
if (isGuardianDeleted) {
|
.map((profileRole) => {
|
||||||
// Si le Guardian est supprimé, retirer le profileRole
|
if (profileRole.associated_person?.id === guardianId) {
|
||||||
return null;
|
if (isGuardianDeleted) {
|
||||||
} else {
|
// Si le Guardian est supprimé, retirer le profileRole
|
||||||
// Si le Guardian n'est pas supprimé, mettre à jour les élèves associés
|
return null;
|
||||||
const updatedStudents = profileRole.associated_person.students.filter(
|
} else {
|
||||||
student => student.id !== studentId
|
// Si le Guardian n'est pas supprimé, mettre à jour les élèves associés
|
||||||
);
|
const updatedStudents =
|
||||||
return {
|
profileRole.associated_person.students.filter(
|
||||||
...profileRole,
|
(student) => student.id !== studentId
|
||||||
associated_person: {
|
);
|
||||||
...profileRole.associated_person,
|
return {
|
||||||
students: updatedStudents, // Mettre à jour les élèves associés
|
...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
|
}
|
||||||
|
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);
|
logger.error('Error dissociating guardian:', error);
|
||||||
throw error;
|
throw error;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='p-8'>
|
<div className="p-8">
|
||||||
<DjangoCSRFToken csrfToken={csrfToken} />
|
<DjangoCSRFToken csrfToken={csrfToken} />
|
||||||
|
|
||||||
<div className="w-full p-4">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
'use client'
|
'use client';
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
return (
|
return (
|
||||||
<div className='p-8'>
|
<div className="p-8">
|
||||||
<h1 className='heading-section'>Statistiques</h1>
|
<h1 className="heading-section">Statistiques</h1>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
'use client'
|
'use client';
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import Sidebar from '@/components/Sidebar';
|
import Sidebar from '@/components/Sidebar';
|
||||||
import { usePathname } from 'next/navigation';
|
import { usePathname } from 'next/navigation';
|
||||||
@ -14,7 +14,7 @@ import {
|
|||||||
Settings,
|
Settings,
|
||||||
LogOut,
|
LogOut,
|
||||||
Menu,
|
Menu,
|
||||||
X
|
X,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import DropdownMenu from '@/components/DropdownMenu';
|
import DropdownMenu from '@/components/DropdownMenu';
|
||||||
|
|
||||||
@ -26,7 +26,7 @@ import {
|
|||||||
FE_ADMIN_DIRECTORY_URL,
|
FE_ADMIN_DIRECTORY_URL,
|
||||||
FE_ADMIN_GRADES_URL,
|
FE_ADMIN_GRADES_URL,
|
||||||
FE_ADMIN_PLANNING_URL,
|
FE_ADMIN_PLANNING_URL,
|
||||||
FE_ADMIN_SETTINGS_URL
|
FE_ADMIN_SETTINGS_URL,
|
||||||
} from '@/utils/Url';
|
} from '@/utils/Url';
|
||||||
|
|
||||||
import { disconnect } from '@/app/actions/authAction';
|
import { disconnect } from '@/app/actions/authAction';
|
||||||
@ -38,25 +38,63 @@ import { getRightStr, RIGHTS } from '@/utils/rights';
|
|||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||||
|
|
||||||
export default function Layout({
|
export default function Layout({ children }) {
|
||||||
children,
|
|
||||||
}) {
|
|
||||||
const t = useTranslations('sidebar');
|
const t = useTranslations('sidebar');
|
||||||
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
||||||
const { data: session } = useSession();
|
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...
|
// Déplacer le reste du code ici...
|
||||||
const sidebarItems = {
|
const sidebarItems = {
|
||||||
"admin": { "id": "admin", "name": t('dashboard'), "url": FE_ADMIN_HOME_URL, "icon": LayoutDashboard },
|
admin: {
|
||||||
"subscriptions": { "id": "subscriptions", "name": t('subscriptions'), "url": FE_ADMIN_SUBSCRIPTIONS_URL, "icon": FileText },
|
id: 'admin',
|
||||||
"structure": { "id": "structure", "name": t('structure'), "url": FE_ADMIN_STRUCTURE_URL, "icon": School },
|
name: t('dashboard'),
|
||||||
"directory": { "id": "directory", "name": t('directory'), "url": FE_ADMIN_DIRECTORY_URL, "icon": Users },
|
url: FE_ADMIN_HOME_URL,
|
||||||
"grades": { "id": "grades", "name": t('grades'), "url": FE_ADMIN_GRADES_URL, "icon": Award },
|
icon: LayoutDashboard,
|
||||||
"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 }
|
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);
|
const [isPopupVisible, setIsPopupVisible] = useState(false);
|
||||||
@ -66,7 +104,7 @@ export default function Layout({
|
|||||||
|
|
||||||
const headerTitle = sidebarItems[currentPage]?.name || t('dashboard');
|
const headerTitle = sidebarItems[currentPage]?.name || t('dashboard');
|
||||||
|
|
||||||
const softwareName = "N3WT School";
|
const softwareName = 'N3WT School';
|
||||||
const softwareVersion = `${process.env.NEXT_PUBLIC_APP_VERSION}`;
|
const softwareVersion = `${process.env.NEXT_PUBLIC_APP_VERSION}`;
|
||||||
|
|
||||||
const handleDisconnect = () => {
|
const handleDisconnect = () => {
|
||||||
@ -84,13 +122,15 @@ export default function Layout({
|
|||||||
content: (
|
content: (
|
||||||
<div className="px-4 py-2">
|
<div className="px-4 py-2">
|
||||||
<div className="font-medium">{user?.email || 'Utilisateur'}</div>
|
<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>
|
</div>
|
||||||
)
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'separator',
|
type: 'separator',
|
||||||
content: <hr className="my-2 border-gray-200" />
|
content: <hr className="my-2 border-gray-200" />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'item',
|
type: 'item',
|
||||||
@ -126,7 +166,9 @@ export default function Layout({
|
|||||||
onEstablishmentChange={(establishmentId) => {
|
onEstablishmentChange={(establishmentId) => {
|
||||||
const parsedEstablishmentId = parseInt(establishmentId, 10);
|
const parsedEstablishmentId = parseInt(establishmentId, 10);
|
||||||
setSelectedEstablishmentId(parsedEstablishmentId);
|
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) {
|
if (roleIndex === -1) {
|
||||||
roleIndex = 0;
|
roleIndex = 0;
|
||||||
}
|
}
|
||||||
@ -155,7 +197,9 @@ export default function Layout({
|
|||||||
>
|
>
|
||||||
{isSidebarOpen ? <X size={24} /> : <Menu size={24} />}
|
{isSidebarOpen ? <X size={24} /> : <Menu size={24} />}
|
||||||
</button>
|
</button>
|
||||||
<div className="text-lg md:text-xl font-semibold">{headerTitle}</div>
|
<div className="text-lg md:text-xl font-semibold">
|
||||||
|
{headerTitle}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<DropdownMenu
|
<DropdownMenu
|
||||||
buttonContent={
|
buttonContent={
|
||||||
@ -175,11 +219,12 @@ export default function Layout({
|
|||||||
{/* Main Content */}
|
{/* Main Content */}
|
||||||
<div className="flex-1 flex flex-col">
|
<div className="flex-1 flex flex-col">
|
||||||
{/* Content avec scroll si nécessaire */}
|
{/* Content avec scroll si nécessaire */}
|
||||||
<div className="flex-1 overflow-auto p-4 md:p-6">
|
<div className="flex-1 overflow-auto p-4 md:p-6">{children}</div>
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
{/* Footer responsive */}
|
{/* Footer responsive */}
|
||||||
<Footer softwareName={softwareName} softwareVersion={softwareVersion} />
|
<Footer
|
||||||
|
softwareName={softwareName}
|
||||||
|
softwareVersion={softwareVersion}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -192,5 +237,3 @@ export default function Layout({
|
|||||||
</ProtectedRoute>
|
</ProtectedRoute>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
'use client'
|
'use client';
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { useTranslations } from 'next-intl';
|
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 Loader from '@/components/Loader';
|
||||||
import ClasseDetails from '@/components/ClasseDetails';
|
import ClasseDetails from '@/components/ClasseDetails';
|
||||||
import { fetchClasses } from '@/app/actions/schoolAction';
|
import { fetchClasses } from '@/app/actions/schoolAction';
|
||||||
@ -11,7 +11,6 @@ import { fetchRegisterForms } from '@/app/actions/subscriptionAction';
|
|||||||
import { fetchUpcomingEvents } from '@/app/actions/planningAction';
|
import { fetchUpcomingEvents } from '@/app/actions/planningAction';
|
||||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||||
|
|
||||||
|
|
||||||
// Composant EventCard pour afficher les événements
|
// Composant EventCard pour afficher les événements
|
||||||
const EventCard = ({ title, date, description, type }) => (
|
const EventCard = ({ title, date, description, type }) => (
|
||||||
<div className="bg-white p-4 rounded-lg shadow-sm border border-gray-100 mb-4">
|
<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 [upcomingEvents, setUpcomingEvents] = useState([]);
|
||||||
const [monthlyStats, setMonthlyStats] = useState({
|
const [monthlyStats, setMonthlyStats] = useState({
|
||||||
inscriptions: [],
|
inscriptions: [],
|
||||||
completionRate: 0
|
completionRate: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
const [classes, setClasses] = useState([]);
|
const [classes, setClasses] = useState([]);
|
||||||
const { selectedEstablishmentId } = useEstablishment();
|
const { selectedEstablishmentId } = useEstablishment();
|
||||||
|
|
||||||
@ -49,35 +47,41 @@ export default function DashboardPage() {
|
|||||||
|
|
||||||
// Fetch des classes
|
// Fetch des classes
|
||||||
fetchClasses(selectedEstablishmentId)
|
fetchClasses(selectedEstablishmentId)
|
||||||
.then(data => {
|
.then((data) => {
|
||||||
setClasses(data);
|
setClasses(data);
|
||||||
logger.info('Classes fetched:', data);
|
logger.info('Classes fetched:', data);
|
||||||
|
|
||||||
const nbMaxStudents = data.reduce((acc, classe) => acc + classe.number_of_students, 0);
|
const nbMaxStudents = data.reduce(
|
||||||
const nbStudents = data.reduce((acc, classe) => acc + classe.students.length, 0);
|
(acc, classe) => acc + classe.number_of_students,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
const nbStudents = data.reduce(
|
||||||
|
(acc, classe) => acc + classe.students.length,
|
||||||
|
0
|
||||||
|
);
|
||||||
setStructureCapacity(nbMaxStudents);
|
setStructureCapacity(nbMaxStudents);
|
||||||
setTotalStudents(nbStudents);
|
setTotalStudents(nbStudents);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch((error) => {
|
||||||
logger.error('Error fetching classes:', error);
|
logger.error('Error fetching classes:', error);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fetch des formulaires d'inscription
|
// Fetch des formulaires d'inscription
|
||||||
fetchRegisterForms()
|
fetchRegisterForms()
|
||||||
.then(data => {
|
.then((data) => {
|
||||||
logger.info('Pending registrations fetched:', data);
|
logger.info('Pending registrations fetched:', data);
|
||||||
setPendingRegistration(data.count);
|
setPendingRegistration(data.count);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch((error) => {
|
||||||
logger.error('Error fetching pending registrations:', error);
|
logger.error('Error fetching pending registrations:', error);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fetch des événements à venir
|
// Fetch des événements à venir
|
||||||
fetchUpcomingEvents()
|
fetchUpcomingEvents()
|
||||||
.then(data => {
|
.then((data) => {
|
||||||
setUpcomingEvents(data);
|
setUpcomingEvents(data);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch((error) => {
|
||||||
logger.error('Error fetching upcoming events:', error);
|
logger.error('Error fetching upcoming events:', error);
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
@ -110,9 +114,9 @@ export default function DashboardPage() {
|
|||||||
icon={<School className="text-green-500" size={24} />}
|
icon={<School className="text-green-500" size={24} />}
|
||||||
color="emerald"
|
color="emerald"
|
||||||
/>
|
/>
|
||||||
<StatCard
|
<StatCard
|
||||||
title={t('capacityRate')}
|
title={t('capacityRate')}
|
||||||
value={`${(totalStudents/structureCapacity * 100).toFixed(1)}%`}
|
value={`${((totalStudents / structureCapacity) * 100).toFixed(1)}%`}
|
||||||
icon={<School className="text-orange-500" size={24} />}
|
icon={<School className="text-orange-500" size={24} />}
|
||||||
color="orange"
|
color="orange"
|
||||||
/>
|
/>
|
||||||
@ -122,7 +126,9 @@ export default function DashboardPage() {
|
|||||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-6">
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-6">
|
||||||
{/* Graphique des inscriptions */}
|
{/* Graphique des inscriptions */}
|
||||||
<div className="lg:col-span-2 bg-white p-6 rounded-lg shadow-sm border border-gray-100">
|
<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 */}
|
{/* Insérer ici un composant de graphique */}
|
||||||
<div className="h-64 bg-gray-50 rounded flex items-center justify-center">
|
<div className="h-64 bg-gray-50 rounded flex items-center justify-center">
|
||||||
<TrendingUp size={48} className="text-gray-300" />
|
<TrendingUp size={48} className="text-gray-300" />
|
||||||
@ -140,11 +146,14 @@ export default function DashboardPage() {
|
|||||||
|
|
||||||
<div className="flex flex-wrap">
|
<div className="flex flex-wrap">
|
||||||
{classes.map((classe) => (
|
{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">
|
<div
|
||||||
<ClasseDetails classe={classe} />
|
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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
'use client'
|
'use client';
|
||||||
import { PlanningProvider } from '@/context/PlanningContext';
|
import { PlanningProvider } from '@/context/PlanningContext';
|
||||||
import Calendar from '@/components/Calendar';
|
import Calendar from '@/components/Calendar';
|
||||||
import EventModal from '@/components/EventModal';
|
import EventModal from '@/components/EventModal';
|
||||||
@ -19,7 +19,7 @@ export default function Page() {
|
|||||||
recurrenceEnd: '',
|
recurrenceEnd: '',
|
||||||
customInterval: 1,
|
customInterval: 1,
|
||||||
customUnit: 'days',
|
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()) => {
|
const initializeNewEvent = (date = new Date()) => {
|
||||||
@ -37,7 +37,7 @@ export default function Page() {
|
|||||||
selectedDays: [],
|
selectedDays: [],
|
||||||
recurrenceEnd: '',
|
recurrenceEnd: '',
|
||||||
customInterval: 1,
|
customInterval: 1,
|
||||||
customUnit: 'days'
|
customUnit: 'days',
|
||||||
});
|
});
|
||||||
setIsModalOpen(true);
|
setIsModalOpen(true);
|
||||||
};
|
};
|
||||||
@ -62,4 +62,4 @@ export default function Page() {
|
|||||||
</div>
|
</div>
|
||||||
</PlanningProvider>
|
</PlanningProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
'use client'
|
'use client';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import Tab from '@/components/Tab';
|
import Tab from '@/components/Tab';
|
||||||
import TabContent from '@/components/TabContent';
|
import TabContent from '@/components/TabContent';
|
||||||
@ -85,18 +85,49 @@ export default function SettingsPage() {
|
|||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<TabContent isActive={activeTab === 'structure'}>
|
<TabContent isActive={activeTab === 'structure'}>
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
<InputText label="Email" value={email} onChange={handleEmailChange} />
|
<InputText
|
||||||
<InputText label="Mot de passe" type="password" value={password} onChange={handlePasswordChange} />
|
label="Email"
|
||||||
<InputText label="Confirmer le mot de passe" type="password" value={confirmPassword} onChange={handleConfirmPasswordChange} />
|
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>
|
<Button type="submit" primary text="Mettre à jour"></Button>
|
||||||
</form>
|
</form>
|
||||||
</TabContent>
|
</TabContent>
|
||||||
<TabContent isActive={activeTab === 'smtp'}>
|
<TabContent isActive={activeTab === 'smtp'}>
|
||||||
<form onSubmit={handleSmtpSubmit}>
|
<form onSubmit={handleSmtpSubmit}>
|
||||||
<InputText label="Serveur SMTP" value={smtpServer} onChange={handleSmtpServerChange} />
|
<InputText
|
||||||
<InputText label="Port SMTP" value={smtpPort} onChange={handleSmtpPortChange} />
|
label="Serveur SMTP"
|
||||||
<InputText label="Utilisateur SMTP" value={smtpUser} onChange={handleSmtpUserChange} />
|
value={smtpServer}
|
||||||
<InputText label="Mot de passe SMTP" type="password" value={smtpPassword} onChange={handleSmtpPasswordChange} />
|
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>
|
<Button type="submit" primary text="Mettre à jour"></Button>
|
||||||
</form>
|
</form>
|
||||||
</TabContent>
|
</TabContent>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
'use client'
|
'use client';
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import StructureManagement from '@/components/Structure/Configuration/StructureManagement';
|
import StructureManagement from '@/components/Structure/Configuration/StructureManagement';
|
||||||
import ScheduleManagement from '@/components/Structure/Planning/ScheduleManagement';
|
import ScheduleManagement from '@/components/Structure/Planning/ScheduleManagement';
|
||||||
@ -6,26 +6,27 @@ import FeesManagement from '@/components/Structure/Tarification/FeesManagement';
|
|||||||
import DjangoCSRFToken from '@/components/DjangoCSRFToken';
|
import DjangoCSRFToken from '@/components/DjangoCSRFToken';
|
||||||
import { useCsrfToken } from '@/context/CsrfContext';
|
import { useCsrfToken } from '@/context/CsrfContext';
|
||||||
import { ClassesProvider } from '@/context/ClassesContext';
|
import { ClassesProvider } from '@/context/ClassesContext';
|
||||||
import {
|
import {
|
||||||
createDatas,
|
createDatas,
|
||||||
updateDatas,
|
updateDatas,
|
||||||
removeDatas,
|
removeDatas,
|
||||||
fetchSpecialities,
|
fetchSpecialities,
|
||||||
fetchTeachers,
|
fetchTeachers,
|
||||||
fetchClasses,
|
fetchClasses,
|
||||||
fetchSchedules,
|
fetchSchedules,
|
||||||
fetchRegistrationDiscounts,
|
fetchRegistrationDiscounts,
|
||||||
fetchTuitionDiscounts,
|
fetchTuitionDiscounts,
|
||||||
fetchRegistrationFees,
|
fetchRegistrationFees,
|
||||||
fetchTuitionFees,
|
fetchTuitionFees,
|
||||||
fetchRegistrationPaymentPlans,
|
fetchRegistrationPaymentPlans,
|
||||||
fetchTuitionPaymentPlans,
|
fetchTuitionPaymentPlans,
|
||||||
fetchRegistrationPaymentModes,
|
fetchRegistrationPaymentModes,
|
||||||
fetchTuitionPaymentModes } from '@/app/actions/schoolAction';
|
fetchTuitionPaymentModes,
|
||||||
|
} from '@/app/actions/schoolAction';
|
||||||
import { fetchProfileRoles, fetchProfiles } from '@/app/actions/authAction';
|
import { fetchProfileRoles, fetchProfiles } from '@/app/actions/authAction';
|
||||||
import SidebarTabs from '@/components/SidebarTabs';
|
import SidebarTabs from '@/components/SidebarTabs';
|
||||||
import FilesGroupsManagement from '@/components/Structure/Files/FilesGroupsManagement';
|
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 logger from '@/utils/logger';
|
||||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||||
|
|
||||||
@ -76,10 +77,10 @@ export default function Page() {
|
|||||||
|
|
||||||
// Fetch data for registration file templates
|
// Fetch data for registration file templates
|
||||||
fetchRegistrationTemplateMaster()
|
fetchRegistrationTemplateMaster()
|
||||||
.then((data)=> {
|
.then((data) => {
|
||||||
setFichiers(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
|
// Fetch data for registration payment plans
|
||||||
handleRegistrationPaymentPlans();
|
handleRegistrationPaymentPlans();
|
||||||
@ -94,145 +95,161 @@ export default function Page() {
|
|||||||
handleTuitionPaymentModes();
|
handleTuitionPaymentModes();
|
||||||
|
|
||||||
fetchProfiles()
|
fetchProfiles()
|
||||||
.then(data => {
|
.then((data) => {
|
||||||
setProfiles(data);
|
setProfiles(data);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch((error) => {
|
||||||
logger.error('Error fetching profileRoles:', error);
|
logger.error('Error fetching profileRoles:', error);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}, [selectedEstablishmentId]);
|
}, [selectedEstablishmentId]);
|
||||||
|
|
||||||
const handleSpecialities = () => {
|
const handleSpecialities = () => {
|
||||||
fetchSpecialities(selectedEstablishmentId)
|
fetchSpecialities(selectedEstablishmentId)
|
||||||
.then(data => {
|
.then((data) => {
|
||||||
setSpecialities(data);
|
setSpecialities(data);
|
||||||
})
|
})
|
||||||
.catch(error => logger.error('Error fetching specialities:', error));
|
.catch((error) => logger.error('Error fetching specialities:', error));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleTeachers = () => {
|
const handleTeachers = () => {
|
||||||
fetchTeachers(selectedEstablishmentId)
|
fetchTeachers(selectedEstablishmentId)
|
||||||
.then(data => {
|
.then((data) => {
|
||||||
setTeachers(data);
|
setTeachers(data);
|
||||||
})
|
})
|
||||||
.catch(error => logger.error('Error fetching teachers:', error));
|
.catch((error) => logger.error('Error fetching teachers:', error));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClasses = () => {
|
const handleClasses = () => {
|
||||||
fetchClasses(selectedEstablishmentId)
|
fetchClasses(selectedEstablishmentId)
|
||||||
.then(data => {
|
.then((data) => {
|
||||||
setClasses(data);
|
setClasses(data);
|
||||||
})
|
})
|
||||||
.catch(error => logger.error('Error fetching classes:', error));
|
.catch((error) => logger.error('Error fetching classes:', error));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSchedules = () => {
|
const handleSchedules = () => {
|
||||||
fetchSchedules()
|
fetchSchedules()
|
||||||
.then(data => {
|
.then((data) => {
|
||||||
setSchedules(data);
|
setSchedules(data);
|
||||||
})
|
})
|
||||||
.catch(error => logger.error('Error fetching schedules:', error));
|
.catch((error) => logger.error('Error fetching schedules:', error));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRegistrationDiscounts = () => {
|
const handleRegistrationDiscounts = () => {
|
||||||
fetchRegistrationDiscounts(selectedEstablishmentId)
|
fetchRegistrationDiscounts(selectedEstablishmentId)
|
||||||
.then(data => {
|
.then((data) => {
|
||||||
setRegistrationDiscounts(data);
|
setRegistrationDiscounts(data);
|
||||||
})
|
})
|
||||||
.catch(error => logger.error('Error fetching registration discounts:', error));
|
.catch((error) =>
|
||||||
|
logger.error('Error fetching registration discounts:', error)
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleTuitionDiscounts = () => {
|
const handleTuitionDiscounts = () => {
|
||||||
fetchTuitionDiscounts(selectedEstablishmentId)
|
fetchTuitionDiscounts(selectedEstablishmentId)
|
||||||
.then(data => {
|
.then((data) => {
|
||||||
setTuitionDiscounts(data);
|
setTuitionDiscounts(data);
|
||||||
})
|
})
|
||||||
.catch(error => logger.error('Error fetching tuition discounts:', error));
|
.catch((error) =>
|
||||||
|
logger.error('Error fetching tuition discounts:', error)
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRegistrationFees = () => {
|
const handleRegistrationFees = () => {
|
||||||
fetchRegistrationFees(selectedEstablishmentId)
|
fetchRegistrationFees(selectedEstablishmentId)
|
||||||
.then(data => {
|
.then((data) => {
|
||||||
setRegistrationFees(data);
|
setRegistrationFees(data);
|
||||||
})
|
})
|
||||||
.catch(error => logger.error('Error fetching registration fees:', error));
|
.catch((error) =>
|
||||||
|
logger.error('Error fetching registration fees:', error)
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleTuitionFees = () => {
|
const handleTuitionFees = () => {
|
||||||
fetchTuitionFees(selectedEstablishmentId)
|
fetchTuitionFees(selectedEstablishmentId)
|
||||||
.then(data => {
|
.then((data) => {
|
||||||
setTuitionFees(data);
|
setTuitionFees(data);
|
||||||
})
|
})
|
||||||
.catch(error => logger.error('Error fetching tuition fees', error));
|
.catch((error) => logger.error('Error fetching tuition fees', error));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRegistrationPaymentPlans = () => {
|
const handleRegistrationPaymentPlans = () => {
|
||||||
fetchRegistrationPaymentPlans(selectedEstablishmentId)
|
fetchRegistrationPaymentPlans(selectedEstablishmentId)
|
||||||
.then(data => {
|
.then((data) => {
|
||||||
setRegistrationPaymentPlans(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 = () => {
|
const handleTuitionPaymentPlans = () => {
|
||||||
fetchTuitionPaymentPlans(selectedEstablishmentId)
|
fetchTuitionPaymentPlans(selectedEstablishmentId)
|
||||||
.then(data => {
|
.then((data) => {
|
||||||
setTuitionPaymentPlans(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 = () => {
|
const handleRegistrationPaymentModes = () => {
|
||||||
fetchRegistrationPaymentModes(selectedEstablishmentId)
|
fetchRegistrationPaymentModes(selectedEstablishmentId)
|
||||||
.then(data => {
|
.then((data) => {
|
||||||
setRegistrationPaymentModes(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 = () => {
|
const handleTuitionPaymentModes = () => {
|
||||||
fetchTuitionPaymentModes(selectedEstablishmentId)
|
fetchTuitionPaymentModes(selectedEstablishmentId)
|
||||||
.then(data => {
|
.then((data) => {
|
||||||
setTuitionPaymentModes(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) => {
|
const handleCreate = (url, newData, setDatas) => {
|
||||||
return createDatas(url, newData, csrfToken)
|
return createDatas(url, newData, csrfToken)
|
||||||
.then(data => {
|
.then((data) => {
|
||||||
setDatas(prevState => [...prevState, data]);
|
setDatas((prevState) => [...prevState, data]);
|
||||||
return data;
|
return data;
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch((error) => {
|
||||||
logger.error('Error creating data:', error);
|
logger.error('Error creating data:', error);
|
||||||
throw error;
|
throw error;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEdit = (url, id, updatedData, setDatas) => {
|
const handleEdit = (url, id, updatedData, setDatas) => {
|
||||||
return updateDatas(url, id, updatedData, csrfToken)
|
return updateDatas(url, id, updatedData, csrfToken)
|
||||||
.then(data => {
|
.then((data) => {
|
||||||
setDatas(prevState => prevState.map(item => item.id === id ? data : item));
|
setDatas((prevState) =>
|
||||||
return data;
|
prevState.map((item) => (item.id === id ? data : item))
|
||||||
})
|
);
|
||||||
.catch(error => {
|
return data;
|
||||||
logger.error('Error editing data:', error);
|
})
|
||||||
throw error;
|
.catch((error) => {
|
||||||
});
|
logger.error('Error editing data:', error);
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = (url, id, setDatas) => {
|
const handleDelete = (url, id, setDatas) => {
|
||||||
return removeDatas(url, id, csrfToken)
|
return removeDatas(url, id, csrfToken)
|
||||||
.then(data => {
|
.then((data) => {
|
||||||
setDatas(prevState => prevState.filter(item => item.id !== id));
|
setDatas((prevState) => prevState.filter((item) => item.id !== id));
|
||||||
return data;
|
return data;
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch((error) => {
|
||||||
logger.error('Error deleting data:', error);
|
logger.error('Error deleting data:', error);
|
||||||
throw error;
|
throw error;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUpdatePlanning = (url, planningId, updatedData) => {
|
const handleUpdatePlanning = (url, planningId, updatedData) => {
|
||||||
@ -240,19 +257,19 @@ export default function Page() {
|
|||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'X-CSRFToken': csrfToken
|
'X-CSRFToken': csrfToken,
|
||||||
},
|
},
|
||||||
body: JSON.stringify(updatedData),
|
body: JSON.stringify(updatedData),
|
||||||
credentials: 'include'
|
credentials: 'include',
|
||||||
})
|
})
|
||||||
.then(response => response.json())
|
.then((response) => response.json())
|
||||||
.then(data => {
|
.then((data) => {
|
||||||
logger.debug('Planning mis à jour avec succès :', data);
|
logger.debug('Planning mis à jour avec succès :', data);
|
||||||
//setDatas(data);
|
//setDatas(data);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch((error) => {
|
||||||
logger.error('Erreur :', error);
|
logger.error('Erreur :', error);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
@ -272,7 +289,7 @@ export default function Page() {
|
|||||||
handleEdit={handleEdit}
|
handleEdit={handleEdit}
|
||||||
handleDelete={handleDelete}
|
handleDelete={handleDelete}
|
||||||
/>
|
/>
|
||||||
)
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'Schedule',
|
id: 'Schedule',
|
||||||
@ -284,7 +301,7 @@ export default function Page() {
|
|||||||
classes={classes}
|
classes={classes}
|
||||||
/>
|
/>
|
||||||
</ClassesProvider>
|
</ClassesProvider>
|
||||||
)
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'Fees',
|
id: 'Fees',
|
||||||
@ -311,23 +328,27 @@ export default function Page() {
|
|||||||
handleEdit={handleEdit}
|
handleEdit={handleEdit}
|
||||||
handleDelete={handleDelete}
|
handleDelete={handleDelete}
|
||||||
/>
|
/>
|
||||||
)
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'Files',
|
id: 'Files',
|
||||||
label: 'Documents d\'inscription',
|
label: "Documents d'inscription",
|
||||||
content: <FilesGroupsManagement csrfToken={csrfToken} selectedEstablishmentId={selectedEstablishmentId} />
|
content: (
|
||||||
}
|
<FilesGroupsManagement
|
||||||
|
csrfToken={csrfToken}
|
||||||
|
selectedEstablishmentId={selectedEstablishmentId}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='p-8'>
|
<div className="p-8">
|
||||||
<DjangoCSRFToken csrfToken={csrfToken} />
|
<DjangoCSRFToken csrfToken={csrfToken} />
|
||||||
|
|
||||||
<div className="w-full p-4">
|
<div className="w-full p-4">
|
||||||
<SidebarTabs tabs={tabs} />
|
<SidebarTabs tabs={tabs} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
'use client'
|
'use client';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useSearchParams, useRouter } from 'next/navigation';
|
import { useSearchParams, useRouter } from 'next/navigation';
|
||||||
import InscriptionFormShared from '@/components/Inscription/InscriptionFormShared';
|
import InscriptionFormShared from '@/components/Inscription/InscriptionFormShared';
|
||||||
@ -9,41 +9,37 @@ import { editRegisterForm } from '@/app/actions/subscriptionAction';
|
|||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const studentId = searchParams.get('studentId'); // Changé de codeDI à studentId
|
const studentId = searchParams.get('studentId'); // Changé de codeDI à studentId
|
||||||
|
|
||||||
const [formErrors, setFormErrors] = useState({});
|
const [formErrors, setFormErrors] = useState({});
|
||||||
const csrfToken = useCsrfToken();
|
const csrfToken = useCsrfToken();
|
||||||
const { selectedEstablishmentId } = useEstablishment();
|
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) => {
|
return (
|
||||||
|
<InscriptionFormShared
|
||||||
editRegisterForm(studentId, data, csrfToken)
|
studentId={studentId}
|
||||||
|
csrfToken={csrfToken}
|
||||||
.then((result) => {
|
selectedEstablishmentId={selectedEstablishmentId}
|
||||||
logger.debug('Success:', result);
|
onSubmit={handleSubmit}
|
||||||
router.push(FE_ADMIN_SUBSCRIPTIONS_URL);
|
cancelUrl={FE_ADMIN_SUBSCRIPTIONS_URL}
|
||||||
})
|
errors={formErrors}
|
||||||
.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}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -1,11 +1,11 @@
|
|||||||
'use client'
|
'use client';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useSearchParams, useRouter } from 'next/navigation';
|
import { useSearchParams, useRouter } from 'next/navigation';
|
||||||
import ValidateSubscription from '@/components/Inscription/ValidateSubscription';
|
import ValidateSubscription from '@/components/Inscription/ValidateSubscription';
|
||||||
import { sendSEPARegisterForm } from "@/app/actions/subscriptionAction"
|
import { sendSEPARegisterForm } from '@/app/actions/subscriptionAction';
|
||||||
import { useCsrfToken } from '@/context/CsrfContext';
|
import { useCsrfToken } from '@/context/CsrfContext';
|
||||||
import logger from '@/utils/logger';
|
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() {
|
export default function Page() {
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
@ -23,11 +23,11 @@ export default function Page() {
|
|||||||
const handleAcceptRF = (data) => {
|
const handleAcceptRF = (data) => {
|
||||||
logger.debug('Mise à jour du RF avec les données:', 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();
|
const formData = new FormData();
|
||||||
formData.append('status', status); // Ajoute le statut
|
formData.append('status', status); // Ajoute le statut
|
||||||
formData.append('sepa_file', sepa_file); // Ajoute le fichier SEPA
|
formData.append('sepa_file', sepa_file); // Ajoute le fichier SEPA
|
||||||
|
|
||||||
// Appeler l'API pour mettre à jour le RF
|
// Appeler l'API pour mettre à jour le RF
|
||||||
sendSEPARegisterForm(studentId, formData, csrfToken)
|
sendSEPARegisterForm(studentId, formData, csrfToken)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
@ -39,7 +39,7 @@ export default function Page() {
|
|||||||
logger.error('Erreur lors de la mise à jour du RF:', error);
|
logger.error('Erreur lors de la mise à jour du RF:', error);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ValidateSubscription
|
<ValidateSubscription
|
||||||
studentId={studentId}
|
studentId={studentId}
|
||||||
@ -50,4 +50,4 @@ export default function Page() {
|
|||||||
onAccept={handleAcceptRF}
|
onAccept={handleAcceptRF}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
'use client'
|
'use client';
|
||||||
import {useTranslations} from 'next-intl';
|
import { useTranslations } from 'next-intl';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
import Logo from '@/components/Logo'; // Import du composant Logo
|
import Logo from '@/components/Logo'; // Import du composant Logo
|
||||||
|
|||||||
@ -1,38 +1,38 @@
|
|||||||
'use client'
|
'use client';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import InscriptionFormShared from '@/components/Inscription/InscriptionFormShared';
|
import InscriptionFormShared from '@/components/Inscription/InscriptionFormShared';
|
||||||
import { useSearchParams, useRouter } from 'next/navigation';
|
import { useSearchParams, useRouter } from 'next/navigation';
|
||||||
import { useCsrfToken } from '@/context/CsrfContext';
|
import { useCsrfToken } from '@/context/CsrfContext';
|
||||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||||
import { FE_PARENTS_HOME_URL} from '@/utils/Url';
|
import { FE_PARENTS_HOME_URL } from '@/utils/Url';
|
||||||
import { editRegisterForm} from '@/app/actions/subscriptionAction';
|
import { editRegisterForm } from '@/app/actions/subscriptionAction';
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const idProfil = searchParams.get('id');
|
const idProfil = searchParams.get('id');
|
||||||
const studentId = searchParams.get('studentId');
|
const studentId = searchParams.get('studentId');
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const csrfToken = useCsrfToken();
|
const csrfToken = useCsrfToken();
|
||||||
const { selectedEstablishmentId } = useEstablishment();
|
const { selectedEstablishmentId } = useEstablishment();
|
||||||
|
|
||||||
const handleSubmit = async (data) => {
|
const handleSubmit = async (data) => {
|
||||||
try {
|
try {
|
||||||
const result = await editRegisterForm(studentId, data, csrfToken);
|
const result = await editRegisterForm(studentId, data, csrfToken);
|
||||||
logger.debug('Success:', result);
|
logger.debug('Success:', result);
|
||||||
router.push(FE_PARENTS_HOME_URL);
|
router.push(FE_PARENTS_HOME_URL);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error:', error);
|
logger.error('Error:', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<InscriptionFormShared
|
<InscriptionFormShared
|
||||||
studentId={studentId}
|
studentId={studentId}
|
||||||
csrfToken={csrfToken}
|
csrfToken={csrfToken}
|
||||||
selectedEstablishmentId={selectedEstablishmentId}
|
selectedEstablishmentId={selectedEstablishmentId}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
cancelUrl={FE_PARENTS_HOME_URL}
|
cancelUrl={FE_PARENTS_HOME_URL}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
'use client'
|
'use client';
|
||||||
// src/components/Layout.js
|
// src/components/Layout.js
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import DropdownMenu from '@/components/DropdownMenu';
|
import DropdownMenu from '@/components/DropdownMenu';
|
||||||
@ -6,7 +6,11 @@ import ProfileSelector from '@/components/ProfileSelector';
|
|||||||
import { useRouter } from 'next/navigation'; // Ajout de l'importation
|
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 { 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 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 { fetchMessages } from '@/app/actions/messagerieAction';
|
||||||
import ProtectedRoute from '@/components/ProtectedRoute';
|
import ProtectedRoute from '@/components/ProtectedRoute';
|
||||||
import { disconnect } from '@/app/actions/authAction';
|
import { disconnect } from '@/app/actions/authAction';
|
||||||
@ -18,19 +22,15 @@ import { useEstablishment } from '@/context/EstablishmentContext';
|
|||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import Footer from '@/components/Footer';
|
import Footer from '@/components/Footer';
|
||||||
|
|
||||||
export default function Layout({
|
export default function Layout({ children }) {
|
||||||
children,
|
|
||||||
}) {
|
|
||||||
|
|
||||||
const router = useRouter(); // Définition de router
|
const router = useRouter(); // Définition de router
|
||||||
const [messages, setMessages] = useState([]);
|
const [messages, setMessages] = useState([]);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [isPopupVisible, setIsPopupVisible] = useState(false);
|
const [isPopupVisible, setIsPopupVisible] = useState(false);
|
||||||
const { profileRole, user } = useEstablishment();
|
const { profileRole, user } = useEstablishment();
|
||||||
const softwareName = "N3WT School";
|
const softwareName = 'N3WT School';
|
||||||
const softwareVersion = `${process.env.NEXT_PUBLIC_APP_VERSION}`;
|
const softwareVersion = `${process.env.NEXT_PUBLIC_APP_VERSION}`;
|
||||||
|
|
||||||
|
|
||||||
const handleDisconnect = () => {
|
const handleDisconnect = () => {
|
||||||
setIsPopupVisible(true);
|
setIsPopupVisible(true);
|
||||||
};
|
};
|
||||||
@ -40,21 +40,29 @@ export default function Layout({
|
|||||||
disconnect();
|
disconnect();
|
||||||
};
|
};
|
||||||
|
|
||||||
const dropdownItems = [
|
const dropdownItems = [
|
||||||
{
|
{
|
||||||
type: 'info',
|
type: 'info',
|
||||||
content: (
|
content: (
|
||||||
<div className="px-4 py-2">
|
<div className="px-4 py-2">
|
||||||
<div className="font-medium">{user?.email || 'Utilisateur'}</div>
|
<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>
|
</div>
|
||||||
)
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'separator',
|
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',
|
type: 'item',
|
||||||
label: 'Déconnexion',
|
label: 'Déconnexion',
|
||||||
@ -63,68 +71,72 @@ const dropdownItems = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
return (
|
return (
|
||||||
<ProtectedRoute requiredRight={RIGHTS.PARENT}>
|
<ProtectedRoute requiredRight={RIGHTS.PARENT}>
|
||||||
<div className="flex flex-col min-h-screen bg-gray-50">
|
<div className="flex flex-col min-h-screen bg-gray-50">
|
||||||
{/* Entête */}
|
{/* 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">
|
<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="flex items-center space-x-2">
|
||||||
<div className="border-b border-gray-200 ">
|
<div className="border-b border-gray-200 ">
|
||||||
<ProfileSelector
|
<ProfileSelector className="w-64 border-r" />
|
||||||
className="w-64 border-r"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="text-lg md:text-xl p-2 font-semibold">Accueil</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center space-x-2 md:space-x-4">
|
<div className="text-lg md:text-xl p-2 font-semibold">Accueil</div>
|
||||||
<button
|
</div>
|
||||||
className="p-1 md:p-2 rounded-full hover:bg-gray-200"
|
<div className="flex items-center space-x-2 md:space-x-4">
|
||||||
onClick={() => { router.push(FE_PARENTS_HOME_URL); }} // Utilisation de router pour revenir à l'accueil parent
|
<button
|
||||||
>
|
className="p-1 md:p-2 rounded-full hover:bg-gray-200"
|
||||||
<Home className="h-5 w-5 md:h-6 md:w-6" />
|
onClick={() => {
|
||||||
</button>
|
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
|
<button
|
||||||
className="p-1 md:p-2 rounded-full hover:bg-gray-200"
|
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" />
|
<MessageSquare className="h-5 w-5 md:h-6 md:w-6" />
|
||||||
</button>
|
</button>
|
||||||
{messages.length > 0 && (
|
{messages.length > 0 && (
|
||||||
<span className="absolute top-0 right-0 block h-2 w-2 rounded-full bg-emerald-600"></span>
|
<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>
|
</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>
|
</header>
|
||||||
|
|
||||||
{/* Content */}
|
{/* 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}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
{/* Footer responsive */}
|
{/* Footer responsive */}
|
||||||
<Footer softwareName={softwareName} softwareVersion={softwareVersion} />
|
<Footer softwareName={softwareName} softwareVersion={softwareVersion} />
|
||||||
</div>
|
</div>
|
||||||
<Popup
|
<Popup
|
||||||
visible={isPopupVisible}
|
visible={isPopupVisible}
|
||||||
message="Êtes-vous sûr(e) de vouloir vous déconnecter ?"
|
message="Êtes-vous sûr(e) de vouloir vous déconnecter ?"
|
||||||
onConfirm={confirmDisconnect}
|
onConfirm={confirmDisconnect}
|
||||||
onCancel={() => setIsPopupVisible(false)}
|
onCancel={() => setIsPopupVisible(false)}
|
||||||
/>
|
/>
|
||||||
</ProtectedRoute>
|
</ProtectedRoute>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,13 +1,25 @@
|
|||||||
'use client'
|
'use client';
|
||||||
import React, { useState, useRef, useEffect } from 'react';
|
import React, { useState, useRef, useEffect } from 'react';
|
||||||
import { SendHorizontal } from 'lucide-react';
|
import { SendHorizontal } from 'lucide-react';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import { getGravatarUrl } from '@/utils/gravatar';
|
import { getGravatarUrl } from '@/utils/gravatar';
|
||||||
|
|
||||||
const contacts = [
|
const contacts = [
|
||||||
{ id: 1, name: 'Facturation', profilePic: getGravatarUrl('facturation@n3wtschool.com') },
|
{
|
||||||
{ id: 2, name: 'Enseignant 1', profilePic: getGravatarUrl('enseignant@n3wtschool.com') },
|
id: 1,
|
||||||
{ id: 3, name: 'Contact', profilePic: getGravatarUrl('contact@n3wtschool.com') },
|
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() {
|
export default function MessageriePage() {
|
||||||
@ -29,7 +41,14 @@ export default function MessageriePage() {
|
|||||||
const contactMessages = messages[selectedContact.id] || [];
|
const contactMessages = messages[selectedContact.id] || [];
|
||||||
setMessages({
|
setMessages({
|
||||||
...messages,
|
...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('');
|
setNewMessage('');
|
||||||
simulateContactResponse(selectedContact.id);
|
simulateContactResponse(selectedContact.id);
|
||||||
@ -48,14 +67,24 @@ export default function MessageriePage() {
|
|||||||
const contactMessages = prevMessages[contactId] || [];
|
const contactMessages = prevMessages[contactId] || [];
|
||||||
return {
|
return {
|
||||||
...prevMessages,
|
...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);
|
}, 2000);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
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 ">
|
<div className="w-1/4 border-r border-gray-200 p-4 overflow-y-auto h-full ">
|
||||||
{contacts.map((contact) => (
|
{contacts.map((contact) => (
|
||||||
<div
|
<div
|
||||||
@ -63,27 +92,49 @@ export default function MessageriePage() {
|
|||||||
className={`p-2 cursor-pointer ${selectedContact?.id === contact.id ? 'bg-gray-200' : ''}`}
|
className={`p-2 cursor-pointer ${selectedContact?.id === contact.id ? 'bg-gray-200' : ''}`}
|
||||||
onClick={() => setSelectedContact(contact)}
|
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}
|
{contact.name}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 flex flex-col h-full">
|
<div className="flex-1 flex flex-col h-full">
|
||||||
<div className="flex-1 overflow-y-auto p-4 h-full">
|
<div className="flex-1 overflow-y-auto p-4 h-full">
|
||||||
{selectedContact && (messages[selectedContact.id] || []).map((message) => (
|
{selectedContact &&
|
||||||
<div
|
(messages[selectedContact.id] || []).map((message) => (
|
||||||
key={message.id}
|
<div
|
||||||
className={`mb-2 p-2 rounded max-w-xs ${message.isResponse ? 'bg-gray-200 justify-self-end' : 'bg-emerald-200 justify-self-start'}`}
|
key={message.id}
|
||||||
style={{ borderRadius: message.isResponse ? '20px 20px 0 20px' : '20px 20px 20px 0', minWidth: '25%' }}
|
className={`mb-2 p-2 rounded max-w-xs ${message.isResponse ? 'bg-gray-200 justify-self-end' : 'bg-emerald-200 justify-self-start'}`}
|
||||||
>
|
style={{
|
||||||
<div className="flex items-center mb-1">
|
borderRadius: message.isResponse
|
||||||
<img src={selectedContact.profilePic} alt={`${selectedContact.name}'s profile`} className="w-8 h-8 rounded-full inline-block mr-2" width={150} height={150} />
|
? '20px 20px 0 20px'
|
||||||
<span className="text-xs text-gray-600">{selectedContact.name}</span>
|
: '20px 20px 20px 0',
|
||||||
<span className="text-xs text-gray-400 ml-2">{new Date(message.date).toLocaleTimeString()}</span>
|
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>
|
</div>
|
||||||
{message.text}
|
))}
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
<div ref={messagesEndRef} />
|
<div ref={messagesEndRef} />
|
||||||
</div>
|
</div>
|
||||||
<div className="p-4 border-t border-gray-200 flex">
|
<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 React, { useEffect, useState } from 'react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
@ -14,35 +14,42 @@ export default function ParentHomePage() {
|
|||||||
const [children, setChildren] = useState([]);
|
const [children, setChildren] = useState([]);
|
||||||
const [userId, setUserId] = useState(null);
|
const [userId, setUserId] = useState(null);
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
const { user, setProfileRole, selectedEstablishmentId, setSelectedEstablishmentId, establishments } = useEstablishment();
|
const {
|
||||||
|
user,
|
||||||
|
setProfileRole,
|
||||||
|
selectedEstablishmentId,
|
||||||
|
setSelectedEstablishmentId,
|
||||||
|
establishments,
|
||||||
|
} = useEstablishment();
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const userIdFromSession = user.user_id;
|
const userIdFromSession = user.user_id;
|
||||||
setUserId(userIdFromSession);
|
setUserId(userIdFromSession);
|
||||||
console.log(selectedEstablishmentId)
|
console.log(selectedEstablishmentId);
|
||||||
fetchChildren(userIdFromSession, selectedEstablishmentId).then(data => {
|
fetchChildren(userIdFromSession, selectedEstablishmentId).then((data) => {
|
||||||
setChildren(data);
|
setChildren(data);
|
||||||
});
|
});
|
||||||
|
}, [selectedEstablishmentId]);
|
||||||
}, [ selectedEstablishmentId]);
|
|
||||||
|
|
||||||
const handleEstablishmentChange = (e) => {
|
const handleEstablishmentChange = (e) => {
|
||||||
const establishmentId = parseInt(e.target.value, 10);
|
const establishmentId = parseInt(e.target.value, 10);
|
||||||
setSelectedEstablishmentId(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);
|
setProfileRole(role);
|
||||||
};
|
};
|
||||||
function handleEdit(eleveId) {
|
function handleEdit(eleveId) {
|
||||||
// Logique pour éditer le dossier de l'élève
|
// Logique pour éditer le dossier de l'élève
|
||||||
logger.debug(`Edit dossier for student id: ${eleveId}`);
|
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 = [
|
const actionColumns = [{ name: 'Action', transform: (row) => row.action }];
|
||||||
{ name: 'Action', transform: (row) => row.action },
|
|
||||||
];
|
|
||||||
|
|
||||||
// Définir les colonnes du tableau
|
// Définir les colonnes du tableau
|
||||||
const childrenColumns = [
|
const childrenColumns = [
|
||||||
@ -52,9 +59,9 @@ export default function ParentHomePage() {
|
|||||||
name: 'Statut',
|
name: 'Statut',
|
||||||
transform: (row) => (
|
transform: (row) => (
|
||||||
<div className="flex justify-center items-center">
|
<div className="flex justify-center items-center">
|
||||||
<StatusLabel status={row.status} showDropdown={false} parent/>
|
<StatusLabel status={row.status} showDropdown={false} parent />
|
||||||
</div>
|
</div>
|
||||||
)
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Actions',
|
name: 'Actions',
|
||||||
@ -71,8 +78,8 @@ export default function ParentHomePage() {
|
|||||||
<Edit className="h-5 w-5" />
|
<Edit className="h-5 w-5" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)
|
),
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const itemsPerPage = 5;
|
const itemsPerPage = 5;
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
'use client'
|
'use client';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
import InputText from '@/components/InputText';
|
import InputText from '@/components/InputText';
|
||||||
@ -61,13 +61,7 @@ export default function SettingsPage() {
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<Button
|
<Button type="submit" primary text={' Mettre à jour'} />
|
||||||
type="submit"
|
|
||||||
primary
|
|
||||||
text={" Mettre à jour"}
|
|
||||||
/>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
"use client";
|
'use client';
|
||||||
|
|
||||||
function ErrorBoundary({
|
function ErrorBoundary({ error }) {
|
||||||
error
|
|
||||||
}) {
|
|
||||||
return <>{error.message}</>;
|
return <>{error.message}</>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,116 +1,159 @@
|
|||||||
'use client'
|
'use client';
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react';
|
||||||
import DjangoCSRFToken from '@/components/DjangoCSRFToken'
|
import DjangoCSRFToken from '@/components/DjangoCSRFToken';
|
||||||
import Logo from '@/components/Logo';
|
import Logo from '@/components/Logo';
|
||||||
import { useSearchParams, useRouter } from 'next/navigation'
|
import { useSearchParams, useRouter } from 'next/navigation';
|
||||||
import InputTextIcon from '@/components/InputTextIcon';
|
import InputTextIcon from '@/components/InputTextIcon';
|
||||||
import Loader from '@/components/Loader'; // Importez le composant Loader
|
import Loader from '@/components/Loader'; // Importez le composant Loader
|
||||||
import Button from '@/components/Button'; // Importez le composant Button
|
import Button from '@/components/Button'; // Importez le composant Button
|
||||||
import { User, KeySquare } from 'lucide-react'; // Importez directement les icônes nécessaires
|
import { User, KeySquare } from 'lucide-react'; // Importez directement les icônes nécessaires
|
||||||
import {
|
import { FE_USERS_NEW_PASSWORD_URL, getRedirectUrlFromRole } from '@/utils/Url';
|
||||||
FE_USERS_NEW_PASSWORD_URL,
|
|
||||||
getRedirectUrlFromRole
|
|
||||||
} from '@/utils/Url';
|
|
||||||
import { login } from '@/app/actions/authAction';
|
import { login } from '@/app/actions/authAction';
|
||||||
import { getSession } from 'next-auth/react';
|
import { getSession } from 'next-auth/react';
|
||||||
import { useCsrfToken } from '@/context/CsrfContext'; // Importez le hook useCsrfToken
|
import { useCsrfToken } from '@/context/CsrfContext'; // Importez le hook useCsrfToken
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
|
|
||||||
|
|
||||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const [errorMessage, setErrorMessage] = useState("");
|
const [errorMessage, setErrorMessage] = useState('');
|
||||||
const [userFieldError, setUserFieldError] = useState("")
|
const [userFieldError, setUserFieldError] = useState('');
|
||||||
const [passwordFieldError, setPasswordFieldError] = useState("")
|
const [passwordFieldError, setPasswordFieldError] = useState('');
|
||||||
const { setUser } = useEstablishment();
|
const { setUser } = useEstablishment();
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const csrfToken = useCsrfToken(); // Utilisez le hook useCsrfToken
|
const csrfToken = useCsrfToken(); // Utilisez le hook useCsrfToken
|
||||||
|
|
||||||
function isOK(data) {
|
function isOK(data) {
|
||||||
return data.errorMessage === ""
|
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) {
|
if (result.error) {
|
||||||
setIsLoading(true);
|
setErrorMessage(result.error);
|
||||||
setErrorMessage("");
|
setIsLoading(false);
|
||||||
|
} else {
|
||||||
login({
|
getSession()
|
||||||
email: formData.get('login'),
|
.then((session) => {
|
||||||
password: formData.get('password')
|
if (!session || !session.user) {
|
||||||
}).then(result => {
|
throw new Error('Session not found');
|
||||||
logger.debug('Sign In Result', result);
|
}
|
||||||
|
const user = session.user;
|
||||||
if (result.error) {
|
logger.debug('User Session:', user);
|
||||||
setErrorMessage(result.error);
|
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);
|
setIsLoading(false);
|
||||||
} else {
|
setErrorMessage(
|
||||||
getSession().then(session => {
|
'Aucun rôle trouvé pour le profil sélectionné.'
|
||||||
if (!session || !session.user) {
|
);
|
||||||
throw new Error('Session not found');
|
}
|
||||||
}
|
})
|
||||||
const user = session.user;
|
.catch((error) => {
|
||||||
logger.debug('User Session:', user);
|
logger.error(
|
||||||
setUser(session.user);
|
'Erreur lors de la récupération de la session:',
|
||||||
if (session.user.roles && session.user.roles.length > 0) {
|
error
|
||||||
let roleIndex = 0;
|
);
|
||||||
if( session.user.roles.length > session.user.roleIndexLoginDefault){
|
setIsLoading(false);
|
||||||
roleIndex = session.user.roleIndexLoginDefault;
|
setErrorMessage(
|
||||||
}
|
'Une erreur est survenue lors de la récupération de la session.'
|
||||||
const role = session.user.roles[roleIndex].role_type;
|
);
|
||||||
const url = getRedirectUrlFromRole(role);
|
});
|
||||||
if (url) {
|
}
|
||||||
router.push(url);
|
})
|
||||||
} else {
|
.catch((error) => {
|
||||||
setIsLoading(false);
|
logger.error('Erreur lors de la connexion:', error);
|
||||||
setErrorMessage('Type de rôle non géré');
|
setIsLoading(false);
|
||||||
}
|
setErrorMessage('Une erreur est survenue lors de la connexion.');
|
||||||
} 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.');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isLoading === true) {
|
if (isLoading === true) {
|
||||||
return <Loader /> // Affichez le composant Loader
|
return <Loader />; // Affichez le composant Loader
|
||||||
} else {
|
} else {
|
||||||
return <>
|
return (
|
||||||
<div className="container max mx-auto p-4">
|
<>
|
||||||
<div className="flex justify-center mb-4">
|
<div className="container max mx-auto p-4">
|
||||||
<Logo className="h-150 w-150" />
|
<div className="flex justify-center mb-4">
|
||||||
</div>
|
<Logo className="h-150 w-150" />
|
||||||
<h1 className="text-2xl font-bold text-center mb-4">Authentification</h1>
|
</div>
|
||||||
<form className="max-w-md mx-auto" onSubmit={(e) => { e.preventDefault(); handleFormLogin(new FormData(e.target)); }}>
|
<h1 className="text-2xl font-bold text-center mb-4">
|
||||||
<DjangoCSRFToken csrfToken={csrfToken} />
|
Authentification
|
||||||
<InputTextIcon name="login" type="text" IconItem={User} label="Identifiant" placeholder="Identifiant" errorMsg={userFieldError} className="w-full mb-5" />
|
</h1>
|
||||||
<InputTextIcon name="password" type="password" IconItem={KeySquare} label="Mot de passe" placeholder="Mot de passe" errorMsg={passwordFieldError} className="w-full mb-5" />
|
<form
|
||||||
<div className="input-group mb-4">
|
className="max-w-md mx-auto"
|
||||||
</div>
|
onSubmit={(e) => {
|
||||||
<label className="text-red-500">{errorMessage}</label>
|
e.preventDefault();
|
||||||
<label><a className="float-right mb-4" href={`${FE_USERS_NEW_PASSWORD_URL}`}>Mot de passe oublié ?</a></label>
|
handleFormLogin(new FormData(e.target));
|
||||||
<div className="form-group-submit mt-4">
|
}}
|
||||||
<Button text="Se Connecter" className="w-full" primary type="submit" name="connect" />
|
>
|
||||||
</div>
|
<DjangoCSRFToken csrfToken={csrfToken} />
|
||||||
</form>
|
<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>
|
</div>
|
||||||
</>
|
</form>
|
||||||
}
|
</div>
|
||||||
};
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
'use client'
|
'use client';
|
||||||
|
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import DjangoCSRFToken from '@/components/DjangoCSRFToken'
|
import DjangoCSRFToken from '@/components/DjangoCSRFToken';
|
||||||
import Logo from '@/components/Logo';
|
import Logo from '@/components/Logo';
|
||||||
import { useSearchParams } from 'next/navigation';
|
import { useSearchParams } from 'next/navigation';
|
||||||
import InputTextIcon from '@/components/InputTextIcon';
|
import InputTextIcon from '@/components/InputTextIcon';
|
||||||
@ -17,80 +17,108 @@ import logger from '@/utils/logger';
|
|||||||
const useFakeData = process.env.NEXT_PUBLIC_USE_FAKE_DATA === 'true';
|
const useFakeData = process.env.NEXT_PUBLIC_USE_FAKE_DATA === 'true';
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const [errorMessage, setErrorMessage] = useState("");
|
const [errorMessage, setErrorMessage] = useState('');
|
||||||
const [userFieldError, setUserFieldError] = useState("");
|
const [userFieldError, setUserFieldError] = useState('');
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [popupVisible, setPopupVisible] = useState(false);
|
const [popupVisible, setPopupVisible] = useState(false);
|
||||||
const [popupMessage, setPopupMessage] = useState("");
|
const [popupMessage, setPopupMessage] = useState('');
|
||||||
const [popupConfirmAction, setPopupConfirmAction] = useState(null);
|
const [popupConfirmAction, setPopupConfirmAction] = useState(null);
|
||||||
const csrfToken = useCsrfToken();
|
const csrfToken = useCsrfToken();
|
||||||
|
|
||||||
function validate(formData) {
|
function validate(formData) {
|
||||||
if (useFakeData) {
|
if (useFakeData) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setUserFieldError("");
|
setUserFieldError('');
|
||||||
setErrorMessage("");
|
setErrorMessage('');
|
||||||
setPopupMessage("Mot de passe réinitialisé avec succès !");
|
setPopupMessage('Mot de passe réinitialisé avec succès !');
|
||||||
setPopupConfirmAction(() => () => setPopupVisible(false));
|
setPopupConfirmAction(() => () => setPopupVisible(false));
|
||||||
setPopupVisible(true);
|
setPopupVisible(true);
|
||||||
}, 1000); // Simule un délai de traitement
|
}, 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
|
|
||||||
} else {
|
} else {
|
||||||
return <>
|
const data = { email: formData.get('email') };
|
||||||
<div className="container max mx-auto p-4">
|
sendNewPassword(data, csrfToken)
|
||||||
<div className="flex justify-center mb-4">
|
.then((data) => {
|
||||||
<Logo className="h-150 w-150" />
|
logger.debug('Success:', data);
|
||||||
</div>
|
setUserFieldError('');
|
||||||
<h1 className="text-2xl font-bold text-center mb-4">Nouveau Mot de passe</h1>
|
setErrorMessage('');
|
||||||
<form className="max-w-md mx-auto" onSubmit={(e) => { e.preventDefault(); validate(new FormData(e.target)); }}>
|
if (data.errorMessage === '') {
|
||||||
<DjangoCSRFToken csrfToken={csrfToken} />
|
setPopupMessage(data.message);
|
||||||
<InputTextIcon name="email" type="text" IconItem={User} label="Identifiant" placeholder="Identifiant" errorMsg={userFieldError} className="w-full" />
|
setPopupConfirmAction(() => () => setPopupVisible(false));
|
||||||
<p className="text-red-500">{errorMessage}</p>
|
setPopupVisible(true);
|
||||||
<div className="form-group-submit mt-4">
|
} else {
|
||||||
<Button text="Réinitialiser" className="w-full" primary type="submit" name="validate" />
|
if (data.errorFields) {
|
||||||
</div>
|
setUserFieldError(data.errorFields.email);
|
||||||
</form>
|
}
|
||||||
<br />
|
if (data.errorMessage) {
|
||||||
<div className='flex justify-center mt-2 max-w-md mx-auto'>
|
setErrorMessage(data.errorMessage);
|
||||||
<Button text="Annuler" className="w-full" href={ `${FE_USERS_LOGIN_URL}`} />
|
}
|
||||||
</div>
|
}
|
||||||
</div>
|
})
|
||||||
<Popup
|
.catch((error) => {
|
||||||
visible={popupVisible}
|
logger.error('Error fetching data:', error);
|
||||||
message={popupMessage}
|
error = error.errorMessage;
|
||||||
onConfirm={popupConfirmAction}
|
logger.debug(error);
|
||||||
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 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
|
// src/app/pages/subscribe.js
|
||||||
|
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import DjangoCSRFToken from '@/components/DjangoCSRFToken'
|
import DjangoCSRFToken from '@/components/DjangoCSRFToken';
|
||||||
import Logo from '@/components/Logo';
|
import Logo from '@/components/Logo';
|
||||||
import { useSearchParams, useRouter } from 'next/navigation'
|
import { useSearchParams, useRouter } from 'next/navigation';
|
||||||
import InputTextIcon from '@/components/InputTextIcon';
|
import InputTextIcon from '@/components/InputTextIcon';
|
||||||
import Loader from '@/components/Loader'; // Importez le composant Loader
|
import Loader from '@/components/Loader'; // Importez le composant Loader
|
||||||
import Button from '@/components/Button'; // Importez le composant Button
|
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';
|
const useFakeData = process.env.NEXT_PUBLIC_USE_FAKE_DATA === 'true';
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const uuid = searchParams.get('uuid');
|
const uuid = searchParams.get('uuid');
|
||||||
const [errorMessage, setErrorMessage] = useState("");
|
const [errorMessage, setErrorMessage] = useState('');
|
||||||
const [password1FieldError,setPassword1FieldError] = useState("")
|
const [password1FieldError, setPassword1FieldError] = useState('');
|
||||||
const [password2FieldError,setPassword2FieldError] = useState("")
|
const [password2FieldError, setPassword2FieldError] = useState('');
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [popupVisible, setPopupVisible] = useState(false);
|
const [popupVisible, setPopupVisible] = useState(false);
|
||||||
const [popupMessage, setPopupMessage] = useState("");
|
const [popupMessage, setPopupMessage] = useState('');
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const csrfToken = useCsrfToken();
|
const csrfToken = useCsrfToken();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (useFakeData) {
|
if (useFakeData) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}, 1000);
|
}, 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
|
|
||||||
} else {
|
} else {
|
||||||
return <>
|
getResetPassword(uuid)
|
||||||
<Popup
|
.then((data) => {
|
||||||
visible={popupVisible}
|
logger.debug('Success:', data);
|
||||||
message={popupMessage}
|
setIsLoading(true);
|
||||||
onConfirm={() => {
|
if (data.errorFields) {
|
||||||
setPopupVisible(false);
|
setPassword1FieldError(data.errorFields.password1);
|
||||||
router.push(`${FE_USERS_LOGIN_URL}`);
|
setPassword2FieldError(data.errorFields.password2);
|
||||||
}}
|
}
|
||||||
onCancel={() => setPopupVisible(false)}
|
if (data.errorMessage) {
|
||||||
/>
|
setErrorMessage(data.errorMessage);
|
||||||
<div className="container max mx-auto p-4">
|
}
|
||||||
<div className="flex justify-center mb-4">
|
setIsLoading(false);
|
||||||
<Logo className="h-150 w-150" />
|
})
|
||||||
</div>
|
.catch((error) => {
|
||||||
<h1 className="text-2xl font-bold text-center mb-4">Réinitialisation du mot de passe</h1>
|
logger.error('Error fetching data:', error);
|
||||||
<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>
|
|
||||||
</>
|
|
||||||
}
|
}
|
||||||
}
|
}, []);
|
||||||
|
|
||||||
|
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
|
// src/app/pages/subscribe.js
|
||||||
|
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import DjangoCSRFToken from '@/components/DjangoCSRFToken'
|
import DjangoCSRFToken from '@/components/DjangoCSRFToken';
|
||||||
import Logo from '@/components/Logo';
|
import Logo from '@/components/Logo';
|
||||||
import { useSearchParams, useRouter } from 'next/navigation'
|
import { useSearchParams, useRouter } from 'next/navigation';
|
||||||
import InputTextIcon from '@/components/InputTextIcon';
|
import InputTextIcon from '@/components/InputTextIcon';
|
||||||
import Loader from '@/components/Loader'; // Importez le composant Loader
|
import Loader from '@/components/Loader'; // Importez le composant Loader
|
||||||
import Button from '@/components/Button'; // Importez le composant Button
|
import Button from '@/components/Button'; // Importez le composant Button
|
||||||
import Popup from '@/components/Popup'; // Importez le composant Popup
|
import Popup from '@/components/Popup'; // Importez le composant Popup
|
||||||
import { User, KeySquare } from 'lucide-react'; // Importez directement les icônes nécessaires
|
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 { useCsrfToken } from '@/context/CsrfContext';
|
||||||
import { subscribe } from '@/app/actions/authAction';
|
import { subscribe } from '@/app/actions/authAction';
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
|
|
||||||
const [errorMessage, setErrorMessage] = useState("");
|
const [errorMessage, setErrorMessage] = useState('');
|
||||||
const [userFieldError,setUserFieldError] = useState("")
|
const [userFieldError, setUserFieldError] = useState('');
|
||||||
const [password1FieldError,setPassword1FieldError] = useState("")
|
const [password1FieldError, setPassword1FieldError] = useState('');
|
||||||
const [password2FieldError,setPassword2FieldError] = useState("")
|
const [password2FieldError, setPassword2FieldError] = useState('');
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [popupVisible, setPopupVisible] = useState(false);
|
const [popupVisible, setPopupVisible] = useState(false);
|
||||||
const [popupMessage, setPopupMessage] = useState("");
|
const [popupMessage, setPopupMessage] = useState('');
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const csrfToken = useCsrfToken();
|
const csrfToken = useCsrfToken();
|
||||||
|
|
||||||
const establishment_id = searchParams.get('establishment_id');
|
|
||||||
|
|
||||||
function isOK(data) {
|
const establishment_id = searchParams.get('establishment_id');
|
||||||
return data.errorMessage === ""
|
|
||||||
}
|
|
||||||
|
|
||||||
function subscribeFormSubmit(formData) {
|
function isOK(data) {
|
||||||
const data ={
|
return data.errorMessage === '';
|
||||||
email: formData.get('login'),
|
}
|
||||||
password1: formData.get('password1'),
|
|
||||||
password2: formData.get('password2'),
|
function subscribeFormSubmit(formData) {
|
||||||
establishment_id: establishment_id
|
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);
|
.catch((error) => {
|
||||||
setUserFieldError("")
|
logger.error('Error fetching data:', error);
|
||||||
setPassword1FieldError("")
|
error = error.errorMessage;
|
||||||
setPassword2FieldError("")
|
logger.debug(error);
|
||||||
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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isLoading === true) {
|
if (isLoading === true) {
|
||||||
return <Loader /> // Affichez le composant Loader
|
return <Loader />; // Affichez le composant Loader
|
||||||
} else {
|
} else {
|
||||||
return <>
|
return (
|
||||||
<div className="container max mx-auto p-4">
|
<>
|
||||||
<div className="flex justify-center mb-4">
|
<div className="container max mx-auto p-4">
|
||||||
<Logo className="h-150 w-150" />
|
<div className="flex justify-center mb-4">
|
||||||
</div>
|
<Logo className="h-150 w-150" />
|
||||||
<h1 className="text-2xl font-bold text-center mb-4">Nouveau profil</h1>
|
</div>
|
||||||
<form className="max-w-md mx-auto" onSubmit={(e) => { e.preventDefault(); subscribeFormSubmit(new FormData(e.target)); }}>
|
<h1 className="text-2xl font-bold text-center mb-4">
|
||||||
<DjangoCSRFToken csrfToken={csrfToken} />
|
Nouveau profil
|
||||||
<InputTextIcon name="login" type="text" IconItem={User} label="Identifiant" placeholder="Identifiant" errorMsg={userFieldError} className="w-full mb-5" />
|
</h1>
|
||||||
<InputTextIcon name="password1" type="password" IconItem={KeySquare} label="Mot de passe" placeholder="Mot de passe" errorMsg={password1FieldError} className="w-full mb-5" />
|
<form
|
||||||
<InputTextIcon name="password2" type="password" IconItem={KeySquare} label="Confirmation mot de passe" placeholder="Confirmation mot de passe" errorMsg={password2FieldError} className="w-full" />
|
className="max-w-md mx-auto"
|
||||||
<p className="text-red-500">{errorMessage}</p>
|
onSubmit={(e) => {
|
||||||
<div className="form-group-submit mt-4">
|
e.preventDefault();
|
||||||
<Button text="Enregistrer" className="w-full" primary type="submit" name="validate" />
|
subscribeFormSubmit(new FormData(e.target));
|
||||||
</div>
|
}}
|
||||||
</form>
|
>
|
||||||
<br/>
|
<DjangoCSRFToken csrfToken={csrfToken} />
|
||||||
<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>
|
<InputTextIcon
|
||||||
</div>
|
name="login"
|
||||||
<Popup
|
type="text"
|
||||||
visible={popupVisible}
|
IconItem={User}
|
||||||
message={popupMessage}
|
label="Identifiant"
|
||||||
onConfirm={() => {
|
placeholder="Identifiant"
|
||||||
setPopupVisible(false);
|
errorMsg={userFieldError}
|
||||||
router.push(`${FE_USERS_LOGIN_URL}`);
|
className="w-full mb-5"
|
||||||
}}
|
|
||||||
onCancel={() => setPopupVisible(false)}
|
|
||||||
/>
|
/>
|
||||||
</>
|
<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';
|
} from '@/utils/Url';
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
|
|
||||||
|
|
||||||
const requestResponseHandler = async (response) => {
|
const requestResponseHandler = async (response) => {
|
||||||
const body = await response.json();
|
const body = await response.json();
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
return body;
|
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;
|
error.details = body;
|
||||||
throw error;
|
throw error;
|
||||||
};
|
};
|
||||||
@ -27,43 +26,39 @@ const requestResponseHandler = async (response) => {
|
|||||||
* Login action
|
* Login action
|
||||||
*/
|
*/
|
||||||
export const login = (data) => {
|
export const login = (data) => {
|
||||||
return signIn('credentials', {
|
return signIn('credentials', {
|
||||||
redirect: false,
|
redirect: false,
|
||||||
email: data.email,
|
email: data.email,
|
||||||
password: data.password,
|
password: data.password,
|
||||||
role_type: data.role_type,
|
role_type: data.role_type,
|
||||||
})
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Login user with API
|
* Login user with API
|
||||||
*/
|
*/
|
||||||
export const getJWT = (data) =>{
|
export const getJWT = (data) => {
|
||||||
const request = new Request(
|
const request = new Request(`${BE_AUTH_LOGIN_URL}`, {
|
||||||
`${BE_AUTH_LOGIN_URL}`, {
|
method: 'POST',
|
||||||
method: 'POST',
|
headers: {
|
||||||
headers: {
|
'Content-Type': 'application/json',
|
||||||
'Content-Type': 'application/json',
|
},
|
||||||
},
|
body: JSON.stringify(data),
|
||||||
body: JSON.stringify(data),
|
credentials: 'include',
|
||||||
credentials: 'include'
|
});
|
||||||
}
|
return fetch(request).then(requestResponseHandler);
|
||||||
);
|
};
|
||||||
return fetch(request).then(requestResponseHandler)
|
export const refreshJWT = (data) => {
|
||||||
}
|
const request = new Request(`${BE_AUTH_REFRESH_JWT_URL}`, {
|
||||||
export const refreshJWT = (data) =>{
|
method: 'POST',
|
||||||
const request = new Request(
|
headers: {
|
||||||
`${BE_AUTH_REFRESH_JWT_URL}`, {
|
'Content-Type': 'application/json',
|
||||||
method: 'POST',
|
},
|
||||||
headers: {
|
body: JSON.stringify(data),
|
||||||
'Content-Type': 'application/json',
|
credentials: 'include',
|
||||||
},
|
});
|
||||||
body: JSON.stringify(data),
|
return fetch(request).then(requestResponseHandler);
|
||||||
credentials: 'include'
|
};
|
||||||
}
|
|
||||||
);
|
|
||||||
return fetch(request).then(requestResponseHandler)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disconnects the user after confirming the action.
|
* Disconnects the user after confirming the action.
|
||||||
@ -75,140 +70,116 @@ export const refreshJWT = (data) =>{
|
|||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
export const disconnect = () => {
|
export const disconnect = () => {
|
||||||
signOut({ callbackUrl: FE_USERS_LOGIN_URL });
|
signOut({ callbackUrl: FE_USERS_LOGIN_URL });
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchProfileRoles = (establishment) => {
|
export const fetchProfileRoles = (establishment) => {
|
||||||
return fetch(`${BE_AUTH_PROFILES_ROLES_URL}?establishment_id=${establishment}`)
|
return fetch(
|
||||||
.then(requestResponseHandler)
|
`${BE_AUTH_PROFILES_ROLES_URL}?establishment_id=${establishment}`
|
||||||
|
).then(requestResponseHandler);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const updateProfileRoles = (id, data, csrfToken) => {
|
export const updateProfileRoles = (id, data, csrfToken) => {
|
||||||
const request = new Request(
|
const request = new Request(`${BE_AUTH_PROFILES_ROLES_URL}/${id}`, {
|
||||||
`${BE_AUTH_PROFILES_ROLES_URL}/${id}`,
|
method: 'PUT',
|
||||||
{
|
headers: {
|
||||||
method: 'PUT',
|
'Content-Type': 'application/json',
|
||||||
headers: {
|
'X-CSRFToken': csrfToken,
|
||||||
'Content-Type': 'application/json',
|
},
|
||||||
'X-CSRFToken': csrfToken
|
credentials: 'include',
|
||||||
},
|
body: JSON.stringify(data),
|
||||||
credentials: 'include',
|
});
|
||||||
body: JSON.stringify(data),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return fetch(request).then(requestResponseHandler);
|
return fetch(request).then(requestResponseHandler);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const deleteProfileRoles = (id, csrfToken) => {
|
export const deleteProfileRoles = (id, csrfToken) => {
|
||||||
const request = new Request(
|
const request = new Request(`${BE_AUTH_PROFILES_ROLES_URL}/${id}`, {
|
||||||
`${BE_AUTH_PROFILES_ROLES_URL}/${id}`,
|
method: 'DELETE',
|
||||||
{
|
headers: {
|
||||||
method: 'DELETE',
|
'X-CSRFToken': csrfToken,
|
||||||
headers: {
|
},
|
||||||
'X-CSRFToken': csrfToken
|
credentials: 'include',
|
||||||
},
|
});
|
||||||
credentials: 'include'
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return fetch(request).then(requestResponseHandler);
|
return fetch(request).then(requestResponseHandler);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchProfiles = () => {
|
export const fetchProfiles = () => {
|
||||||
return fetch(`${BE_AUTH_PROFILES_URL}`)
|
return fetch(`${BE_AUTH_PROFILES_URL}`).then(requestResponseHandler);
|
||||||
.then(requestResponseHandler)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createProfile = (data, csrfToken) => {
|
export const createProfile = (data, csrfToken) => {
|
||||||
const request = new Request(
|
const request = new Request(`${BE_AUTH_PROFILES_URL}`, {
|
||||||
`${BE_AUTH_PROFILES_URL}`,
|
method: 'POST',
|
||||||
{
|
headers: {
|
||||||
method: 'POST',
|
'Content-Type': 'application/json',
|
||||||
headers: {
|
'X-CSRFToken': csrfToken,
|
||||||
'Content-Type': 'application/json',
|
},
|
||||||
'X-CSRFToken': csrfToken
|
credentials: 'include',
|
||||||
},
|
body: JSON.stringify(data),
|
||||||
credentials: 'include',
|
});
|
||||||
body: JSON.stringify(data),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return fetch(request).then(requestResponseHandler);
|
return fetch(request).then(requestResponseHandler);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const deleteProfile = (id, csrfToken) => {
|
export const deleteProfile = (id, csrfToken) => {
|
||||||
const request = new Request(
|
const request = new Request(`${BE_AUTH_PROFILES_URL}/${id}`, {
|
||||||
`${BE_AUTH_PROFILES_URL}/${id}`,
|
method: 'DELETE',
|
||||||
{
|
headers: {
|
||||||
method: 'DELETE',
|
'X-CSRFToken': csrfToken,
|
||||||
headers: {
|
},
|
||||||
'X-CSRFToken': csrfToken
|
credentials: 'include',
|
||||||
},
|
});
|
||||||
credentials: 'include'
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return fetch(request).then(requestResponseHandler);
|
return fetch(request).then(requestResponseHandler);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const updateProfile = (id, data, csrfToken) => {
|
export const updateProfile = (id, data, csrfToken) => {
|
||||||
const request = new Request(
|
const request = new Request(`${BE_AUTH_PROFILES_URL}/${id}`, {
|
||||||
`${BE_AUTH_PROFILES_URL}/${id}`,
|
method: 'PUT',
|
||||||
{
|
headers: {
|
||||||
method: 'PUT',
|
'Content-Type': 'application/json',
|
||||||
headers: {
|
'X-CSRFToken': csrfToken,
|
||||||
'Content-Type': 'application/json',
|
},
|
||||||
'X-CSRFToken': csrfToken
|
credentials: 'include',
|
||||||
},
|
body: JSON.stringify(data),
|
||||||
credentials: 'include',
|
});
|
||||||
body: JSON.stringify(data),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return fetch(request).then(requestResponseHandler);
|
return fetch(request).then(requestResponseHandler);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const sendNewPassword = (data, csrfToken) => {
|
export const sendNewPassword = (data, csrfToken) => {
|
||||||
const request = new Request(
|
const request = new Request(`${BE_AUTH_NEW_PASSWORD_URL}`, {
|
||||||
`${BE_AUTH_NEW_PASSWORD_URL}`,
|
method: 'POST',
|
||||||
{
|
headers: {
|
||||||
method: 'POST',
|
'Content-Type': 'application/json',
|
||||||
headers: {
|
'X-CSRFToken': csrfToken,
|
||||||
'Content-Type': 'application/json',
|
},
|
||||||
'X-CSRFToken': csrfToken
|
credentials: 'include',
|
||||||
},
|
body: JSON.stringify(data),
|
||||||
credentials: 'include',
|
});
|
||||||
body: JSON.stringify(data),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return fetch(request).then(requestResponseHandler);
|
return fetch(request).then(requestResponseHandler);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const subscribe = (data, csrfToken) => {
|
export const subscribe = (data, csrfToken) => {
|
||||||
const request = new Request(
|
const request = new Request(`${BE_AUTH_REGISTER_URL}`, {
|
||||||
`${BE_AUTH_REGISTER_URL}`,
|
method: 'POST',
|
||||||
{
|
headers: {
|
||||||
method: 'POST',
|
'Content-Type': 'application/json',
|
||||||
headers: {
|
'X-CSRFToken': csrfToken,
|
||||||
'Content-Type': 'application/json',
|
},
|
||||||
'X-CSRFToken': csrfToken
|
credentials: 'include',
|
||||||
},
|
body: JSON.stringify(data),
|
||||||
credentials: 'include',
|
});
|
||||||
body: JSON.stringify(data),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return fetch(request).then(requestResponseHandler);
|
return fetch(request).then(requestResponseHandler);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const resetPassword = (uuid, data, csrfToken) => {
|
export const resetPassword = (uuid, data, csrfToken) => {
|
||||||
const request = new Request(
|
const request = new Request(`${BE_AUTH_RESET_PASSWORD_URL}/${uuid}`, {
|
||||||
`${BE_AUTH_RESET_PASSWORD_URL}/${uuid}`,
|
method: 'POST',
|
||||||
{
|
headers: {
|
||||||
method: 'POST',
|
'Content-Type': 'application/json',
|
||||||
headers: {
|
'X-CSRFToken': csrfToken,
|
||||||
'Content-Type': 'application/json',
|
},
|
||||||
'X-CSRFToken': csrfToken
|
credentials: 'include',
|
||||||
},
|
body: JSON.stringify(data),
|
||||||
credentials: 'include',
|
});
|
||||||
body: JSON.stringify(data),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return fetch(request).then(requestResponseHandler);
|
return fetch(request).then(requestResponseHandler);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,24 +1,20 @@
|
|||||||
import {
|
import { BE_GESTIONMESSAGERIE_MESSAGES_URL } from '@/utils/Url';
|
||||||
BE_GESTIONMESSAGERIE_MESSAGES_URL
|
|
||||||
} from '@/utils/Url';
|
|
||||||
|
|
||||||
const requestResponseHandler = async (response) => {
|
const requestResponseHandler = async (response) => {
|
||||||
|
|
||||||
const body = await response.json();
|
const body = await response.json();
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
// Throw an error with the JSON body containing the form errors
|
// Throw an error with the JSON body containing the form errors
|
||||||
const error = new Error(body?.errorMessage || "Une erreur est survenue");
|
const error = new Error(body?.errorMessage || 'Une erreur est survenue');
|
||||||
error.details = body;
|
error.details = body;
|
||||||
throw error;
|
throw error;
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export const fetchMessages = (id) => {
|
||||||
export const fetchMessages = (id) =>{
|
return fetch(`${BE_GESTIONMESSAGERIE_MESSAGES_URL}/${id}`, {
|
||||||
return fetch(`${BE_GESTIONMESSAGERIE_MESSAGES_URL}/${id}`, {
|
headers: {
|
||||||
headers: {
|
'Content-Type': 'application/json',
|
||||||
'Content-Type': 'application/json',
|
},
|
||||||
},
|
}).then(requestResponseHandler);
|
||||||
}).then(requestResponseHandler)
|
};
|
||||||
}
|
|
||||||
|
|||||||
@ -1,112 +1,96 @@
|
|||||||
import { BE_PLANNING_PLANNINGS_URL,
|
import { BE_PLANNING_PLANNINGS_URL, BE_PLANNING_EVENTS_URL } from '@/utils/Url';
|
||||||
BE_PLANNING_EVENTS_URL
|
|
||||||
} from '@/utils/Url';
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const requestResponseHandler = async (response) => {
|
const requestResponseHandler = async (response) => {
|
||||||
const body = response.status !== 204 ? await response?.json() : {};
|
const body = response.status !== 204 ? await response?.json() : {};
|
||||||
console.log(response)
|
console.log(response);
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
// Throw an error with the JSON body containing the form errors
|
// Throw an error with the JSON body containing the form errors
|
||||||
const error = new Error(body?.errorMessage || "Une erreur est survenue");
|
const error = new Error(body?.errorMessage || 'Une erreur est survenue');
|
||||||
error.details = body;
|
error.details = body;
|
||||||
throw error;
|
throw error;
|
||||||
}
|
};
|
||||||
|
|
||||||
const getData = (url) => {
|
const getData = (url) => {
|
||||||
return fetch(`${url}`).then(requestResponseHandler);
|
return fetch(`${url}`).then(requestResponseHandler);
|
||||||
}
|
};
|
||||||
|
|
||||||
const createDatas = (url, newData, csrfToken) => {
|
const createDatas = (url, newData, csrfToken) => {
|
||||||
return fetch(url, {
|
return fetch(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'X-CSRFToken': csrfToken
|
'X-CSRFToken': csrfToken,
|
||||||
},
|
},
|
||||||
body: JSON.stringify(newData),
|
body: JSON.stringify(newData),
|
||||||
credentials: 'include'
|
credentials: 'include',
|
||||||
})
|
}).then(requestResponseHandler);
|
||||||
.then(requestResponseHandler)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateDatas = (url, updatedData, csrfToken) => {
|
const updateDatas = (url, updatedData, csrfToken) => {
|
||||||
return fetch(`${url}`, {
|
return fetch(`${url}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'X-CSRFToken': csrfToken
|
'X-CSRFToken': csrfToken,
|
||||||
},
|
},
|
||||||
body: JSON.stringify(updatedData),
|
body: JSON.stringify(updatedData),
|
||||||
credentials: 'include'
|
credentials: 'include',
|
||||||
})
|
}).then(requestResponseHandler);
|
||||||
.then(requestResponseHandler)
|
|
||||||
};
|
|
||||||
|
|
||||||
const removeDatas = (url, csrfToken) => {
|
|
||||||
return fetch(`${url}`, {
|
|
||||||
method: 'DELETE',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'X-CSRFToken': csrfToken
|
|
||||||
},
|
|
||||||
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 = () => {
|
export const fetchPlannings = () => {
|
||||||
return getData(`${BE_PLANNING_PLANNINGS_URL}`)
|
return getData(`${BE_PLANNING_PLANNINGS_URL}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const getPlanning = (id) => {
|
export const getPlanning = (id) => {
|
||||||
return getData(`${BE_PLANNING_PLANNINGS_URL}/${id}`)
|
return getData(`${BE_PLANNING_PLANNINGS_URL}/${id}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createPlanning = (newData, csrfToken) => {
|
export const createPlanning = (newData, csrfToken) => {
|
||||||
return createDatas(`${BE_PLANNING_PLANNINGS_URL}`, 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}`)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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) => {
|
export const getEvent = (id) => {
|
||||||
return getData(`${BE_PLANNING_EVENTS_URL}/${id}`)
|
return getData(`${BE_PLANNING_EVENTS_URL}/${id}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createEvent = (newData, csrfToken) => {
|
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) => {
|
export const updateEvent = (id, newData, csrfToken) => {
|
||||||
return updateDatas(`${BE_PLANNING_EVENTS_URL}/${id}`, newData, csrfToken)
|
return updateDatas(`${BE_PLANNING_EVENTS_URL}/${id}`, newData, csrfToken);
|
||||||
}
|
};
|
||||||
|
|
||||||
export const deleteEvent = (id, 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 = () => {
|
export const fetchUpcomingEvents = () => {
|
||||||
return getData(`${BE_PLANNING_EVENTS_URL}/upcoming`);
|
return getData(`${BE_PLANNING_EVENTS_URL}/upcoming`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,30 +1,33 @@
|
|||||||
import { BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL,
|
import {
|
||||||
BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL,
|
BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL,
|
||||||
BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL,
|
BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL,
|
||||||
FE_API_DOCUSEAL_CLONE_URL,
|
BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL,
|
||||||
FE_API_DOCUSEAL_DOWNLOAD_URL,
|
FE_API_DOCUSEAL_CLONE_URL,
|
||||||
FE_API_DOCUSEAL_GENERATE_TOKEN
|
FE_API_DOCUSEAL_DOWNLOAD_URL,
|
||||||
|
FE_API_DOCUSEAL_GENERATE_TOKEN,
|
||||||
} from '@/utils/Url';
|
} from '@/utils/Url';
|
||||||
|
|
||||||
const requestResponseHandler = async (response) => {
|
const requestResponseHandler = async (response) => {
|
||||||
|
|
||||||
const body = await response.json();
|
const body = await response.json();
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
// Throw an error with the JSON body containing the form errors
|
// Throw an error with the JSON body containing the form errors
|
||||||
const error = new Error(body?.errorMessage || "Une erreur est survenue");
|
const error = new Error(body?.errorMessage || 'Une erreur est survenue');
|
||||||
error.details = body;
|
error.details = body;
|
||||||
throw error;
|
throw error;
|
||||||
}
|
};
|
||||||
|
|
||||||
export async function fetchRegistrationFileGroups(establishment) {
|
export async function fetchRegistrationFileGroups(establishment) {
|
||||||
const response = await fetch(`${BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL}?establishment_id=${establishment}`, {
|
const response = await fetch(
|
||||||
credentials: 'include',
|
`${BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL}?establishment_id=${establishment}`,
|
||||||
headers: {
|
{
|
||||||
'Accept': 'application/json',
|
credentials: 'include',
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error('Failed to fetch file groups');
|
throw new Error('Failed to fetch file groups');
|
||||||
}
|
}
|
||||||
@ -32,15 +35,18 @@ export async function fetchRegistrationFileGroups(establishment) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function createRegistrationFileGroup(groupData, csrfToken) {
|
export async function createRegistrationFileGroup(groupData, csrfToken) {
|
||||||
const response = await fetch(`${BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL}`, {
|
const response = await fetch(
|
||||||
method: 'POST',
|
`${BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL}`,
|
||||||
headers: {
|
{
|
||||||
'Content-Type': 'application/json',
|
method: 'POST',
|
||||||
'X-CSRFToken': csrfToken,
|
headers: {
|
||||||
},
|
'Content-Type': 'application/json',
|
||||||
body: JSON.stringify(groupData),
|
'X-CSRFToken': csrfToken,
|
||||||
credentials: 'include'
|
},
|
||||||
});
|
body: JSON.stringify(groupData),
|
||||||
|
credentials: 'include',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error('Failed to create file group');
|
throw new Error('Failed to create file group');
|
||||||
@ -50,26 +56,36 @@ export async function createRegistrationFileGroup(groupData, csrfToken) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteRegistrationFileGroup(groupId, csrfToken) {
|
export async function deleteRegistrationFileGroup(groupId, csrfToken) {
|
||||||
const response = await fetch(`${BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL}/${groupId}`, {
|
const response = await fetch(
|
||||||
method: 'DELETE',
|
`${BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL}/${groupId}`,
|
||||||
headers: {
|
{
|
||||||
'X-CSRFToken': csrfToken,
|
method: 'DELETE',
|
||||||
},
|
headers: {
|
||||||
credentials: 'include'
|
'X-CSRFToken': csrfToken,
|
||||||
});
|
},
|
||||||
|
credentials: 'include',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const editRegistrationFileGroup = async (groupId, groupData, csrfToken) => {
|
export const editRegistrationFileGroup = async (
|
||||||
const response = await fetch(`${BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL}/${groupId}`, {
|
groupId,
|
||||||
method: 'PUT',
|
groupData,
|
||||||
headers: {
|
csrfToken
|
||||||
'Content-Type': 'application/json',
|
) => {
|
||||||
'X-CSRFToken': csrfToken,
|
const response = await fetch(
|
||||||
},
|
`${BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL}/${groupId}`,
|
||||||
body: JSON.stringify(groupData),
|
{
|
||||||
});
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRFToken': csrfToken,
|
||||||
|
},
|
||||||
|
body: JSON.stringify(groupData),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error('Erreur lors de la modification du groupe');
|
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) => {
|
export const fetchRegistrationFileFromGroup = async (groupId) => {
|
||||||
const response = await fetch(`${BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL}/${groupId}/templates`, {
|
const response = await fetch(
|
||||||
credentials: 'include',
|
`${BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL}/${groupId}/templates`,
|
||||||
headers: {
|
{
|
||||||
'Accept': 'application/json',
|
credentials: 'include',
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
if (!response.ok) {
|
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();
|
return response.json();
|
||||||
}
|
};
|
||||||
|
|
||||||
export const fetchRegistrationTemplates = (id = null) => {
|
export const fetchRegistrationTemplates = (id = null) => {
|
||||||
let url = `${BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL}`
|
let url = `${BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL}`;
|
||||||
if (id) {
|
if (id) {
|
||||||
url = `${BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL}/${id}`;
|
url = `${BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL}/${id}`;
|
||||||
}
|
}
|
||||||
const request = new Request(
|
const request = new Request(`${url}`, {
|
||||||
`${url}`,
|
method: 'GET',
|
||||||
{
|
headers: {
|
||||||
method:'GET',
|
'Content-Type': 'application/json',
|
||||||
headers: {
|
},
|
||||||
'Content-Type':'application/json'
|
});
|
||||||
},
|
return fetch(request).then(requestResponseHandler);
|
||||||
}
|
|
||||||
);
|
|
||||||
return fetch(request).then(requestResponseHandler)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const editRegistrationTemplates = (fileId, data, csrfToken) => {
|
export const editRegistrationTemplates = (fileId, data, csrfToken) => {
|
||||||
@ -116,12 +134,10 @@ export const editRegistrationTemplates = (fileId, data, csrfToken) => {
|
|||||||
'X-CSRFToken': csrfToken,
|
'X-CSRFToken': csrfToken,
|
||||||
},
|
},
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
})
|
}).then(requestResponseHandler);
|
||||||
.then(requestResponseHandler)
|
};
|
||||||
}
|
|
||||||
|
|
||||||
export const createRegistrationTemplates = (data,csrfToken) => {
|
|
||||||
|
|
||||||
|
export const createRegistrationTemplates = (data, csrfToken) => {
|
||||||
return fetch(`${BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL}`, {
|
return fetch(`${BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL}`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify(data),
|
body: JSON.stringify(data),
|
||||||
@ -130,73 +146,72 @@ export const createRegistrationTemplates = (data,csrfToken) => {
|
|||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
})
|
}).then(requestResponseHandler);
|
||||||
.then(requestResponseHandler)
|
};
|
||||||
}
|
|
||||||
|
|
||||||
export const deleteRegistrationTemplates = (fileId,csrfToken) => {
|
export const deleteRegistrationTemplates = (fileId, csrfToken) => {
|
||||||
return fetch(`${BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL}/${fileId}`, {
|
return fetch(`${BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL}/${fileId}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: {
|
headers: {
|
||||||
'X-CSRFToken': csrfToken,
|
'X-CSRFToken': csrfToken,
|
||||||
},
|
},
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
export const fetchRegistrationTemplateMaster = (id = null) => {
|
export const fetchRegistrationTemplateMaster = (id = null) => {
|
||||||
let url = `${BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL}`;
|
let url = `${BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL}`;
|
||||||
if(id){
|
if (id) {
|
||||||
url = `${BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL}/${id}`;
|
url = `${BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL}/${id}`;
|
||||||
}
|
}
|
||||||
const request = new Request(
|
const request = new Request(`${url}`, {
|
||||||
`${url}`,
|
method: 'GET',
|
||||||
{
|
headers: {
|
||||||
method:'GET',
|
'Content-Type': 'application/json',
|
||||||
headers: {
|
},
|
||||||
'Content-Type':'application/json'
|
});
|
||||||
},
|
return fetch(request).then(requestResponseHandler);
|
||||||
}
|
|
||||||
);
|
|
||||||
return fetch(request).then(requestResponseHandler)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createRegistrationTemplateMaster = (data,csrfToken) => {
|
export const createRegistrationTemplateMaster = (data, csrfToken) => {
|
||||||
|
|
||||||
return fetch(`${BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL}`, {
|
return fetch(`${BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL}`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify(data),
|
body: JSON.stringify(data),
|
||||||
headers: {
|
headers: {
|
||||||
'X-CSRFToken': csrfToken,
|
'X-CSRFToken': csrfToken,
|
||||||
'Content-Type':'application/json'
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
})
|
}).then(requestResponseHandler);
|
||||||
.then(requestResponseHandler)
|
};
|
||||||
}
|
|
||||||
|
|
||||||
export const deleteRegistrationTemplateMaster = (fileId,csrfToken) => {
|
export const deleteRegistrationTemplateMaster = (fileId, csrfToken) => {
|
||||||
return fetch(`${BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL}/${fileId}`, {
|
return fetch(
|
||||||
method: 'DELETE',
|
`${BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL}/${fileId}`,
|
||||||
headers: {
|
{
|
||||||
'X-CSRFToken': csrfToken,
|
method: 'DELETE',
|
||||||
},
|
headers: {
|
||||||
credentials: 'include',
|
'X-CSRFToken': csrfToken,
|
||||||
})
|
},
|
||||||
}
|
credentials: 'include',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const editRegistrationTemplateMaster = (fileId, data, csrfToken) => {
|
export const editRegistrationTemplateMaster = (fileId, data, csrfToken) => {
|
||||||
return fetch(`${BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL}/${fileId}`, {
|
return fetch(
|
||||||
method: 'PUT',
|
`${BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL}/${fileId}`,
|
||||||
body: JSON.stringify(data),
|
{
|
||||||
headers: {
|
method: 'PUT',
|
||||||
'X-CSRFToken': csrfToken,
|
body: JSON.stringify(data),
|
||||||
'Content-Type':'application/json'
|
headers: {
|
||||||
},
|
'X-CSRFToken': csrfToken,
|
||||||
credentials: 'include',
|
'Content-Type': 'application/json',
|
||||||
})
|
},
|
||||||
.then(requestResponseHandler)
|
credentials: 'include',
|
||||||
}
|
}
|
||||||
|
).then(requestResponseHandler);
|
||||||
|
};
|
||||||
|
|
||||||
export const cloneTemplate = (templateId, email, is_required) => {
|
export const cloneTemplate = (templateId, email, is_required) => {
|
||||||
return fetch(`${FE_API_DOCUSEAL_CLONE_URL}`, {
|
return fetch(`${FE_API_DOCUSEAL_CLONE_URL}`, {
|
||||||
@ -207,21 +222,19 @@ export const cloneTemplate = (templateId, email, is_required) => {
|
|||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
templateId,
|
templateId,
|
||||||
email,
|
email,
|
||||||
is_required
|
is_required,
|
||||||
})
|
}),
|
||||||
})
|
}).then(requestResponseHandler);
|
||||||
.then(requestResponseHandler)
|
};
|
||||||
}
|
|
||||||
|
|
||||||
export const downloadTemplate = (slug) => {
|
export const downloadTemplate = (slug) => {
|
||||||
return fetch(`${FE_API_DOCUSEAL_DOWNLOAD_URL}/${slug}`, {
|
return fetch(`${FE_API_DOCUSEAL_DOWNLOAD_URL}/${slug}`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
}
|
},
|
||||||
})
|
}).then(requestResponseHandler);
|
||||||
.then(requestResponseHandler)
|
};
|
||||||
}
|
|
||||||
|
|
||||||
export const generateToken = (email, id = null) => {
|
export const generateToken = (email, id = null) => {
|
||||||
return fetch(`${FE_API_DOCUSEAL_GENERATE_TOKEN}`, {
|
return fetch(`${FE_API_DOCUSEAL_GENERATE_TOKEN}`, {
|
||||||
@ -231,4 +244,4 @@ export const generateToken = (email, id = null) => {
|
|||||||
},
|
},
|
||||||
body: JSON.stringify({ user_email: email, id }),
|
body: JSON.stringify({ user_email: email, id }),
|
||||||
}).then(requestResponseHandler);
|
}).then(requestResponseHandler);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,127 +1,133 @@
|
|||||||
import {
|
import {
|
||||||
BE_SCHOOL_SPECIALITIES_URL,
|
BE_SCHOOL_SPECIALITIES_URL,
|
||||||
BE_SCHOOL_TEACHERS_URL,
|
BE_SCHOOL_TEACHERS_URL,
|
||||||
BE_SCHOOL_SCHOOLCLASSES_URL,
|
BE_SCHOOL_SCHOOLCLASSES_URL,
|
||||||
BE_SCHOOL_PLANNINGS_URL,
|
BE_SCHOOL_PLANNINGS_URL,
|
||||||
BE_SCHOOL_FEES_URL,
|
BE_SCHOOL_FEES_URL,
|
||||||
BE_SCHOOL_DISCOUNTS_URL,
|
BE_SCHOOL_DISCOUNTS_URL,
|
||||||
BE_SCHOOL_PAYMENT_PLANS_URL,
|
BE_SCHOOL_PAYMENT_PLANS_URL,
|
||||||
BE_SCHOOL_PAYMENT_MODES_URL,
|
BE_SCHOOL_PAYMENT_MODES_URL,
|
||||||
BE_SCHOOL_ESTABLISHMENT_URL
|
BE_SCHOOL_ESTABLISHMENT_URL,
|
||||||
} from '@/utils/Url';
|
} from '@/utils/Url';
|
||||||
|
|
||||||
const requestResponseHandler = async (response) => {
|
const requestResponseHandler = async (response) => {
|
||||||
|
|
||||||
const body = await response.json();
|
const body = await response.json();
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
// Throw an error with the JSON body containing the form errors
|
// Throw an error with the JSON body containing the form errors
|
||||||
const error = new Error(body?.errorMessage || "Une erreur est survenue");
|
const error = new Error(body?.errorMessage || 'Une erreur est survenue');
|
||||||
error.details = body;
|
error.details = body;
|
||||||
throw error;
|
throw error;
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
export const fetchSpecialities = (establishment) => {
|
export const fetchSpecialities = (establishment) => {
|
||||||
return fetch(`${BE_SCHOOL_SPECIALITIES_URL}?establishment_id=${establishment}`)
|
return fetch(
|
||||||
.then(requestResponseHandler)
|
`${BE_SCHOOL_SPECIALITIES_URL}?establishment_id=${establishment}`
|
||||||
|
).then(requestResponseHandler);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchTeachers = (establishment) => {
|
export const fetchTeachers = (establishment) => {
|
||||||
return fetch(`${BE_SCHOOL_TEACHERS_URL}?establishment_id=${establishment}`)
|
return fetch(
|
||||||
.then(requestResponseHandler)
|
`${BE_SCHOOL_TEACHERS_URL}?establishment_id=${establishment}`
|
||||||
|
).then(requestResponseHandler);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchClasses = (establishment) => {
|
export const fetchClasses = (establishment) => {
|
||||||
return fetch(`${BE_SCHOOL_SCHOOLCLASSES_URL}?establishment_id=${establishment}`)
|
return fetch(
|
||||||
.then(requestResponseHandler)
|
`${BE_SCHOOL_SCHOOLCLASSES_URL}?establishment_id=${establishment}`
|
||||||
|
).then(requestResponseHandler);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchSchedules = () => {
|
export const fetchSchedules = () => {
|
||||||
return fetch(`${BE_SCHOOL_PLANNINGS_URL}`)
|
return fetch(`${BE_SCHOOL_PLANNINGS_URL}`).then(requestResponseHandler);
|
||||||
.then(requestResponseHandler)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchRegistrationDiscounts = (establishment) => {
|
export const fetchRegistrationDiscounts = (establishment) => {
|
||||||
return fetch(`${BE_SCHOOL_DISCOUNTS_URL}?filter=registration&establishment_id=${establishment}`)
|
return fetch(
|
||||||
.then(requestResponseHandler)
|
`${BE_SCHOOL_DISCOUNTS_URL}?filter=registration&establishment_id=${establishment}`
|
||||||
|
).then(requestResponseHandler);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchTuitionDiscounts = (establishment) => {
|
export const fetchTuitionDiscounts = (establishment) => {
|
||||||
return fetch(`${BE_SCHOOL_DISCOUNTS_URL}?filter=tuition&establishment_id=${establishment}`)
|
return fetch(
|
||||||
.then(requestResponseHandler)
|
`${BE_SCHOOL_DISCOUNTS_URL}?filter=tuition&establishment_id=${establishment}`
|
||||||
|
).then(requestResponseHandler);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchRegistrationFees = (establishment) => {
|
export const fetchRegistrationFees = (establishment) => {
|
||||||
return fetch(`${BE_SCHOOL_FEES_URL}?filter=registration&establishment_id=${establishment}`)
|
return fetch(
|
||||||
.then(requestResponseHandler)
|
`${BE_SCHOOL_FEES_URL}?filter=registration&establishment_id=${establishment}`
|
||||||
|
).then(requestResponseHandler);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchTuitionFees = (establishment) => {
|
export const fetchTuitionFees = (establishment) => {
|
||||||
return fetch(`${BE_SCHOOL_FEES_URL}?filter=tuition&establishment_id=${establishment}`)
|
return fetch(
|
||||||
.then(requestResponseHandler)
|
`${BE_SCHOOL_FEES_URL}?filter=tuition&establishment_id=${establishment}`
|
||||||
|
).then(requestResponseHandler);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchRegistrationPaymentPlans = (establishment) => {
|
export const fetchRegistrationPaymentPlans = (establishment) => {
|
||||||
return fetch(`${BE_SCHOOL_PAYMENT_PLANS_URL}?filter=registration&establishment_id=${establishment}`)
|
return fetch(
|
||||||
.then(requestResponseHandler)
|
`${BE_SCHOOL_PAYMENT_PLANS_URL}?filter=registration&establishment_id=${establishment}`
|
||||||
}
|
).then(requestResponseHandler);
|
||||||
|
};
|
||||||
|
|
||||||
export const fetchTuitionPaymentPlans = (establishment) => {
|
export const fetchTuitionPaymentPlans = (establishment) => {
|
||||||
return fetch(`${BE_SCHOOL_PAYMENT_PLANS_URL}?filter=tuition&establishment_id=${establishment}`)
|
return fetch(
|
||||||
.then(requestResponseHandler)
|
`${BE_SCHOOL_PAYMENT_PLANS_URL}?filter=tuition&establishment_id=${establishment}`
|
||||||
}
|
).then(requestResponseHandler);
|
||||||
|
};
|
||||||
|
|
||||||
export const fetchRegistrationPaymentModes = (establishment) => {
|
export const fetchRegistrationPaymentModes = (establishment) => {
|
||||||
return fetch(`${BE_SCHOOL_PAYMENT_MODES_URL}?filter=registration&establishment_id=${establishment}`)
|
return fetch(
|
||||||
.then(requestResponseHandler)
|
`${BE_SCHOOL_PAYMENT_MODES_URL}?filter=registration&establishment_id=${establishment}`
|
||||||
}
|
).then(requestResponseHandler);
|
||||||
|
};
|
||||||
|
|
||||||
export const fetchTuitionPaymentModes = (establishment) => {
|
export const fetchTuitionPaymentModes = (establishment) => {
|
||||||
return fetch(`${BE_SCHOOL_PAYMENT_MODES_URL}?filter=tuition&establishment_id=${establishment}`)
|
return fetch(
|
||||||
.then(requestResponseHandler)
|
`${BE_SCHOOL_PAYMENT_MODES_URL}?filter=tuition&establishment_id=${establishment}`
|
||||||
}
|
).then(requestResponseHandler);
|
||||||
|
};
|
||||||
|
|
||||||
export const fetchEstablishment = (establishment) => {
|
export const fetchEstablishment = (establishment) => {
|
||||||
return fetch(`${BE_SCHOOL_ESTABLISHMENT_URL}/${establishment}`)
|
return fetch(`${BE_SCHOOL_ESTABLISHMENT_URL}/${establishment}`).then(
|
||||||
.then(requestResponseHandler)
|
requestResponseHandler
|
||||||
}
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const createDatas = (url, newData, csrfToken) => {
|
export const createDatas = (url, newData, csrfToken) => {
|
||||||
return fetch(url, {
|
return fetch(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'X-CSRFToken': csrfToken
|
'X-CSRFToken': csrfToken,
|
||||||
},
|
},
|
||||||
body: JSON.stringify(newData),
|
body: JSON.stringify(newData),
|
||||||
credentials: 'include'
|
credentials: 'include',
|
||||||
})
|
}).then(requestResponseHandler);
|
||||||
.then(requestResponseHandler)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const updateDatas = (url, id, updatedData, csrfToken) => {
|
export const updateDatas = (url, id, updatedData, csrfToken) => {
|
||||||
return fetch(`${url}/${id}`, {
|
return fetch(`${url}/${id}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'X-CSRFToken': csrfToken
|
'X-CSRFToken': csrfToken,
|
||||||
},
|
},
|
||||||
body: JSON.stringify(updatedData),
|
body: JSON.stringify(updatedData),
|
||||||
credentials: 'include'
|
credentials: 'include',
|
||||||
})
|
}).then(requestResponseHandler);
|
||||||
.then(requestResponseHandler)
|
};
|
||||||
};
|
|
||||||
|
|
||||||
export const removeDatas = (url, id, csrfToken) => {
|
export const removeDatas = (url, id, csrfToken) => {
|
||||||
return fetch(`${url}/${id}`, {
|
return fetch(`${url}/${id}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'X-CSRFToken': csrfToken
|
'X-CSRFToken': csrfToken,
|
||||||
},
|
},
|
||||||
credentials: 'include'
|
credentials: 'include',
|
||||||
})
|
}).then(requestResponseHandler);
|
||||||
.then(requestResponseHandler)
|
};
|
||||||
};
|
|
||||||
|
|||||||
@ -1,150 +1,152 @@
|
|||||||
import {
|
import {
|
||||||
BE_SUBSCRIPTION_STUDENTS_URL,
|
BE_SUBSCRIPTION_STUDENTS_URL,
|
||||||
BE_SUBSCRIPTION_CHILDRENS_URL,
|
BE_SUBSCRIPTION_CHILDRENS_URL,
|
||||||
BE_SUBSCRIPTION_REGISTERFORMS_URL,
|
BE_SUBSCRIPTION_REGISTERFORMS_URL,
|
||||||
BE_SUBSCRIPTION_LAST_GUARDIAN_ID_URL
|
BE_SUBSCRIPTION_LAST_GUARDIAN_ID_URL,
|
||||||
} from '@/utils/Url';
|
} from '@/utils/Url';
|
||||||
|
|
||||||
export const PENDING = 'pending';
|
export const PENDING = 'pending';
|
||||||
export const SUBSCRIBED = 'subscribed';
|
export const SUBSCRIBED = 'subscribed';
|
||||||
export const ARCHIVED = 'archived';
|
export const ARCHIVED = 'archived';
|
||||||
|
|
||||||
|
|
||||||
const requestResponseHandler = async (response) => {
|
const requestResponseHandler = async (response) => {
|
||||||
|
|
||||||
const body = await response.json();
|
const body = await response.json();
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
// Throw an error with the JSON body containing the form errors
|
// Throw an error with the JSON body containing the form errors
|
||||||
const error = new Error(body?.errorMessage || "Une erreur est survenue");
|
const error = new Error(body?.errorMessage || 'Une erreur est survenue');
|
||||||
error.details = body;
|
error.details = body;
|
||||||
throw error;
|
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}`, {
|
return fetch(`${BE_SUBSCRIPTION_REGISTERFORMS_URL}/${id}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: {
|
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,
|
body: data,
|
||||||
credentials: 'include'
|
credentials: 'include',
|
||||||
})
|
}).then(requestResponseHandler);
|
||||||
.then(requestResponseHandler)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createRegisterForm=(data, csrfToken)=>{
|
export const createRegisterForm = (data, csrfToken) => {
|
||||||
const url = `${BE_SUBSCRIPTION_REGISTERFORMS_URL}`;
|
const url = `${BE_SUBSCRIPTION_REGISTERFORMS_URL}`;
|
||||||
return fetch(url, {
|
return fetch(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'X-CSRFToken': csrfToken
|
'X-CSRFToken': csrfToken,
|
||||||
},
|
},
|
||||||
body: JSON.stringify(data),
|
body: JSON.stringify(data),
|
||||||
credentials: 'include'
|
credentials: 'include',
|
||||||
})
|
}).then(requestResponseHandler);
|
||||||
.then(requestResponseHandler)
|
};
|
||||||
}
|
|
||||||
|
|
||||||
export const sendRegisterForm = (id) => {
|
export const sendRegisterForm = (id) => {
|
||||||
const url = `${BE_SUBSCRIPTION_REGISTERFORMS_URL}/${id}/send`;
|
const url = `${BE_SUBSCRIPTION_REGISTERFORMS_URL}/${id}/send`;
|
||||||
return fetch(url, {
|
return fetch(url, {
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
}).then(requestResponseHandler)
|
}).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) =>{
|
export const resendRegisterForm = (id) => {
|
||||||
const request = new Request(
|
const url = `${BE_SUBSCRIPTION_REGISTERFORMS_URL}/${id}/resend`;
|
||||||
`${BE_SUBSCRIPTION_CHILDRENS_URL}/${id}?establishment_id=${establishment}`,
|
return fetch(url, {
|
||||||
{
|
headers: {
|
||||||
method:'GET',
|
'Content-Type': 'application/json',
|
||||||
headers: {
|
},
|
||||||
'Content-Type':'application/json'
|
}).then(requestResponseHandler);
|
||||||
},
|
};
|
||||||
}
|
export const archiveRegisterForm = (id) => {
|
||||||
);
|
const url = `${BE_SUBSCRIPTION_REGISTERFORMS_URL}/${id}/archive`;
|
||||||
return fetch(request).then(requestResponseHandler)
|
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) {
|
export async function getRegisterFormFileTemplate(fileId) {
|
||||||
const response = await fetch(`${BE_SUBSCRIPTION_REGISTERFORM_FILE_TEMPLATE_URL}/${fileId}`, {
|
const response = await fetch(
|
||||||
credentials: 'include',
|
`${BE_SUBSCRIPTION_REGISTERFORM_FILE_TEMPLATE_URL}/${fileId}`,
|
||||||
headers: {
|
{
|
||||||
'Accept': 'application/json',
|
credentials: 'include',
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error('Failed to fetch file template');
|
throw new Error('Failed to fetch file template');
|
||||||
}
|
}
|
||||||
@ -152,28 +154,36 @@ export async function getRegisterFormFileTemplate(fileId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const fetchTemplatesFromRegistrationFiles = async (id) => {
|
export const fetchTemplatesFromRegistrationFiles = async (id) => {
|
||||||
const response = await fetch(`${BE_SUBSCRIPTION_REGISTERFORMS_URL}/${id}/templates`, {
|
const response = await fetch(
|
||||||
credentials: 'include',
|
`${BE_SUBSCRIPTION_REGISTERFORMS_URL}/${id}/templates`,
|
||||||
headers: {
|
{
|
||||||
'Accept': 'application/json',
|
credentials: 'include',
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
if (!response.ok) {
|
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();
|
return response.json();
|
||||||
}
|
};
|
||||||
|
|
||||||
export const dissociateGuardian = async (studentId, guardianId) => {
|
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',
|
credentials: 'include',
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: {
|
headers: {
|
||||||
'Accept': 'application/json',
|
Accept: 'application/json',
|
||||||
},
|
},
|
||||||
});
|
}
|
||||||
|
);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error('Erreur lors de la dissociation.');
|
throw new Error('Erreur lors de la dissociation.');
|
||||||
}
|
}
|
||||||
return response.json();
|
return response.json();
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { getMessages } from 'next-intl/server';
|
import { getMessages } from 'next-intl/server';
|
||||||
import Providers from '@/components/Providers'
|
import Providers from '@/components/Providers';
|
||||||
import "@/css/tailwind.css";
|
import '@/css/tailwind.css';
|
||||||
import { headers } from 'next/headers';
|
import { headers } from 'next/headers';
|
||||||
|
|
||||||
export const metadata = {
|
export const metadata = {
|
||||||
title: "N3WT-SCHOOL",
|
title: 'N3WT-SCHOOL',
|
||||||
description: "Gestion de l'école",
|
description: "Gestion de l'école",
|
||||||
icons: {
|
icons: {
|
||||||
icon: [
|
icon: [
|
||||||
@ -14,7 +14,7 @@ export const metadata = {
|
|||||||
type: 'image/svg+xml',
|
type: 'image/svg+xml',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
url: '/favicon.ico', // Fallback pour les anciens navigateurs
|
url: '/favicon.ico', // Fallback pour les anciens navigateurs
|
||||||
sizes: 'any',
|
sizes: 'any',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@ -1,15 +1,21 @@
|
|||||||
import Link from 'next/link'
|
import Link from 'next/link';
|
||||||
import Logo from '../components/Logo'
|
import Logo from '../components/Logo';
|
||||||
|
|
||||||
export default function NotFound() {
|
export default function NotFound() {
|
||||||
return (
|
return (
|
||||||
<div className='flex items-center justify-center min-h-screen bg-emerald-500'>
|
<div className="flex items-center justify-center min-h-screen bg-emerald-500">
|
||||||
<div className='text-center p-6 '>
|
<div className="text-center p-6 ">
|
||||||
<Logo className="w-32 h-32 mx-auto mb-4" />
|
<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>
|
<h2 className="text-2xl font-bold text-emerald-900 mb-4">
|
||||||
<p className='text-emerald-900 mb-4'>La ressource que vous souhaitez consulter n'existe pas ou plus.</p>
|
404 | Page non trouvée
|
||||||
<Link className="text-gray-900 hover:underline" href="/">Retour Accueil</Link>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
const AffectationClasseForm = ({ eleve = {}, onSubmit, classes }) => {
|
const AffectationClasseForm = ({ eleve = {}, onSubmit, classes }) => {
|
||||||
|
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
classeAssocie_id: eleve?.classeAssocie_id || null,
|
classeAssocie_id: eleve?.classeAssocie_id || null,
|
||||||
});
|
});
|
||||||
@ -17,10 +16,10 @@ const AffectationClasseForm = ({ eleve = {}, onSubmit, classes }) => {
|
|||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
onSubmit({
|
onSubmit({
|
||||||
eleve: {
|
eleve: {
|
||||||
...formData
|
...formData,
|
||||||
},
|
},
|
||||||
etat:5
|
etat: 5,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -30,18 +29,21 @@ const AffectationClasseForm = ({ eleve = {}, onSubmit, classes }) => {
|
|||||||
Classes
|
Classes
|
||||||
</label>
|
</label>
|
||||||
<div className="mt-2 grid grid-cols-1 gap-4">
|
<div className="mt-2 grid grid-cols-1 gap-4">
|
||||||
{classes.map(classe => (
|
{classes.map((classe) => (
|
||||||
<div key={classe.id} className="flex items-center">
|
<div key={classe.id} className="flex items-center">
|
||||||
<input
|
<input
|
||||||
type="radio"
|
type="radio"
|
||||||
id={`classe-${classe.id}`}
|
id={`classe-${classe.id}`}
|
||||||
name="classeAssocie_id"
|
name="classeAssocie_id"
|
||||||
value={classe.id}
|
value={classe.id}
|
||||||
checked={formData.classeAssocie_id === classe.id}
|
checked={formData.classeAssocie_id === classe.id}
|
||||||
onChange={handleChange}
|
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"
|
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}
|
{classe.atmosphere_name}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@ -50,15 +52,15 @@ const AffectationClasseForm = ({ eleve = {}, onSubmit, classes }) => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex justify-end mt-4 space-x-4">
|
<div className="flex justify-end mt-4 space-x-4">
|
||||||
<button
|
<button
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
className={`px-4 py-2 rounded-md shadow-sm focus:outline-none ${
|
className={`px-4 py-2 rounded-md shadow-sm focus:outline-none ${
|
||||||
(!formData.classeAssocie_id )
|
!formData.classeAssocie_id
|
||||||
? "bg-gray-300 text-gray-700 cursor-not-allowed"
|
? 'bg-gray-300 text-gray-700 cursor-not-allowed'
|
||||||
: "bg-emerald-500 text-white hover:bg-emerald-600"
|
: 'bg-emerald-500 text-white hover:bg-emerald-600'
|
||||||
}`}
|
}`}
|
||||||
disabled={(!formData.classeAssocie_id)}
|
disabled={!formData.classeAssocie_id}
|
||||||
>
|
>
|
||||||
Associer
|
Associer
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@ -2,11 +2,17 @@ import React from 'react';
|
|||||||
|
|
||||||
const AlertMessage = ({ title, message, buttonText, buttonLink }) => {
|
const AlertMessage = ({ title, message, buttonText, buttonLink }) => {
|
||||||
return (
|
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>
|
<h3 className="font-bold">{title}</h3>
|
||||||
<p className="mt-2">{message}</p>
|
<p className="mt-2">{message}</p>
|
||||||
<div className="alert-actions mt-4">
|
<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>
|
{buttonText} <i className="icon profile-add"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -2,28 +2,31 @@ import React, { useState } from 'react';
|
|||||||
import Modal from '@/components/Modal';
|
import Modal from '@/components/Modal';
|
||||||
import { UserPlus } from 'lucide-react';
|
import { UserPlus } from 'lucide-react';
|
||||||
|
|
||||||
const AlertWithModal = ({ title, message, buttonText}) => {
|
const AlertWithModal = ({ title, message, buttonText }) => {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
|
||||||
const openModal = () => {
|
const openModal = () => {
|
||||||
setIsOpen(true);
|
setIsOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="alert centered bg-yellow-100 border-l-4 border-yellow-500 text-yellow-700 p-4" role="alert">
|
<div
|
||||||
<h3 className="font-bold">{title}</h3>
|
className="alert centered bg-yellow-100 border-l-4 border-yellow-500 text-yellow-700 p-4"
|
||||||
<p className="mt-2">{message}</p>
|
role="alert"
|
||||||
<div className="alert-actions mt-4">
|
>
|
||||||
<button
|
<h3 className="font-bold">{title}</h3>
|
||||||
className="btn primary bg-emerald-500 text-white rounded-md px-4 py-2 hover:bg-emerald-600 flex items-center"
|
<p className="mt-2">{message}</p>
|
||||||
onClick={openModal}
|
<div className="alert-actions mt-4">
|
||||||
>
|
<button
|
||||||
{buttonText} <UserPlus size={20} className="ml-2" />
|
className="btn primary bg-emerald-500 text-white rounded-md px-4 py-2 hover:bg-emerald-600 flex items-center"
|
||||||
</button>
|
onClick={openModal}
|
||||||
</div>
|
>
|
||||||
<Modal isOpen={isOpen} setIsOpen={setIsOpen} />
|
{buttonText} <UserPlus size={20} className="ml-2" />
|
||||||
</div>
|
</button>
|
||||||
);
|
</div>
|
||||||
|
<Modal isOpen={isOpen} setIsOpen={setIsOpen} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AlertWithModal;
|
export default AlertWithModal;
|
||||||
|
|||||||
@ -1,37 +1,39 @@
|
|||||||
|
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
|
const AlphabetPaginationNumber = ({ letter, active, onClick }) => (
|
||||||
const AlphabetPaginationNumber = ({ letter, active , onClick}) => (
|
<button
|
||||||
<button className={`w-8 h-8 flex items-center justify-center rounded ${
|
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'
|
active
|
||||||
}`} onClick={onClick}>
|
? 'bg-emerald-500 text-white'
|
||||||
|
: 'text-gray-600 bg-gray-200 hover:bg-gray-50'
|
||||||
|
}`}
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
{letter}
|
{letter}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const AlphabetLinks = ({ filter, onLetterClick }) => {
|
||||||
const AlphabetLinks = ({filter, onLetterClick }) => {
|
|
||||||
const [currentLetter, setCurrentLetter] = useState(filter);
|
const [currentLetter, setCurrentLetter] = useState(filter);
|
||||||
const alphabet = "*ABCDEFGHIJKLMNOPQRSTUVWXYZ".split('');
|
const alphabet = '*ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="px-6 py-4 border-t border-gray-200 flex items-center justify-between">
|
<div className="px-6 py-4 border-t border-gray-200 flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{alphabet.map((letter) => (
|
{alphabet.map((letter) => (
|
||||||
<AlphabetPaginationNumber
|
<AlphabetPaginationNumber
|
||||||
key={letter}
|
key={letter}
|
||||||
letter={letter}
|
letter={letter}
|
||||||
active={currentLetter === letter }
|
active={currentLetter === letter}
|
||||||
onClick={() => {setCurrentLetter(letter);onLetterClick(letter)}}
|
onClick={() => {
|
||||||
/>
|
setCurrentLetter(letter);
|
||||||
|
onLetterClick(letter);
|
||||||
|
}}
|
||||||
))}
|
/>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AlphabetLinks;
|
export default AlphabetLinks;
|
||||||
|
|||||||
@ -1,9 +1,18 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useRouter } from 'next/navigation';
|
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 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 primaryClass = 'bg-emerald-500 hover:bg-emerald-600';
|
||||||
const secondaryClass = 'bg-gray-300 hover:bg-gray-400 text-black';
|
const secondaryClass = 'bg-gray-300 hover:bg-gray-400 text-black';
|
||||||
const buttonClass = `${baseClass} ${primary && !disabled ? primaryClass : secondaryClass} ${className}`;
|
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 PlanningView from '@/components/Calendar/PlanningView';
|
||||||
import ToggleView from '@/components/ToggleView';
|
import ToggleView from '@/components/ToggleView';
|
||||||
import { ChevronLeft, ChevronRight, Plus, ChevronDown } from 'lucide-react';
|
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 { fr } from 'date-fns/locale';
|
||||||
import { AnimatePresence, motion } from 'framer-motion'; // Ajouter cet import
|
import { AnimatePresence, motion } from 'framer-motion'; // Ajouter cet import
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
|
|
||||||
const Calendar = ({ onDateClick, onEventClick }) => {
|
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 [visibleEvents, setVisibleEvents] = useState([]);
|
||||||
const [showDatePicker, setShowDatePicker] = useState(false);
|
const [showDatePicker, setShowDatePicker] = useState(false);
|
||||||
|
|
||||||
// Ajouter ces fonctions pour la gestion des mois et années
|
// Ajouter ces fonctions pour la gestion des mois et années
|
||||||
const months = Array.from({ length: 12 }, (_, i) => ({
|
const months = Array.from({ length: 12 }, (_, i) => ({
|
||||||
value: 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) => ({
|
const years = Array.from({ length: 10 }, (_, i) => ({
|
||||||
value: new Date().getFullYear() - 5 + i,
|
value: new Date().getFullYear() - 5 + i,
|
||||||
label: new Date().getFullYear() - 5 + i
|
label: new Date().getFullYear() - 5 + i,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const handleMonthSelect = (monthIndex) => {
|
const handleMonthSelect = (monthIndex) => {
|
||||||
@ -39,7 +57,9 @@ const Calendar = ({ onDateClick, onEventClick }) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// S'assurer que le filtrage est fait au niveau parent
|
// 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);
|
setVisibleEvents(filtered);
|
||||||
logger.debug('Events filtrés:', filtered); // Debug
|
logger.debug('Events filtrés:', filtered); // Debug
|
||||||
}, [events, hiddenSchedules]);
|
}, [events, hiddenSchedules]);
|
||||||
@ -78,7 +98,10 @@ const Calendar = ({ onDateClick, onEventClick }) => {
|
|||||||
>
|
>
|
||||||
Aujourd'hui
|
Aujourd'hui
|
||||||
</button>
|
</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" />
|
<ChevronLeft className="w-5 h-5" />
|
||||||
</button>
|
</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"
|
className="flex items-center gap-1 px-2 py-1 hover:bg-gray-100 rounded-md"
|
||||||
>
|
>
|
||||||
<h2 className="text-xl font-semibold">
|
<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>
|
</h2>
|
||||||
<ChevronDown className="w-4 h-4" />
|
<ChevronDown className="w-4 h-4" />
|
||||||
</button>
|
</button>
|
||||||
@ -129,7 +156,10 @@ const Calendar = ({ onDateClick, onEventClick }) => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</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" />
|
<ChevronRight className="w-5 h-5" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -168,7 +198,11 @@ const Calendar = ({ onDateClick, onEventClick }) => {
|
|||||||
transition={{ duration: 0.2 }}
|
transition={{ duration: 0.2 }}
|
||||||
className="h-full flex flex-col"
|
className="h-full flex flex-col"
|
||||||
>
|
>
|
||||||
<WeekView onDateClick={onDateClick} onEventClick={onEventClick} events={visibleEvents} />
|
<WeekView
|
||||||
|
onDateClick={onDateClick}
|
||||||
|
onEventClick={onEventClick}
|
||||||
|
events={visibleEvents}
|
||||||
|
/>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
{viewType === 'month' && (
|
{viewType === 'month' && (
|
||||||
@ -179,7 +213,11 @@ const Calendar = ({ onDateClick, onEventClick }) => {
|
|||||||
exit={{ opacity: 0, y: -20 }}
|
exit={{ opacity: 0, y: -20 }}
|
||||||
transition={{ duration: 0.2 }}
|
transition={{ duration: 0.2 }}
|
||||||
>
|
>
|
||||||
<MonthView onDateClick={onDateClick} onEventClick={onEventClick} events={visibleEvents} />
|
<MonthView
|
||||||
|
onDateClick={onDateClick}
|
||||||
|
onEventClick={onEventClick}
|
||||||
|
events={visibleEvents}
|
||||||
|
/>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
{viewType === 'year' && (
|
{viewType === 'year' && (
|
||||||
@ -201,7 +239,10 @@ const Calendar = ({ onDateClick, onEventClick }) => {
|
|||||||
exit={{ opacity: 0, y: -20 }}
|
exit={{ opacity: 0, y: -20 }}
|
||||||
transition={{ duration: 0.2 }}
|
transition={{ duration: 0.2 }}
|
||||||
>
|
>
|
||||||
<PlanningView onEventClick={onEventClick} events={visibleEvents} />
|
<PlanningView
|
||||||
|
onEventClick={onEventClick}
|
||||||
|
events={visibleEvents}
|
||||||
|
/>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
@ -210,4 +251,4 @@ const Calendar = ({ onDateClick, onEventClick }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Calendar;
|
export default Calendar;
|
||||||
|
|||||||
@ -1,6 +1,15 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { usePlanning } from '@/context/PlanningContext';
|
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 { fr } from 'date-fns/locale';
|
||||||
import { getEventsForDate } from '@/utils/events';
|
import { getEventsForDate } from '@/utils/events';
|
||||||
|
|
||||||
@ -35,7 +44,8 @@ const MonthView = ({ onDateClick, onEventClick }) => {
|
|||||||
onClick={() => handleDayClick(day)}
|
onClick={() => handleDayClick(day)}
|
||||||
>
|
>
|
||||||
<div className="flex justify-between items-center mb-1">
|
<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' : ''}
|
${isCurrentDay ? 'bg-emerald-500 text-white' : ''}
|
||||||
${!isCurrentMonth ? 'text-gray-400' : ''}`}
|
${!isCurrentMonth ? 'text-gray-400' : ''}`}
|
||||||
>
|
>
|
||||||
@ -50,7 +60,7 @@ const MonthView = ({ onDateClick, onEventClick }) => {
|
|||||||
style={{
|
style={{
|
||||||
backgroundColor: `${event.color}15`,
|
backgroundColor: `${event.color}15`,
|
||||||
color: event.color,
|
color: event.color,
|
||||||
borderLeft: `2px solid ${event.color}`
|
borderLeft: `2px solid ${event.color}`,
|
||||||
}}
|
}}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
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">
|
<div className="h-full flex flex-col border border-gray-200 rounded-lg bg-white">
|
||||||
{/* En-tête des jours de la semaine */}
|
{/* En-tête des jours de la semaine */}
|
||||||
<div className="grid grid-cols-7 border-b">
|
<div className="grid grid-cols-7 border-b">
|
||||||
{['Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam', 'Dim'].map(day => (
|
{['Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam', 'Dim'].map((day) => (
|
||||||
<div key={day} className="p-2 text-center text-sm font-medium text-gray-500">
|
<div
|
||||||
|
key={day}
|
||||||
|
className="p-2 text-center text-sm font-medium text-gray-500"
|
||||||
|
>
|
||||||
{day}
|
{day}
|
||||||
</div>
|
</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
|
// Sinon, créer une entrée pour chaque jour
|
||||||
const days = eachDayOfInterval({ start, end });
|
const days = eachDayOfInterval({ start, end });
|
||||||
return days.map(day => ({
|
return days.map((day) => ({
|
||||||
...event,
|
...event,
|
||||||
displayDate: day,
|
displayDate: day,
|
||||||
isMultiDay: true
|
isMultiDay: true,
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
// Aplatir tous les événements en incluant les événements sur plusieurs jours
|
// Aplatir tous les événements en incluant les événements sur plusieurs jours
|
||||||
const flattenedEvents = events
|
const flattenedEvents = events
|
||||||
.flatMap(splitEventByDays)
|
.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 (
|
return (
|
||||||
<div className="bg-white h-full overflow-auto">
|
<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">
|
<td className="py-3 px-4 text-sm text-gray-900 whitespace-nowrap">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<span className="font-extrabold">{format(start, 'd')}</span>
|
<span className="font-extrabold">{format(start, 'd')}</span>
|
||||||
<span className="font-semibold">{format(start, 'MMM', { locale: fr }).toLowerCase()}</span>
|
<span className="font-semibold">
|
||||||
<span className="font-semibold">{format(start, 'EEE', { locale: fr })}</span>
|
{format(start, 'MMM', { locale: fr }).toLowerCase()}
|
||||||
|
</span>
|
||||||
|
<span className="font-semibold">
|
||||||
|
{format(start, 'EEE', { locale: fr })}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td className="py-3 px-4 text-sm text-gray-900 whitespace-nowrap">
|
<td className="py-3 px-4 text-sm text-gray-900 whitespace-nowrap">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<div
|
<div
|
||||||
className="w-2 h-2 rounded-full mr-2"
|
className="w-2 h-2 rounded-full mr-2"
|
||||||
style={{ backgroundColor: event.color }}
|
style={{ backgroundColor: event.color }}
|
||||||
/>
|
/>
|
||||||
{isMultiDay
|
{isMultiDay
|
||||||
? (isSameDay(start, new Date(event.start))
|
? isSameDay(start, new Date(event.start))
|
||||||
? "À partir de "
|
? 'À partir de '
|
||||||
: isSameDay(start, end)
|
: isSameDay(start, end)
|
||||||
? "Jusqu'à "
|
? "Jusqu'à "
|
||||||
: "Toute la journée")
|
: 'Toute la journée'
|
||||||
: ""
|
: ''}
|
||||||
}
|
{format(new Date(event.start), 'HH:mm')}
|
||||||
{format(new Date(event.start), 'HH:mm')}
|
{!isMultiDay && ` - ${format(end, 'HH:mm')}`}
|
||||||
{!isMultiDay && ` - ${format(end, 'HH:mm')}`}
|
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td className="py-3 px-4">
|
<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 React, { useEffect, useState, useRef } from 'react';
|
||||||
import { usePlanning } from '@/context/PlanningContext';
|
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 { fr } from 'date-fns/locale';
|
||||||
import { getWeekEvents } from '@/utils/events';
|
import { getWeekEvents } from '@/utils/events';
|
||||||
import { isToday } from 'date-fns';
|
import { isToday } from 'date-fns';
|
||||||
@ -16,7 +22,7 @@ const WeekView = ({ onDateClick, onEventClick, events }) => {
|
|||||||
const weekDays = Array.from({ length: 7 }, (_, i) => addDays(weekStart, i));
|
const weekDays = Array.from({ length: 7 }, (_, i) => addDays(weekStart, i));
|
||||||
|
|
||||||
// Maintenant on peut utiliser weekDays
|
// 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
|
// Mettre à jour la position de la ligne toutes les minutes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -61,7 +67,7 @@ const WeekView = ({ onDateClick, onEventClick, events }) => {
|
|||||||
const eventStart = new Date(event.start);
|
const eventStart = new Date(event.start);
|
||||||
const eventEnd = new Date(event.end);
|
const eventEnd = new Date(event.end);
|
||||||
|
|
||||||
return dayEvents.filter(otherEvent => {
|
return dayEvents.filter((otherEvent) => {
|
||||||
if (otherEvent.id === event.id) return false;
|
if (otherEvent.id === event.id) return false;
|
||||||
const otherStart = new Date(otherEvent.start);
|
const otherStart = new Date(otherEvent.start);
|
||||||
const otherEnd = new Date(otherEvent.end);
|
const otherEnd = new Date(otherEvent.end);
|
||||||
@ -77,7 +83,7 @@ const WeekView = ({ onDateClick, onEventClick, events }) => {
|
|||||||
|
|
||||||
// Trouver les événements qui se chevauchent
|
// Trouver les événements qui se chevauchent
|
||||||
const overlappingEvents = findOverlappingEvents(event, dayEvents);
|
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;
|
const totalOverlapping = overlappingEvents.length + 1;
|
||||||
|
|
||||||
// Calculer la largeur et la position horizontale
|
// Calculer la largeur et la position horizontale
|
||||||
@ -93,7 +99,7 @@ const WeekView = ({ onDateClick, onEventClick, events }) => {
|
|||||||
borderLeft: `3px solid ${event.color}`,
|
borderLeft: `3px solid ${event.color}`,
|
||||||
borderRadius: '0.25rem',
|
borderRadius: '0.25rem',
|
||||||
zIndex: 1,
|
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="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}
|
{event.title}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs" style={{ color: event.color, opacity: 0.75 }}>
|
<div
|
||||||
{format(new Date(event.start), 'HH:mm')} - {format(new Date(event.end), 'HH:mm')}
|
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>
|
</div>
|
||||||
{event.location && (
|
{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}
|
{event.location}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -130,7 +146,10 @@ const WeekView = ({ onDateClick, onEventClick, events }) => {
|
|||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-full overflow-hidden">
|
<div className="flex flex-col h-full overflow-hidden">
|
||||||
{/* En-tête des jours */}
|
{/* 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>
|
<div className="bg-white h-14"></div>
|
||||||
{weekDays.map((day) => (
|
{weekDays.map((day) => (
|
||||||
<div
|
<div
|
||||||
@ -142,8 +161,10 @@ const WeekView = ({ onDateClick, onEventClick, events }) => {
|
|||||||
<div className="text-xs font-medium text-gray-500">
|
<div className="text-xs font-medium text-gray-500">
|
||||||
{format(day, 'EEEE', { locale: fr })}
|
{format(day, 'EEEE', { locale: fr })}
|
||||||
</div>
|
</div>
|
||||||
<div className={`text-sm font-semibold inline-block rounded-full w-7 h-7 leading-7
|
<div
|
||||||
${isToday(day) ? 'bg-emerald-500 text-white' : ''}`}>
|
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 })}
|
{format(day, 'd', { locale: fr })}
|
||||||
</div>
|
</div>
|
||||||
</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"
|
className="absolute left-0 right-0 z-10 border-emerald-500 border pointer-events-none"
|
||||||
style={{
|
style={{
|
||||||
top: getCurrentTimePosition(),
|
top: getCurrentTimePosition(),
|
||||||
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div className="absolute -left-2 -top-1 w-2 h-2 rounded-full bg-emerald-500" />
|
||||||
className="absolute -left-2 -top-1 w-2 h-2 rounded-full bg-emerald-500"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="grid gap-[1px] bg-gray-100" style={{ gridTemplateColumns: "2.5rem repeat(7, 1fr)" }}>
|
<div
|
||||||
{timeSlots.map(hour => (
|
className="grid gap-[1px] bg-gray-100"
|
||||||
|
style={{ gridTemplateColumns: '2.5rem repeat(7, 1fr)' }}
|
||||||
|
>
|
||||||
|
{timeSlots.map((hour) => (
|
||||||
<React.Fragment key={hour}>
|
<React.Fragment key={hour}>
|
||||||
<div className="h-20 p-1 text-right text-sm text-gray-500 bg-gray-100 font-medium">
|
<div className="h-20 p-1 text-right text-sm text-gray-500 bg-gray-100 font-medium">
|
||||||
{`${hour.toString().padStart(2, '0')}:00`}
|
{`${hour.toString().padStart(2, '0')}:00`}
|
||||||
@ -188,11 +209,15 @@ const WeekView = ({ onDateClick, onEventClick, events }) => {
|
|||||||
onDateClick(date);
|
onDateClick(date);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex gap-1"> {/* Ajout de gap-1 */}
|
<div className="flex gap-1">
|
||||||
{dayEvents.filter(event => {
|
{' '}
|
||||||
const eventStart = new Date(event.start);
|
{/* Ajout de gap-1 */}
|
||||||
return eventStart.getHours() === hour;
|
{dayEvents
|
||||||
}).map(event => renderEventInCell(event, dayEvents))}
|
.filter((event) => {
|
||||||
|
const eventStart = new Date(event.start);
|
||||||
|
return eventStart.getHours() === hour;
|
||||||
|
})
|
||||||
|
.map((event) => renderEventInCell(event, dayEvents))}
|
||||||
</div>
|
</div>
|
||||||
</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 (
|
return (
|
||||||
<div className="grid grid-cols-4 gap-4 p-4">
|
<div className="grid grid-cols-4 gap-4 p-4">
|
||||||
{months.map(month => (
|
{months.map((month) => (
|
||||||
<MonthCard
|
<MonthCard
|
||||||
key={month.getTime()}
|
key={month.getTime()}
|
||||||
month={month}
|
month={month}
|
||||||
@ -49,4 +49,4 @@ const YearView = ({ onDateClick }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default YearView;
|
export default YearView;
|
||||||
|
|||||||
@ -1,13 +1,24 @@
|
|||||||
import React from 'react';
|
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 isChecked = formData[fieldName].includes(parseInt(item.id));
|
||||||
const isAttenuated = labelAttenuated(item) && !isChecked;
|
const isAttenuated = labelAttenuated(item) && !isChecked;
|
||||||
return (
|
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 && (
|
{horizontal && (
|
||||||
<label
|
<label
|
||||||
htmlFor={`${fieldName}-${item.id}`}
|
htmlFor={`${fieldName}-${item.id}`}
|
||||||
className={`block text-sm text-center mb-1 ${isAttenuated ? 'text-gray-300' : 'font-bold text-emerald-600'}`}
|
className={`block text-sm text-center mb-1 ${isAttenuated ? 'text-gray-300' : 'font-bold text-emerald-600'}`}
|
||||||
>
|
>
|
||||||
{itemLabelFunc(item)}
|
{itemLabelFunc(item)}
|
||||||
@ -24,8 +35,8 @@ const CheckBox = ({ item, formData, handleChange, fieldName, itemLabelFunc = ()
|
|||||||
style={{ borderRadius: '6px', outline: 'none', boxShadow: 'none' }}
|
style={{ borderRadius: '6px', outline: 'none', boxShadow: 'none' }}
|
||||||
/>
|
/>
|
||||||
{!horizontal && (
|
{!horizontal && (
|
||||||
<label
|
<label
|
||||||
htmlFor={`${fieldName}-${item.id}`}
|
htmlFor={`${fieldName}-${item.id}`}
|
||||||
className={`block text-sm ${isAttenuated ? 'text-gray-300' : 'font-bold text-emerald-600'}`}
|
className={`block text-sm ${isAttenuated ? 'text-gray-300' : 'font-bold text-emerald-600'}`}
|
||||||
>
|
>
|
||||||
{itemLabelFunc(item)}
|
{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,
|
className,
|
||||||
itemLabelFunc = (item) => item.name,
|
itemLabelFunc = (item) => item.name,
|
||||||
labelAttenuated = () => false,
|
labelAttenuated = () => false,
|
||||||
horizontal = false // Ajouter l'option horizontal
|
horizontal = false, // Ajouter l'option horizontal
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className={`mb-4 w-full ${className}`}>
|
<div className={`mb-4 w-full ${className}`}>
|
||||||
@ -19,8 +19,10 @@ const CheckBoxList = ({
|
|||||||
{Icon && <Icon className="w-5 h-5 mr-2" />}
|
{Icon && <Icon className="w-5 h-5 mr-2" />}
|
||||||
{label}
|
{label}
|
||||||
</label>
|
</label>
|
||||||
<div className={`mt-2 grid ${horizontal ? 'grid-cols-6 gap-2' : 'grid-cols-1 gap-4'}`}>
|
<div
|
||||||
{items.map(item => (
|
className={`mt-2 grid ${horizontal ? 'grid-cols-6 gap-2' : 'grid-cols-1 gap-4'}`}
|
||||||
|
>
|
||||||
|
{items.map((item) => (
|
||||||
<CheckBox
|
<CheckBox
|
||||||
key={`${fieldName}-${item.id}`}
|
key={`${fieldName}-${item.id}`}
|
||||||
item={item}
|
item={item}
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { GraduationCap } from 'lucide-react';
|
|||||||
const ClasseDetails = ({ classe }) => {
|
const ClasseDetails = ({ classe }) => {
|
||||||
if (!classe) return null;
|
if (!classe) return null;
|
||||||
|
|
||||||
const nombreElevesInscrits = classe?.eleves?.length||0;
|
const nombreElevesInscrits = classe?.eleves?.length || 0;
|
||||||
const capaciteTotale = classe.number_of_students;
|
const capaciteTotale = classe.number_of_students;
|
||||||
const pourcentage = Math.round((nombreElevesInscrits / capaciteTotale) * 100);
|
const pourcentage = Math.round((nombreElevesInscrits / capaciteTotale) * 100);
|
||||||
|
|
||||||
@ -42,14 +42,12 @@ const ClasseDetails = ({ classe }) => {
|
|||||||
{nombreElevesInscrits}/{capaciteTotale}
|
{nombreElevesInscrits}/{capaciteTotale}
|
||||||
</span>
|
</span>
|
||||||
<div className="w-32 bg-gray-200 rounded-full h-6 shadow-inner">
|
<div className="w-32 bg-gray-200 rounded-full h-6 shadow-inner">
|
||||||
<div
|
<div
|
||||||
className={`h-full rounded-full ${getColor(pourcentage)}`}
|
className={`h-full rounded-full ${getColor(pourcentage)}`}
|
||||||
style={{ width: `${pourcentage}%` }}
|
style={{ width: `${pourcentage}%` }}
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
<span className="ml-4 font-bold text-gray-700">
|
<span className="ml-4 font-bold text-gray-700">{pourcentage}%</span>
|
||||||
{pourcentage}%
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -60,7 +58,7 @@ const ClasseDetails = ({ classe }) => {
|
|||||||
columns={[
|
columns={[
|
||||||
{ name: 'NOM', transform: (row) => row.name },
|
{ name: 'NOM', transform: (row) => row.name },
|
||||||
{ name: 'PRENOM', transform: (row) => row.first_name },
|
{ name: 'PRENOM', transform: (row) => row.first_name },
|
||||||
{ name: 'AGE', transform: (row) => `${row.age}` }
|
{ name: 'AGE', transform: (row) => `${row.age}` },
|
||||||
]}
|
]}
|
||||||
data={classe.students}
|
data={classe.students}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -2,8 +2,8 @@ import React from 'react';
|
|||||||
|
|
||||||
const LevelLabel = ({ label, index }) => {
|
const LevelLabel = ({ label, index }) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
className={`ml-2 px-3 py-1 rounded-md shadow-sm ${
|
className={`ml-2 px-3 py-1 rounded-md shadow-sm ${
|
||||||
index % 2 === 0 ? 'bg-white' : 'bg-gray-100'
|
index % 2 === 0 ? 'bg-white' : 'bg-gray-100'
|
||||||
} border border-gray-200 text-gray-700`}
|
} border border-gray-200 text-gray-700`}
|
||||||
|
|||||||
@ -2,13 +2,15 @@ import React from 'react';
|
|||||||
|
|
||||||
const TeacherLabel = ({ nom, prenom, index }) => {
|
const TeacherLabel = ({ nom, prenom, index }) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
className={`ml-2 px-3 py-1 rounded-md shadow-sm ${
|
className={`ml-2 px-3 py-1 rounded-md shadow-sm ${
|
||||||
index % 2 === 0 ? 'bg-white' : 'bg-gray-100'
|
index % 2 === 0 ? 'bg-white' : 'bg-gray-100'
|
||||||
} border border-gray-200 text-gray-700`}
|
} border border-gray-200 text-gray-700`}
|
||||||
>
|
>
|
||||||
<span className="font-bold">{nom} {prenom}</span>
|
<span className="font-bold">
|
||||||
|
{nom} {prenom}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,9 +2,17 @@ import React, { useState, useEffect } from 'react';
|
|||||||
import { Check } from 'lucide-react';
|
import { Check } from 'lucide-react';
|
||||||
import Popup from '@/components/Popup';
|
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 [popupVisible, setPopupVisible] = useState(false);
|
||||||
const [popupMessage, setPopupMessage] = useState("");
|
const [popupMessage, setPopupMessage] = useState('');
|
||||||
const [modifiedDates, setModifiedDates] = useState({});
|
const [modifiedDates, setModifiedDates] = useState({});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -16,22 +24,24 @@ const DateTab = ({ dates, activeTab, handleDateChange, handleEdit, type, payment
|
|||||||
const submit = (updatedData) => {
|
const submit = (updatedData) => {
|
||||||
const dataWithType = {
|
const dataWithType = {
|
||||||
...updatedData,
|
...updatedData,
|
||||||
type: type
|
type: type,
|
||||||
};
|
};
|
||||||
handleEdit(paymentPlanId, dataWithType)
|
handleEdit(paymentPlanId, dataWithType)
|
||||||
.then(() => {
|
.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);
|
setPopupVisible(true);
|
||||||
setModifiedDates({});
|
setModifiedDates({});
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch((error) => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDateChangeWithModification = (tab, index, value) => {
|
const handleDateChangeWithModification = (tab, index, value) => {
|
||||||
handleDateChange(tab, index, value);
|
handleDateChange(tab, index, value);
|
||||||
setModifiedDates(prev => ({ ...prev, [`${tab}-${index}`]: true }));
|
setModifiedDates((prev) => ({ ...prev, [`${tab}-${index}`]: true }));
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -39,17 +49,30 @@ const DateTab = ({ dates, activeTab, handleDateChange, handleEdit, type, payment
|
|||||||
<div className="flex flex-col space-y-3">
|
<div className="flex flex-col space-y-3">
|
||||||
{dates[activeTab]?.map((date, index) => (
|
{dates[activeTab]?.map((date, index) => (
|
||||||
<div key={index} className="flex items-center space-x-3">
|
<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
|
<input
|
||||||
type="date"
|
type="date"
|
||||||
value={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"
|
className="p-2 border border-emerald-300 rounded focus:outline-none focus:ring-2 focus:ring-emerald-500 cursor-pointer"
|
||||||
/>
|
/>
|
||||||
{modifiedDates[`${activeTab}-${index}`] && (
|
{modifiedDates[`${activeTab}-${index}`] && (
|
||||||
<button
|
<button
|
||||||
type="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"
|
className="text-emerald-500 hover:text-emerald-800"
|
||||||
>
|
>
|
||||||
<Check className="w-5 h-5" />
|
<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';
|
import { useCookies } from 'react-cookie';
|
||||||
|
|
||||||
export default function DjangoCSRFToken({ csrfToken }) {
|
export default function DjangoCSRFToken({ csrfToken }) {
|
||||||
const [cookies, setCookie] = useCookies(['csrftoken']);
|
const [cookies, setCookie] = useCookies(['csrftoken']);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (csrfToken && csrfToken !== cookies.csrftoken) {
|
if (csrfToken && csrfToken !== cookies.csrftoken) {
|
||||||
setCookie('csrftoken', csrfToken, { path: '/' });
|
setCookie('csrftoken', csrfToken, { path: '/' });
|
||||||
}
|
}
|
||||||
}, [csrfToken, cookies.csrftoken, setCookie]);
|
}, [csrfToken, cookies.csrftoken, setCookie]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<input type="hidden" value={cookies.csrftoken} name="csrfmiddlewaretoken" />
|
<input type="hidden" value={cookies.csrftoken} name="csrfmiddlewaretoken" />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,4 +14,4 @@ const DocusealBuilder = ({ onSave, onSend, ...props }) => {
|
|||||||
return <OriginalDocusealBuilder {...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 }) {
|
export default function DraggableFileUpload({ fileName, onFileSelect }) {
|
||||||
const [dragActive, setDragActive] = useState(false);
|
const [dragActive, setDragActive] = useState(false);
|
||||||
|
|
||||||
|
|
||||||
const handleDragOver = (event) => {
|
const handleDragOver = (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
setDragActive(true);
|
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`}
|
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' }}
|
style={{ height: '200px' }}
|
||||||
>
|
>
|
||||||
<input type="file" onChange={handleFileChange} className="hidden" id="fileInput" />
|
<input
|
||||||
<label htmlFor="fileInput" className="cursor-pointer flex flex-col items-center">
|
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" />
|
<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>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,13 +2,23 @@
|
|||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import React, { useState, useEffect, useRef } from 'react';
|
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 [dropdownOpen, setDropdownOpen] = useState(false);
|
||||||
const menuRef = useRef(null);
|
const menuRef = useRef(null);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const isControlled = propDropdownOpen !== undefined && propSetDropdownOpen !== undefined;
|
const isControlled =
|
||||||
|
propDropdownOpen !== undefined && propSetDropdownOpen !== undefined;
|
||||||
const actualDropdownOpen = isControlled ? propDropdownOpen : dropdownOpen;
|
const actualDropdownOpen = isControlled ? propDropdownOpen : dropdownOpen;
|
||||||
const actualSetDropdownOpen = isControlled ? propSetDropdownOpen : setDropdownOpen;
|
const actualSetDropdownOpen = isControlled
|
||||||
|
? propSetDropdownOpen
|
||||||
|
: setDropdownOpen;
|
||||||
|
|
||||||
const handleClickOutside = (event) => {
|
const handleClickOutside = (event) => {
|
||||||
if (menuRef.current && !menuRef.current.contains(event.target)) {
|
if (menuRef.current && !menuRef.current.contains(event.target)) {
|
||||||
@ -52,7 +62,10 @@ const DropdownMenu = ({ buttonContent, items, buttonClassName, menuClassName, dr
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative" ref={menuRef}>
|
<div className="relative" ref={menuRef}>
|
||||||
<button className={buttonClassName} onClick={() => actualSetDropdownOpen(!actualDropdownOpen)}>
|
<button
|
||||||
|
className={buttonClassName}
|
||||||
|
onClick={() => actualSetDropdownOpen(!actualDropdownOpen)}
|
||||||
|
>
|
||||||
{buttonContent}
|
{buttonContent}
|
||||||
</button>
|
</button>
|
||||||
{actualDropdownOpen && (
|
{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 { format } from 'date-fns';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
export default function EventModal({ isOpen, onClose, eventData, setEventData }) {
|
export default function EventModal({
|
||||||
const { addEvent, handleUpdateEvent, handleDeleteEvent, schedules } = usePlanning();
|
isOpen,
|
||||||
|
onClose,
|
||||||
|
eventData,
|
||||||
|
setEventData,
|
||||||
|
}) {
|
||||||
|
const { addEvent, handleUpdateEvent, handleDeleteEvent, schedules } =
|
||||||
|
usePlanning();
|
||||||
|
|
||||||
// S'assurer que planning est défini lors du premier rendu
|
// S'assurer que planning est défini lors du premier rendu
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!eventData.planning && schedules.length > 0) {
|
if (!eventData.planning && schedules.length > 0) {
|
||||||
setEventData(prev => ({
|
setEventData((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
planning: schedules[0].id,
|
planning: schedules[0].id,
|
||||||
color: schedules[0].color
|
color: schedules[0].color,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}, [schedules, eventData.planning]);
|
}, [schedules, eventData.planning]);
|
||||||
@ -23,7 +29,7 @@ export default function EventModal({ isOpen, onClose, eventData, setEventData })
|
|||||||
{ value: 'daily', label: 'Quotidienne' },
|
{ value: 'daily', label: 'Quotidienne' },
|
||||||
{ value: 'weekly', label: 'Hebdomadaire' },
|
{ value: 'weekly', label: 'Hebdomadaire' },
|
||||||
{ value: 'monthly', label: 'Mensuelle' },
|
{ value: 'monthly', label: 'Mensuelle' },
|
||||||
{ value: 'custom', label: 'Personnalisée' } // Nouvelle option
|
{ value: 'custom', label: 'Personnalisée' }, // Nouvelle option
|
||||||
];
|
];
|
||||||
|
|
||||||
const daysOfWeek = [
|
const daysOfWeek = [
|
||||||
@ -33,7 +39,7 @@ export default function EventModal({ isOpen, onClose, eventData, setEventData })
|
|||||||
{ value: 4, label: 'Jeu' },
|
{ value: 4, label: 'Jeu' },
|
||||||
{ value: 5, label: 'Ven' },
|
{ value: 5, label: 'Ven' },
|
||||||
{ value: 6, label: 'Sam' },
|
{ value: 6, label: 'Sam' },
|
||||||
{ value: 0, label: 'Dim' }
|
{ value: 0, label: 'Dim' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const handleSubmit = (e) => {
|
const handleSubmit = (e) => {
|
||||||
@ -44,27 +50,30 @@ export default function EventModal({ isOpen, onClose, eventData, setEventData })
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedSchedule = schedules.find(s => s.id === eventData.planning);
|
const selectedSchedule = schedules.find((s) => s.id === eventData.planning);
|
||||||
|
|
||||||
if (eventData.id) {
|
if (eventData.id) {
|
||||||
handleUpdateEvent(eventData.id, {
|
handleUpdateEvent(eventData.id, {
|
||||||
...eventData,
|
...eventData,
|
||||||
planning: eventData.planning, // S'assurer que planning est bien défini
|
planning: eventData.planning, // S'assurer que planning est bien défini
|
||||||
color: eventData.color || selectedSchedule?.color
|
color: eventData.color || selectedSchedule?.color,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
addEvent({
|
addEvent({
|
||||||
...eventData,
|
...eventData,
|
||||||
id: `event-${Date.now()}`,
|
id: `event-${Date.now()}`,
|
||||||
planning: eventData.planning, // S'assurer que planning est bien défini
|
planning: eventData.planning, // S'assurer que planning est bien défini
|
||||||
color: eventData.color || selectedSchedule?.color
|
color: eventData.color || selectedSchedule?.color,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
onClose();
|
onClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = () => {
|
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);
|
handleDeleteEvent(eventData.id);
|
||||||
onClose();
|
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="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">
|
<div className="bg-white p-6 rounded-lg w-full max-w-md">
|
||||||
<h2 className="text-xl font-semibold mb-4">
|
<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>
|
</h2>
|
||||||
|
|
||||||
<form onSubmit={handleSubmit} className="space-y-4">
|
<form onSubmit={handleSubmit} className="space-y-4">
|
||||||
@ -86,7 +95,9 @@ export default function EventModal({ isOpen, onClose, eventData, setEventData })
|
|||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={eventData.title || ''}
|
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"
|
className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-emerald-500"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
@ -99,7 +110,9 @@ export default function EventModal({ isOpen, onClose, eventData, setEventData })
|
|||||||
</label>
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
value={eventData.description || ''}
|
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"
|
className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-emerald-500"
|
||||||
rows="3"
|
rows="3"
|
||||||
/>
|
/>
|
||||||
@ -113,17 +126,19 @@ export default function EventModal({ isOpen, onClose, eventData, setEventData })
|
|||||||
<select
|
<select
|
||||||
value={eventData.planning || schedules[0]?.id}
|
value={eventData.planning || schedules[0]?.id}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const selectedSchedule = schedules.find(s => s.id === e.target.value);
|
const selectedSchedule = schedules.find(
|
||||||
|
(s) => s.id === e.target.value
|
||||||
|
);
|
||||||
setEventData({
|
setEventData({
|
||||||
...eventData,
|
...eventData,
|
||||||
planning: e.target.value,
|
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"
|
className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-emerald-500"
|
||||||
required
|
required
|
||||||
>
|
>
|
||||||
{schedules.map(schedule => (
|
{schedules.map((schedule) => (
|
||||||
<option key={schedule.id} value={schedule.id}>
|
<option key={schedule.id} value={schedule.id}>
|
||||||
{schedule.name}
|
{schedule.name}
|
||||||
</option>
|
</option>
|
||||||
@ -138,8 +153,14 @@ export default function EventModal({ isOpen, onClose, eventData, setEventData })
|
|||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="color"
|
type="color"
|
||||||
value={eventData.color || schedules.find(s => s.id === eventData.planning)?.color || '#10b981'}
|
value={
|
||||||
onChange={(e) => setEventData({ ...eventData, color: e.target.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"
|
className="w-full h-10 p-1 rounded border"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -151,10 +172,12 @@ export default function EventModal({ isOpen, onClose, eventData, setEventData })
|
|||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
value={eventData.recurrence || 'none'}
|
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"
|
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 key={option.value} value={option.value}>
|
||||||
{option.label}
|
{option.label}
|
||||||
</option>
|
</option>
|
||||||
@ -174,18 +197,22 @@ export default function EventModal({ isOpen, onClose, eventData, setEventData })
|
|||||||
type="number"
|
type="number"
|
||||||
min="1"
|
min="1"
|
||||||
value={eventData.customInterval || 1}
|
value={eventData.customInterval || 1}
|
||||||
onChange={(e) => setEventData({
|
onChange={(e) =>
|
||||||
...eventData,
|
setEventData({
|
||||||
customInterval: parseInt(e.target.value) || 1
|
...eventData,
|
||||||
})}
|
customInterval: parseInt(e.target.value) || 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
className="w-20 p-2 border rounded focus:outline-none focus:ring-2 focus:ring-emerald-500"
|
className="w-20 p-2 border rounded focus:outline-none focus:ring-2 focus:ring-emerald-500"
|
||||||
/>
|
/>
|
||||||
<select
|
<select
|
||||||
value={eventData.customUnit || 'days'}
|
value={eventData.customUnit || 'days'}
|
||||||
onChange={(e) => setEventData({
|
onChange={(e) =>
|
||||||
...eventData,
|
setEventData({
|
||||||
customUnit: e.target.value
|
...eventData,
|
||||||
})}
|
customUnit: e.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
className="flex-1 p-2 border rounded focus:outline-none focus:ring-2 focus:ring-emerald-500"
|
className="flex-1 p-2 border rounded focus:outline-none focus:ring-2 focus:ring-emerald-500"
|
||||||
>
|
>
|
||||||
<option value="days">Jours</option>
|
<option value="days">Jours</option>
|
||||||
@ -204,14 +231,14 @@ export default function EventModal({ isOpen, onClose, eventData, setEventData })
|
|||||||
Jours de répétition
|
Jours de répétition
|
||||||
</label>
|
</label>
|
||||||
<div className="flex gap-2 flex-wrap">
|
<div className="flex gap-2 flex-wrap">
|
||||||
{daysOfWeek.map(day => (
|
{daysOfWeek.map((day) => (
|
||||||
<button
|
<button
|
||||||
key={day.value}
|
key={day.value}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const days = eventData.selectedDays || [];
|
const days = eventData.selectedDays || [];
|
||||||
const newDays = days.includes(day.value)
|
const newDays = days.includes(day.value)
|
||||||
? days.filter(d => d !== day.value)
|
? days.filter((d) => d !== day.value)
|
||||||
: [...days, day.value];
|
: [...days, day.value];
|
||||||
setEventData({ ...eventData, selectedDays: newDays });
|
setEventData({ ...eventData, selectedDays: newDays });
|
||||||
}}
|
}}
|
||||||
@ -237,7 +264,9 @@ export default function EventModal({ isOpen, onClose, eventData, setEventData })
|
|||||||
<input
|
<input
|
||||||
type="date"
|
type="date"
|
||||||
value={eventData.recurrenceEnd || ''}
|
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"
|
className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-emerald-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -252,7 +281,12 @@ export default function EventModal({ isOpen, onClose, eventData, setEventData })
|
|||||||
<input
|
<input
|
||||||
type="datetime-local"
|
type="datetime-local"
|
||||||
value={format(new Date(eventData.start), "yyyy-MM-dd'T'HH:mm")}
|
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"
|
className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-emerald-500"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
@ -264,7 +298,12 @@ export default function EventModal({ isOpen, onClose, eventData, setEventData })
|
|||||||
<input
|
<input
|
||||||
type="datetime-local"
|
type="datetime-local"
|
||||||
value={format(new Date(eventData.end), "yyyy-MM-dd'T'HH:mm")}
|
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"
|
className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-emerald-500"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
@ -279,7 +318,9 @@ export default function EventModal({ isOpen, onClose, eventData, setEventData })
|
|||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={eventData.location || ''}
|
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"
|
className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-emerald-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -317,4 +358,4 @@ export default function EventModal({ isOpen, onClose, eventData, setEventData })
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,14 +8,14 @@ const FileStatusLabel = ({ status }) => {
|
|||||||
return {
|
return {
|
||||||
label: 'En attente',
|
label: 'En attente',
|
||||||
className: 'bg-green-50 text-green-600',
|
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':
|
case 'pending':
|
||||||
default:
|
default:
|
||||||
return {
|
return {
|
||||||
label: 'En attente',
|
label: 'En attente',
|
||||||
className: 'bg-orange-50 text-orange-600',
|
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();
|
const { label, className, icon } = getStatusConfig();
|
||||||
|
|
||||||
return (
|
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}
|
{icon}
|
||||||
<span>{label}</span>
|
<span>{label}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -15,7 +15,9 @@ export default function FileUpload({ onFileUpload, fileToEdit = null }) {
|
|||||||
const { selectedEstablishmentId } = useEstablishment();
|
const { selectedEstablishmentId } = useEstablishment();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchRegistrationFileGroups(selectedEstablishmentId).then(data => setGroups(data));
|
fetchRegistrationFileGroups(selectedEstablishmentId).then((data) =>
|
||||||
|
setGroups(data)
|
||||||
|
);
|
||||||
|
|
||||||
if (fileToEdit) {
|
if (fileToEdit) {
|
||||||
setFileName(fileToEdit.name || '');
|
setFileName(fileToEdit.name || '');
|
||||||
@ -35,7 +37,7 @@ export default function FileUpload({ onFileUpload, fileToEdit = null }) {
|
|||||||
name: fileName,
|
name: fileName,
|
||||||
is_required: isRequired,
|
is_required: isRequired,
|
||||||
order: parseInt(order, 10),
|
order: parseInt(order, 10),
|
||||||
groupId: selectedGroup || null
|
groupId: selectedGroup || null,
|
||||||
});
|
});
|
||||||
setFile(null);
|
setFile(null);
|
||||||
setFileName('');
|
setFileName('');
|
||||||
@ -50,7 +52,7 @@ export default function FileUpload({ onFileUpload, fileToEdit = null }) {
|
|||||||
fileName={fileName}
|
fileName={fileName}
|
||||||
onFileSelect={(selectedFile) => {
|
onFileSelect={(selectedFile) => {
|
||||||
setFile(selectedFile);
|
setFile(selectedFile);
|
||||||
setFileName(selectedFile.name.replace(/\.[^/.]+$/, ""));
|
setFileName(selectedFile.name.replace(/\.[^/.]+$/, ''));
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="flex mt-2">
|
<div className="flex mt-2">
|
||||||
@ -70,8 +72,8 @@ export default function FileUpload({ onFileUpload, fileToEdit = null }) {
|
|||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
onClick={handleUpload}
|
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'}`}
|
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 === ""}
|
disabled={fileName === ''}
|
||||||
>
|
>
|
||||||
Ajouter
|
Ajouter
|
||||||
</button>
|
</button>
|
||||||
@ -91,11 +93,13 @@ export default function FileUpload({ onFileUpload, fileToEdit = null }) {
|
|||||||
className="w-full border rounded p-2"
|
className="w-full border rounded p-2"
|
||||||
>
|
>
|
||||||
<option value="">Aucun groupe</option>
|
<option value="">Aucun groupe</option>
|
||||||
{groups.map(group => (
|
{groups.map((group) => (
|
||||||
<option key={group.id} value={group.id}>{group.name}</option>
|
<option key={group.id} value={group.id}>
|
||||||
|
{group.name}
|
||||||
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,17 +1,19 @@
|
|||||||
import Logo from '@/components/Logo';
|
import Logo from '@/components/Logo';
|
||||||
|
|
||||||
export default function Footer ({softwareName, softwareVersion}) {
|
export default function Footer({ softwareName, softwareVersion }) {
|
||||||
|
return (
|
||||||
return (
|
|
||||||
<footer className="h-16 bg-white border-t border-gray-200 px-8 py-4 flex items-center justify-between">
|
<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">
|
<div className="text-sm font-light">
|
||||||
<span>© {new Date().getFullYear()} N3WT-INNOV Tous droits réservés.</span>
|
<span>
|
||||||
</div>
|
© {new Date().getFullYear()} N3WT-INNOV Tous droits réservés.
|
||||||
<div className="text-sm font-light flex items-center justify-between">
|
</span>
|
||||||
<div className="text-sm font-light mr-4">{softwareName} - {softwareVersion}</div>
|
</div>
|
||||||
<Logo className="w-8 h-8" />
|
<div className="text-sm font-light flex items-center justify-between">
|
||||||
|
<div className="text-sm font-light mr-4">
|
||||||
|
{softwareName} - {softwareVersion}
|
||||||
</div>
|
</div>
|
||||||
|
<Logo className="w-8 h-8" />
|
||||||
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,31 +2,37 @@ import React from 'react';
|
|||||||
import { PhoneInput } from 'react-international-phone';
|
import { PhoneInput } from 'react-international-phone';
|
||||||
import 'react-international-phone/style.css';
|
import 'react-international-phone/style.css';
|
||||||
|
|
||||||
export default function InputPhone({ name, label, value, onChange, errorMsg, className, required }) {
|
export default function InputPhone({
|
||||||
return (
|
name,
|
||||||
<div className={`${className}`}>
|
label,
|
||||||
<label htmlFor={name} className="block text-sm font-medium text-gray-700">
|
value,
|
||||||
{label}
|
onChange,
|
||||||
{required && <span className="text-red-500 ml-1">*</span>}
|
errorMsg,
|
||||||
</label>
|
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
|
<PhoneInput
|
||||||
defaultCountry="fr"
|
defaultCountry="fr"
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(phone) => onChange(phone)}
|
onChange={(phone) => onChange(phone)}
|
||||||
inputProps={{
|
inputProps={{
|
||||||
name: name,
|
name: name,
|
||||||
required: required,
|
required: required,
|
||||||
}}
|
}}
|
||||||
className="!w-full mt-1 !h-[38px]"
|
className="!w-full mt-1 !h-[38px]"
|
||||||
containerClassName="!w-full !h-[36px] !flex !items-center !rounded-md"
|
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` }
|
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"
|
buttonClassName="!h-[38px] !flex !items-center !justify-center !rounded-l-md !border border-gray-200 !border-r-0"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{errorMsg && (
|
{errorMsg && <p className="mt-2 text-sm text-red-600">{errorMsg}</p>}
|
||||||
<p className="mt-2 text-sm text-red-600">{errorMsg}</p>
|
</div>
|
||||||
)}
|
);
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,25 +1,40 @@
|
|||||||
export default function InputText({name, type, label, value, onChange, errorMsg, placeholder, className, required}) {
|
export default function InputText({
|
||||||
return (
|
name,
|
||||||
<>
|
type,
|
||||||
<div className={`${className}`}>
|
label,
|
||||||
<label htmlFor={name} className="block text-sm font-medium text-gray-700">
|
value,
|
||||||
{label}
|
onChange,
|
||||||
{required && <span className="text-red-500 ml-1">*</span>}
|
errorMsg,
|
||||||
</label>
|
placeholder,
|
||||||
<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`}>
|
className,
|
||||||
<input
|
required,
|
||||||
type={type}
|
}) {
|
||||||
id={name}
|
return (
|
||||||
placeholder={placeholder}
|
<>
|
||||||
name={name}
|
<div className={`${className}`}>
|
||||||
value={value}
|
<label
|
||||||
onChange={onChange}
|
htmlFor={name}
|
||||||
className="flex-1 px-3 py-2 block w-full sm:text-sm border-none focus:ring-0 outline-none rounded-md"
|
className="block text-sm font-medium text-gray-700"
|
||||||
required={required}
|
>
|
||||||
/>
|
{label}
|
||||||
</div>
|
{required && <span className="text-red-500 ml-1">*</span>}
|
||||||
{errorMsg && <p className="mt-2 text-sm text-red-600">{errorMsg}</p>}
|
</label>
|
||||||
</div>
|
<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}) {
|
export default function InputTextIcon({
|
||||||
return (
|
name,
|
||||||
<>
|
type,
|
||||||
<div className={`${className}`}>
|
IconItem,
|
||||||
<label htmlFor={name} className="block text-sm font-medium text-gray-700">{label}</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`}>
|
value,
|
||||||
<span className="inline-flex items-center px-3 rounded-l-md bg-gray-50 text-gray-500 text-sm">
|
onChange,
|
||||||
{IconItem && <IconItem />}
|
errorMsg,
|
||||||
</span>
|
placeholder,
|
||||||
<input
|
className,
|
||||||
type={type}
|
}) {
|
||||||
id={name}
|
return (
|
||||||
placeholder={placeholder}
|
<>
|
||||||
name={name}
|
<div className={`${className}`}>
|
||||||
value={value}
|
<label
|
||||||
onChange={onChange}
|
htmlFor={name}
|
||||||
className="flex-1 px-3 py-2 block w-full rounded-r-md sm:text-sm border-none focus:ring-0 outline-none"
|
className="block text-sm font-medium text-gray-700"
|
||||||
/>
|
>
|
||||||
</div>
|
{label}
|
||||||
{errorMsg && <p className="mt-2 text-sm text-red-600">{errorMsg}</p>}
|
</label>
|
||||||
</div>
|
<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 React from 'react';
|
||||||
import { Palette } from 'lucide-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 (
|
return (
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<input
|
<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';
|
import Table from '@/components/Table';
|
||||||
|
|
||||||
export default function FilesToSign({ fileTemplates, columns }) {
|
export default function FilesToSign({ fileTemplates, columns }) {
|
||||||
return (
|
return (
|
||||||
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
|
<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>
|
<h2 className="text-xl font-bold mb-4 text-gray-800">
|
||||||
<Table
|
Fichiers à remplir
|
||||||
data={fileTemplates}
|
</h2>
|
||||||
columns={columns}
|
<Table
|
||||||
itemsPerPage={5}
|
data={fileTemplates}
|
||||||
currentPage={1}
|
columns={columns}
|
||||||
totalPages={1}
|
itemsPerPage={5}
|
||||||
onPageChange={() => {}}
|
currentPage={1}
|
||||||
/>
|
totalPages={1}
|
||||||
</div>
|
onPageChange={() => {}}
|
||||||
);
|
/>
|
||||||
}
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@ -2,17 +2,19 @@ import React from 'react';
|
|||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
|
|
||||||
export default function FilesToUpload({ fileTemplates, columns }) {
|
export default function FilesToUpload({ fileTemplates, columns }) {
|
||||||
return (
|
return (
|
||||||
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
|
<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>
|
<h2 className="text-xl font-bold mb-4 text-gray-800">
|
||||||
<Table
|
Fichiers à uploader
|
||||||
data={fileTemplates}
|
</h2>
|
||||||
columns={columns}
|
<Table
|
||||||
itemsPerPage={5}
|
data={fileTemplates}
|
||||||
currentPage={1}
|
columns={columns}
|
||||||
totalPages={1}
|
itemsPerPage={5}
|
||||||
onPageChange={() => {}}
|
currentPage={1}
|
||||||
/>
|
totalPages={1}
|
||||||
</div>
|
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 Loader from '@/components/Loader';
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
import DjangoCSRFToken from '@/components/DjangoCSRFToken';
|
import DjangoCSRFToken from '@/components/DjangoCSRFToken';
|
||||||
import { fetchRegisterForm, fetchTemplatesFromRegistrationFiles } from '@/app/actions/subscriptionAction';
|
import {
|
||||||
import { downloadTemplate,
|
fetchRegisterForm,
|
||||||
createRegistrationTemplates,
|
fetchTemplatesFromRegistrationFiles,
|
||||||
editRegistrationTemplates,
|
} from '@/app/actions/subscriptionAction';
|
||||||
deleteRegistrationTemplates
|
import {
|
||||||
|
downloadTemplate,
|
||||||
|
createRegistrationTemplates,
|
||||||
|
editRegistrationTemplates,
|
||||||
|
deleteRegistrationTemplates,
|
||||||
} from '@/app/actions/registerFileGroupAction';
|
} from '@/app/actions/registerFileGroupAction';
|
||||||
import {
|
import {
|
||||||
fetchRegistrationPaymentModes,
|
fetchRegistrationPaymentModes,
|
||||||
fetchTuitionPaymentModes } from '@/app/actions/schoolAction';
|
fetchTuitionPaymentModes,
|
||||||
|
} from '@/app/actions/schoolAction';
|
||||||
import { Download, Upload, Trash2, Eye } from 'lucide-react';
|
import { Download, Upload, Trash2, Eye } from 'lucide-react';
|
||||||
import { BASE_URL } from '@/utils/Url';
|
import { BASE_URL } from '@/utils/Url';
|
||||||
import DraggableFileUpload from '@/components/DraggableFileUpload';
|
import DraggableFileUpload from '@/components/DraggableFileUpload';
|
||||||
import Modal from '@/components/Modal';
|
import Modal from '@/components/Modal';
|
||||||
import FileStatusLabel from '@/components/FileStatusLabel';
|
import FileStatusLabel from '@/components/FileStatusLabel';
|
||||||
import logger from '@/utils/logger';
|
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 FilesToUpload from '@/components/Inscription/FilesToUpload';
|
||||||
import { DocusealForm } from '@docuseal/react';
|
import { DocusealForm } from '@docuseal/react';
|
||||||
|
|
||||||
@ -31,440 +38,498 @@ import { DocusealForm } from '@docuseal/react';
|
|||||||
* @param {object} errors - Erreurs de validation du formulaire
|
* @param {object} errors - Erreurs de validation du formulaire
|
||||||
*/
|
*/
|
||||||
export default function InscriptionFormShared({
|
export default function InscriptionFormShared({
|
||||||
studentId,
|
studentId,
|
||||||
csrfToken,
|
csrfToken,
|
||||||
selectedEstablishmentId,
|
selectedEstablishmentId,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
cancelUrl,
|
cancelUrl,
|
||||||
errors = {} // Nouvelle prop pour les erreurs
|
errors = {}, // Nouvelle prop pour les erreurs
|
||||||
}) {
|
}) {
|
||||||
// États pour gérer les données du formulaire
|
// États pour gérer les données du formulaire
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
id: '',
|
id: '',
|
||||||
last_name: '',
|
last_name: '',
|
||||||
first_name: '',
|
first_name: '',
|
||||||
address: '',
|
address: '',
|
||||||
birth_date: '',
|
birth_date: '',
|
||||||
birth_place: '',
|
birth_place: '',
|
||||||
birth_postal_code: '',
|
birth_postal_code: '',
|
||||||
nationality: '',
|
nationality: '',
|
||||||
attending_physician: '',
|
attending_physician: '',
|
||||||
level: '',
|
level: '',
|
||||||
registration_payment: '',
|
registration_payment: '',
|
||||||
tuition_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([]);
|
// Fetch data for tuition payment modes
|
||||||
const [tuitionPaymentModes, setTuitionPaymentModes] = useState([]);
|
handleTuitionPaymentModes();
|
||||||
|
}
|
||||||
|
}, [selectedEstablishmentId]);
|
||||||
|
|
||||||
// États pour la gestion des fichiers
|
const handleRegistrationPaymentModes = () => {
|
||||||
const [uploadedFiles, setUploadedFiles] = useState([]);
|
fetchRegistrationPaymentModes(selectedEstablishmentId)
|
||||||
const [fileTemplates, setFileTemplates] = useState([]);
|
.then((data) => {
|
||||||
const [fileGroup, setFileGroup] = useState(null);
|
const activePaymentModes = data.filter(
|
||||||
const [fileName, setFileName] = useState("");
|
(mode) => mode.is_active === true
|
||||||
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
|
|
||||||
);
|
);
|
||||||
};
|
setRegistrationPaymentModes(activePaymentModes);
|
||||||
|
})
|
||||||
|
.catch((error) =>
|
||||||
|
logger.error('Error fetching registration payment modes:', error)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
// Récupération d'un fichier uploadé
|
const handleTuitionPaymentModes = () => {
|
||||||
const getUploadedFile = (templateId) => {
|
fetchTuitionPaymentModes(selectedEstablishmentId)
|
||||||
return uploadedFiles.find(file => parseInt(file.template) === templateId);
|
.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
|
// Fonctions de gestion du formulaire et des fichiers
|
||||||
const handleDeleteFile = async (templateId) => {
|
const updateFormField = (field, value) => {
|
||||||
const fileToDelete = getUploadedFile(templateId);
|
setFormData((prev) => ({ ...prev, [field]: value }));
|
||||||
if (!fileToDelete) return;
|
};
|
||||||
|
|
||||||
try {
|
// Gestion du téléversement de fichiers
|
||||||
await deleteRegistrationTemplates(fileToDelete.id, csrfToken);
|
const handleFileUpload = async (file, fileName) => {
|
||||||
setUploadedFiles(prev => prev.filter(f => parseInt(f.template) !== templateId));
|
if (!file || !currentTemplateId || !formData.id) {
|
||||||
} catch (error) {
|
logger.error('Missing required data for upload');
|
||||||
logger.error('Error deleting file:', error);
|
return;
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
// Soumission du formulaire
|
const data = new FormData();
|
||||||
const handleSubmit = (e) => {
|
data.append('file', file);
|
||||||
e.preventDefault();
|
data.append('name', fileName);
|
||||||
const data ={
|
data.append('template', currentTemplateId);
|
||||||
student: {
|
data.append('register_form', formData.id);
|
||||||
...formData,
|
|
||||||
guardians
|
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
|
// Vérification si un fichier est déjà uploadé
|
||||||
const handleSave = (e) => {
|
const isFileUploaded = (templateId) => {
|
||||||
e.preventDefault();
|
return uploadedFiles.find((template) => template.template === templateId);
|
||||||
const data ={
|
};
|
||||||
student: {
|
|
||||||
...formData,
|
// Récupération d'un fichier uploadé
|
||||||
guardians
|
const getUploadedFile = (templateId) => {
|
||||||
},
|
return uploadedFiles.find((file) => parseInt(file.template) === templateId);
|
||||||
establishment: selectedEstablishmentId
|
};
|
||||||
|
|
||||||
|
// 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 = () => {
|
return (
|
||||||
setCurrentPage(currentPage + 1);
|
<button
|
||||||
};
|
className="text-emerald-500 hover:text-emerald-700"
|
||||||
|
type="button"
|
||||||
const handlePreviousPage = () => {
|
onClick={() => {
|
||||||
setCurrentPage(currentPage - 1);
|
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
|
// Rendu du composant
|
||||||
const columns = [
|
return (
|
||||||
{ name: 'Nom du fichier', transform: (row) => row.name },
|
<div className="max-w-4xl mx-auto p-6">
|
||||||
{ name: 'Fichier à Remplir', transform: (row) => row.is_required ? 'Oui' : 'Non' },
|
<form onSubmit={handleSubmit} className="space-y-8">
|
||||||
{ 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">
|
<DjangoCSRFToken csrfToken={csrfToken} />
|
||||||
<Download size={16} />
|
{/* Page 1 : Informations de l'élève et Responsables */}
|
||||||
</a> </div>},
|
{currentPage === 1 && (
|
||||||
{ name: 'Statut', transform: (row) =>
|
<StudentInfoForm
|
||||||
row.is_required && (
|
formData={formData}
|
||||||
<FileStatusLabel
|
updateFormField={updateFormField}
|
||||||
status={isFileUploaded(row.id) ? 'sent' : 'pending'}
|
guardians={guardians}
|
||||||
/>
|
setGuardians={setGuardians}
|
||||||
)
|
registrationPaymentModes={registrationPaymentModes}
|
||||||
},
|
tuitionPaymentModes={tuitionPaymentModes}
|
||||||
{ name: 'Actions', transform: (row) => {
|
errors={errors}
|
||||||
if (!row.is_required) return null;
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
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) {
|
{/* Affichage du formulaire ou du document */}
|
||||||
return (
|
{requiredFileTemplates[currentPage - 2].file === '' ? (
|
||||||
<div className="flex items-center justify-center gap-2">
|
<DocusealForm
|
||||||
<a
|
id="docusealForm"
|
||||||
href={`${BASE_URL}${uploadedFile.file}`}
|
src={
|
||||||
target="_blank"
|
'https://docuseal.com/s/' +
|
||||||
className="text-blue-500 hover:text-blue-700"
|
requiredFileTemplates[currentPage - 2].slug
|
||||||
>
|
}
|
||||||
<Eye size={16} />
|
withDownloadButton={false}
|
||||||
</a>
|
onComplete={() => {
|
||||||
<button
|
downloadTemplate(requiredFileTemplates[currentPage - 2].slug)
|
||||||
className="text-red-500 hover:text-red-700"
|
.then((data) => fetch(data))
|
||||||
onClick={() => handleDeleteFile(row.id)}
|
.then((response) => response.blob())
|
||||||
type="button"
|
.then((blob) => {
|
||||||
>
|
const file = new File(
|
||||||
<Trash2 size={16} />
|
[blob],
|
||||||
</button>
|
`${requiredFileTemplates[currentPage - 2].name}.pdf`,
|
||||||
</div>
|
{ type: blob.type }
|
||||||
);
|
);
|
||||||
}
|
const updateData = new FormData();
|
||||||
|
updateData.append('file', file);
|
||||||
|
|
||||||
return (
|
return editRegistrationTemplates(
|
||||||
<button
|
requiredFileTemplates[currentPage - 2].id,
|
||||||
className="text-emerald-500 hover:text-emerald-700"
|
updateData,
|
||||||
type="button"
|
csrfToken
|
||||||
onClick={() => {
|
);
|
||||||
setCurrentTemplateId(row.id);
|
})
|
||||||
setShowUploadModal(true);
|
.then((data) => {
|
||||||
}}
|
logger.debug('EDIT TEMPLATE : ', data);
|
||||||
>
|
})
|
||||||
<Upload size={16} />
|
.catch((error) => {
|
||||||
</button>
|
logger.error('error editing template : ', error);
|
||||||
);
|
});
|
||||||
}},
|
}}
|
||||||
];
|
/>
|
||||||
|
) : (
|
||||||
// Affichage du loader pendant le chargement
|
<iframe
|
||||||
if (isLoading) return <Loader />;
|
src={`${BASE_URL}/${requiredFileTemplates[currentPage - 2].file}`}
|
||||||
|
title="Document Viewer"
|
||||||
// Rendu du composant
|
className="w-full"
|
||||||
return (
|
style={{
|
||||||
<div className="max-w-4xl mx-auto p-6">
|
height: '75vh', // Ajuster la hauteur à 75% de la fenêtre
|
||||||
<form onSubmit={handleSubmit} className="space-y-8">
|
border: 'none',
|
||||||
<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>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
|
</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>
|
</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 React from 'react';
|
||||||
import SelectChoice from '@/components/SelectChoice';
|
import SelectChoice from '@/components/SelectChoice';
|
||||||
|
|
||||||
export default function PaymentMethodSelector({ formData, title, name, updateFormField, selected, paymentModes, paymentModesOptions, amount, getError }) {
|
export default function PaymentMethodSelector({
|
||||||
//console.log(paymentModes)
|
formData,
|
||||||
//console.log(selected)
|
title,
|
||||||
return (
|
name,
|
||||||
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
|
updateFormField,
|
||||||
{/* Titre */}
|
selected,
|
||||||
<h2 className="text-2xl font-semibold mb-6 text-gray-800 border-b pb-2">{title}</h2>
|
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 */}
|
{/* Section d'information */}
|
||||||
<div className="mb-6 bg-gray-50 p-4 rounded-lg border border-gray-100">
|
<div className="mb-6 bg-gray-50 p-4 rounded-lg border border-gray-100">
|
||||||
<p className="text-gray-700 text-sm mb-2">
|
<p className="text-gray-700 text-sm mb-2">
|
||||||
<strong className="text-gray-900">Montant :</strong> {amount} €
|
<strong className="text-gray-900">Montant :</strong> {amount} €
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<SelectChoice
|
<SelectChoice
|
||||||
name={name}
|
name={name}
|
||||||
label="Mode de Paiement"
|
label="Mode de Paiement"
|
||||||
placeHolder="Sélectionner un mode de paiement"
|
placeHolder="Sélectionner un mode de paiement"
|
||||||
selected={selected || ''}
|
selected={selected || ''}
|
||||||
callback={(e) => updateFormField(name, e.target.value)}
|
callback={(e) => updateFormField(name, e.target.value)}
|
||||||
choices={paymentModes.map((mode) => ({
|
choices={paymentModes.map((mode) => ({
|
||||||
value: mode.mode,
|
value: mode.mode,
|
||||||
label: paymentModesOptions.find(option => option.id === mode.mode)?.name || 'Mode inconnu'
|
label:
|
||||||
}))}
|
paymentModesOptions.find((option) => option.id === mode.mode)
|
||||||
required
|
?.name || 'Mode inconnu',
|
||||||
errorMsg={getError('payment_method')}
|
}))}
|
||||||
/>
|
required
|
||||||
</div>
|
errorMsg={getError('payment_method')}
|
||||||
);
|
/>
|
||||||
}
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@ -5,112 +5,137 @@ import React from 'react';
|
|||||||
import { useTranslations } from 'next-intl';
|
import { useTranslations } from 'next-intl';
|
||||||
import { Trash2, Plus } from 'lucide-react';
|
import { Trash2, Plus } from 'lucide-react';
|
||||||
|
|
||||||
export default function ResponsableInputFields({guardians, onGuardiansChange, addGuardian, deleteGuardian, errors = []}) {
|
export default function ResponsableInputFields({
|
||||||
const t = useTranslations('ResponsableInputFields');
|
guardians,
|
||||||
|
onGuardiansChange,
|
||||||
|
addGuardian,
|
||||||
|
deleteGuardian,
|
||||||
|
errors = [],
|
||||||
|
}) {
|
||||||
|
const t = useTranslations('ResponsableInputFields');
|
||||||
|
|
||||||
const getError = (index, field) => {
|
const getError = (index, field) => {
|
||||||
return errors[index]?.[field]?.[0];
|
return errors[index]?.[field]?.[0];
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-8">
|
<div className="space-y-8">
|
||||||
{guardians.map((item, index) => (
|
{guardians.map((item, index) => (
|
||||||
<div className="p-6 bg-gray-50 rounded-lg shadow-sm" key={index}>
|
<div className="p-6 bg-gray-50 rounded-lg shadow-sm" key={index}>
|
||||||
<div className='flex justify-between items-center mb-4'>
|
<div className="flex justify-between items-center mb-4">
|
||||||
<h3 className='text-xl font-bold'>{t('responsable')} {index+1}</h3>
|
<h3 className="text-xl font-bold">
|
||||||
{guardians.length > 1 && (
|
{t('responsable')} {index + 1}
|
||||||
<Trash2
|
</h3>
|
||||||
className="w-5 h-5 text-red-500 cursor-pointer hover:text-red-700 transition-colors"
|
{guardians.length > 1 && (
|
||||||
onClick={() => deleteGuardian(index)}
|
<Trash2
|
||||||
/>
|
className="w-5 h-5 text-red-500 cursor-pointer hover:text-red-700 transition-colors"
|
||||||
)}
|
onClick={() => deleteGuardian(index)}
|
||||||
</div>
|
/>
|
||||||
|
)}
|
||||||
|
</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'>
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||||
<InputText
|
<InputText
|
||||||
name="nomResponsable"
|
name="mailResponsable"
|
||||||
type="text"
|
type="email"
|
||||||
label={t('lastname')}
|
label={t('email')}
|
||||||
value={item.last_name}
|
value={item.associated_profile_email}
|
||||||
onChange={(event) => {onGuardiansChange(item.id, "last_name", event.target.value)}}
|
onChange={(event) => {
|
||||||
errorMsg={getError(index, 'last_name')}
|
onGuardiansChange(
|
||||||
required
|
item.id,
|
||||||
/>
|
'associated_profile_email',
|
||||||
<InputText
|
event.target.value
|
||||||
name="prenomResponsable"
|
);
|
||||||
type="text"
|
}}
|
||||||
label={t('firstname')}
|
required
|
||||||
value={item.first_name}
|
errorMsg={getError(index, 'email')}
|
||||||
onChange={(event) => {onGuardiansChange(item.id, "first_name", event.target.value)}}
|
/>
|
||||||
errorMsg={getError(index, 'first_name')}
|
<InputPhone
|
||||||
required
|
name="telephoneResponsable"
|
||||||
/>
|
label={t('phone')}
|
||||||
</div>
|
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'>
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||||
<InputText
|
<InputText
|
||||||
name="mailResponsable"
|
name="dateNaissanceResponsable"
|
||||||
type="email"
|
type="date"
|
||||||
label={t('email')}
|
label={t('birthdate')}
|
||||||
value={item.associated_profile_email}
|
value={item.birth_date}
|
||||||
onChange={(event) => {onGuardiansChange(item.id, "associated_profile_email", event.target.value)}}
|
onChange={(event) => {
|
||||||
required
|
onGuardiansChange(item.id, 'birth_date', event.target.value);
|
||||||
errorMsg={getError(index, 'email')}
|
}}
|
||||||
/>
|
required
|
||||||
<InputPhone
|
errorMsg={getError(index, 'birth_date')}
|
||||||
name="telephoneResponsable"
|
/>
|
||||||
label={t('phone')}
|
<InputText
|
||||||
value={item.phone}
|
name="professionResponsable"
|
||||||
onChange={(event) => {onGuardiansChange(item.id, "phone", event)}}
|
type="text"
|
||||||
required
|
label={t('profession')}
|
||||||
errorMsg={getError(index, 'phone')}
|
value={item.profession}
|
||||||
/>
|
onChange={(event) => {
|
||||||
</div>
|
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'>
|
<div className="grid grid-cols-1 gap-4">
|
||||||
<InputText
|
<InputText
|
||||||
name="dateNaissanceResponsable"
|
name="adresseResponsable"
|
||||||
type="date"
|
type="text"
|
||||||
label={t('birthdate')}
|
label={t('address')}
|
||||||
value={item.birth_date}
|
value={item.address}
|
||||||
onChange={(event) => {onGuardiansChange(item.id, "birth_date", event.target.value)}}
|
onChange={(event) => {
|
||||||
required
|
onGuardiansChange(item.id, 'address', event.target.value);
|
||||||
errorMsg={getError(index, 'birth_date')}
|
}}
|
||||||
/>
|
required
|
||||||
<InputText
|
errorMsg={getError(index, 'address')}
|
||||||
name="professionResponsable"
|
/>
|
||||||
type="text"
|
</div>
|
||||||
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>
|
</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';
|
import PaymentMethodSelector from '@/components/Inscription/PaymentMethodSelector';
|
||||||
|
|
||||||
const levels = [
|
const levels = [
|
||||||
{ value:'1', label: 'TPS - Très Petite Section'},
|
{ value: '1', label: 'TPS - Très Petite Section' },
|
||||||
{ value:'2', label: 'PS - Petite Section'},
|
{ value: '2', label: 'PS - Petite Section' },
|
||||||
{ value:'3', label: 'MS - Moyenne Section'},
|
{ value: '3', label: 'MS - Moyenne Section' },
|
||||||
{ value:'4', label: 'GS - Grande Section'},
|
{ value: '4', label: 'GS - Grande Section' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const paymentModesOptions = [
|
const paymentModesOptions = [
|
||||||
{ id: 1, name: 'Prélèvement SEPA' },
|
{ id: 1, name: 'Prélèvement SEPA' },
|
||||||
{ id: 2, name: 'Virement' },
|
{ id: 2, name: 'Virement' },
|
||||||
{ id: 3, name: 'Chèque' },
|
{ id: 3, name: 'Chèque' },
|
||||||
{ id: 4, name: 'Espèce' },
|
{ id: 4, name: 'Espèce' },
|
||||||
];
|
];
|
||||||
|
|
||||||
// Fonction de validation pour vérifier les champs requis
|
// Fonction de validation pour vérifier les champs requis
|
||||||
export function validateStudentInfo(formData) {
|
export function validateStudentInfo(formData) {
|
||||||
const requiredFields = [
|
const requiredFields = [
|
||||||
'last_name',
|
'last_name',
|
||||||
'first_name',
|
'first_name',
|
||||||
'nationality',
|
'nationality',
|
||||||
'birth_date',
|
'birth_date',
|
||||||
'birth_place',
|
'birth_place',
|
||||||
'birth_postal_code',
|
'birth_postal_code',
|
||||||
'address',
|
'address',
|
||||||
'attending_physician',
|
'attending_physician',
|
||||||
'level',
|
'level',
|
||||||
];
|
];
|
||||||
|
|
||||||
const isValid = requiredFields.every((field) => {
|
const isValid = requiredFields.every((field) => {
|
||||||
const value = formData[field];
|
const value = formData[field];
|
||||||
return typeof value === 'string' ? value.trim() !== '' : Boolean(value);
|
return typeof value === 'string' ? value.trim() !== '' : Boolean(value);
|
||||||
});
|
});
|
||||||
|
|
||||||
return isValid;
|
return isValid;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function StudentInfoForm({ formData, updateFormField, guardians, setGuardians, registrationPaymentModes, tuitionPaymentModes, errors }) {
|
export default function StudentInfoForm({
|
||||||
const getError = (field) => {
|
formData,
|
||||||
return errors?.student?.[field]?.[0];
|
updateFormField,
|
||||||
};
|
guardians,
|
||||||
|
setGuardians,
|
||||||
|
registrationPaymentModes,
|
||||||
|
tuitionPaymentModes,
|
||||||
|
errors,
|
||||||
|
}) {
|
||||||
|
const getError = (field) => {
|
||||||
|
return errors?.student?.[field]?.[0];
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
|
<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>
|
<h2 className="text-xl font-bold mb-4 text-gray-800">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
Informations de l'élève
|
||||||
<InputText
|
</h2>
|
||||||
name="last_name"
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
label="Nom"
|
<InputText
|
||||||
value={formData.last_name}
|
name="last_name"
|
||||||
onChange={(e) => updateFormField('last_name', e.target.value)}
|
label="Nom"
|
||||||
required
|
value={formData.last_name}
|
||||||
errorMsg={getError('last_name')}
|
onChange={(e) => updateFormField('last_name', e.target.value)}
|
||||||
/>
|
required
|
||||||
<InputText
|
errorMsg={getError('last_name')}
|
||||||
name="first_name"
|
/>
|
||||||
label="Prénom"
|
<InputText
|
||||||
value={formData.first_name}
|
name="first_name"
|
||||||
onChange={(e) => updateFormField('first_name', e.target.value)}
|
label="Prénom"
|
||||||
errorMsg={getError('first_name')}
|
value={formData.first_name}
|
||||||
required
|
onChange={(e) => updateFormField('first_name', e.target.value)}
|
||||||
/>
|
errorMsg={getError('first_name')}
|
||||||
<InputText
|
required
|
||||||
name="nationality"
|
/>
|
||||||
label="Nationalité"
|
<InputText
|
||||||
value={formData.nationality}
|
name="nationality"
|
||||||
required
|
label="Nationalité"
|
||||||
onChange={(e) => updateFormField('nationality', e.target.value)}
|
value={formData.nationality}
|
||||||
/>
|
required
|
||||||
<InputText
|
onChange={(e) => updateFormField('nationality', e.target.value)}
|
||||||
name="birth_date"
|
/>
|
||||||
type="date"
|
<InputText
|
||||||
label="Date de Naissance"
|
name="birth_date"
|
||||||
value={formData.birth_date}
|
type="date"
|
||||||
onChange={(e) => updateFormField('birth_date', e.target.value)}
|
label="Date de Naissance"
|
||||||
required
|
value={formData.birth_date}
|
||||||
errorMsg={getError('birth_date')}
|
onChange={(e) => updateFormField('birth_date', e.target.value)}
|
||||||
/>
|
required
|
||||||
<InputText
|
errorMsg={getError('birth_date')}
|
||||||
name="birth_place"
|
/>
|
||||||
label="Lieu de Naissance"
|
<InputText
|
||||||
value={formData.birth_place}
|
name="birth_place"
|
||||||
onChange={(e) => updateFormField('birth_place', e.target.value)}
|
label="Lieu de Naissance"
|
||||||
required
|
value={formData.birth_place}
|
||||||
errorMsg={getError('birth_place')}
|
onChange={(e) => updateFormField('birth_place', e.target.value)}
|
||||||
/>
|
required
|
||||||
<InputText
|
errorMsg={getError('birth_place')}
|
||||||
name="birth_postal_code"
|
/>
|
||||||
label="Code Postal de Naissance"
|
<InputText
|
||||||
value={formData.birth_postal_code}
|
name="birth_postal_code"
|
||||||
onChange={(e) => updateFormField('birth_postal_code', e.target.value)}
|
label="Code Postal de Naissance"
|
||||||
required
|
value={formData.birth_postal_code}
|
||||||
errorMsg={getError('birth_postal_code')}
|
onChange={(e) =>
|
||||||
/>
|
updateFormField('birth_postal_code', e.target.value)
|
||||||
<div className="md:col-span-2">
|
}
|
||||||
<InputText
|
required
|
||||||
name="address"
|
errorMsg={getError('birth_postal_code')}
|
||||||
label="Adresse"
|
/>
|
||||||
value={formData.address}
|
<div className="md:col-span-2">
|
||||||
onChange={(e) => updateFormField('address', e.target.value)}
|
<InputText
|
||||||
required
|
name="address"
|
||||||
errorMsg={getError('address')}
|
label="Adresse"
|
||||||
/>
|
value={formData.address}
|
||||||
</div>
|
onChange={(e) => updateFormField('address', e.target.value)}
|
||||||
<InputText
|
required
|
||||||
name="attending_physician"
|
errorMsg={getError('address')}
|
||||||
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}
|
|
||||||
/>
|
/>
|
||||||
|
</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
|
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
|
||||||
formData={formData}
|
<h2 className="text-xl font-bold mb-4 text-gray-800">Responsables</h2>
|
||||||
title="Frais de scolarité"
|
<ResponsableInputFields
|
||||||
name="tuition_payment"
|
guardians={guardians}
|
||||||
updateFormField={updateFormField}
|
onGuardiansChange={(id, field, value) => {
|
||||||
selected={formData.tuition_payment}
|
const updatedGuardians = guardians.map((resp) =>
|
||||||
paymentModes={tuitionPaymentModes}
|
resp.id === id ? { ...resp, [field]: value } : resp
|
||||||
paymentModesOptions={paymentModesOptions}
|
);
|
||||||
amount={formData.totalTuitionFees}
|
setGuardians(updatedGuardians);
|
||||||
getError={getError}
|
}}
|
||||||
/>
|
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 React, { useState, useEffect } from 'react';
|
||||||
import { DocusealBuilder } from '@docuseal/react';
|
import { DocusealBuilder } from '@docuseal/react';
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
@ -7,7 +7,14 @@ import { generateToken } from '@/app/actions/registerFileGroupAction';
|
|||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
import { GraduationCap, CloudUpload } from 'lucide-react';
|
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 [token, setToken] = useState(null);
|
||||||
const [uploadedFileName, setUploadedFileName] = useState('');
|
const [uploadedFileName, setUploadedFileName] = useState('');
|
||||||
const [pdfUrl, setPdfUrl] = useState(`${BASE_URL}/${file}`);
|
const [pdfUrl, setPdfUrl] = useState(`${BASE_URL}/${file}`);
|
||||||
@ -20,7 +27,9 @@ export default function ValidateSubscription({ studentId, firstName, lastName, p
|
|||||||
.then((data) => {
|
.then((data) => {
|
||||||
setToken(data.token);
|
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]);
|
}, [isSepa]);
|
||||||
|
|
||||||
@ -32,23 +41,23 @@ export default function ValidateSubscription({ studentId, firstName, lastName, p
|
|||||||
const handleAccept = () => {
|
const handleAccept = () => {
|
||||||
const fileInput = document.getElementById('fileInput'); // Récupère l'élément input
|
const fileInput = document.getElementById('fileInput'); // Récupère l'élément input
|
||||||
const file = fileInput?.files[0]; // Récupère le fichier sélectionné
|
const file = fileInput?.files[0]; // Récupère le fichier sélectionné
|
||||||
|
|
||||||
if (!file) {
|
if (!file) {
|
||||||
logger.error('Aucun fichier sélectionné pour le champ SEPA.');
|
logger.error('Aucun fichier sélectionné pour le champ SEPA.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
status: 7,
|
status: 7,
|
||||||
sepa_file: file,
|
sepa_file: file,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Appeler la fonction passée par le parent pour mettre à jour le RF
|
// Appeler la fonction passée par le parent pour mettre à jour le RF
|
||||||
onAccept(data);
|
onAccept(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRefuse = () => {
|
const handleRefuse = () => {
|
||||||
logger.debug('Dossier refusé pour l\'étudiant:', studentId);
|
logger.debug("Dossier refusé pour l'étudiant:", studentId);
|
||||||
// Logique pour refuser l'inscription
|
// Logique pour refuser l'inscription
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -75,10 +84,14 @@ export default function ValidateSubscription({ studentId, firstName, lastName, p
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold text-gray-800">
|
<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>
|
</h1>
|
||||||
<p className="text-sm text-gray-500 italic">
|
<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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -101,45 +114,54 @@ export default function ValidateSubscription({ studentId, firstName, lastName, p
|
|||||||
|
|
||||||
{currentPage === 2 && isSepa && (
|
{currentPage === 2 && isSepa && (
|
||||||
<div className="border p-4 rounded-md shadow-md">
|
<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
|
<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"
|
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
|
onClick={() => document.getElementById('fileInput').click()} // Ouvre l'explorateur de fichiers au clic
|
||||||
onDragOver={(e) => e.preventDefault()}
|
onDragOver={(e) => e.preventDefault()}
|
||||||
onDrop={(e) => {
|
onDrop={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const file = e.dataTransfer.files[0];
|
const file = e.dataTransfer.files[0];
|
||||||
if (file) {
|
if (file) {
|
||||||
setUploadedFileName(file.name); // Stocke uniquement le nom du fichier
|
setUploadedFileName(file.name); // Stocke uniquement le nom du fichier
|
||||||
logger.debug('Fichier déposé:', file.name);
|
logger.debug('Fichier déposé:', file.name);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CloudUpload className="w-12 h-12 text-emerald-500 mb-4" /> {/* Icône de cloud */}
|
<CloudUpload className="w-12 h-12 text-emerald-500 mb-4" />{' '}
|
||||||
<input
|
{/* Icône de cloud */}
|
||||||
type="file"
|
<input
|
||||||
accept=".pdf"
|
type="file"
|
||||||
onChange={(e) => {
|
accept=".pdf"
|
||||||
const file = e.target.files[0];
|
onChange={(e) => {
|
||||||
if (file) {
|
const file = e.target.files[0];
|
||||||
setUploadedFileName(file.name); // Stocke uniquement le nom du fichier
|
if (file) {
|
||||||
logger.debug('Fichier sélectionné:', file.name);
|
setUploadedFileName(file.name); // Stocke uniquement le nom du fichier
|
||||||
}
|
logger.debug('Fichier sélectionné:', file.name);
|
||||||
}}
|
}
|
||||||
className="hidden"
|
}}
|
||||||
id="fileInput"
|
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>
|
<label htmlFor="fileInput" className="text-center text-gray-500">
|
||||||
<p className="text-sm text-gray-500 mt-2">ou cliquez pour sélectionner un fichier PDF</p>
|
<p className="text-lg font-semibold text-gray-800">
|
||||||
</label>
|
Déposez votre fichier ici
|
||||||
</div>
|
</p>
|
||||||
|
<p className="text-sm text-gray-500 mt-2">
|
||||||
|
ou cliquez pour sélectionner un fichier PDF
|
||||||
|
</p>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
{uploadedFileName && (
|
{uploadedFileName && (
|
||||||
<div className="mt-4 flex items-center space-x-4 bg-gray-100 p-3 rounded-md shadow-sm">
|
<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" />
|
<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>
|
<p className="text-sm font-medium text-gray-800">
|
||||||
</div>
|
<span className="font-semibold">{uploadedFileName}</span>
|
||||||
)}
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -172,4 +194,4 @@ export default function ValidateSubscription({ studentId, firstName, lastName, p
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,12 @@ import logoImage from '@/img/logo_min.svg'; // Assurez-vous que le chemin vers l
|
|||||||
const Logo = ({ className }) => {
|
const Logo = ({ className }) => {
|
||||||
return (
|
return (
|
||||||
<div className={`max-w-[150px] ${className}`}>
|
<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>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,36 +1,55 @@
|
|||||||
import * as Dialog from '@radix-ui/react-dialog';
|
import * as Dialog from '@radix-ui/react-dialog';
|
||||||
|
|
||||||
const Modal = ({ isOpen, setIsOpen, title, ContentComponent, modalClassName }) => {
|
const Modal = ({
|
||||||
return (
|
isOpen,
|
||||||
<Dialog.Root open={isOpen} onOpenChange={setIsOpen}>
|
setIsOpen,
|
||||||
<Dialog.Portal>
|
title,
|
||||||
<Dialog.Overlay className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
|
ContentComponent,
|
||||||
<Dialog.Content className="fixed inset-0 flex items-center justify-center p-4">
|
modalClassName,
|
||||||
<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">
|
return (
|
||||||
<Dialog.Title className="text-xl font-medium text-gray-900">
|
<Dialog.Root open={isOpen} onOpenChange={setIsOpen}>
|
||||||
{title}
|
<Dialog.Portal>
|
||||||
</Dialog.Title>
|
<Dialog.Overlay className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
|
||||||
<Dialog.Close asChild>
|
<Dialog.Content className="fixed inset-0 flex items-center justify-center p-4">
|
||||||
<button
|
<div
|
||||||
onClick={() => setIsOpen(false)}
|
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'}`}
|
||||||
className="text-gray-400 hover:text-gray-500 ml-4 focus:outline-none"
|
>
|
||||||
>
|
<div className="flex justify-between items-start mb-4">
|
||||||
<span className="sr-only">Fermer</span>
|
<Dialog.Title className="text-xl font-medium text-gray-900">
|
||||||
<svg className="h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
{title}
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12" />
|
</Dialog.Title>
|
||||||
</svg>
|
<Dialog.Close asChild>
|
||||||
</button>
|
<button
|
||||||
</Dialog.Close>
|
onClick={() => setIsOpen(false)}
|
||||||
</div>
|
className="text-gray-400 hover:text-gray-500 ml-4 focus:outline-none"
|
||||||
<div className="w-full h-full">
|
>
|
||||||
<ContentComponent />
|
<span className="sr-only">Fermer</span>
|
||||||
</div>
|
<svg
|
||||||
</div>
|
className="h-6 w-6"
|
||||||
</Dialog.Content>
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
</Dialog.Portal>
|
fill="none"
|
||||||
</Dialog.Root>
|
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;
|
export default Modal;
|
||||||
|
|||||||
@ -1,15 +1,26 @@
|
|||||||
import React, { useState, useEffect, useRef } from 'react';
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
import { Check, ChevronDown } from 'lucide-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 [isOpen, setIsOpen] = useState(false);
|
||||||
const containerRef = useRef(null);
|
const containerRef = useRef(null);
|
||||||
|
|
||||||
const handleSelect = (option) => {
|
const handleSelect = (option) => {
|
||||||
const isSelected = selectedOptions.some(selected => selected.id === option.id);
|
const isSelected = selectedOptions.some(
|
||||||
|
(selected) => selected.id === option.id
|
||||||
|
);
|
||||||
let newSelectedOptions;
|
let newSelectedOptions;
|
||||||
if (isSelected) {
|
if (isSelected) {
|
||||||
newSelectedOptions = selectedOptions.filter(selected => selected.id !== option.id);
|
newSelectedOptions = selectedOptions.filter(
|
||||||
|
(selected) => selected.id !== option.id
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
newSelectedOptions = [...selectedOptions, option];
|
newSelectedOptions = [...selectedOptions, option];
|
||||||
}
|
}
|
||||||
@ -39,8 +50,11 @@ const MultiSelect = ({ name, label, options, selectedOptions, onChange, errorMsg
|
|||||||
>
|
>
|
||||||
{selectedOptions.length > 0 ? (
|
{selectedOptions.length > 0 ? (
|
||||||
<div className="flex flex-wrap gap-1 justify-center items-center">
|
<div className="flex flex-wrap gap-1 justify-center items-center">
|
||||||
{selectedOptions.map(option => (
|
{selectedOptions.map((option) => (
|
||||||
<span key={option.id} className="bg-emerald-100 text-emerald-700 px-2 py-1 rounded-md text-sm">
|
<span
|
||||||
|
key={option.id}
|
||||||
|
className="bg-emerald-100 text-emerald-700 px-2 py-1 rounded-md text-sm"
|
||||||
|
>
|
||||||
{option.name}
|
{option.name}
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
@ -52,18 +66,24 @@ const MultiSelect = ({ name, label, options, selectedOptions, onChange, errorMsg
|
|||||||
</button>
|
</button>
|
||||||
{isOpen && (
|
{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">
|
<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
|
<li
|
||||||
key={option.id}
|
key={option.id}
|
||||||
className={`cursor-pointer select-none relative py-2 pl-3 pr-9 ${
|
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)}
|
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}
|
{option.name}
|
||||||
</span>
|
</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">
|
<span className="absolute inset-y-0 right-0 flex items-center pr-4 text-white">
|
||||||
<Check className="h-5 w-5" />
|
<Check className="h-5 w-5" />
|
||||||
</span>
|
</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 React from 'react';
|
||||||
import {useTranslations} from 'next-intl';
|
import { useTranslations } from 'next-intl';
|
||||||
|
|
||||||
const Pagination = ({ currentPage, totalPages, onPageChange }) => {
|
const Pagination = ({ currentPage, totalPages, onPageChange }) => {
|
||||||
const t = useTranslations('pagination');
|
const t = useTranslations('pagination');
|
||||||
const pages = Array.from({ length: totalPages }, (_, i) => i + 1);
|
const pages = Array.from({ length: totalPages }, (_, i) => i + 1);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
||||||
<div className="px-6 py-4 border-t border-gray-200 flex items-center justify-between">
|
<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">
|
<div className="flex items-center gap-2">
|
||||||
{currentPage > 1 && (
|
{currentPage > 1 && (
|
||||||
<PaginationButton text={t('previous')} onClick={() => onPageChange(currentPage - 1)}/>
|
<PaginationButton
|
||||||
)}
|
text={t('previous')}
|
||||||
{pages.map((page) => (
|
onClick={() => onPageChange(currentPage - 1)}
|
||||||
<PaginationNumber
|
/>
|
||||||
key={page}
|
)}
|
||||||
number={page}
|
{pages.map((page) => (
|
||||||
active={page === currentPage}
|
<PaginationNumber
|
||||||
onClick={() => onPageChange(page)}
|
key={page}
|
||||||
/>
|
number={page}
|
||||||
|
active={page === currentPage}
|
||||||
))}
|
onClick={() => onPageChange(page)}
|
||||||
{currentPage < totalPages && (
|
/>
|
||||||
<PaginationButton text={t('next')} onClick={() => onPageChange(currentPage + 1)} />
|
))}
|
||||||
)}
|
{currentPage < totalPages && (
|
||||||
|
<PaginationButton
|
||||||
|
text={t('next')}
|
||||||
|
onClick={() => onPageChange(currentPage + 1)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const PaginationButton = ({ text , onClick}) => (
|
const PaginationButton = ({ text, onClick }) => (
|
||||||
<button className="px-3 py-1 text-sm text-gray-600 hover:bg-gray-50 rounded" onClick={onClick}>
|
<button
|
||||||
|
className="px-3 py-1 text-sm text-gray-600 hover:bg-gray-50 rounded"
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
{text}
|
{text}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|
||||||
const PaginationNumber = ({ number, active , onClick}) => (
|
const PaginationNumber = ({ number, active, onClick }) => (
|
||||||
<button className={`w-8 h-8 flex items-center justify-center rounded ${
|
<button
|
||||||
active ? 'bg-emerald-500 text-white' : 'text-gray-600 hover:bg-gray-50'
|
className={`w-8 h-8 flex items-center justify-center rounded ${
|
||||||
}`} onClick={onClick}>
|
active ? 'bg-emerald-500 text-white' : 'text-gray-600 hover:bg-gray-50'
|
||||||
|
}`}
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
{number}
|
{number}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -8,12 +8,19 @@ const paymentModesOptions = [
|
|||||||
{ id: 4, name: 'Espèce' },
|
{ id: 4, name: 'Espèce' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const PaymentModeSelector = ({ paymentModes, setPaymentModes, handleEdit, type }) => {
|
const PaymentModeSelector = ({
|
||||||
|
paymentModes,
|
||||||
|
setPaymentModes,
|
||||||
|
handleEdit,
|
||||||
|
type,
|
||||||
|
}) => {
|
||||||
const [activePaymentModes, setActivePaymentModes] = useState([]);
|
const [activePaymentModes, setActivePaymentModes] = useState([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Initialiser activePaymentModes avec les modes dont is_active est à true
|
// 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);
|
setActivePaymentModes(activeModes);
|
||||||
}, [paymentModes]);
|
}, [paymentModes]);
|
||||||
|
|
||||||
@ -24,9 +31,12 @@ const PaymentModeSelector = ({ paymentModes, setPaymentModes, handleEdit, type }
|
|||||||
: [...prevActiveModes, modeId];
|
: [...prevActiveModes, modeId];
|
||||||
|
|
||||||
// Mettre à jour le mode de paiement dans le backend
|
// 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) {
|
if (updatedMode) {
|
||||||
handleEdit(updatedMode.id, { ...updatedMode, is_active: !updatedMode.is_active });
|
handleEdit(updatedMode.id, {
|
||||||
|
...updatedMode,
|
||||||
|
is_active: !updatedMode.is_active,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return newActiveModes;
|
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 },
|
{ id: 3, name: '12 fois', frequency: 12 },
|
||||||
];
|
];
|
||||||
|
|
||||||
const PaymentPlanSelector = ({ paymentPlans, setPaymentPlans, handleEdit, type }) => {
|
const PaymentPlanSelector = ({
|
||||||
|
paymentPlans,
|
||||||
|
setPaymentPlans,
|
||||||
|
handleEdit,
|
||||||
|
type,
|
||||||
|
}) => {
|
||||||
const [dates, setDates] = useState({});
|
const [dates, setDates] = useState({});
|
||||||
const [selectedFrequency, setSelectedFrequency] = useState(null);
|
const [selectedFrequency, setSelectedFrequency] = useState(null);
|
||||||
const [activeFrequencies, setActiveFrequencies] = useState([]);
|
const [activeFrequencies, setActiveFrequencies] = useState([]);
|
||||||
const [defaultDay, setDefaultDay] = useState('-');
|
const [defaultDay, setDefaultDay] = useState('-');
|
||||||
const [isDefaultDayModified, setIsDefaultDayModified] = useState(false);
|
const [isDefaultDayModified, setIsDefaultDayModified] = useState(false);
|
||||||
const [popupVisible, setPopupVisible] = useState(false);
|
const [popupVisible, setPopupVisible] = useState(false);
|
||||||
const [popupMessage, setPopupMessage] = useState("");
|
const [popupMessage, setPopupMessage] = useState('');
|
||||||
const [errorMsg, setErrorMsg] = useState('');
|
const [errorMsg, setErrorMsg] = useState('');
|
||||||
const [resetModifiedDates, setResetModifiedDates] = useState(false);
|
const [resetModifiedDates, setResetModifiedDates] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (paymentPlans && paymentPlans.length > 0) {
|
if (paymentPlans && paymentPlans.length > 0) {
|
||||||
const activePlans = paymentPlans.filter(plan => plan.is_active);
|
const activePlans = paymentPlans.filter((plan) => plan.is_active);
|
||||||
const frequencies = activePlans.map(plan => {
|
const frequencies = activePlans
|
||||||
const paymentPlanOption = paymentPlansOptions.find(p => p.frequency === plan.frequency);
|
.map((plan) => {
|
||||||
return paymentPlanOption ? paymentPlanOption.id : null;
|
const paymentPlanOption = paymentPlansOptions.find(
|
||||||
}).filter(id => id !== null);
|
(p) => p.frequency === plan.frequency
|
||||||
|
);
|
||||||
|
return paymentPlanOption ? paymentPlanOption.id : null;
|
||||||
|
})
|
||||||
|
.filter((id) => id !== null);
|
||||||
setActiveFrequencies(frequencies);
|
setActiveFrequencies(frequencies);
|
||||||
|
|
||||||
if (activePlans.length > 0) {
|
if (activePlans.length > 0) {
|
||||||
@ -38,8 +47,10 @@ const PaymentPlanSelector = ({ paymentPlans, setPaymentPlans, handleEdit, type }
|
|||||||
}
|
}
|
||||||
|
|
||||||
const initialDates = {};
|
const initialDates = {};
|
||||||
paymentPlans.forEach(plan => {
|
paymentPlans.forEach((plan) => {
|
||||||
const paymentPlanOption = paymentPlansOptions.find(p => p.frequency === plan.frequency);
|
const paymentPlanOption = paymentPlansOptions.find(
|
||||||
|
(p) => p.frequency === plan.frequency
|
||||||
|
);
|
||||||
if (paymentPlanOption) {
|
if (paymentPlanOption) {
|
||||||
initialDates[paymentPlanOption.id] = plan.due_dates;
|
initialDates[paymentPlanOption.id] = plan.due_dates;
|
||||||
}
|
}
|
||||||
@ -55,8 +66,8 @@ const PaymentPlanSelector = ({ paymentPlans, setPaymentPlans, handleEdit, type }
|
|||||||
const updateDefaultDay = () => {
|
const updateDefaultDay = () => {
|
||||||
const currentDates = dates[selectedFrequency];
|
const currentDates = dates[selectedFrequency];
|
||||||
if (currentDates && currentDates.length > 0) {
|
if (currentDates && currentDates.length > 0) {
|
||||||
const days = currentDates.map(date => new Date(date).getDate());
|
const days = currentDates.map((date) => new Date(date).getDate());
|
||||||
const allSameDay = days.every(day => day === days[0]);
|
const allSameDay = days.every((day) => day === days[0]);
|
||||||
if (allSameDay) {
|
if (allSameDay) {
|
||||||
setDefaultDay(days[0]);
|
setDefaultDay(days[0]);
|
||||||
} else {
|
} else {
|
||||||
@ -69,32 +80,44 @@ const PaymentPlanSelector = ({ paymentPlans, setPaymentPlans, handleEdit, type }
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleActivationChange = (value) => {
|
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;
|
if (!selectedPlan) return;
|
||||||
|
|
||||||
const updatedData = {
|
const updatedData = {
|
||||||
...selectedPlan,
|
...selectedPlan,
|
||||||
is_active: !selectedPlan.is_active
|
is_active: !selectedPlan.is_active,
|
||||||
};
|
};
|
||||||
|
|
||||||
handleEdit(selectedPlan.id, updatedData)
|
handleEdit(selectedPlan.id, updatedData)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
setPaymentPlans(prevPlans => prevPlans.map(plan =>
|
setPaymentPlans((prevPlans) =>
|
||||||
plan.id === selectedPlan.id ? { ...plan, is_active: updatedData.is_active } : plan
|
prevPlans.map((plan) =>
|
||||||
));
|
plan.id === selectedPlan.id
|
||||||
setActiveFrequencies(prevFrequencies => {
|
? { ...plan, is_active: updatedData.is_active }
|
||||||
|
: plan
|
||||||
|
)
|
||||||
|
);
|
||||||
|
setActiveFrequencies((prevFrequencies) => {
|
||||||
if (updatedData.is_active) {
|
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);
|
setPopupVisible(true);
|
||||||
return [...prevFrequencies, value];
|
return [...prevFrequencies, value];
|
||||||
} else {
|
} 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);
|
setPopupVisible(true);
|
||||||
return prevFrequencies.filter(item => item !== value);
|
return prevFrequencies.filter((item) => item !== value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch((error) => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -106,18 +129,21 @@ const PaymentPlanSelector = ({ paymentPlans, setPaymentPlans, handleEdit, type }
|
|||||||
} else {
|
} else {
|
||||||
setSelectedFrequency(value);
|
setSelectedFrequency(value);
|
||||||
if (!dates[value]) {
|
if (!dates[value]) {
|
||||||
const frequencyValue = paymentPlansOptions.find(plan => plan.id === value)?.frequency || 1;
|
const frequencyValue =
|
||||||
const newDates = Array(frequencyValue).fill('').map((_, index) => {
|
paymentPlansOptions.find((plan) => plan.id === value)?.frequency || 1;
|
||||||
const newDate = new Date();
|
const newDates = Array(frequencyValue)
|
||||||
newDate.setDate(defaultDay);
|
.fill('')
|
||||||
if (value === 1) {
|
.map((_, index) => {
|
||||||
newDate.setMonth(newDate.getMonth() + index * 4); // Espacer de 4 mois pour le paiement en 3 fois
|
const newDate = new Date();
|
||||||
} else {
|
newDate.setDate(defaultDay);
|
||||||
newDate.setMonth(newDate.getMonth() + index);
|
if (value === 1) {
|
||||||
}
|
newDate.setMonth(newDate.getMonth() + index * 4); // Espacer de 4 mois pour le paiement en 3 fois
|
||||||
return newDate.toISOString().split('T')[0];
|
} else {
|
||||||
});
|
newDate.setMonth(newDate.getMonth() + index);
|
||||||
setDates(prevDates => ({ ...prevDates, [value]: newDates }));
|
}
|
||||||
|
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);
|
setTimeout(() => setResetModifiedDates(false), 0);
|
||||||
|
|
||||||
// Mettre à jour les dates d'échéance en fonction du jour sélectionné
|
// 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);
|
const newDate = new Date(date);
|
||||||
newDate.setDate(day);
|
newDate.setDate(day);
|
||||||
return newDate.toISOString().split('T')[0];
|
return newDate.toISOString().split('T')[0];
|
||||||
});
|
});
|
||||||
setDates(prevDates => ({ ...prevDates, [selectedFrequency]: updatedDates }));
|
setDates((prevDates) => ({
|
||||||
|
...prevDates,
|
||||||
|
[selectedFrequency]: updatedDates,
|
||||||
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmitDefaultDay = () => {
|
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;
|
if (!selectedPlan) return;
|
||||||
|
|
||||||
const updatedData = {
|
const updatedData = {
|
||||||
...selectedPlan,
|
...selectedPlan,
|
||||||
due_dates: dates[selectedFrequency]
|
due_dates: dates[selectedFrequency],
|
||||||
};
|
};
|
||||||
|
|
||||||
handleEdit(selectedPlan.id, updatedData)
|
handleEdit(selectedPlan.id, updatedData)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
setPopupMessage(`Mise à jour des dates d'échéances effectuée avec succès`);
|
setPopupMessage(
|
||||||
|
`Mise à jour des dates d'échéances effectuée avec succès`
|
||||||
|
);
|
||||||
setPopupVisible(true);
|
setPopupVisible(true);
|
||||||
setIsDefaultDayModified(false);
|
setIsDefaultDayModified(false);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch((error) => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -190,7 +225,9 @@ const PaymentPlanSelector = ({ paymentPlans, setPaymentPlans, handleEdit, type }
|
|||||||
const renderCell = (row, column) => {
|
const renderCell = (row, column) => {
|
||||||
switch (column) {
|
switch (column) {
|
||||||
case 'OPTIONS':
|
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':
|
case 'ACTIONS':
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
@ -199,9 +236,17 @@ const PaymentPlanSelector = ({ paymentPlans, setPaymentPlans, handleEdit, type }
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
handleActivationChange(row.id);
|
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>
|
</button>
|
||||||
);
|
);
|
||||||
default:
|
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 (
|
return (
|
||||||
<div className="space-y-4">
|
<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}){
|
export function PhoneLabel({ phoneNumber }) {
|
||||||
return (
|
return (
|
||||||
<a className="text-sm font-semibold text-gray-800" href={"tel:"+phoneNumber}>{formatPhoneNumber(phoneNumber)}</a>
|
<a
|
||||||
);
|
className="text-sm font-semibold text-gray-800"
|
||||||
}
|
href={'tel:' + phoneNumber}
|
||||||
|
>
|
||||||
|
{formatPhoneNumber(phoneNumber)}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@ -1,7 +1,13 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
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;
|
if (!visible) return null;
|
||||||
|
|
||||||
// Vérifier si le message est une chaîne de caractères
|
// 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="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="bg-white p-6 rounded-lg shadow-xl max-w-md w-full">
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
{isStringMessage ? (
|
{isStringMessage
|
||||||
// Afficher le message sous forme de lignes si c'est une chaîne
|
? // Afficher le message sous forme de lignes si c'est une chaîne
|
||||||
messageLines.map((line, index) => (
|
messageLines.map((line, index) => (
|
||||||
<p key={index} className="text-gray-700">
|
<p key={index} className="text-gray-700">
|
||||||
{line}
|
{line}
|
||||||
</p>
|
</p>
|
||||||
))
|
))
|
||||||
) : (
|
: // Sinon, afficher directement le contenu React
|
||||||
// Sinon, afficher directement le contenu React
|
message}
|
||||||
message
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-end space-x-2">
|
<div className="flex justify-end space-x-2">
|
||||||
{!uniqueConfirmButton && (
|
{!uniqueConfirmButton && (
|
||||||
|
|||||||
@ -1,5 +1,13 @@
|
|||||||
import React, { useState } from 'react';
|
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 Table from '@/components/Table';
|
||||||
import Popup from '@/components/Popup';
|
import Popup from '@/components/Popup';
|
||||||
import StatusLabel from '@/components/StatusLabel';
|
import StatusLabel from '@/components/StatusLabel';
|
||||||
@ -32,14 +40,23 @@ const roleTypeToBadgeClass = (roleType) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const ProfileDirectory = ({ profileRoles, handleActivateProfile, handleDeleteProfile, handleDissociateGuardian }) => {
|
const ProfileDirectory = ({
|
||||||
const parentProfiles = profileRoles.filter(profileRole => profileRole.role_type === 2);
|
profileRoles,
|
||||||
const schoolAdminProfiles = profileRoles.filter(profileRole => profileRole.role_type !== 2);
|
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 [popupVisible, setPopupVisible] = useState(false);
|
||||||
const [popupMessage, setPopupMessage] = useState("");
|
const [popupMessage, setPopupMessage] = useState('');
|
||||||
const [confirmPopupVisible, setConfirmPopupVisible] = useState(false);
|
const [confirmPopupVisible, setConfirmPopupVisible] = useState(false);
|
||||||
const [confirmPopupMessage, setConfirmPopupMessage] = useState("");
|
const [confirmPopupMessage, setConfirmPopupMessage] = useState('');
|
||||||
const [confirmPopupOnConfirm, setConfirmPopupOnConfirm] = useState(() => {});
|
const [confirmPopupOnConfirm, setConfirmPopupOnConfirm] = useState(() => {});
|
||||||
const [visibleTooltipId, setVisibleTooltipId] = useState(null);
|
const [visibleTooltipId, setVisibleTooltipId] = useState(null);
|
||||||
|
|
||||||
@ -49,18 +66,24 @@ const ProfileDirectory = ({ profileRoles, handleActivateProfile, handleDeletePro
|
|||||||
|
|
||||||
const handleTooltipHide = () => {
|
const handleTooltipHide = () => {
|
||||||
setVisibleTooltipId(null); // Cacher toutes les tooltips
|
setVisibleTooltipId(null); // Cacher toutes les tooltips
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleConfirmActivateProfile = (profileRole) => {
|
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(() => () => {
|
setConfirmPopupOnConfirm(() => () => {
|
||||||
handleActivateProfile(profileRole)
|
handleActivateProfile(profileRole)
|
||||||
.then(() => {
|
.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);
|
setPopupVisible(true);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch((error) => {
|
||||||
setPopupMessage(`Erreur lors de la ${profileRole.is_active ? 'désactivation' : 'activation'} du profil.`);
|
setPopupMessage(
|
||||||
|
`Erreur lors de la ${profileRole.is_active ? 'désactivation' : 'activation'} du profil.`
|
||||||
|
);
|
||||||
setPopupVisible(true);
|
setPopupVisible(true);
|
||||||
});
|
});
|
||||||
setConfirmPopupVisible(false);
|
setConfirmPopupVisible(false);
|
||||||
@ -69,15 +92,15 @@ const ProfileDirectory = ({ profileRoles, handleActivateProfile, handleDeletePro
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleConfirmDeleteProfile = (id) => {
|
const handleConfirmDeleteProfile = (id) => {
|
||||||
setConfirmPopupMessage("Êtes-vous sûr de vouloir supprimer ce profil ?");
|
setConfirmPopupMessage('Êtes-vous sûr de vouloir supprimer ce profil ?');
|
||||||
setConfirmPopupOnConfirm(() => () => {
|
setConfirmPopupOnConfirm(() => () => {
|
||||||
handleDeleteProfile(id)
|
handleDeleteProfile(id)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
setPopupMessage("Le profil a été supprimé avec succès.");
|
setPopupMessage('Le profil a été supprimé avec succès.');
|
||||||
setPopupVisible(true);
|
setPopupVisible(true);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch((error) => {
|
||||||
setPopupMessage("Erreur lors de la suppression du profil.");
|
setPopupMessage('Erreur lors de la suppression du profil.');
|
||||||
setPopupVisible(true);
|
setPopupVisible(true);
|
||||||
});
|
});
|
||||||
setConfirmPopupVisible(false);
|
setConfirmPopupVisible(false);
|
||||||
@ -93,11 +116,11 @@ const ProfileDirectory = ({ profileRoles, handleActivateProfile, handleDeletePro
|
|||||||
setConfirmPopupOnConfirm(() => () => {
|
setConfirmPopupOnConfirm(() => () => {
|
||||||
handleDissociateGuardian(student.id, profileRole.associated_person?.id)
|
handleDissociateGuardian(student.id, profileRole.associated_person?.id)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
setPopupMessage("Le responsable a été dissocié avec succès.");
|
setPopupMessage('Le responsable a été dissocié avec succès.');
|
||||||
setPopupVisible(true);
|
setPopupVisible(true);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch((error) => {
|
||||||
setPopupMessage("Erreur lors de la dissociation du responsable.");
|
setPopupMessage('Erreur lors de la dissociation du responsable.');
|
||||||
setPopupVisible(true);
|
setPopupVisible(true);
|
||||||
});
|
});
|
||||||
setConfirmPopupVisible(false);
|
setConfirmPopupVisible(false);
|
||||||
@ -108,11 +131,15 @@ const ProfileDirectory = ({ profileRoles, handleActivateProfile, handleDeletePro
|
|||||||
const parentColumns = [
|
const parentColumns = [
|
||||||
{ name: 'Identifiant', transform: (row) => row.associated_profile_email },
|
{ name: 'Identifiant', transform: (row) => row.associated_profile_email },
|
||||||
{ name: 'Mise à jour', transform: (row) => row.updated_date_formatted },
|
{ 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)}
|
{roleTypeToLabel(row.role_type)}
|
||||||
</span>
|
</span>
|
||||||
)
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Utilisateur',
|
name: 'Utilisateur',
|
||||||
@ -121,47 +148,53 @@ const ProfileDirectory = ({ profileRoles, handleActivateProfile, handleDeletePro
|
|||||||
<span>{row.associated_person?.guardian_name}</span>
|
<span>{row.associated_person?.guardian_name}</span>
|
||||||
{row.associated_person && (
|
{row.associated_person && (
|
||||||
<div
|
<div
|
||||||
className="relative group"
|
className="relative group"
|
||||||
onMouseEnter={() => handleTooltipVisibility(row.id)} // Afficher la tooltip pour cette ligne
|
onMouseEnter={() => handleTooltipVisibility(row.id)} // Afficher la tooltip pour cette ligne
|
||||||
onMouseLeave={handleTooltipHide} // Cacher la tooltip
|
onMouseLeave={handleTooltipHide} // Cacher la tooltip
|
||||||
>
|
>
|
||||||
<button className="relative text-blue-500 hover:text-blue-700 flex items-center justify-center">
|
<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">
|
<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}
|
{row.associated_person?.students?.length || 0}
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
{visibleTooltipId === row.id && ( // Afficher uniquement si l'ID correspond
|
{visibleTooltipId === row.id && ( // Afficher uniquement si l'ID correspond
|
||||||
<div
|
<div className="fixed z-50 w-96 p-4 bg-white border border-gray-200 rounded shadow-lg -translate-x-1/2">
|
||||||
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="mb-2">
|
<div className="flex flex-col justify-center space-y-2 mt-4">
|
||||||
<strong>Elève(s) associé(s):</strong>
|
{row.associated_person?.students?.map((student) => (
|
||||||
<div className="flex flex-col justify-center space-y-2 mt-4">
|
<div
|
||||||
{row.associated_person?.students?.map(student => (
|
key={student.student_name}
|
||||||
<div key={student.student_name} className="flex justify-between items-center">
|
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 className="px-2 py-1 rounded-full text-gray-800 whitespace-nowrap inline-block min-w-0 max-w-fit">
|
||||||
</span>
|
{student.student_name}
|
||||||
<div className="flex items-center space-x-2">
|
</span>
|
||||||
<StatusLabel status={student.registration_status} showDropdown={false} />
|
<div className="flex items-center space-x-2">
|
||||||
<button
|
<StatusLabel
|
||||||
className="text-red-500 hover:text-red-700 flex items-center space-x-1"
|
status={student.registration_status}
|
||||||
onClick={() => handleConfirmDissociateGuardian(row, student)}
|
showDropdown={false}
|
||||||
>
|
/>
|
||||||
<XCircle className="w-5 h-5" />
|
<button
|
||||||
<span className="text-sm">Dissocier</span>
|
className="text-red-500 hover:text-red-700 flex items-center space-x-1"
|
||||||
</button>
|
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>
|
)}
|
||||||
)}
|
</div>
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Actions',
|
name: 'Actions',
|
||||||
@ -169,10 +202,18 @@ const ProfileDirectory = ({ profileRoles, handleActivateProfile, handleDeletePro
|
|||||||
<div className="flex justify-center space-x-2">
|
<div className="flex justify-center space-x-2">
|
||||||
<button
|
<button
|
||||||
type="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)}
|
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>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@ -182,20 +223,26 @@ const ProfileDirectory = ({ profileRoles, handleActivateProfile, handleDeletePro
|
|||||||
<Trash2 className="w-5 h-5" />
|
<Trash2 className="w-5 h-5" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)
|
),
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const schoolAdminColumns = [
|
const schoolAdminColumns = [
|
||||||
{ name: 'Identifiant', transform: (row) => row.associated_profile_email },
|
{ name: 'Identifiant', transform: (row) => row.associated_profile_email },
|
||||||
{ name: 'Mise à jour', transform: (row) => row.updated_date_formatted },
|
{ 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)}
|
{roleTypeToLabel(row.role_type)}
|
||||||
</span>
|
</span>
|
||||||
)
|
),
|
||||||
},
|
},
|
||||||
{ name: 'Utilisateur', transform: (row) => (
|
{
|
||||||
|
name: 'Utilisateur',
|
||||||
|
transform: (row) => (
|
||||||
<div className="flex items-center justify-center space-x-2 relative">
|
<div className="flex items-center justify-center space-x-2 relative">
|
||||||
<span>{row.associated_person?.teacher_name}</span>
|
<span>{row.associated_person?.teacher_name}</span>
|
||||||
{row.associated_person && (
|
{row.associated_person && (
|
||||||
@ -210,14 +257,15 @@ const ProfileDirectory = ({ profileRoles, handleActivateProfile, handleDeletePro
|
|||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
{visibleTooltipId === row.id && ( // Afficher uniquement si l'ID correspond
|
{visibleTooltipId === row.id && ( // Afficher uniquement si l'ID correspond
|
||||||
<div
|
<div className="fixed z-50 w-96 p-4 bg-white border border-gray-200 rounded shadow-lg -translate-x-1/2">
|
||||||
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">
|
<div className="mb-2">
|
||||||
<strong>Classes associées:</strong>
|
<strong>Classes associées:</strong>
|
||||||
<div className="flex flex-wrap justify-center space-x-2 mt-4">
|
<div className="flex flex-wrap justify-center space-x-2 mt-4">
|
||||||
{row.associated_person?.classes?.map(classe => (
|
{row.associated_person?.classes?.map((classe) => (
|
||||||
<span key={classe.id} className="px-2 py-1 rounded-full bg-gray-200 text-gray-800">
|
<span
|
||||||
|
key={classe.id}
|
||||||
|
className="px-2 py-1 rounded-full bg-gray-200 text-gray-800"
|
||||||
|
>
|
||||||
{classe.name}
|
{classe.name}
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
@ -226,9 +274,15 @@ const ProfileDirectory = ({ profileRoles, handleActivateProfile, handleDeletePro
|
|||||||
<div>
|
<div>
|
||||||
<strong>Spécialités:</strong>
|
<strong>Spécialités:</strong>
|
||||||
<div className="flex flex-wrap justify-center space-x-2 mt-4">
|
<div className="flex flex-wrap justify-center space-x-2 mt-4">
|
||||||
{row.associated_person?.specialities?.map(speciality => (
|
{row.associated_person?.specialities?.map(
|
||||||
<SpecialityItem key={speciality.name} speciality={speciality} isDraggable={false} />
|
(speciality) => (
|
||||||
))}
|
<SpecialityItem
|
||||||
|
key={speciality.name}
|
||||||
|
speciality={speciality}
|
||||||
|
isDraggable={false}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -236,7 +290,7 @@ const ProfileDirectory = ({ profileRoles, handleActivateProfile, handleDeletePro
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Actions',
|
name: 'Actions',
|
||||||
@ -244,10 +298,18 @@ const ProfileDirectory = ({ profileRoles, handleActivateProfile, handleDeletePro
|
|||||||
<div className="flex justify-center space-x-2">
|
<div className="flex justify-center space-x-2">
|
||||||
<button
|
<button
|
||||||
type="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)}
|
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>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@ -257,8 +319,8 @@ const ProfileDirectory = ({ profileRoles, handleActivateProfile, handleDeletePro
|
|||||||
<Trash2 className="w-5 h-5" />
|
<Trash2 className="w-5 h-5" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)
|
),
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -268,20 +330,14 @@ const ProfileDirectory = ({ profileRoles, handleActivateProfile, handleDeletePro
|
|||||||
{parentProfiles.length === 0 ? (
|
{parentProfiles.length === 0 ? (
|
||||||
<div>Aucun profil trouvé</div>
|
<div>Aucun profil trouvé</div>
|
||||||
) : (
|
) : (
|
||||||
<Table
|
<Table data={parentProfiles} columns={parentColumns} />
|
||||||
data={parentProfiles}
|
|
||||||
columns={parentColumns}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="max-h-128 overflow-y-auto border rounded p-4">
|
<div className="max-h-128 overflow-y-auto border rounded p-4">
|
||||||
{schoolAdminProfiles.length === 0 ? (
|
{schoolAdminProfiles.length === 0 ? (
|
||||||
<div>Aucun profil trouvé</div>
|
<div>Aucun profil trouvé</div>
|
||||||
) : (
|
) : (
|
||||||
<Table
|
<Table data={schoolAdminProfiles} columns={schoolAdminColumns} />
|
||||||
data={schoolAdminProfiles}
|
|
||||||
columns={schoolAdminColumns}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</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
|
import { ChevronDown } from 'lucide-react'; // Import de l'icône
|
||||||
|
|
||||||
const ProfileSelector = ({ onEstablishmentChange, className = '' }) => {
|
const ProfileSelector = ({ onEstablishmentChange, className = '' }) => {
|
||||||
const { establishments, selectedEstablishmentId, setSelectedEstablishmentId, setProfileRole } = useEstablishment();
|
const {
|
||||||
|
establishments,
|
||||||
|
selectedEstablishmentId,
|
||||||
|
setSelectedEstablishmentId,
|
||||||
|
setProfileRole,
|
||||||
|
} = useEstablishment();
|
||||||
const [dropdownOpen, setDropdownOpen] = useState(false);
|
const [dropdownOpen, setDropdownOpen] = useState(false);
|
||||||
|
|
||||||
const handleEstablishmentChange = (establishmentId) => {
|
const handleEstablishmentChange = (establishmentId) => {
|
||||||
setSelectedEstablishmentId(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);
|
setProfileRole(role);
|
||||||
|
|
||||||
if (onEstablishmentChange) {
|
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
|
// 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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedEstablishment = establishments.find(est => est.id === selectedEstablishmentId);
|
const selectedEstablishment = establishments.find(
|
||||||
|
(est) => est.id === selectedEstablishmentId
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`relative ${className}`}>
|
<div className={`relative ${className}`}>
|
||||||
@ -32,7 +45,9 @@ const ProfileSelector = ({ onEstablishmentChange, className = '' }) => {
|
|||||||
buttonContent={
|
buttonContent={
|
||||||
<div className="h-16 flex items-center gap-2 cursor-pointer px-4 bg-white">
|
<div className="h-16 flex items-center gap-2 cursor-pointer px-4 bg-white">
|
||||||
<div className="flex-1">
|
<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">
|
<div className="text-sm text-gray-500 text-left">
|
||||||
{selectedEstablishment?.name || 'Sélectionnez un établissement'}
|
{selectedEstablishment?.name || 'Sélectionnez un établissement'}
|
||||||
</div>
|
</div>
|
||||||
@ -45,11 +60,13 @@ const ProfileSelector = ({ onEstablishmentChange, className = '' }) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
items={establishments.map(establishment => ({
|
items={establishments.map((establishment) => ({
|
||||||
type: 'item',
|
type: 'item',
|
||||||
label: (
|
label: (
|
||||||
<div className="text-left">
|
<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 className="text-sm text-gray-500">{establishment.name}</div>
|
||||||
</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';
|
import React, { useState, useEffect } from 'react';
|
||||||
|
|
||||||
const Step = ({ number, title, isActive, isValid, isCompleted, onClick }) => {
|
const Step = ({ number, title, isActive, isValid, isCompleted, onClick }) => {
|
||||||
return (
|
return (
|
||||||
<div className="flex-shrink-0 flex justify-center relative mx-4">
|
<div className="flex-shrink-0 flex justify-center relative mx-4">
|
||||||
<div className={`
|
<div
|
||||||
|
className={`
|
||||||
w-8 h-8 rounded-full
|
w-8 h-8 rounded-full
|
||||||
flex items-center justify-center
|
flex items-center justify-center
|
||||||
text-sm font-semibold
|
text-sm font-semibold
|
||||||
${isCompleted
|
${
|
||||||
|
isCompleted
|
||||||
? 'bg-emerald-600 text-white'
|
? 'bg-emerald-600 text-white'
|
||||||
: isActive
|
: isActive
|
||||||
? 'bg-emerald-600 text-white'
|
? 'bg-emerald-600 text-white'
|
||||||
: 'bg-gray-200 text-gray-600'
|
: '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">
|
{isCompleted ? (
|
||||||
<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
|
||||||
</svg>
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
) : (
|
className="h-5 w-5"
|
||||||
number
|
viewBox="0 0 20 20"
|
||||||
)}
|
fill="currentColor"
|
||||||
</div>
|
>
|
||||||
<div className="absolute top-12 left-1/2 -translate-x-1/2">
|
<path
|
||||||
<span className={`
|
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
|
text-xs font-medium w-20 text-center block break-words
|
||||||
${isActive ? 'text-emerald-600' : 'text-gray-500'}
|
${isActive ? 'text-emerald-600' : 'text-gray-500'}
|
||||||
`}>
|
`}
|
||||||
{title}
|
>
|
||||||
</span>
|
{title}
|
||||||
</div>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const SpacerStep = ({ isCompleted }) => {
|
const SpacerStep = ({ isCompleted }) => {
|
||||||
return (
|
return (
|
||||||
<div className={`flex-1 h-0.5 ${isCompleted ? 'bg-emerald-600' : 'bg-gray-200'}`} />
|
<div
|
||||||
);
|
className={`flex-1 h-0.5 ${isCompleted ? 'bg-emerald-600' : 'bg-gray-200'}`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const Dots = () => {
|
const Dots = () => {
|
||||||
return (
|
return (
|
||||||
<div className="text-gray-500 relative flex items-center mx-4">
|
<div className="text-gray-500 relative flex items-center mx-4">
|
||||||
<span>...</span>
|
<span>...</span>
|
||||||
<div className="absolute top-8 left-1/2 -translate-x-1/2">
|
<div className="absolute top-8 left-1/2 -translate-x-1/2">
|
||||||
<span className="text-xs font-medium w-20 text-center block">...</span>
|
<span className="text-xs font-medium w-20 text-center block">...</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const ProgressStep = ({ steps, stepTitles, currentStep, setStep, isStepValid }) => {
|
const ProgressStep = ({
|
||||||
const [windowWidth, setWindowWidth] = useState(window.innerWidth);
|
steps,
|
||||||
const [visibleSteps, setVisibleSteps] = useState(steps);
|
stepTitles,
|
||||||
|
currentStep,
|
||||||
|
setStep,
|
||||||
|
isStepValid,
|
||||||
|
}) => {
|
||||||
|
const [windowWidth, setWindowWidth] = useState(window.innerWidth);
|
||||||
|
const [visibleSteps, setVisibleSteps] = useState(steps);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleResize = () => setWindowWidth(window.innerWidth);
|
const handleResize = () => setWindowWidth(window.innerWidth);
|
||||||
window.addEventListener('resize', handleResize);
|
window.addEventListener('resize', handleResize);
|
||||||
return () => window.removeEventListener('resize', handleResize);
|
return () => window.removeEventListener('resize', handleResize);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const calculateVisibleSteps = () => {
|
const calculateVisibleSteps = () => {
|
||||||
const minWidth = 150; // Largeur minimale estimée par étape
|
const minWidth = 150; // Largeur minimale estimée par étape
|
||||||
const maxVisibleSteps = Math.floor(windowWidth / minWidth);
|
const maxVisibleSteps = Math.floor(windowWidth / minWidth);
|
||||||
|
|
||||||
if (maxVisibleSteps >= steps.length) {
|
if (maxVisibleSteps >= steps.length) {
|
||||||
setVisibleSteps(steps);
|
setVisibleSteps(steps);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (maxVisibleSteps < 4) {
|
if (maxVisibleSteps < 4) {
|
||||||
// Garder seulement première, dernière et courante
|
// Garder seulement première, dernière et courante
|
||||||
let filtered = [steps[0]];
|
let filtered = [steps[0]];
|
||||||
if (currentStep > 1 && currentStep < steps.length) {
|
if (currentStep > 1 && currentStep < steps.length) {
|
||||||
filtered.push('...');
|
filtered.push('...');
|
||||||
filtered.push(steps[currentStep - 1]);
|
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 (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 (
|
calculateVisibleSteps();
|
||||||
<div className="w-full py-6">
|
}, [windowWidth, currentStep, steps]);
|
||||||
<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);
|
const handleStepClick = (stepIndex) => {
|
||||||
return (
|
// Vérifie si on peut naviguer vers l'étape (toutes les étapes précédentes doivent être valides)
|
||||||
<div
|
const canNavigate = Array.from(
|
||||||
key={index}
|
{ length: stepIndex },
|
||||||
className={`
|
(_, 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
|
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)}
|
onClick={() => handleStepClick(originalIndex)}
|
||||||
>
|
>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<div className="w-full flex items-center">
|
<div className="w-full flex items-center">
|
||||||
<Step
|
<Step
|
||||||
number={originalIndex + 1}
|
number={originalIndex + 1}
|
||||||
title={stepTitles ? stepTitles[originalIndex + 1] : step}
|
title={stepTitles ? stepTitles[originalIndex + 1] : step}
|
||||||
isActive={currentStep === originalIndex + 1}
|
isActive={currentStep === originalIndex + 1}
|
||||||
isCompleted={currentStep > originalIndex + 1}
|
isCompleted={currentStep > originalIndex + 1}
|
||||||
isValid={isStepValid(originalIndex + 1)}
|
isValid={isStepValid(originalIndex + 1)}
|
||||||
/>
|
/>
|
||||||
{index !== visibleSteps.length - 1 && (
|
{index !== visibleSteps.length - 1 && (
|
||||||
<SpacerStep isCompleted={currentStep > originalIndex + 1} />
|
<SpacerStep isCompleted={currentStep > originalIndex + 1} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
);
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ProgressStep;
|
export default ProgressStep;
|
||||||
|
|||||||
@ -4,37 +4,33 @@ import { useEstablishment } from '@/context/EstablishmentContext';
|
|||||||
import { FE_USERS_LOGIN_URL, getRedirectUrlFromRole } from '@/utils/Url';
|
import { FE_USERS_LOGIN_URL, getRedirectUrlFromRole } from '@/utils/Url';
|
||||||
|
|
||||||
const ProtectedRoute = ({ children, requiredRight }) => {
|
const ProtectedRoute = ({ children, requiredRight }) => {
|
||||||
|
|
||||||
const { user, profileRole } = useEstablishment();
|
const { user, profileRole } = useEstablishment();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
let hasRequiredRight = false;
|
let hasRequiredRight = false;
|
||||||
|
|
||||||
if(requiredRight && Array.isArray(requiredRight) ){
|
if (requiredRight && Array.isArray(requiredRight)) {
|
||||||
// Vérifier si l'utilisateur a le droit requis
|
// Vérifier si l'utilisateur a le droit requis
|
||||||
hasRequiredRight = requiredRight.some((right) => profileRole === right);
|
hasRequiredRight = requiredRight.some((right) => profileRole === right);
|
||||||
}else{
|
} else {
|
||||||
hasRequiredRight = (profileRole === requiredRight);
|
hasRequiredRight = profileRole === requiredRight;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vérifier si l'utilisateur a au moins un rôle correspondant au requiredRight
|
// Vérifier si l'utilisateur a au moins un rôle correspondant au requiredRight
|
||||||
useEffect(() => {
|
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
|
// 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) {
|
if (!hasRequiredRight) {
|
||||||
const redirectUrl = getRedirectUrlFromRole(profileRole);
|
const redirectUrl = getRedirectUrlFromRole(profileRole);
|
||||||
router.push(`${redirectUrl}`);
|
router.push(`${redirectUrl}`);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
}else{
|
|
||||||
// User non authentifié
|
// User non authentifié
|
||||||
router.push(`${FE_USERS_LOGIN_URL}`);
|
router.push(`${FE_USERS_LOGIN_URL}`);
|
||||||
}
|
}
|
||||||
}, [profileRole]);
|
}, [profileRole]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Autoriser l'affichage si authentifié et rôle correct
|
// Autoriser l'affichage si authentifié et rôle correct
|
||||||
return hasRequiredRight ? children : null;
|
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 { SessionProvider } from 'next-auth/react';
|
||||||
import { CsrfProvider } from '@/context/CsrfContext'
|
import { CsrfProvider } from '@/context/CsrfContext';
|
||||||
import { NextIntlClientProvider } from 'next-intl'
|
import { NextIntlClientProvider } from 'next-intl';
|
||||||
import { EstablishmentProvider } from '@/context/EstablishmentContext';
|
import { EstablishmentProvider } from '@/context/EstablishmentContext';
|
||||||
import { DndProvider } from 'react-dnd';
|
import { DndProvider } from 'react-dnd';
|
||||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||||
|
|
||||||
|
|
||||||
export default function Providers({ children, messages, locale, session }) {
|
export default function Providers({ children, messages, locale, session }) {
|
||||||
if (!locale) {
|
if (!locale) {
|
||||||
console.error('Locale non définie dans Providers');
|
console.error('Locale non définie dans Providers');
|
||||||
@ -25,5 +24,5 @@ export default function Providers({ children, messages, locale, session }) {
|
|||||||
</CsrfProvider>
|
</CsrfProvider>
|
||||||
</DndProvider>
|
</DndProvider>
|
||||||
</SessionProvider>
|
</SessionProvider>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,17 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
const RadioList = ({ items, formData, handleChange, fieldName, icon: Icon, className }) => {
|
const RadioList = ({
|
||||||
|
items,
|
||||||
|
formData,
|
||||||
|
handleChange,
|
||||||
|
fieldName,
|
||||||
|
icon: Icon,
|
||||||
|
className,
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className={`mb-4 ${className}`}>
|
<div className={`mb-4 ${className}`}>
|
||||||
<div className="grid grid-cols-1 gap-4">
|
<div className="grid grid-cols-1 gap-4">
|
||||||
{items.map(item => (
|
{items.map((item) => (
|
||||||
<div key={item.id} className="flex items-center">
|
<div key={item.id} className="flex items-center">
|
||||||
<input
|
<input
|
||||||
key={`${item.id}-${Math.random()}`}
|
key={`${item.id}-${Math.random()}`}
|
||||||
|
|||||||
@ -3,12 +3,23 @@ import { usePlanning } from '@/context/PlanningContext';
|
|||||||
import { Plus, Edit2, Eye, EyeOff, Check, X } from 'lucide-react';
|
import { Plus, Edit2, Eye, EyeOff, Check, X } from 'lucide-react';
|
||||||
|
|
||||||
export default function ScheduleNavigation() {
|
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 [editingId, setEditingId] = useState(null);
|
||||||
const [editedName, setEditedName] = useState('');
|
const [editedName, setEditedName] = useState('');
|
||||||
const [editedColor, setEditedColor] = useState('');
|
const [editedColor, setEditedColor] = useState('');
|
||||||
const [isAddingNew, setIsAddingNew] = useState(false);
|
const [isAddingNew, setIsAddingNew] = useState(false);
|
||||||
const [newSchedule, setNewSchedule] = useState({ name: '', color: '#10b981' });
|
const [newSchedule, setNewSchedule] = useState({
|
||||||
|
name: '',
|
||||||
|
color: '#10b981',
|
||||||
|
});
|
||||||
|
|
||||||
const handleEdit = (schedule) => {
|
const handleEdit = (schedule) => {
|
||||||
setEditingId(schedule.id);
|
setEditingId(schedule.id);
|
||||||
@ -19,9 +30,9 @@ export default function ScheduleNavigation() {
|
|||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
if (editingId) {
|
if (editingId) {
|
||||||
updateSchedule(editingId, {
|
updateSchedule(editingId, {
|
||||||
...schedules.find(s => s.id === editingId),
|
...schedules.find((s) => s.id === editingId),
|
||||||
name: editedName,
|
name: editedName,
|
||||||
color: editedColor
|
color: editedColor,
|
||||||
});
|
});
|
||||||
setEditingId(null);
|
setEditingId(null);
|
||||||
}
|
}
|
||||||
@ -31,7 +42,7 @@ export default function ScheduleNavigation() {
|
|||||||
if (newSchedule.name) {
|
if (newSchedule.name) {
|
||||||
addSchedule({
|
addSchedule({
|
||||||
id: `schedule-${Date.now()}`,
|
id: `schedule-${Date.now()}`,
|
||||||
...newSchedule
|
...newSchedule,
|
||||||
});
|
});
|
||||||
setIsAddingNew(false);
|
setIsAddingNew(false);
|
||||||
setNewSchedule({ name: '', color: '#10b981' });
|
setNewSchedule({ name: '', color: '#10b981' });
|
||||||
@ -55,7 +66,9 @@ export default function ScheduleNavigation() {
|
|||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={newSchedule.name}
|
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"
|
className="w-full p-1 mb-2 border rounded"
|
||||||
placeholder="Nom du planning"
|
placeholder="Nom du planning"
|
||||||
/>
|
/>
|
||||||
@ -64,7 +77,9 @@ export default function ScheduleNavigation() {
|
|||||||
<input
|
<input
|
||||||
type="color"
|
type="color"
|
||||||
value={newSchedule.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"
|
className="w-8 h-8"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -86,11 +101,13 @@ export default function ScheduleNavigation() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<ul className="space-y-2">
|
<ul className="space-y-2">
|
||||||
{schedules.map(schedule => (
|
{schedules.map((schedule) => (
|
||||||
<li
|
<li
|
||||||
key={schedule.id}
|
key={schedule.id}
|
||||||
className={`p-2 rounded ${
|
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 ? (
|
{editingId === schedule.id ? (
|
||||||
@ -135,7 +152,13 @@ export default function ScheduleNavigation() {
|
|||||||
className="w-3 h-3 rounded-full"
|
className="w-3 h-3 rounded-full"
|
||||||
style={{ backgroundColor: schedule.color }}
|
style={{ backgroundColor: schedule.color }}
|
||||||
/>
|
/>
|
||||||
<span className={hiddenSchedules.includes(schedule.id) ? 'text-gray-400' : ''}>
|
<span
|
||||||
|
className={
|
||||||
|
hiddenSchedules.includes(schedule.id)
|
||||||
|
? 'text-gray-400'
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
>
|
||||||
{schedule.name}
|
{schedule.name}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -167,4 +190,4 @@ export default function ScheduleNavigation() {
|
|||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<div>
|
<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}
|
{label}
|
||||||
{required && <span className="text-red-500 ml-1">*</span>}
|
{required && <span className="text-red-500 ml-1">*</span>}
|
||||||
</label>
|
</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'}`}>
|
<div
|
||||||
{IconItem &&
|
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">
|
<span className="inline-flex items-center px-3 text-gray-500 text-sm">
|
||||||
{<IconItem />}
|
{<IconItem />}
|
||||||
</span>
|
</span>
|
||||||
}
|
)}
|
||||||
<select
|
<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' : ''}`}
|
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}
|
type={type}
|
||||||
@ -33,4 +50,4 @@ export default function SelectChoice({ type, name, label, required, placeHolder,
|
|||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user