Files
planflow/src/app/(app)/equipe/[id]/planification/page.tsx
T
MichaelandClaude Opus 5 93724b9d66 Rendre le rattachement et le périmètre modifiables
L'onglet montrait les équipes et le périmètre sans jamais permettre de les
changer : un salarié rattaché à la mauvaise équipe n'apparaissait pas sur
la bonne grille, et rien à l'écran n'y remédiait.

Deux formulaires et deux capacités, parce que ce sont deux décisions.
Élargir un périmètre revient à donner accès à des dossiers et des
plannings qu'on n'avait pas : c'est distribuer des droits, donc
`settings.roles.manage`. Rattacher à une équipe relève de l'organisation
du travail, donc `settings.teams.manage`. Les réunir donnerait à l'une le
pouvoir de l'autre.

Le périmètre est remplacé, pas fusionné : un périmètre qui ne fait que
grandir ne se réduit jamais, et retirer un établissement resterait sans
effet — l'exact contraire de ce que l'écran promet. Un périmètre vide sans
accès généralisé est refusé : il ne laisserait plus rien voir.

L'établissement de rattachement reste en lecture seule. Il est porté par
le contrat, et le changer sans avenant ferait diverger le planning du
document opposable.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 12:05:01 +02:00

131 lines
3.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { notFound } from 'next/navigation';
import { InfoCard, InfoGrid, InfoRow } from '@/app/(app)/equipe/[id]/InfoCard';
import {
ScopeForm,
TeamsForm,
} from '@/app/(app)/equipe/[id]/planification/PlacementPanel';
import { Badge } from '@/components/ui/Badge';
import { can } from '@/domain/access/authorize';
import { requireSession } from '@/server/context';
import {
getEmployee,
getMemberPlacement,
listPlacementOptions,
} from '@/server/employees/queries';
export const dynamic = 'force-dynamic';
/**
* Rattachement et périmètre.
*
* L'établissement de rattachement reste en lecture seule : il est porté par le
* contrat, et le changer sans avenant ferait diverger le planning du document
* opposable.
*/
export default async function PlacementTab({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
const employee = await getEmployee(id);
if (!employee) notFound();
const [placement, locations, session] = await Promise.all([
getMemberPlacement(id),
listPlacementOptions(),
requireSession(),
]);
const canScope = can(session.actor, 'settings.roles.manage');
const canTeams = can(session.actor, 'settings.teams.manage');
return (
<InfoGrid>
<InfoCard title="Rattachement">
<InfoRow
label="Établissement"
value={placement?.contractLocationName ?? employee.locationName}
/>
<InfoRow
label="Équipes"
value={
placement && placement.teams.length > 0
? placement.teams
.map((team) =>
team.isPrimary ? `${team.name} (principale)` : team.name,
)
.join(', ')
: ''
}
/>
<InfoRow label="Matricule" value={employee.employeeNumber} tnum />
<InfoRow
label="Responsable hiérarchique"
value={employee.headline.lineManagerName}
/>
<p className="pt-3 text-micro text-ink-3">
L’établissement vient du contrat : il se change par avenant, pas ici.
</p>
{canTeams ? (
<div className="pt-3">
<TeamsForm
membershipId={employee.id}
locations={locations}
teamIds={placement?.teamIds ?? []}
primaryTeamId={placement?.primaryTeamId ?? null}
/>
</div>
) : null}
</InfoCard>
<InfoCard title="Périmètre d’accès">
<div className="flex flex-wrap items-baseline gap-x-4 gap-y-1 border-b border-line-1 py-3">
<dt className="min-w-[11rem] flex-none text-sm font-medium text-ink-1">
Accès généralisé
</dt>
<dd className="text-sm">
{placement?.allLocations ? (
<Badge tone="ok">Tous les établissements</Badge>
) : (
<Badge tone="neutral">Limité</Badge>
)}
</dd>
</div>
<InfoRow
label="Établissements"
value={
placement?.allLocations
? 'Tous, ouvertures futures comprises'
: (placement?.scopedLocations.join(', ') ?? '')
}
/>
<InfoRow
label="Équipes"
value={placement?.scopedTeams.join(', ') ?? ''}
/>
<InfoRow label="Rôle" value={employee.roleName} />
{canScope ? (
<div className="pt-3">
<ScopeForm
membershipId={employee.id}
locations={locations}
allLocations={placement?.allLocations ?? false}
scopedLocationIds={placement?.scopedLocationIds ?? []}
/>
</div>
) : (
<p className="pt-3 text-micro text-ink-3">
Élargir un périmètre revient à distribuer des droits : la capacité
« Gérer les rôles » est requise.
</p>
)}
</InfoCard>
</InfoGrid>
);
}