mirror of
https://github.com/siop-spelev/siop2.git
synced 2026-08-08 12:41:54 +00:00
feat(r3.3): écrans web de la gestion — stock, achats, coûts OT, documents, statistiques
- Stock : alertes sous seuil (strie ambre), « Préparer le BC » sur la ligne ; fiche pièce où les mouvements SONT le stock (entrée / ajustement motivé) - Bons de commande : liste + panneau (envoi, annulation, réception → entrées de stock à PU figé) ; création préremplie depuis l'alerte de seuil - Fiche OT : carte « Pièces & main-d'œuvre » réelle (consommation à prix figé annoncé avant le clic, temps au taux figé) + carte Documents (upload/download) ; même carte Documents sur la fiche ascenseur - Statistiques : 4 KPI, pannes par organe (bilans codés), coûts par mois, top équipements ; le tableau de bord allume « Pannes par organe » - Tiers, bibliothèque (upload rattaché, filtre par type), taux horaire éditable dans Personnes ; nav Ressources/Pilotage activée par la matrice - Recette R3 Playwright (2 parcours autonomes) ; cleanup-e2e étendu aux entités R3 ; le formulaire BC attend la fin du refetch (stock périmé en cache) 13/13 e2e verts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import {
|
||||
useResendInvitation,
|
||||
useRoles,
|
||||
useTeams,
|
||||
useUpdateUser,
|
||||
useUsers,
|
||||
} from '@/api/referentiel';
|
||||
import { usePermissions } from '@/auth/use-permissions';
|
||||
@@ -44,6 +45,7 @@ export default function PagePersonnes() {
|
||||
<th>Personne</th>
|
||||
<th>Rôle</th>
|
||||
<th>Équipe</th>
|
||||
<th>Taux horaire</th>
|
||||
<th>Statut</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
@@ -66,6 +68,13 @@ export default function PagePersonnes() {
|
||||
</td>
|
||||
<td>{u.role.name}</td>
|
||||
<td>{u.teams.map((t) => t.name).join(', ') || '—'}</td>
|
||||
<td>
|
||||
<CelluleTaux
|
||||
userId={u.id}
|
||||
taux={u.hourlyRate}
|
||||
editable={can('PEOPLE_TEAMS', 'edit')}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
{u.status === 'active' ? (
|
||||
<span className="st termine">Actif</span>
|
||||
@@ -139,6 +148,64 @@ export default function PagePersonnes() {
|
||||
);
|
||||
}
|
||||
|
||||
/** Taux horaire courant (R3) — appliqué aux futures saisies de temps ; les
|
||||
* temps déjà valorisés gardent leur taux figé. */
|
||||
function CelluleTaux({
|
||||
userId,
|
||||
taux,
|
||||
editable,
|
||||
}: {
|
||||
userId: string;
|
||||
taux: number | null;
|
||||
editable: boolean;
|
||||
}) {
|
||||
const maj = useUpdateUser();
|
||||
const [edition, setEdition] = useState(false);
|
||||
|
||||
if (!edition) {
|
||||
return (
|
||||
<span className="num">
|
||||
{taux != null ? `${new Intl.NumberFormat('fr-FR').format(taux)} MAD/h` : '—'}
|
||||
{editable ? (
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="ml-1 px-2 py-0.5 text-[12px]"
|
||||
aria-label="Modifier le taux horaire"
|
||||
onClick={() => setEdition(true)}
|
||||
>
|
||||
✎
|
||||
</Button>
|
||||
) : null}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<input
|
||||
type="number"
|
||||
min={0}
|
||||
step="0.01"
|
||||
autoFocus
|
||||
defaultValue={taux ?? ''}
|
||||
aria-label="Nouveau taux horaire (MAD/h)"
|
||||
className="num w-24 rounded-lg border border-bordure-forte bg-surface px-2 py-1 text-encre"
|
||||
disabled={maj.isPending}
|
||||
onBlur={(e) => {
|
||||
const valeur = e.target.value.trim();
|
||||
const nombre = Number(valeur);
|
||||
if (valeur && nombre > 0 && nombre !== taux) {
|
||||
maj.mutate({ id: userId, hourlyRate: nombre }, { onSettled: () => setEdition(false) });
|
||||
} else {
|
||||
setEdition(false);
|
||||
}
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') e.currentTarget.blur();
|
||||
if (e.key === 'Escape') setEdition(false);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function ModaleInvitation({
|
||||
ouverte,
|
||||
surFermeture,
|
||||
|
||||
Reference in New Issue
Block a user