import { useEffect, useMemo, useRef, useState, type ReactNode } from 'react'; import { ApiError } from '@/api'; import { roleLabelKey } from '@/auth/roles'; import { useAccounts, useAnalyzeImport, useArtists, useCreateAccount, useCreateVolunteer, useDeleteAccount, useDeleteVolunteer, useEdition, useExecuteImport, usePositions, useReplaceAvailability, useUpdateAccount, useUpdatePosition, useUpdateVolunteer, useVolunteerAvailability, useVolunteerPhotoUrl, useVolunteers, } from '@/api/hooks'; import type { ArtistRead, ImportAnalyzeResponse, ImportFieldKey, ImportIssue, ImportMapping, ImportReport, PositionRead, VolunteerCreate, VolunteerRead, VolunteerUpdate, } from '@/api/schema'; import { Chip, FieldLabel, HelpTip, TextInput } from '@/components/ui'; import { ErrorNote, Loading } from '@/components/state'; import { foldAccents } from '@/lib/planning'; import { t } from '@/i18n'; import { shortDayLabel } from '@/lib/time'; import { cx } from '@/lib/cx'; const MAP_FIELDS: Array<{ key: ImportFieldKey; required: boolean }> = [ { key: 'first_name', required: true }, { key: 'last_name', required: true }, { key: 'email', required: false }, { key: 'phone', required: false }, { key: 'comment', required: false }, ]; const FIELD_LABEL: Record = { first_name: 'config.vol.field.firstName', last_name: 'config.vol.field.lastName', email: 'config.vol.field.email', phone: 'config.vol.field.phone', comment: 'config.vol.field.comment', }; type WishOption = { id: string; label: string; hint?: string }; function artistOptions(artists: ArtistRead[] | undefined): WishOption[] { return [...(artists ?? [])] .sort( (a, b) => a.performance_date.localeCompare(b.performance_date) || a.name.localeCompare(b.name, 'fr'), ) .map((a) => ({ id: a.id, label: a.name, hint: shortDayLabel(`${a.performance_date}T12:00`), })); } function positionOptions(positions: PositionRead[] | undefined): WishOption[] { return [...(positions ?? [])] .sort((a, b) => a.name.localeCompare(b.name, 'fr')) .map((p) => ({ id: p.id, label: p.name, hint: p.location ?? undefined })); } /** Les autres bénévoles, pour le vœu de compagnon (jamais soi-même). */ function volunteerOptions( volunteers: VolunteerRead[] | undefined, excludeId?: string, ): WishOption[] { return [...(volunteers ?? [])] .filter((v) => v.id !== excludeId) .sort( (a, b) => a.last_name.localeCompare(b.last_name, 'fr') || a.first_name.localeCompare(b.first_name, 'fr'), ) .map((v) => ({ id: v.id, label: `${v.first_name} ${v.last_name}` })); } /** * Accès de connexion (lien magique) du bénévole. Ajouter un email sur sa * fiche ne crée aucun compte : sans ce geste explicite, `auth/magic-link` ne * trouve personne à qui écrire et ne fait rien (par conception, pour ne * jamais révéler si un email existe). */ function VolunteerAccessSection({ volunteerId, email, }: { volunteerId: string; email: string | null; }) { const accounts = useAccounts(); const createAccount = useCreateAccount(); const deleteAccount = useDeleteAccount(); const account = accounts.data?.find((a) => a.volunteer_id === volunteerId); return (
{t('config.vol.access.title')} {account ? (
{t('config.vol.access.active', { email: account.email })} {t(roleLabelKey(account.role))}
) : ( )} {!account && !email ? (
{t('config.vol.access.needEmail')}
) : null} {createAccount.isError ? : null} {deleteAccount.isError ? : null}
); } /** * Réponses du formulaire public d'inscription (voir `app/api/forms.py`) — * note de compagnon en texte libre (à résoudre manuellement en vrai vœu via * le sélecteur ci-dessus une fois la personne visée elle-même inscrite), * cases informatives, photo. N'apparaît que si le bénévole vient * effectivement de ce formulaire (sinon rien à afficher). */ function PublicFormAnswers({ volunteer, patch, }: { volunteer: VolunteerRead; patch: (body: VolunteerUpdate) => void; }) { const photoUrl = useVolunteerPhotoUrl(volunteer.id, volunteer.has_photo); const hasAnything = volunteer.has_photo || Boolean(volunteer.companion_wish_note) || volunteer.self_declared_available || volunteer.willing_to_pay_fee; if (!hasAnything) return null; return (
{t('config.vol.publicForm.title')} {photoUrl ? ( ) : null} {volunteer.companion_wish_note ? (