diff --git a/.gitignore b/.gitignore index 2e51005..5783875 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,6 @@ next-env.d.ts # uploads /public/uploads + +# participations PDF +/data/participations diff --git a/app/api/dashboard/stats/route.ts b/app/api/dashboard/stats/route.ts index fcee248..8ae2085 100644 --- a/app/api/dashboard/stats/route.ts +++ b/app/api/dashboard/stats/route.ts @@ -23,25 +23,26 @@ export async function GET(request: NextRequest) { const startOfYesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1); const endOfYesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1, 23, 59, 59, 999); - // 1. Participations du mois (montant total des trajets validés/terminés + nombre de factures) - // Pour l'instant, on considère qu'un trajet terminé = une facture - // Montant estimé : 6.80€ par trajet (valeur moyenne basée sur l'image) - const trajetsMois = await prisma.trajet.findMany({ + // 1. Participations du mois (trajets validés/terminés ce mois) + const participationsMoisData = await prisma.participationFinanciere.findMany({ where: { - archived: false, - statut: { - in: ['Terminé', 'Validé'], - }, - date: { - gte: startOfMonth, - lte: endOfMonth, + trajet: { + date: { + gte: startOfMonth, + lte: endOfMonth, + }, }, }, + include: { trajet: { select: { date: true } } }, }); - const montantMoyenParTrajet = 6.80; // Montant moyen par trajet en euros - const participationsMois = trajetsMois.length * montantMoyenParTrajet; - const nombreFactures = trajetsMois.length; + const participationsCeMois = participationsMoisData; + const montantMoyenParTrajet = 6.80; + const participationsMois = participationsCeMois.reduce( + (sum, p) => sum + (p.montant ?? montantMoyenParTrajet), + 0 + ); + const nombreFactures = participationsCeMois.length; // 2. Trajets aujourd'hui const trajetsAujourdhui = await prisma.trajet.count({ diff --git a/app/api/participations/[id]/pdf/route.ts b/app/api/participations/[id]/pdf/route.ts new file mode 100644 index 0000000..aa3a328 --- /dev/null +++ b/app/api/participations/[id]/pdf/route.ts @@ -0,0 +1,57 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { prisma } from '@/lib/prisma'; +import { getCurrentUser } from '@/lib/auth'; +import { generateParticipationPDF, getParticipationStoragePath } from '@/lib/participation-pdf'; + +// GET - Récupérer le PDF d'une participation (régénéré à chaque vue pour le design à jour) +export async function GET( + request: NextRequest, + { params }: { params: { id: string } } +) { + try { + const user = await getCurrentUser(); + if (!user) { + return new NextResponse('Non autorisé', { status: 401 }); + } + + const participation = await prisma.participationFinanciere.findUnique({ + where: { id: params.id }, + include: { + adherent: true, + trajet: true, + }, + }); + + if (!participation) { + return new NextResponse('Participation non trouvée', { status: 404 }); + } + + const filePath = getParticipationStoragePath(participation.id); + const pdfBuffer = await generateParticipationPDF( + { + adherentNom: participation.adherent.nom, + adherentPrenom: participation.adherent.prenom, + adherentAdresse: participation.adherent.adresse, + destinataireEmail: participation.destinataireEmail, + destinataireNom: participation.destinataireNom, + dateTrajet: participation.trajet.date, + adresseDepart: participation.trajet.adresseDepart, + adresseArrivee: participation.trajet.adresseArrivee, + montant: participation.montant ?? undefined, + complement: participation.complement ?? undefined, + participationId: participation.id, + }, + filePath + ); + + return new NextResponse(pdfBuffer, { + headers: { + 'Content-Type': 'application/pdf', + 'Content-Disposition': `inline; filename="participation-${params.id}.pdf"`, + }, + }); + } catch (error) { + console.error('Erreur lors de la récupération du PDF:', error); + return new NextResponse('Erreur serveur', { status: 500 }); + } +} diff --git a/app/api/participations/[id]/route.ts b/app/api/participations/[id]/route.ts new file mode 100644 index 0000000..3760b71 --- /dev/null +++ b/app/api/participations/[id]/route.ts @@ -0,0 +1,203 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { prisma } from '@/lib/prisma'; +import { getCurrentUser } from '@/lib/auth'; +import { generateParticipationPDF, getParticipationStoragePath } from '@/lib/participation-pdf'; + +// GET - Récupérer une participation +export async function GET( + request: NextRequest, + { params }: { params: { id: string } } +) { + try { + const user = await getCurrentUser(); + if (!user) { + return NextResponse.json({ error: 'Non autorisé' }, { status: 401 }); + } + + const participation = await prisma.participationFinanciere.findUnique({ + where: { id: params.id }, + include: { + adherent: true, + trajet: { + include: { + chauffeur: { select: { nom: true, prenom: true } }, + universPro: { select: { nom: true, prenom: true, nomEntreprise: true, email: true } }, + }, + }, + }, + }); + + if (!participation) { + return NextResponse.json({ error: 'Participation non trouvée' }, { status: 404 }); + } + + return NextResponse.json(participation); + } catch (error) { + console.error('Erreur lors de la récupération de la participation:', error); + return NextResponse.json( + { error: 'Erreur serveur' }, + { status: 500 } + ); + } +} + +// PUT - Modifier une participation +export async function PUT( + request: NextRequest, + { params }: { params: { id: string } } +) { + try { + const user = await getCurrentUser(); + if (!user) { + return NextResponse.json({ error: 'Non autorisé' }, { status: 401 }); + } + + const participation = await prisma.participationFinanciere.findUnique({ + where: { id: params.id }, + include: { + adherent: true, + trajet: { include: { adherent: true } }, + }, + }); + + if (!participation) { + return NextResponse.json({ error: 'Participation non trouvée' }, { status: 404 }); + } + + const body = await request.json(); + const { destinataireEmail, destinataireNom, destinataireType, montant, complement } = body; + + const updated = await prisma.participationFinanciere.update({ + where: { id: params.id }, + data: { + ...(destinataireEmail && { destinataireEmail }), + ...(destinataireNom && { destinataireNom }), + ...(destinataireType && { destinataireType }), + ...(montant !== undefined && { montant }), + ...(complement !== undefined && { complement }), + }, + }); + + // Régénérer le PDF si les données ont changé + const dataToUpdate = + destinataireEmail || destinataireNom || montant !== undefined || complement !== undefined; + if (dataToUpdate) { + const filePath = getParticipationStoragePath(participation.id); + await generateParticipationPDF( + { + adherentNom: participation.adherent.nom, + adherentPrenom: participation.adherent.prenom, + adherentAdresse: participation.adherent.adresse, + destinataireEmail: updated.destinataireEmail, + destinataireNom: updated.destinataireNom, + dateTrajet: participation.trajet.date, + adresseDepart: participation.trajet.adresseDepart, + adresseArrivee: participation.trajet.adresseArrivee, + montant: updated.montant ?? undefined, + complement: updated.complement ?? undefined, + participationId: participation.id, + }, + filePath + ); + await prisma.participationFinanciere.update({ + where: { id: params.id }, + data: { filePath }, + }); + } + + const fullUpdated = await prisma.participationFinanciere.findUnique({ + where: { id: params.id }, + include: { + adherent: { select: { id: true, nom: true, prenom: true, email: true } }, + trajet: { select: { id: true, date: true, adresseDepart: true, adresseArrivee: true, statut: true } }, + }, + }); + + return NextResponse.json(fullUpdated); + } catch (error) { + console.error('Erreur lors de la mise à jour de la participation:', error); + return NextResponse.json( + { error: 'Erreur serveur' }, + { status: 500 } + ); + } +} + +// PATCH - Changer le statut d'une participation +export async function PATCH( + request: NextRequest, + { params }: { params: { id: string } } +) { + try { + const user = await getCurrentUser(); + if (!user) { + return NextResponse.json({ error: 'Non autorisé' }, { status: 401 }); + } + + const body = await request.json(); + const { statut } = body; + + const validStatuts = ['en_attente', 'envoye', 'paye', 'archive']; + if (!statut || !validStatuts.includes(statut)) { + return NextResponse.json({ error: 'Statut invalide' }, { status: 400 }); + } + + const participation = await prisma.participationFinanciere.findUnique({ + where: { id: params.id }, + }); + + if (!participation) { + return NextResponse.json({ error: 'Participation non trouvée' }, { status: 404 }); + } + + const updated = await prisma.participationFinanciere.update({ + where: { id: params.id }, + data: { statut }, + }); + + return NextResponse.json(updated); + } catch (error) { + console.error('Erreur lors du changement de statut:', error); + return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 }); + } +} + +// DELETE - Supprimer une participation +export async function DELETE( + request: NextRequest, + { params }: { params: { id: string } } +) { + try { + const user = await getCurrentUser(); + if (!user) { + return NextResponse.json({ error: 'Non autorisé' }, { status: 401 }); + } + + const participation = await prisma.participationFinanciere.findUnique({ + where: { id: params.id }, + }); + + if (!participation) { + return NextResponse.json({ error: 'Participation non trouvée' }, { status: 404 }); + } + + const fs = await import('fs'); + const path = await import('path'); + const filePath = getParticipationStoragePath(participation.id); + if (fs.existsSync(filePath)) { + fs.unlinkSync(filePath); + } + + await prisma.participationFinanciere.delete({ + where: { id: params.id }, + }); + + return NextResponse.json({ message: 'Participation supprimée' }); + } catch (error) { + console.error('Erreur lors de la suppression de la participation:', error); + return NextResponse.json( + { error: 'Erreur serveur' }, + { status: 500 } + ); + } +} diff --git a/app/api/participations/[id]/send/route.ts b/app/api/participations/[id]/send/route.ts new file mode 100644 index 0000000..c5bdced --- /dev/null +++ b/app/api/participations/[id]/send/route.ts @@ -0,0 +1,110 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { prisma } from '@/lib/prisma'; +import { getCurrentUser } from '@/lib/auth'; +import { getParticipationStoragePath } from '@/lib/participation-pdf'; +import fs from 'fs'; + +// POST - Envoyer la participation par email +export async function POST( + request: NextRequest, + { params }: { params: { id: string } } +) { + try { + const user = await getCurrentUser(); + if (!user) { + return NextResponse.json({ error: 'Non autorisé' }, { status: 401 }); + } + + const participation = await prisma.participationFinanciere.findUnique({ + where: { id: params.id }, + include: { + adherent: { select: { prenom: true, nom: true } }, + trajet: { select: { date: true } }, + }, + }); + + if (!participation) { + return NextResponse.json({ error: 'Participation non trouvée' }, { status: 404 }); + } + + const filePath = getParticipationStoragePath(participation.id); + if (!fs.existsSync(filePath)) { + return NextResponse.json( + { error: 'Le document PDF n\'a pas été généré' }, + { status: 400 } + ); + } + + // Vérifier la configuration email + const smtpHost = process.env.SMTP_HOST; + const smtpUser = process.env.SMTP_USER; + const smtpPass = process.env.SMTP_PASS; + + if (!smtpHost || !smtpUser || !smtpPass) { + return NextResponse.json( + { + error: + "L'envoi par email n'est pas configuré. Configurez SMTP_HOST, SMTP_USER et SMTP_PASS dans les variables d'environnement.", + }, + { status: 503 } + ); + } + + const nodemailer = await import('nodemailer'); + const transporter = nodemailer.default.createTransport({ + host: smtpHost, + port: parseInt(process.env.SMTP_PORT || '587'), + secure: process.env.SMTP_SECURE === 'true', + auth: { + user: smtpUser, + pass: smtpPass, + }, + }); + + const pdfBuffer = fs.readFileSync(filePath); + const dateTrajet = participation.trajet?.date + ? new Date(participation.trajet.date).toLocaleDateString('fr-FR', { + day: '2-digit', + month: 'long', + year: 'numeric', + }) + : ''; + + await transporter.sendMail({ + from: process.env.SMTP_FROM || smtpUser, + to: participation.destinataireEmail, + subject: `Participation financière - ${participation.adherent.prenom} ${participation.adherent.nom} - ${dateTrajet}`, + text: `Bonjour,\n\nVeuillez trouver ci-joint la participation financière concernant le trajet du ${dateTrajet} pour ${participation.adherent.prenom} ${participation.adherent.nom}.\n\nCordialement`, + html: ` +

Bonjour,

+

Veuillez trouver ci-joint la participation financière concernant le trajet du ${dateTrajet} pour ${participation.adherent.prenom} ${participation.adherent.nom}.

+

Cordialement

+ `, + attachments: [ + { + filename: `participation-financiere-${params.id}.pdf`, + content: pdfBuffer, + }, + ], + }); + + // Mettre à jour le statut en "envoyé" + await prisma.participationFinanciere.update({ + where: { id: params.id }, + data: { statut: 'envoye' }, + }); + + return NextResponse.json({ + message: `Participation envoyée à ${participation.destinataireEmail}`, + }); + } catch (error) { + console.error('Erreur lors de l\'envoi de l\'email:', error); + return NextResponse.json( + { + error: + error instanceof Error ? error.message : 'Erreur lors de l\'envoi de l\'email', + }, + { status: 500 } + ); + } +} diff --git a/app/api/participations/route.ts b/app/api/participations/route.ts new file mode 100644 index 0000000..e2e880d --- /dev/null +++ b/app/api/participations/route.ts @@ -0,0 +1,51 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { prisma } from '@/lib/prisma'; +import { getCurrentUser } from '@/lib/auth'; + +// GET - Liste toutes les participations financières +export async function GET(request: NextRequest) { + try { + const user = await getCurrentUser(); + if (!user) { + return NextResponse.json({ error: 'Non autorisé' }, { status: 401 }); + } + + const participations = await prisma.participationFinanciere.findMany({ + orderBy: { createdAt: 'desc' }, + include: { + adherent: { + select: { + id: true, + nom: true, + prenom: true, + email: true, + }, + }, + trajet: { + select: { + id: true, + date: true, + adresseDepart: true, + adresseArrivee: true, + statut: true, + chauffeur: { + select: { + id: true, + nom: true, + prenom: true, + }, + }, + }, + }, + }, + }); + + return NextResponse.json(participations); + } catch (error) { + console.error('Erreur lors de la récupération des participations:', error); + return NextResponse.json( + { error: 'Erreur serveur' }, + { status: 500 } + ); + } +} diff --git a/app/api/trajets/[id]/route.ts b/app/api/trajets/[id]/route.ts index 5192245..a20b655 100644 --- a/app/api/trajets/[id]/route.ts +++ b/app/api/trajets/[id]/route.ts @@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from 'next/server'; import { prisma } from '@/lib/prisma'; import { getCurrentUser } from '@/lib/auth'; import { createNotificationForAllUsers } from '@/lib/notifications'; +import { createParticipationForTrajet } from '@/lib/participation-financiere'; // GET - Récupérer un trajet spécifique export async function GET( @@ -69,6 +70,13 @@ export async function PUT( const body = await request.json(); const { date, adresseDepart, adresseArrivee, commentaire, instructions, statut, adherentId, chauffeurId } = body; + const previousTrajet = statut + ? await prisma.trajet.findUnique({ + where: { id: params.id }, + select: { statut: true }, + }) + : null; + const trajet = await prisma.trajet.update({ where: { id: params.id }, data: { @@ -80,6 +88,7 @@ export async function PUT( ...(statut && { statut }), ...(adherentId && { adherentId }), ...(chauffeurId !== undefined && { chauffeurId }), + ...(body.universProId !== undefined && { universProId: body.universProId || null }), }, include: { adherent: { @@ -105,52 +114,47 @@ export async function PUT( }); // Créer une notification si le statut a changé - if (statut) { - const oldTrajet = await prisma.trajet.findUnique({ - where: { id: params.id }, - include: { - adherent: { - select: { - nom: true, - prenom: true, - }, - }, - }, + if (statut && previousTrajet && previousTrajet.statut !== statut) { + const dateFormatted = new Date(trajet.date).toLocaleDateString('fr-FR', { + day: 'numeric', + month: 'long', + year: 'numeric', + hour: '2-digit', + minute: '2-digit', }); - if (oldTrajet && oldTrajet.statut !== statut) { - const dateFormatted = new Date(trajet.date).toLocaleDateString('fr-FR', { - day: 'numeric', - month: 'long', - year: 'numeric', - hour: '2-digit', - minute: '2-digit', - }); + let notificationType: 'trajet_cancelled' | 'trajet_completed' | null = null; + let notificationTitle = ''; + let notificationMessage = ''; - let notificationType: 'trajet_cancelled' | 'trajet_completed' | null = null; - let notificationTitle = ''; - let notificationMessage = ''; + if (statut === 'Annulé') { + notificationType = 'trajet_cancelled'; + notificationTitle = 'Trajet annulé'; + notificationMessage = `Le trajet pour ${trajet.adherent.prenom} ${trajet.adherent.nom} du ${dateFormatted} a été annulé`; + } else if (statut === 'Terminé') { + notificationType = 'trajet_completed'; + notificationTitle = 'Trajet terminé'; + notificationMessage = `Le trajet pour ${trajet.adherent.prenom} ${trajet.adherent.nom} du ${dateFormatted} est terminé`; + } - if (statut === 'Annulé') { - notificationType = 'trajet_cancelled'; - notificationTitle = 'Trajet annulé'; - notificationMessage = `Le trajet pour ${trajet.adherent.prenom} ${trajet.adherent.nom} du ${dateFormatted} a été annulé`; - } else if (statut === 'Terminé') { - notificationType = 'trajet_completed'; - notificationTitle = 'Trajet terminé'; - notificationMessage = `Le trajet pour ${trajet.adherent.prenom} ${trajet.adherent.nom} du ${dateFormatted} est terminé`; - } + if (notificationType) { + await createNotificationForAllUsers( + { + type: notificationType, + title: notificationTitle, + message: notificationMessage, + link: '/dashboard/calendrier', + }, + user.id + ); + } - if (notificationType) { - await createNotificationForAllUsers( - { - type: notificationType, - title: notificationTitle, - message: notificationMessage, - link: '/dashboard/calendrier', - }, - user.id - ); + // Créer la participation financière quand le trajet est terminé ou validé + if (statut === 'Terminé' || statut === 'Validé') { + try { + await createParticipationForTrajet(params.id); + } catch (err) { + console.error('Erreur création participation:', err); } } } diff --git a/app/api/trajets/[id]/validate/route.ts b/app/api/trajets/[id]/validate/route.ts index b8b5abc..cd1d87e 100644 --- a/app/api/trajets/[id]/validate/route.ts +++ b/app/api/trajets/[id]/validate/route.ts @@ -1,6 +1,7 @@ import { NextRequest, NextResponse } from 'next/server'; import { prisma } from '@/lib/prisma'; import { getCurrentUser } from '@/lib/auth'; +import { createParticipationForTrajet } from '@/lib/participation-financiere'; // POST - Valider un trajet et déduire les heures du chauffeur export async function POST( @@ -23,11 +24,12 @@ export async function POST( ); } - // Récupérer le trajet avec le chauffeur + // Récupérer le trajet avec le chauffeur et univers pro const trajet = await prisma.trajet.findUnique({ where: { id: params.id }, include: { chauffeur: true, + universPro: true, }, }); @@ -97,6 +99,13 @@ export async function POST( }), ]); + // Créer la participation financière (document) + try { + await createParticipationForTrajet(params.id); + } catch (err) { + console.error('Erreur création participation:', err); + } + return NextResponse.json({ trajet: trajetUpdated, chauffeur: { diff --git a/app/dashboard/factures/page.tsx b/app/dashboard/factures/page.tsx new file mode 100644 index 0000000..c3cc81d --- /dev/null +++ b/app/dashboard/factures/page.tsx @@ -0,0 +1,31 @@ +import { getCurrentUser } from '@/lib/auth'; +import { hasPageAccess } from '@/lib/permissions'; +import { redirect } from 'next/navigation'; +import DashboardLayout from '@/components/DashboardLayout'; +import ParticipationFinanciereList from '@/components/ParticipationFinanciereList'; + +export default async function ParticipationFinancierePage() { + const user = await getCurrentUser(); + if (!user) { + redirect('/login'); + } + + const hasAccess = await hasPageAccess(user.id, '/dashboard/factures'); + if (!hasAccess) { + redirect('/dashboard/parametres'); + } + + return ( + +
+

+ Participation financière +

+

+ Documents de participation générés à la fin de chaque trajet +

+ +
+
+ ); +} diff --git a/app/globals.css b/app/globals.css index bbc4cd1..ecdd64c 100644 --- a/app/globals.css +++ b/app/globals.css @@ -121,3 +121,17 @@ body { .animate-slideInRight { animation: slideInRight 0.3s ease-out; } + +/* Sélection lisible dans les modales de formulaire */ +.participation-form input, +.participation-form select, +.participation-form textarea { + color: #181818; + color-scheme: light; +} +.participation-form input::selection, +.participation-form select::selection, +.participation-form textarea::selection { + background-color: #17B6C4; + color: white; +} diff --git a/components/CalendrierTrajets.tsx b/components/CalendrierTrajets.tsx index 7bc7ffe..f3c210b 100644 --- a/components/CalendrierTrajets.tsx +++ b/components/CalendrierTrajets.tsx @@ -428,11 +428,12 @@ export default function CalendrierTrajets({ refreshTrigger }: CalendrierTrajetsP date.getDate() === new Date().getDate() && date.getMonth() === new Date().getMonth() && date.getFullYear() === new Date().getFullYear(); - const isSelected = + const isSelected = !!( selectedDate && date.getDate() === selectedDate.getDate() && date.getMonth() === selectedDate.getMonth() && - date.getFullYear() === selectedDate.getFullYear(); + date.getFullYear() === selectedDate.getFullYear() + ); return (

- {stats ? `${stats.participationsMois.nombreFactures} ${stats.participationsMois.nombreFactures > 1 ? 'Factures' : 'Facture'}` : '0 Facture'} + {stats ? `${stats.participationsMois.nombreFactures} participation${stats.participationsMois.nombreFactures > 1 ? 's' : ''}` : '0 participation'}

@@ -303,7 +303,7 @@ export default function DashboardContent({ userName }: DashboardContentProps) { -

Nouvelle facture

+

Participation financière

diff --git a/components/DashboardLayout.tsx b/components/DashboardLayout.tsx index f64d836..4696899 100644 --- a/components/DashboardLayout.tsx +++ b/components/DashboardLayout.tsx @@ -157,7 +157,7 @@ export default function DashboardLayout({ user, children }: DashboardLayoutProps ), }, { - label: 'Factures', + label: 'Participation financière', href: '/dashboard/factures', icon: ( diff --git a/components/ParticipationEditModal.tsx b/components/ParticipationEditModal.tsx new file mode 100644 index 0000000..48e5a54 --- /dev/null +++ b/components/ParticipationEditModal.tsx @@ -0,0 +1,180 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { useNotification } from './NotificationProvider'; + +interface Participation { + id: string; + destinataireEmail: string; + destinataireNom: string; + destinataireType: string; + montant: number | null; + complement: string | null; + adherent: { id: string; nom: string; prenom: string; email: string }; + trajet: { id: string; date: string; adresseDepart: string; adresseArrivee: string }; +} + +interface ParticipationEditModalProps { + participation: Participation; + onClose: () => void; + onSuccess: () => void; +} + +export default function ParticipationEditModal({ + participation, + onClose, + onSuccess, +}: ParticipationEditModalProps) { + const { showNotification } = useNotification(); + const [loading, setLoading] = useState(false); + const [formData, setFormData] = useState({ + destinataireEmail: '', + destinataireNom: '', + destinataireType: 'adherent', + montant: '', + complement: '', + }); + + useEffect(() => { + setFormData({ + destinataireEmail: participation.destinataireEmail, + destinataireNom: participation.destinataireNom, + destinataireType: participation.destinataireType || 'adherent', + montant: participation.montant != null ? String(participation.montant) : '', + complement: participation.complement || '', + }); + }, [participation]); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setLoading(true); + try { + const response = await fetch(`/api/participations/${participation.id}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + destinataireEmail: formData.destinataireEmail, + destinataireNom: formData.destinataireNom, + destinataireType: formData.destinataireType, + montant: formData.montant ? parseFloat(formData.montant) : null, + complement: formData.complement || null, + }), + }); + if (response.ok) { + onSuccess(); + onClose(); + } else { + const data = await response.json(); + showNotification('error', data.error || 'Erreur lors de la mise à jour'); + } + } catch (error) { + showNotification('error', 'Erreur lors de la mise à jour'); + } finally { + setLoading(false); + } + }; + + const handleBackdropClick = (e: React.MouseEvent) => { + if (e.target === e.currentTarget) onClose(); + }; + + const inputBaseClass = + 'w-full px-3 py-2 border border-gray-300 rounded-lg bg-white text-gray-900 placeholder-gray-400 focus:ring-2 focus:ring-lblue focus:border-transparent selection:bg-lblue selection:text-white'; + + return ( +
+
e.stopPropagation()} + > +
+

Modifier la participation

+

+ {participation.adherent.prenom} {participation.adherent.nom} -{' '} + {new Date(participation.trajet.date).toLocaleDateString('fr-FR')} +

+
+ +
+
+ + setFormData({ ...formData, destinataireEmail: e.target.value })} + className={inputBaseClass} + required + /> +
+
+ + setFormData({ ...formData, destinataireNom: e.target.value })} + className={inputBaseClass} + required + /> +
+
+ + +
+
+ + setFormData({ ...formData, montant: e.target.value })} + className={inputBaseClass} + placeholder="6.80" + /> +
+
+ +