Vue par bénévole : tri par total d'heures
Tri des lignes par nom ou par total d'heures affectées, croissant ou décroissant (boutons « Trier par » et clic sur l'en-tête « TOTAL H ») ; à total égal, l'ordre par nom est conservé. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
ced0855d47
commit
4f86398ccb
@@ -981,6 +981,7 @@ Aucune dépendance locale requise hors Docker.
|
||||
| 50 | Badges : les créneaux affectés s'affichent en tableau — le jour de festival en en-tête, puis une colonne horaire et une colonne poste (le référent n'est plus imprimé sur le badge), filets à la couleur du champ, cellules repliées dans leur colonne ; même tableau dans l'aperçu de l'éditeur | à valider |
|
||||
| 51 | Refonte du créateur de badges d'après la maquette (`design/`) : champs sur le recto **et le verso** (`BadgeField.face`, verso en miroir dans le PDF), fond de zone, réduction automatique du texte, photo ovale, initiales sans photo, nom sur deux lignes (migration `0026`) ; éditeur en 3 colonnes (champs, aperçu recto/verso avec de vrais bénévoles, inspecteur), formats prédéfinis, annuler/flèches/zoom/repères, dépôt d'image sur l'aperçu, contrôle de tous les badges avant génération | à valider |
|
||||
| 52 | Nombre de badges par bénévole (`Volunteer.badge_count`, 1 par défaut, migration `0027`) : champ « Nombre de badges » sur la fiche, autant d'exemplaires sur les planches, total à imprimer affiché à la génération, colonne dans l'export Excel | à valider |
|
||||
| 53 | Vue par bénévole : tri des lignes par nom ou par total d'heures affectées, croissant / décroissant (boutons « Trier par » et clic sur l'en-tête « TOTAL H ») — à total égal, l'ordre par nom est conservé | à valider |
|
||||
|
||||
**Reste hors périmètre** (modèle prêt à les accueillir) : SMS, constructeur
|
||||
de formulaire, pointage sur site, IA. Frontend : squelette + 6 écrans +
|
||||
|
||||
@@ -109,6 +109,11 @@
|
||||
"volunteers.legend.assigned": "AFFECTATION",
|
||||
"volunteers.count": "{count} BÉNÉVOLES",
|
||||
"volunteers.totalHours": "TOTAL H",
|
||||
"volunteers.sort.label": "TRIER PAR",
|
||||
"volunteers.sort.name": "NOM",
|
||||
"volunteers.sort.hoursAsc": "HEURES ↑",
|
||||
"volunteers.sort.hoursDesc": "HEURES ↓",
|
||||
"volunteers.sort.toggle": "Trier par total d'heures (croissant / décroissant)",
|
||||
"volunteers.name": "NOM",
|
||||
"volunteers.cumulative": "CUMUL",
|
||||
"volunteers.badge.unassigned": "○ OUBLIÉ",
|
||||
|
||||
@@ -10,6 +10,9 @@ import { t } from '@/i18n';
|
||||
import { cx } from '@/lib/cx';
|
||||
|
||||
type Filter = 'all' | 'unassigned';
|
||||
/** Ordre des lignes : celui de la liste des bénévoles (par nom), ou par
|
||||
* total d'heures affectées, croissant ou décroissant. */
|
||||
type Sort = 'name' | 'hoursAsc' | 'hoursDesc';
|
||||
|
||||
export function VolunteerViewPage() {
|
||||
const volunteers = useVolunteers();
|
||||
@@ -18,6 +21,7 @@ export function VolunteerViewPage() {
|
||||
const positions = usePositions();
|
||||
const dayParts = useDayParts();
|
||||
const [filter, setFilter] = useState<Filter>('all');
|
||||
const [sort, setSort] = useState<Sort>('name');
|
||||
|
||||
const shiftsById = useMemo(
|
||||
() => new Map((shifts.data ?? []).map((s) => [s.id, s])),
|
||||
@@ -45,7 +49,7 @@ export function VolunteerViewPage() {
|
||||
list.push(a);
|
||||
byVolunteer.set(a.volunteer_id, list);
|
||||
}
|
||||
return (volunteers.data ?? [])
|
||||
const built = (volunteers.data ?? [])
|
||||
.filter((v) => {
|
||||
const own = byVolunteer.get(v.id) ?? [];
|
||||
if (filter === 'unassigned') return own.length === 0;
|
||||
@@ -69,7 +73,11 @@ export function VolunteerViewPage() {
|
||||
empty: own.length === 0,
|
||||
};
|
||||
});
|
||||
}, [volunteers.data, assignments.data, shiftsById, hours, filter]);
|
||||
if (sort === 'name') return built;
|
||||
const direction = sort === 'hoursAsc' ? 1 : -1;
|
||||
// `sort` est stable : à total égal, l'ordre par nom est conservé.
|
||||
return built.sort((a, b) => direction * (a.total - b.total));
|
||||
}, [volunteers.data, assignments.data, shiftsById, hours, filter, sort]);
|
||||
|
||||
if (
|
||||
volunteers.isLoading ||
|
||||
@@ -104,6 +112,18 @@ export function VolunteerViewPage() {
|
||||
{t('volunteers.filter.unassigned')}
|
||||
</Chip>
|
||||
</div>
|
||||
<div className="flex items-center gap-[6px]">
|
||||
<span className="font-mono text-[9px] opacity-60">{t('volunteers.sort.label')}</span>
|
||||
<Chip active={sort === 'name'} onClick={() => setSort('name')}>
|
||||
{t('volunteers.sort.name')}
|
||||
</Chip>
|
||||
<Chip active={sort === 'hoursAsc'} onClick={() => setSort('hoursAsc')}>
|
||||
{t('volunteers.sort.hoursAsc')}
|
||||
</Chip>
|
||||
<Chip active={sort === 'hoursDesc'} onClick={() => setSort('hoursDesc')}>
|
||||
{t('volunteers.sort.hoursDesc')}
|
||||
</Chip>
|
||||
</div>
|
||||
<div className="ml-auto flex items-center gap-3 border-2 border-ink bg-cream px-[9px] py-[5px]">
|
||||
<span className="font-mono text-[8.5px] opacity-60">{t('legend.title')}</span>
|
||||
<LegendItem swatch="bg-blue border-ink">{t('volunteers.legend.assigned')}</LegendItem>
|
||||
@@ -130,9 +150,15 @@ export function VolunteerViewPage() {
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
<div className="sticky right-0 top-0 z-[7] flex items-center border-b-2 border-l-[3px] border-ink bg-yellow px-2 py-[6px] font-display text-[10px] font-extrabold uppercase tracking-wider">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSort(sort === 'hoursDesc' ? 'hoursAsc' : 'hoursDesc')}
|
||||
title={t('volunteers.sort.toggle')}
|
||||
className="sticky right-0 top-0 z-[7] flex cursor-pointer items-center gap-1 border-b-2 border-l-[3px] border-ink bg-yellow px-2 py-[6px] font-display text-[10px] font-extrabold uppercase tracking-wider hover:bg-yellow-soft"
|
||||
>
|
||||
{t('volunteers.totalHours')}
|
||||
</div>
|
||||
{sort === 'hoursAsc' ? ' ↑' : sort === 'hoursDesc' ? ' ↓' : null}
|
||||
</button>
|
||||
|
||||
<div className="sticky left-0 top-[27px] z-[7] flex items-center border-b-[3px] border-r-[3px] border-ink bg-sand px-2 font-mono text-[8.5px]">
|
||||
{t('volunteers.name')}
|
||||
|
||||
Reference in New Issue
Block a user