Grille & vue bénévole : message quand il n'y a pas de données
- components/state : composant EmptyNote (encadré pointillés). - GrillePage : si aucun créneau, message au lieu de la grille vide. - VolunteerViewPage : si aucun bénévole, message ; si un filtre ne renvoie rien, message à la place du tableau (avant, l'en-tête collant restait affiché). typecheck + lint + build + smoke 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
385c46c241
commit
06f8736c33
@@ -3,7 +3,7 @@ import { useMemo, useState } from 'react';
|
||||
import { useMatrix } from '@/api/hooks';
|
||||
import type { MatrixPosition, MatrixShift, PositionCategory } from '@/api/schema';
|
||||
import { Chip } from '@/components/ui';
|
||||
import { ErrorNote, Loading } from '@/components/state';
|
||||
import { EmptyNote, ErrorNote, Loading } from '@/components/state';
|
||||
import { AssignmentPanel } from '@/pages/grille/AssignmentPanel';
|
||||
import { categoryLabel, latticeFromMatrix, type LatticeColumn } from '@/lib/planning';
|
||||
import { datePart, shortDayLabel, timePart } from '@/lib/time';
|
||||
@@ -25,6 +25,15 @@ export function GrillePage() {
|
||||
if (matrix.error) return <ErrorNote error={matrix.error} />;
|
||||
if (!matrix.data) return null;
|
||||
|
||||
const hasShifts = matrix.data.positions.some((p) => p.shifts.length > 0);
|
||||
if (!hasShifts) {
|
||||
return (
|
||||
<div className="flex min-h-0 flex-1 flex-col">
|
||||
<EmptyNote>{t('grille.empty')}</EmptyNote>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const categories = [...new Set(matrix.data.positions.map((p) => p.category))].sort();
|
||||
const shownDays = dayFilter === 'all' ? lattice.days : lattice.days.filter((d) => d.date === dayFilter);
|
||||
const shownColumns = shownDays.flatMap((d) => d.columns);
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useMemo, useState } from 'react';
|
||||
import { useAssignments, usePositions, useShifts, useVolunteers } from '@/api/hooks';
|
||||
import type { AssignmentRead, ShiftRead, VolunteerRead } from '@/api/schema';
|
||||
import { Chip } from '@/components/ui';
|
||||
import { ErrorNote, Loading } from '@/components/state';
|
||||
import { EmptyNote, ErrorNote, Loading } from '@/components/state';
|
||||
import { buildLattice, hoursByVolunteer, isOverloaded } from '@/lib/planning';
|
||||
import { datePart, shortDayLabel, timePart } from '@/lib/time';
|
||||
import { t } from '@/i18n';
|
||||
@@ -67,10 +67,24 @@ export function VolunteerViewPage() {
|
||||
});
|
||||
}, [volunteers.data, assignments.data, shiftsById, hours, filter]);
|
||||
|
||||
if (volunteers.isLoading || shifts.isLoading || assignments.isLoading) return <Loading />;
|
||||
const error = volunteers.error ?? shifts.error ?? assignments.error;
|
||||
if (
|
||||
volunteers.isLoading ||
|
||||
shifts.isLoading ||
|
||||
assignments.isLoading ||
|
||||
positions.isLoading
|
||||
)
|
||||
return <Loading />;
|
||||
const error = volunteers.error ?? shifts.error ?? assignments.error ?? positions.error;
|
||||
if (error) return <ErrorNote error={error} />;
|
||||
|
||||
if ((volunteers.data ?? []).length === 0) {
|
||||
return (
|
||||
<div className="flex min-h-0 flex-1 flex-col">
|
||||
<EmptyNote>{t('volunteers.noData')}</EmptyNote>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const gridTemplateColumns = `230px repeat(${lattice.columns.length}, 62px) 84px`;
|
||||
const abbr = (positionId: string) => (positionsById.get(positionId)?.name ?? positionId).slice(0, 4).toUpperCase();
|
||||
|
||||
@@ -96,6 +110,9 @@ export function VolunteerViewPage() {
|
||||
</div>
|
||||
|
||||
<div className="max-h-[calc(100vh-118px)] min-w-0 flex-1 overflow-auto">
|
||||
{rows.length === 0 ? (
|
||||
<EmptyNote>{t('volunteers.empty')}</EmptyNote>
|
||||
) : (
|
||||
<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">
|
||||
{t('volunteers.count', { count: rows.length })}
|
||||
@@ -139,18 +156,13 @@ export function VolunteerViewPage() {
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{rows.length === 0 ? (
|
||||
<div className="m-[18px] flex max-w-[640px] items-center gap-[14px] border-[3px] border-dashed border-ink bg-paper p-[22px]">
|
||||
<span className="h-9 w-9 flex-none border-[3px] border-ink bg-yellow" />
|
||||
<span className="font-display text-[14px] font-extrabold leading-snug">
|
||||
{t('volunteers.empty')}
|
||||
</span>
|
||||
{rows.length > 0 ? (
|
||||
<div className="px-[14px] py-[10px] font-mono text-[9.5px] leading-relaxed opacity-60">
|
||||
{t('volunteers.footnote')}
|
||||
</div>
|
||||
) : null}
|
||||
<div className="px-[14px] py-[10px] font-mono text-[9.5px] leading-relaxed opacity-60">
|
||||
{t('volunteers.footnote')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user