Alléger l'annuaire
Le formulaire d'embauche, posé au bas de la liste, occupait plus de place que l'effectif qu'on vient consulter — une quinzaine de champs déployés en permanence pour un geste occasionnel. Il s'ouvre désormais en modale, depuis le bouton d'en-tête. `<dialog>` natif plutôt qu'un panneau maison : le piège de focus, la fermeture par Échap et le fond inerte viennent avec, et une `<div>` doit les réimplémenter sans jamais les tenir tout à fait. Le tableau perd sa carte et ses fonds : une liste de personnes se lit mieux sans cadre autour. Les colonnes suivent l'annuaire de référence — collaborateur, rôle, email, mobile, rattachement, invitation. Le contrat quitte le tableau, où il doublait le filtre qui le cherche déjà ; son absence reste signalée là où elle compte, dans le rattachement. Le décompte ne s'affiche plus que filtré : « 87 salariés » au-dessus de 87 lignes n'apprend rien. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
93724b9d66
commit
894e880198
11 files changed
+225
-124
No files matched your search
+108
-118
@@ -1,11 +1,11 @@
|
||||
import Link from 'next/link';
|
||||
import { Suspense } from 'react';
|
||||
|
||||
import { AddEmployeeForm } from '@/app/(app)/equipe/AddEmployeeForm';
|
||||
import { AddEmployeeDialog } from '@/app/(app)/equipe/AddEmployeeDialog';
|
||||
import { DirectoryFilters } from '@/app/(app)/equipe/DirectoryFilters';
|
||||
import { PageBody, PageHeader } from '@/components/shell/PageHeader';
|
||||
import { Badge, type Tone } from '@/components/ui/Badge';
|
||||
import { Card, CardHeader, EmptyState } from '@/components/ui/Card';
|
||||
import { EmptyState } from '@/components/ui/Card';
|
||||
import {
|
||||
listContractLocations,
|
||||
listEmployees,
|
||||
@@ -21,8 +21,6 @@ const INVITATION_TONES: Record<string, { label: string; tone: Tone }> = {
|
||||
REVOKED: { label: 'Révoquée', tone: 'neutral' },
|
||||
};
|
||||
|
||||
const dateFormat = new Intl.DateTimeFormat('fr-FR', { dateStyle: 'medium' });
|
||||
|
||||
const one = (value: string | string[] | undefined): string | undefined =>
|
||||
Array.isArray(value) ? value[0] : value;
|
||||
|
||||
@@ -59,25 +57,28 @@ export default async function EquipePage({
|
||||
<PageBody>
|
||||
<PageHeader
|
||||
title="Équipe"
|
||||
subtitle={
|
||||
filtered
|
||||
? `${directory.rows.length} sur ${directory.total} salariés`
|
||||
: `${directory.total} salarié${directory.total > 1 ? 's' : ''}`
|
||||
}
|
||||
// Le décompte n'apparaît que s'il apprend quelque chose : « 87 salariés »
|
||||
// au-dessus d'une liste de 87 lignes ne dit rien que la liste ne dise.
|
||||
{...(filtered
|
||||
? { subtitle: `${directory.rows.length} sur ${directory.total} salariés` }
|
||||
: {})}
|
||||
actions={
|
||||
<Link
|
||||
href="/reglages/registre"
|
||||
className="inline-flex h-8 items-center rounded-2 border border-line-3 bg-surface px-3.5 text-sm font-medium text-ink-1 hover:bg-surface-2"
|
||||
>
|
||||
Registre unique du personnel
|
||||
</Link>
|
||||
<>
|
||||
<Link
|
||||
href="/reglages/registre"
|
||||
className="inline-flex h-8 items-center rounded-2 border border-line-3 bg-surface px-3.5 text-sm font-medium text-ink-1 hover:bg-surface-2"
|
||||
>
|
||||
Registre unique du personnel
|
||||
</Link>
|
||||
<AddEmployeeDialog locations={hiringLocations} />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
|
||||
{/* `useSearchParams` impose une frontière de suspense : sans elle, la
|
||||
page entière serait rendue à la demande au lieu d'être servie puis
|
||||
hydratée. */}
|
||||
<Suspense fallback={<div className="h-[5.5rem]" />}>
|
||||
<Suspense fallback={<div className="h-[6rem]" />}>
|
||||
<DirectoryFilters
|
||||
options={{
|
||||
locations: directory.locations,
|
||||
@@ -87,114 +88,103 @@ export default async function EquipePage({
|
||||
/>
|
||||
</Suspense>
|
||||
|
||||
<Card>
|
||||
{directory.rows.length === 0 ? (
|
||||
<EmptyState
|
||||
title={filtered ? 'Aucun résultat' : 'Aucun salarié'}
|
||||
description={
|
||||
filtered
|
||||
? 'Aucun salarié ne répond à ces critères. Élargissez la recherche.'
|
||||
: 'Ajoutez un premier salarié pour commencer à planifier.'
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full min-w-[980px] border-collapse text-sm">
|
||||
<thead>
|
||||
<tr className="border-b border-line-2 bg-surface-2 text-left text-micro font-semibold tracking-[0.06em] text-ink-3 uppercase">
|
||||
<th className="px-4 py-2.5">Collaborateur</th>
|
||||
<th className="px-4 py-2.5">Rôle</th>
|
||||
<th className="px-4 py-2.5">Contrat</th>
|
||||
<th className="px-4 py-2.5">Mobile</th>
|
||||
<th className="px-4 py-2.5">Rattachement</th>
|
||||
<th className="px-4 py-2.5">Invitation</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{directory.rows.map((employee) => {
|
||||
const invitation = employee.invitationState
|
||||
? INVITATION_TONES[employee.invitationState]
|
||||
: null;
|
||||
{directory.rows.length === 0 ? (
|
||||
<EmptyState
|
||||
title={filtered ? 'Aucun résultat' : 'Aucun salarié'}
|
||||
description={
|
||||
filtered
|
||||
? 'Aucun salarié ne répond à ces critères. Élargissez la recherche.'
|
||||
: 'Ajoutez un premier salarié pour commencer à planifier.'
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full min-w-[940px] border-collapse text-sm">
|
||||
<thead>
|
||||
<tr className="border-b border-line-2 text-left text-micro font-semibold tracking-[0.04em] text-ink-3">
|
||||
<th className="px-3 py-3 font-semibold">Collaborateur</th>
|
||||
<th className="px-3 py-3 font-semibold">Rôle</th>
|
||||
<th className="px-3 py-3 font-semibold">Email</th>
|
||||
<th className="px-3 py-3 font-semibold">Mobile</th>
|
||||
<th className="px-3 py-3 font-semibold">Rattachement</th>
|
||||
<th className="px-3 py-3 font-semibold">Invitation</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{directory.rows.map((employee) => {
|
||||
const invitation = employee.invitationState
|
||||
? INVITATION_TONES[employee.invitationState]
|
||||
: null;
|
||||
|
||||
return (
|
||||
<tr
|
||||
key={employee.id}
|
||||
className="border-b border-line-1 last:border-b-0 hover:bg-surface-2"
|
||||
>
|
||||
<td className="px-4 py-2.5">
|
||||
<Link
|
||||
href={`/equipe/${employee.id}`}
|
||||
className="flex items-center gap-2.5 rounded-2"
|
||||
return (
|
||||
<tr
|
||||
key={employee.id}
|
||||
className="border-b border-line-1 last:border-b-0 hover:bg-surface-2"
|
||||
>
|
||||
<td className="px-3 py-3">
|
||||
<Link
|
||||
href={`/equipe/${employee.id}`}
|
||||
className="flex items-center gap-2.5 rounded-2"
|
||||
>
|
||||
<span
|
||||
aria-hidden
|
||||
className="flex size-8 flex-none items-center justify-center rounded-full bg-accent-soft text-micro font-semibold text-accent-soft-ink"
|
||||
>
|
||||
<span
|
||||
aria-hidden
|
||||
className="flex size-7 flex-none items-center justify-center rounded-full bg-accent-soft text-micro font-semibold text-accent-soft-ink"
|
||||
>
|
||||
{employee.firstName.charAt(0)}
|
||||
{employee.lastName.charAt(0)}
|
||||
{employee.firstName.charAt(0)}
|
||||
{employee.lastName.charAt(0)}
|
||||
</span>
|
||||
<span className="min-w-0">
|
||||
<span className="block truncate font-medium underline decoration-line-3 underline-offset-2">
|
||||
{employee.firstName} {employee.lastName}
|
||||
</span>
|
||||
<span className="min-w-0">
|
||||
<span className="block truncate font-medium">
|
||||
{employee.firstName} {employee.lastName}
|
||||
</span>
|
||||
{/* Le matricule ne double la colonne « Email » que
|
||||
lorsqu'il n'y en a pas : c'est alors la seule
|
||||
adresse à laquelle on désigne ce salarié. */}
|
||||
{employee.email ? null : (
|
||||
<span className="block truncate text-micro text-ink-3">
|
||||
{employee.email ?? `Matricule ${employee.employeeNumber}`}
|
||||
Matricule {employee.employeeNumber}
|
||||
</span>
|
||||
</span>
|
||||
</Link>
|
||||
</td>
|
||||
<td className="px-4 py-2.5">
|
||||
<Badge tone="accent">{employee.roleName}</Badge>
|
||||
</td>
|
||||
<td className="px-4 py-2.5">
|
||||
{employee.contract ? (
|
||||
<span className="flex flex-wrap items-center gap-1.5">
|
||||
<span>{employee.contract.label}</span>
|
||||
{employee.contract.forfaitJours ? (
|
||||
<Badge tone="accent">Forfait jours</Badge>
|
||||
) : null}
|
||||
<span className="tnum text-micro text-ink-3">
|
||||
depuis le{' '}
|
||||
{dateFormat.format(employee.contract.since)}
|
||||
</span>
|
||||
</span>
|
||||
) : (
|
||||
<Badge tone="warn">Sans contrat</Badge>
|
||||
)}
|
||||
</td>
|
||||
<td className="tnum px-4 py-2.5 text-ink-2">
|
||||
{employee.phone ?? (
|
||||
<span className="text-ink-3">Non renseigné</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="px-4 py-2.5 text-ink-2">
|
||||
{employee.locationName ?? '—'}
|
||||
{employee.teamName ? ` / ${employee.teamName}` : ''}
|
||||
</td>
|
||||
<td className="px-4 py-2.5">
|
||||
{invitation ? (
|
||||
<Badge tone={invitation.tone}>{invitation.label}</Badge>
|
||||
) : (
|
||||
<span className="text-micro text-ink-3">
|
||||
Sans accès applicatif
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader title="Ajouter un collaborateur" />
|
||||
<div className="p-4">
|
||||
<AddEmployeeForm locations={hiringLocations} />
|
||||
)}
|
||||
</span>
|
||||
</Link>
|
||||
</td>
|
||||
<td className="px-3 py-3">
|
||||
<Badge tone="accent">{employee.roleName}</Badge>
|
||||
</td>
|
||||
<td className="px-3 py-3 text-ink-2">
|
||||
{employee.email ?? (
|
||||
<span className="text-ink-3">Sans compte</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="tnum px-3 py-3 text-ink-2">
|
||||
{employee.phone ?? (
|
||||
<span className="text-ink-3">Non renseigné</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="px-3 py-3 text-ink-2">
|
||||
{employee.locationName ? (
|
||||
<>
|
||||
{employee.locationName}
|
||||
{employee.teamName ? ` / ${employee.teamName}` : ''}
|
||||
</>
|
||||
) : (
|
||||
<Badge tone="warn">Sans contrat</Badge>
|
||||
)}
|
||||
</td>
|
||||
<td className="px-3 py-3">
|
||||
{invitation ? (
|
||||
<Badge tone={invitation.tone}>{invitation.label}</Badge>
|
||||
) : (
|
||||
<span className="text-micro text-ink-3">—</span>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
</PageBody>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user