Front : les indications d'usage passent en bulle d'aide (icône « ? »)
Les explications sur comment utiliser un écran (grille des besoins, vue
bénévoles, artistes, import, disponibilités, créneaux types, seuils,
notification par email…) étaient affichées en permanence dans la page,
prenant de la place pour un contenu qu'on ne lit qu'une fois. Elles passent
dans une bulle affichée au survol (ou au focus clavier/tactile) d'un petit
repère « ? » posé à côté du titre ou du champ concerné.
- `HelpTip` (frontend/src/components/ui.tsx) : nouveau composant partagé,
cohérent avec le design (bordures franches, pas d'arrondi) ; `side`
('top'/'bottom') pour éviter que la bulle ne sorte du cadre visible selon
que le repère est en haut ou en bas de son conteneur.
- Appliqué à GrillePage, VolunteerViewPage, ConfigArtists, ConfigVolunteers
(import, ajout manuel, disponibilités), ConfigGrid, ConfigRules, ConfigMail.
- Aucun texte déplacé n'a changé : mêmes clés i18n, seul l'endroit où elles
s'affichent change.
Laissé en l'état (pas des indications d'usage mais des messages d'état) :
bannières identifiants de démo, liens de connexion en dev, états vides, et
la description des rôles (contenu propre des cartes, pas un mode d'emploi).
Vérifié : typecheck, lint, build, smoke (7 routes) OK.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
897a76ca6d
commit
fb6e26eddb
@@ -182,6 +182,41 @@ export function StatCard({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Petit repère « ? » : au survol (ou au focus clavier/tactile), affiche
|
||||||
|
* `text` dans une bulle. Sert à sortir les indications d'usage du flux de la
|
||||||
|
* page plutôt que de les y afficher en permanence.
|
||||||
|
*/
|
||||||
|
export function HelpTip({
|
||||||
|
text,
|
||||||
|
side = 'top',
|
||||||
|
className,
|
||||||
|
}: {
|
||||||
|
text: string;
|
||||||
|
side?: 'top' | 'bottom';
|
||||||
|
className?: string;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<span className={cx('group relative inline-flex align-middle', className)}>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="flex h-[15px] w-[15px] flex-none cursor-help items-center justify-center border-[1.5px] border-ink bg-cream font-mono text-[9px] font-bold leading-none text-ink hover:bg-yellow focus-visible:bg-yellow focus-visible:outline-none"
|
||||||
|
aria-label={text}
|
||||||
|
>
|
||||||
|
?
|
||||||
|
</button>
|
||||||
|
<span
|
||||||
|
className={cx(
|
||||||
|
'pointer-events-none absolute left-1/2 z-20 w-max max-w-[260px] -translate-x-1/2 border-2 border-ink bg-ink px-[9px] py-[7px] font-mono text-[10px] font-normal normal-case leading-relaxed tracking-normal text-cream opacity-0 shadow-brutal-md transition-opacity duration-100 group-hover:opacity-100 group-focus-within:opacity-100',
|
||||||
|
side === 'top' ? 'bottom-full mb-[6px]' : 'top-full mt-[6px]',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{text}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function FieldLabel({ children }: { children: ReactNode }) {
|
export function FieldLabel({ children }: { children: ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<span className="mb-[5px] block font-display text-[9px] font-extrabold uppercase tracking-widest">
|
<span className="mb-[5px] block font-display text-[9px] font-extrabold uppercase tracking-widest">
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useMemo, useState } from 'react';
|
|||||||
|
|
||||||
import { useMatrix, useRunSolver } from '@/api/hooks';
|
import { useMatrix, useRunSolver } from '@/api/hooks';
|
||||||
import type { MatrixPosition, MatrixShift, PositionCategory } from '@/api/schema';
|
import type { MatrixPosition, MatrixShift, PositionCategory } from '@/api/schema';
|
||||||
import { Chip } from '@/components/ui';
|
import { Chip, HelpTip } from '@/components/ui';
|
||||||
import { EmptyNote, ErrorNote, Loading } from '@/components/state';
|
import { EmptyNote, ErrorNote, Loading } from '@/components/state';
|
||||||
import { AssignmentPanel } from '@/pages/grille/AssignmentPanel';
|
import { AssignmentPanel } from '@/pages/grille/AssignmentPanel';
|
||||||
import { categoryLabel, columnWidthPx, latticeFromMatrix, type LatticeColumn } from '@/lib/planning';
|
import { categoryLabel, columnWidthPx, latticeFromMatrix, type LatticeColumn } from '@/lib/planning';
|
||||||
@@ -132,8 +132,9 @@ export function GrillePage() {
|
|||||||
<div className="flex min-h-0 flex-1 items-stretch">
|
<div className="flex min-h-0 flex-1 items-stretch">
|
||||||
<div className="max-h-[calc(100vh-118px)] min-w-0 flex-1 overflow-auto">
|
<div className="max-h-[calc(100vh-118px)] min-w-0 flex-1 overflow-auto">
|
||||||
<div className="grid w-max border-b-[3px] border-ink" style={{ gridTemplateColumns }}>
|
<div className="grid w-max border-b-[3px] border-ink" style={{ gridTemplateColumns }}>
|
||||||
<div className="sticky left-0 top-0 z-[6] flex items-center border-b-2 border-r-[3px] border-ink border-b-cream bg-ink px-2 py-[6px] font-display text-[10px] font-extrabold uppercase tracking-wider text-cream">
|
<div className="sticky left-0 top-0 z-[6] flex items-center gap-[6px] border-b-2 border-r-[3px] border-ink border-b-cream bg-ink px-2 py-[6px] font-display text-[10px] font-extrabold uppercase tracking-wider text-cream">
|
||||||
{t('grille.positionsCount', { count: matrix.data.positions.length })}
|
{t('grille.positionsCount', { count: matrix.data.positions.length })}
|
||||||
|
<HelpTip text={t('grille.footnote')} side="bottom" />
|
||||||
</div>
|
</div>
|
||||||
{shownDays.map((day) => (
|
{shownDays.map((day) => (
|
||||||
<div
|
<div
|
||||||
@@ -169,9 +170,6 @@ export function GrillePage() {
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className="px-[14px] py-[10px] font-mono text-[9.5px] leading-relaxed opacity-60">
|
|
||||||
{t('grille.footnote')}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<aside className="max-h-[calc(100vh-118px)] w-[392px] flex-none overflow-auto border-l-[3px] border-ink bg-paper">
|
<aside className="max-h-[calc(100vh-118px)] w-[392px] flex-none overflow-auto border-l-[3px] border-ink bg-paper">
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useMemo, useState } from 'react';
|
|||||||
|
|
||||||
import { useAssignments, usePositions, useShifts, useVolunteers } from '@/api/hooks';
|
import { useAssignments, usePositions, useShifts, useVolunteers } from '@/api/hooks';
|
||||||
import type { AssignmentRead, ShiftRead, VolunteerRead } from '@/api/schema';
|
import type { AssignmentRead, ShiftRead, VolunteerRead } from '@/api/schema';
|
||||||
import { Chip } from '@/components/ui';
|
import { Chip, HelpTip } from '@/components/ui';
|
||||||
import { EmptyNote, ErrorNote, Loading } from '@/components/state';
|
import { EmptyNote, ErrorNote, Loading } from '@/components/state';
|
||||||
import { buildLattice, hoursByVolunteer, isOverloaded } from '@/lib/planning';
|
import { buildLattice, hoursByVolunteer, isOverloaded } from '@/lib/planning';
|
||||||
import { datePart, shortDayLabel, timePart } from '@/lib/time';
|
import { datePart, shortDayLabel, timePart } from '@/lib/time';
|
||||||
@@ -118,8 +118,9 @@ export function VolunteerViewPage() {
|
|||||||
<EmptyNote>{t('volunteers.empty')}</EmptyNote>
|
<EmptyNote>{t('volunteers.empty')}</EmptyNote>
|
||||||
) : (
|
) : (
|
||||||
<div className="grid w-max border-b-[3px] border-ink" style={{ gridTemplateColumns }}>
|
<div className="grid w-max border-b-[3px] border-ink" style={{ gridTemplateColumns }}>
|
||||||
<div className="sticky left-0 top-0 z-[7] flex items-center border-b-2 border-r-[3px] border-ink border-b-cream bg-ink px-2 py-[6px] font-display text-[10px] font-extrabold uppercase tracking-wider text-cream">
|
<div className="sticky left-0 top-0 z-[7] flex items-center gap-[6px] border-b-2 border-r-[3px] border-ink border-b-cream bg-ink px-2 py-[6px] font-display text-[10px] font-extrabold uppercase tracking-wider text-cream">
|
||||||
{t('volunteers.count', { count: rows.length })}
|
{t('volunteers.count', { count: rows.length })}
|
||||||
|
<HelpTip text={t('volunteers.footnote')} side="bottom" />
|
||||||
</div>
|
</div>
|
||||||
{lattice.days.map((day) => (
|
{lattice.days.map((day) => (
|
||||||
<div
|
<div
|
||||||
@@ -161,12 +162,6 @@ export function VolunteerViewPage() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{rows.length > 0 ? (
|
|
||||||
<div className="px-[14px] py-[10px] font-mono text-[9.5px] leading-relaxed opacity-60">
|
|
||||||
{t('volunteers.footnote')}
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
} from '@/api/hooks';
|
} from '@/api/hooks';
|
||||||
import type { ArtistRead } from '@/api/schema';
|
import type { ArtistRead } from '@/api/schema';
|
||||||
import { ErrorNote, Loading } from '@/components/state';
|
import { ErrorNote, Loading } from '@/components/state';
|
||||||
import { Panel, PanelHeader } from '@/components/ui';
|
import { HelpTip, Panel, PanelHeader } from '@/components/ui';
|
||||||
import { t } from '@/i18n';
|
import { t } from '@/i18n';
|
||||||
import { longDayLabel } from '@/lib/time';
|
import { longDayLabel } from '@/lib/time';
|
||||||
|
|
||||||
@@ -62,9 +62,9 @@ export function ConfigArtists() {
|
|||||||
<div className="max-h-[calc(100vh-118px)] min-h-0 flex-1 overflow-auto p-4">
|
<div className="max-h-[calc(100vh-118px)] min-h-0 flex-1 overflow-auto p-4">
|
||||||
<div className="mb-[14px] flex flex-wrap items-stretch gap-3">
|
<div className="mb-[14px] flex flex-wrap items-stretch gap-3">
|
||||||
<div className="flex flex-1 flex-col justify-center gap-1 border-[3px] border-ink bg-paper p-[14px] shadow-brutal-lg">
|
<div className="flex flex-1 flex-col justify-center gap-1 border-[3px] border-ink bg-paper p-[14px] shadow-brutal-lg">
|
||||||
<span className="font-display text-[15px] font-black">{t('config.artists.title')}</span>
|
<span className="flex items-center gap-[7px] font-display text-[15px] font-black">
|
||||||
<span className="font-mono text-[10px] leading-relaxed opacity-70">
|
{t('config.artists.title')}
|
||||||
{t('config.artists.hint')}
|
<HelpTip text={t('config.artists.hint')} side="bottom" />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-none border-[3px] border-ink bg-paper px-[14px] py-3 shadow-brutal-lg">
|
<div className="flex-none border-[3px] border-ink bg-paper px-[14px] py-3 shadow-brutal-lg">
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
} from '@/api/hooks';
|
} from '@/api/hooks';
|
||||||
import type { PositionCategory, PositionRead, ShiftRead } from '@/api/schema';
|
import type { PositionCategory, PositionRead, ShiftRead } from '@/api/schema';
|
||||||
import { ErrorNote, Loading } from '@/components/state';
|
import { ErrorNote, Loading } from '@/components/state';
|
||||||
import { Btn } from '@/components/ui';
|
import { Btn, HelpTip } from '@/components/ui';
|
||||||
import { categoryLabel } from '@/lib/planning';
|
import { categoryLabel } from '@/lib/planning';
|
||||||
import { hourRangeLabel, hoursBetween, longDayLabel } from '@/lib/time';
|
import { hourRangeLabel, hoursBetween, longDayLabel } from '@/lib/time';
|
||||||
import { t } from '@/i18n';
|
import { t } from '@/i18n';
|
||||||
@@ -95,8 +95,9 @@ export function ConfigGrid() {
|
|||||||
return (
|
return (
|
||||||
<div className="flex min-h-0 flex-1 items-stretch">
|
<div className="flex min-h-0 flex-1 items-stretch">
|
||||||
<div className="max-h-[calc(100vh-118px)] w-[250px] flex-none overflow-auto border-r-[3px] border-ink">
|
<div className="max-h-[calc(100vh-118px)] w-[250px] flex-none overflow-auto border-r-[3px] border-ink">
|
||||||
<div className="sticky top-0 z-[2] bg-ink px-[10px] py-[7px] font-display text-[10px] font-extrabold uppercase tracking-wider text-cream">
|
<div className="sticky top-0 z-[2] flex items-center gap-[6px] bg-ink px-[10px] py-[7px] font-display text-[10px] font-extrabold uppercase tracking-wider text-cream">
|
||||||
{t('config.grid.positionsHeader', { count: totals.count })}
|
{t('config.grid.positionsHeader', { count: totals.count })}
|
||||||
|
<HelpTip text={t('config.grid.note')} side="bottom" />
|
||||||
</div>
|
</div>
|
||||||
{(positions.data ?? []).map((entry) => {
|
{(positions.data ?? []).map((entry) => {
|
||||||
const active = entry.id === position.id;
|
const active = entry.id === position.id;
|
||||||
@@ -197,10 +198,6 @@ export function ConfigGrid() {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-[14px] max-w-[760px] border-[3px] border-dashed border-ink p-[11px] font-mono text-[10.5px] leading-relaxed opacity-75">
|
|
||||||
{t('config.grid.note')}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useState } from 'react';
|
|||||||
|
|
||||||
import { useSendPlanningNotification } from '@/api/hooks';
|
import { useSendPlanningNotification } from '@/api/hooks';
|
||||||
import { ErrorNote } from '@/components/state';
|
import { ErrorNote } from '@/components/state';
|
||||||
import { Btn, FieldLabel, Panel, PanelHeader } from '@/components/ui';
|
import { Btn, FieldLabel, HelpTip, Panel, PanelHeader } from '@/components/ui';
|
||||||
import { t } from '@/i18n';
|
import { t } from '@/i18n';
|
||||||
|
|
||||||
const MAIL_VARIABLES = [
|
const MAIL_VARIABLES = [
|
||||||
@@ -32,18 +32,16 @@ export function ConfigMail() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<div className="mb-4 border-[3px] border-dashed border-ink bg-yellow-soft p-3 font-display text-[12px] font-bold">
|
|
||||||
{t('config.mail.smtpBanner')}
|
|
||||||
</div>
|
|
||||||
<div className="grid items-start gap-4 [grid-template-columns:repeat(auto-fit,minmax(320px,1fr))]">
|
<div className="grid items-start gap-4 [grid-template-columns:repeat(auto-fit,minmax(320px,1fr))]">
|
||||||
<Panel shadow="lg">
|
<Panel shadow="lg">
|
||||||
<PanelHeader>{t('config.mail.templateTitle')}</PanelHeader>
|
<PanelHeader>
|
||||||
|
{t('config.mail.templateTitle')} <HelpTip text={t('config.mail.smtpBanner')} side="bottom" />
|
||||||
|
</PanelHeader>
|
||||||
<div className="flex flex-col gap-3 p-[13px]">
|
<div className="flex flex-col gap-3 p-[13px]">
|
||||||
<div className="font-mono text-[10px] leading-relaxed opacity-60">
|
|
||||||
{t('config.mail.senderNote')}
|
|
||||||
</div>
|
|
||||||
<label className="block">
|
<label className="block">
|
||||||
<FieldLabel>{t('config.mail.subject')}</FieldLabel>
|
<FieldLabel>
|
||||||
|
{t('config.mail.subject')} <HelpTip text={t('config.mail.senderNote')} />
|
||||||
|
</FieldLabel>
|
||||||
<input
|
<input
|
||||||
value={subject}
|
value={subject}
|
||||||
onChange={(e) => setSubject(e.target.value)}
|
onChange={(e) => setSubject(e.target.value)}
|
||||||
@@ -60,7 +58,9 @@ export function ConfigMail() {
|
|||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<div>
|
<div>
|
||||||
<FieldLabel>{t('config.mail.variables')}</FieldLabel>
|
<FieldLabel>
|
||||||
|
{t('config.mail.variables')} <HelpTip text={t('config.mail.variablesHint')} />
|
||||||
|
</FieldLabel>
|
||||||
<div className="flex flex-wrap gap-[6px]">
|
<div className="flex flex-wrap gap-[6px]">
|
||||||
{MAIL_VARIABLES.map((variable) => (
|
{MAIL_VARIABLES.map((variable) => (
|
||||||
<button
|
<button
|
||||||
@@ -73,9 +73,6 @@ export function ConfigMail() {
|
|||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-1 font-mono text-[9.5px] opacity-60">
|
|
||||||
{t('config.mail.variablesHint')}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-wrap gap-2 border-t-2 border-ink pt-3">
|
<div className="flex flex-wrap gap-2 border-t-2 border-ink pt-3">
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
useVolunteers,
|
useVolunteers,
|
||||||
} from '@/api/hooks';
|
} from '@/api/hooks';
|
||||||
import { ErrorNote } from '@/components/state';
|
import { ErrorNote } from '@/components/state';
|
||||||
import { Btn, Panel, PanelHeader } from '@/components/ui';
|
import { Btn, HelpTip, Panel, PanelHeader } from '@/components/ui';
|
||||||
import { t } from '@/i18n';
|
import { t } from '@/i18n';
|
||||||
|
|
||||||
const hm = (time: string) => time.slice(0, 5); // "HH:MM:SS" -> "HH:MM"
|
const hm = (time: string) => time.slice(0, 5); // "HH:MM:SS" -> "HH:MM"
|
||||||
@@ -48,23 +48,22 @@ export function ConfigRules() {
|
|||||||
</Panel>
|
</Panel>
|
||||||
|
|
||||||
<Panel shadow="lg">
|
<Panel shadow="lg">
|
||||||
<PanelHeader>{t('config.rules.slotsTitle')}</PanelHeader>
|
<PanelHeader>
|
||||||
|
{t('config.rules.slotsTitle')} <HelpTip text={t('config.rules.slotsNote')} side="bottom" />
|
||||||
|
</PanelHeader>
|
||||||
<div className="flex flex-col gap-2 p-[13px]">
|
<div className="flex flex-col gap-2 p-[13px]">
|
||||||
<DayPartsForm />
|
<DayPartsForm />
|
||||||
<div className="mt-1 border-t-2 border-ink pt-2 font-mono text-[10px] leading-relaxed opacity-70">
|
|
||||||
{t('config.rules.slotsNote')}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</Panel>
|
</Panel>
|
||||||
|
|
||||||
<Panel shadow="lg">
|
<Panel shadow="lg">
|
||||||
<PanelHeader>{t('config.rules.thresholdsTitle')}</PanelHeader>
|
<PanelHeader>
|
||||||
|
{t('config.rules.thresholdsTitle')}{' '}
|
||||||
|
<HelpTip text={t('config.rules.thresholdsNote')} side="bottom" />
|
||||||
|
</PanelHeader>
|
||||||
<div className="flex flex-col gap-2 p-[13px]">
|
<div className="flex flex-col gap-2 p-[13px]">
|
||||||
<InfoRow label={t('config.rules.maxDaily')} value="8 h" />
|
<InfoRow label={t('config.rules.maxDaily')} value="8 h" />
|
||||||
<InfoRow label={t('config.rules.minRest')} value="8 h" />
|
<InfoRow label={t('config.rules.minRest')} value="8 h" />
|
||||||
<div className="mt-1 font-mono text-[10px] leading-relaxed opacity-70">
|
|
||||||
{t('config.rules.thresholdsNote')}
|
|
||||||
</div>
|
|
||||||
<div className="mt-2 border-t-2 border-ink pt-2">
|
<div className="mt-2 border-t-2 border-ink pt-2">
|
||||||
<div className="mb-1 font-display text-[9px] font-extrabold uppercase tracking-widest">
|
<div className="mb-1 font-display text-[9px] font-extrabold uppercase tracking-widest">
|
||||||
{t('config.rules.rulesTitle')}
|
{t('config.rules.rulesTitle')}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import type {
|
|||||||
PositionRead,
|
PositionRead,
|
||||||
VolunteerCreate,
|
VolunteerCreate,
|
||||||
} from '@/api/schema';
|
} from '@/api/schema';
|
||||||
import { FieldLabel, TextInput } from '@/components/ui';
|
import { FieldLabel, HelpTip, TextInput } from '@/components/ui';
|
||||||
import { ErrorNote, Loading } from '@/components/state';
|
import { ErrorNote, Loading } from '@/components/state';
|
||||||
import { t } from '@/i18n';
|
import { t } from '@/i18n';
|
||||||
import { shortDayLabel } from '@/lib/time';
|
import { shortDayLabel } from '@/lib/time';
|
||||||
@@ -183,11 +183,9 @@ export function ConfigVolunteers() {
|
|||||||
<div className="mb-[14px] flex flex-wrap items-stretch gap-3">
|
<div className="mb-[14px] flex flex-wrap items-stretch gap-3">
|
||||||
<div className="flex flex-1 flex-col justify-center gap-[10px] border-[3px] border-ink bg-paper p-[14px] shadow-brutal-lg">
|
<div className="flex flex-1 flex-col justify-center gap-[10px] border-[3px] border-ink bg-paper p-[14px] shadow-brutal-lg">
|
||||||
<div>
|
<div>
|
||||||
<span className="block font-display text-[15px] font-black">
|
<span className="flex items-center gap-[7px] font-display text-[15px] font-black">
|
||||||
{t('config.vol.importTitle')}
|
{t('config.vol.importTitle')}
|
||||||
</span>
|
<HelpTip text={t('config.vol.importHint')} side="bottom" />
|
||||||
<span className="block font-mono text-[10px] leading-relaxed opacity-70">
|
|
||||||
{t('config.vol.importHint')}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap gap-[10px]">
|
<div className="flex flex-wrap gap-[10px]">
|
||||||
@@ -292,7 +290,10 @@ export function ConfigVolunteers() {
|
|||||||
<aside className="max-h-[calc(100vh-118px)] w-[360px] flex-none overflow-auto border-l-[3px] border-ink bg-paper">
|
<aside className="max-h-[calc(100vh-118px)] w-[360px] flex-none overflow-auto border-l-[3px] border-ink bg-paper">
|
||||||
<div className="bg-ink px-[13px] py-[11px] text-cream">
|
<div className="bg-ink px-[13px] py-[11px] text-cream">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="font-mono text-[10px] text-yellow">{t('config.vol.ficheKicker')}</div>
|
<div className="flex items-center gap-[6px] font-mono text-[10px] text-yellow">
|
||||||
|
{t('config.vol.ficheKicker')}
|
||||||
|
<HelpTip text={t('config.vol.note')} side="bottom" />
|
||||||
|
</div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setSelectedId(null)}
|
onClick={() => setSelectedId(null)}
|
||||||
@@ -372,9 +373,6 @@ export function ConfigVolunteers() {
|
|||||||
>
|
>
|
||||||
{t('config.vol.delete')}
|
{t('config.vol.delete')}
|
||||||
</button>
|
</button>
|
||||||
<div className="font-mono text-[10px] leading-relaxed opacity-65">
|
|
||||||
{t('config.vol.note')}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -426,7 +424,10 @@ function AddVolunteerForm({ onClose }: { onClose: () => void }) {
|
|||||||
<div className="max-h-[calc(100vh-118px)] min-h-0 flex-1 overflow-auto p-4">
|
<div className="max-h-[calc(100vh-118px)] min-h-0 flex-1 overflow-auto p-4">
|
||||||
<div className="mx-auto max-w-[560px] border-[3px] border-ink bg-paper shadow-brutal-lg">
|
<div className="mx-auto max-w-[560px] border-[3px] border-ink bg-paper shadow-brutal-lg">
|
||||||
<div className="flex items-center justify-between bg-ink px-[14px] py-[11px] text-cream">
|
<div className="flex items-center justify-between bg-ink px-[14px] py-[11px] text-cream">
|
||||||
<span className="font-display text-[14px] font-black">{t('config.vol.addTitle')}</span>
|
<span className="flex items-center gap-[7px] font-display text-[14px] font-black">
|
||||||
|
{t('config.vol.addTitle')}
|
||||||
|
<HelpTip text={t('config.vol.addAvailabilityHint')} side="bottom" />
|
||||||
|
</span>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
@@ -442,9 +443,6 @@ function AddVolunteerForm({ onClose }: { onClose: () => void }) {
|
|||||||
}}
|
}}
|
||||||
className="flex flex-col gap-3 p-[14px]"
|
className="flex flex-col gap-3 p-[14px]"
|
||||||
>
|
>
|
||||||
<div className="font-mono text-[10px] leading-relaxed opacity-65">
|
|
||||||
{t('config.vol.addHint')}
|
|
||||||
</div>
|
|
||||||
<div className="grid grid-cols-2 gap-2">
|
<div className="grid grid-cols-2 gap-2">
|
||||||
<label className="block">
|
<label className="block">
|
||||||
<FieldLabel>{t('config.vol.field.firstName')} *</FieldLabel>
|
<FieldLabel>{t('config.vol.field.firstName')} *</FieldLabel>
|
||||||
@@ -456,7 +454,9 @@ function AddVolunteerForm({ onClose }: { onClose: () => void }) {
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<label className="block">
|
<label className="block">
|
||||||
<FieldLabel>{t('config.vol.field.email')}</FieldLabel>
|
<FieldLabel>
|
||||||
|
{t('config.vol.field.email')} <HelpTip text={t('config.vol.addHint')} />
|
||||||
|
</FieldLabel>
|
||||||
<TextInput mono value={form.email ?? ''} onChange={(v) => set('email', v)} />
|
<TextInput mono value={form.email ?? ''} onChange={(v) => set('email', v)} />
|
||||||
</label>
|
</label>
|
||||||
<label className="block">
|
<label className="block">
|
||||||
@@ -490,10 +490,6 @@ function AddVolunteerForm({ onClose }: { onClose: () => void }) {
|
|||||||
addLabel={t('config.vol.pickPosition')}
|
addLabel={t('config.vol.pickPosition')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="font-mono text-[10px] leading-relaxed opacity-60">
|
|
||||||
{t('config.vol.addAvailabilityHint')}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{localError ? (
|
{localError ? (
|
||||||
<div className="border-2 border-ink bg-orange p-[9px] font-display text-[11px] font-bold">
|
<div className="border-2 border-ink bg-orange p-[9px] font-display text-[11px] font-bold">
|
||||||
{localError}
|
{localError}
|
||||||
@@ -586,7 +582,10 @@ function ImportWizard({ onClose }: { onClose: () => void }) {
|
|||||||
<div className="max-h-[calc(100vh-118px)] min-h-0 flex-1 overflow-auto p-4">
|
<div className="max-h-[calc(100vh-118px)] min-h-0 flex-1 overflow-auto p-4">
|
||||||
<div className="mx-auto max-w-[820px] border-[3px] border-ink bg-paper shadow-brutal-lg">
|
<div className="mx-auto max-w-[820px] border-[3px] border-ink bg-paper shadow-brutal-lg">
|
||||||
<div className="flex items-center justify-between bg-ink px-[14px] py-[11px] text-cream">
|
<div className="flex items-center justify-between bg-ink px-[14px] py-[11px] text-cream">
|
||||||
<span className="font-display text-[14px] font-black">{t('config.vol.wiz.title')}</span>
|
<span className="flex items-center gap-[7px] font-display text-[14px] font-black">
|
||||||
|
{t('config.vol.wiz.title')}
|
||||||
|
{step === 1 ? <HelpTip text={t('config.vol.wiz.fileHint')} side="bottom" /> : null}
|
||||||
|
</span>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
@@ -638,9 +637,6 @@ function ImportWizard({ onClose }: { onClose: () => void }) {
|
|||||||
{file ? (
|
{file ? (
|
||||||
<div className="font-mono text-[11px] opacity-70">{file.name}</div>
|
<div className="font-mono text-[11px] opacity-70">{file.name}</div>
|
||||||
) : null}
|
) : null}
|
||||||
<div className="font-mono text-[10px] leading-relaxed opacity-65">
|
|
||||||
{t('config.vol.wiz.fileHint')}
|
|
||||||
</div>
|
|
||||||
{analyze.error ? <ErrorNote error={analyze.error} /> : null}
|
{analyze.error ? <ErrorNote error={analyze.error} /> : null}
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -656,10 +652,9 @@ function ImportWizard({ onClose }: { onClose: () => void }) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<FieldLabel>{t('config.vol.wiz.mapTitle')}</FieldLabel>
|
<FieldLabel>
|
||||||
<div className="mb-1 font-mono text-[10px] leading-relaxed opacity-65">
|
{t('config.vol.wiz.mapTitle')} <HelpTip text={t('config.vol.wiz.mapHint')} />
|
||||||
{t('config.vol.wiz.mapHint')}
|
</FieldLabel>
|
||||||
</div>
|
|
||||||
<div className="grid gap-2 sm:grid-cols-2">
|
<div className="grid gap-2 sm:grid-cols-2">
|
||||||
{MAP_FIELDS.map(({ key, required }) => (
|
{MAP_FIELDS.map(({ key, required }) => (
|
||||||
<label key={key} className="block">
|
<label key={key} className="block">
|
||||||
@@ -912,10 +907,9 @@ function AvailabilityEditor({ volunteerId }: { volunteerId: string }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<FieldLabel>{t('config.vol.avail.title')}</FieldLabel>
|
<FieldLabel>
|
||||||
<div className="mb-2 font-mono text-[10px] leading-relaxed opacity-60">
|
{t('config.vol.avail.title')} <HelpTip text={t('config.vol.avail.hint')} />
|
||||||
{t('config.vol.avail.hint')}
|
</FieldLabel>
|
||||||
</div>
|
|
||||||
<div className="flex flex-col gap-[6px]">
|
<div className="flex flex-col gap-[6px]">
|
||||||
{rows.map((row, i) => (
|
{rows.map((row, i) => (
|
||||||
<div key={i} className="flex flex-wrap items-center gap-1">
|
<div key={i} className="flex flex-wrap items-center gap-1">
|
||||||
|
|||||||
Reference in New Issue
Block a user