Écrans bénévole et référent redessinés, messages aux bénévoles
Étape 55 : écrans /mobile-benevole et /mobile-referent d'après la maquette (design/Planning bénévole mobile) — créneau en cours ou prochain mis en avant, planning par journée de festival avec frise horaire, compteurs et pointage du référent par créneau. Étape 56 : message de l'organisation (Edition.volunteer_message, onglet « Message aux bénévoles ») et message de chaque poste (Position.message, saisi par le référent), migration 0029, affichés sur l'écran bénévole. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
4a3f93e137
commit
16d8fb44a7
@@ -0,0 +1,96 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { useEdition, usePositions, useUpdateEdition, useUpdatePosition } from '@/api/hooks';
|
||||
import type { PositionRead } from '@/api/schema';
|
||||
import { MessageEditor } from '@/components/MessageEditor';
|
||||
import { ErrorNote, Loading } from '@/components/state';
|
||||
import { Panel, PanelHeader } from '@/components/ui';
|
||||
import { cx } from '@/lib/cx';
|
||||
import { t } from '@/i18n';
|
||||
|
||||
// Messages montrés sur l'écran bénévole : celui de l'organisation, le même
|
||||
// pour tous (`Edition.volunteer_message`), et, pour suivi, ceux des postes
|
||||
// (`Position.message`) — saisis d'ordinaire par chaque référent depuis son
|
||||
// écran, mais la coordination peut aussi les reprendre ici.
|
||||
export function ConfigMessage() {
|
||||
const edition = useEdition();
|
||||
const positions = usePositions();
|
||||
const updateEdition = useUpdateEdition();
|
||||
|
||||
if (edition.isLoading || positions.isLoading) return <Loading />;
|
||||
const error = edition.error ?? positions.error;
|
||||
if (error) return <ErrorNote error={error} />;
|
||||
|
||||
return (
|
||||
<div className="grid items-start gap-4 p-3 sm:p-4 [grid-template-columns:repeat(auto-fit,minmax(min(360px,100%),1fr))]">
|
||||
<Panel shadow="lg">
|
||||
<PanelHeader>{t('config.message.orgTitle')}</PanelHeader>
|
||||
<div className="flex flex-col gap-3 p-[13px]">
|
||||
<p className="font-mono text-[10.5px] leading-relaxed opacity-70">{t('config.message.orgHelp')}</p>
|
||||
<MessageEditor
|
||||
value={edition.data?.volunteer_message ?? null}
|
||||
updatedAt={edition.data?.volunteer_message_updated_at ?? null}
|
||||
placeholder={t('config.message.orgPlaceholder')}
|
||||
saving={updateEdition.isPending}
|
||||
error={updateEdition.error}
|
||||
onSave={(text) => updateEdition.mutateAsync({ volunteer_message: text })}
|
||||
/>
|
||||
</div>
|
||||
</Panel>
|
||||
|
||||
<Panel shadow="lg">
|
||||
<PanelHeader>{t('config.message.positionsTitle')}</PanelHeader>
|
||||
<div className="flex flex-col gap-3 p-[13px]">
|
||||
<p className="font-mono text-[10.5px] leading-relaxed opacity-70">
|
||||
{t('config.message.positionsHelp')}
|
||||
</p>
|
||||
<div className="flex flex-col border-2 border-ink">
|
||||
{(positions.data ?? []).map((position) => (
|
||||
<PositionMessageRow key={position.id} position={position} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Panel>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function PositionMessageRow({ position }: { position: PositionRead }) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const update = useUpdatePosition();
|
||||
return (
|
||||
<div className="border-b-2 border-ink last:border-b-0">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setOpen((o) => !o)}
|
||||
aria-expanded={open}
|
||||
className={cx(
|
||||
'flex w-full cursor-pointer items-baseline gap-2 px-[10px] py-2 text-left hover:bg-yellow-soft',
|
||||
open && 'bg-sand',
|
||||
)}
|
||||
>
|
||||
<span className="min-w-0 flex-1">
|
||||
<span className="block font-display text-[12.5px] font-extrabold">{position.name}</span>
|
||||
<span className="block truncate font-mono text-[10px] opacity-65">
|
||||
{position.message ?? t('config.message.none')}
|
||||
</span>
|
||||
</span>
|
||||
<span aria-hidden className="font-mono text-[10px]">
|
||||
{open ? '▲' : '▼'}
|
||||
</span>
|
||||
</button>
|
||||
{open ? (
|
||||
<div className="border-t-2 border-ink bg-paper p-[10px]">
|
||||
<MessageEditor
|
||||
value={position.message}
|
||||
updatedAt={position.message_updated_at}
|
||||
placeholder={t('mobileLead.message.placeholder')}
|
||||
saving={update.isPending}
|
||||
error={update.error}
|
||||
onSave={(text) => update.mutateAsync({ id: position.id, body: { message: text } })}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user