Emplois, étiquettes et types d'absence vivaient en base sans écran : le seed les posait, et rien ne permettait ensuite d'en ajouter un. Ils s'éditent désormais depuis les réglages. Trois choix portent la conformité plutôt que le confort : - Archiver, jamais supprimer. Un emploi figure au registre du personnel, une étiquette colore des semaines closes, un type d'absence justifie des écritures de compteur. Supprimer l'un des trois rendrait illisible un dossier dont la conservation court encore. - Le code Silae manquant est signalé. Un type sans code fait échouer l'export de la période ; l'écran le dit avant l'échéance de paie plutôt qu'après. - La convention se réédite, elle ne se modifie pas. Chaque enregistrement crée une version datée : une paie de mars doit rester reproductible après un changement de seuil en juin. L'origine de chaque valeur — ordre public, convention, accord d'entreprise — est affichée, parce que les trois ne se corrigent pas de la même main. Les mutations de types d'absence passent par settings.agreement.manage : le catalogue §5 ne déclare pas de code dédié, et ces drapeaux relèvent du paramétrage légal. Inventer une capacité aurait créé un droit sans rien protéger de neuf. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
86 lines
2.4 KiB
TypeScript
86 lines
2.4 KiB
TypeScript
'use client';
|
||
|
||
import { useActionState } from 'react';
|
||
|
||
import { Field, FormError, SubmitButton } from '@/components/ui/Form';
|
||
import { PersistentForm } from '@/components/ui/PersistentForm';
|
||
import { POSTE_CODES, POSTE_LABELS } from '@/lib/design/postes';
|
||
import {
|
||
createLabelAction,
|
||
updateLabelAction,
|
||
type ActionState,
|
||
} from '@/server/settings/labels';
|
||
|
||
const empty: ActionState = {};
|
||
|
||
function PaletteSelect({ defaultValue }: { defaultValue?: string }) {
|
||
return (
|
||
<label className="flex flex-col gap-1.5">
|
||
<span className="text-sm font-medium">Teinte</span>
|
||
<select
|
||
name="paletteKey"
|
||
defaultValue={defaultValue ?? POSTE_CODES[0]}
|
||
className="h-9 rounded-2 border border-line-2 bg-surface px-2 text-sm text-ink-1"
|
||
>
|
||
{POSTE_CODES.map((code) => (
|
||
<option key={code} value={code}>
|
||
{POSTE_LABELS[code]}
|
||
</option>
|
||
))}
|
||
</select>
|
||
</label>
|
||
);
|
||
}
|
||
|
||
export function AddLabelForm() {
|
||
const [state, action] = useActionState(createLabelAction, empty);
|
||
|
||
return (
|
||
<PersistentForm
|
||
action={action}
|
||
resetAfter={state.ok ? state : null}
|
||
className="flex flex-col gap-3"
|
||
>
|
||
<div className="flex flex-wrap items-end gap-3">
|
||
<Field
|
||
label="Code"
|
||
name="code"
|
||
required
|
||
maxLength={12}
|
||
placeholder="cai"
|
||
hint="Affiché sur le bloc de planning : la couleur ne porte jamais l’information seule."
|
||
/>
|
||
<Field label="Libellé" name="name" required maxLength={120} placeholder="Caisse" />
|
||
<PaletteSelect />
|
||
<SubmitButton>Ajouter l’étiquette</SubmitButton>
|
||
</div>
|
||
<FormError>{state.error}</FormError>
|
||
{state.ok ? (
|
||
<p className="text-xs text-ok-soft-ink">Étiquette enregistrée.</p>
|
||
) : null}
|
||
</PersistentForm>
|
||
);
|
||
}
|
||
|
||
export function EditLabelForm({
|
||
id,
|
||
name,
|
||
paletteKey,
|
||
}: {
|
||
id: string;
|
||
name: string;
|
||
paletteKey: string;
|
||
}) {
|
||
const [state, action] = useActionState(updateLabelAction, empty);
|
||
|
||
return (
|
||
<PersistentForm action={action} className="flex flex-1 flex-wrap items-end gap-2">
|
||
<input type="hidden" name="id" value={id} />
|
||
<Field label="Libellé" name="name" defaultValue={name} required maxLength={120} />
|
||
<PaletteSelect defaultValue={paletteKey} />
|
||
<SubmitButton size="sm">Enregistrer</SubmitButton>
|
||
<FormError>{state.error}</FormError>
|
||
</PersistentForm>
|
||
);
|
||
}
|