mirror of
https://github.com/siop-spelev/siop2.git
synced 2026-08-08 12:41:54 +00:00
fix(mobile): R6.7 — assignation à un technicien lors de l'approbation
Le Gestionnaire approuvait une demande sur mobile sans pouvoir assigner
l'OT à un technicien (l'OT partait non assigné) — remonté en recette :
"ce n'est pas à lui d'agir comme un technicien". Le web le fait déjà
(PanneauApprobation, demandes.tsx).
PanneauDemandes.tsx : "Approuver → OT" ouvre un panneau avec un ChoixTel
"Assigner à" (techniciens actifs, même filtre que le web —
status active && role.name.startsWith('Technicien')) avant de confirmer.
assigneeIds déjà supporté par l'API et le contrat — aucun changement
serveur nécessaire, seule l'UI mobile manquait cette étape.
Vérifié de bout en bout : demande → approuvée avec technicien assigné →
OT confirmé avec le bon assignee. Typecheck/tests/lint verts.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,9 +7,10 @@ import {
|
||||
useRejectRequest,
|
||||
useRequests,
|
||||
} from '@/api/exploitation';
|
||||
import { useUsers } from '@/api/pilotage';
|
||||
import { usePermissions } from '@/auth/use-permissions';
|
||||
import { useTokens, type Tokens } from '@/theme/tokens';
|
||||
import { BoutonTel } from './ui';
|
||||
import { BoutonTel, ChoixTel } from './ui';
|
||||
|
||||
/** Panneau Demandes — UN SEUL composant pour tous les rôles (maquette
|
||||
* « mobile ouvert à tous les rôles », écran 5) : le Demandeur y crée et
|
||||
@@ -68,9 +69,20 @@ const LABEL_STATUT: Record<RequestSummary['status'], string> = {
|
||||
|
||||
function CarteDemande({ demande: r, peutTraiter }: { demande: RequestSummary; peutTraiter: boolean }) {
|
||||
const t = useTokens();
|
||||
const { data: users } = useUsers();
|
||||
const approbation = useApproveRequest();
|
||||
const rejet = useRejectRequest();
|
||||
const [motif, setMotif] = useState<string | null>(null); // null = fermé
|
||||
// Un seul panneau ouvert à la fois : approbation (avec assignation — ce
|
||||
// n'est pas au Gestionnaire d'agir comme un technicien, il délègue) ou
|
||||
// rejet (motif obligatoire). null = fermé, les deux boutons côte à côte.
|
||||
const [panneau, setPanneau] = useState<'approuver' | 'rejeter' | null>(null);
|
||||
const [motif, setMotif] = useState('');
|
||||
const [assigneId, setAssigneId] = useState<string | null>(null);
|
||||
|
||||
const techniciens = (users ?? []).filter(
|
||||
(u) => u.status === 'active' && u.role.name.startsWith('Technicien'),
|
||||
);
|
||||
const technicien = techniciens.find((tt) => tt.id === assigneId) ?? null;
|
||||
|
||||
const [enc, fond] = STYLE_STATUT[r.status](t);
|
||||
const enCours = r.status === 'RECEIVED';
|
||||
@@ -128,22 +140,51 @@ function CarteDemande({ demande: r, peutTraiter }: { demande: RequestSummary; pe
|
||||
) : null}
|
||||
|
||||
{peutTraiter && enCours ? (
|
||||
motif === null ? (
|
||||
panneau === null ? (
|
||||
<View style={{ flexDirection: 'row', gap: 8, marginTop: 4 }}>
|
||||
<View style={{ flex: 1 }}>
|
||||
<BoutonTel
|
||||
libelle="Approuver → OT"
|
||||
variante="vert"
|
||||
surAppui={() =>
|
||||
approbation.mutate({
|
||||
id: r.id,
|
||||
priority: r.isPersonTrapped ? 'PERSON_TRAPPED' : 'HIGH',
|
||||
})
|
||||
}
|
||||
/>
|
||||
<BoutonTel libelle="Approuver → OT" variante="vert" surAppui={() => setPanneau('approuver')} />
|
||||
</View>
|
||||
<View style={{ flex: 1 }}>
|
||||
<BoutonTel libelle="Rejeter" variante="gris" surAppui={() => setMotif('')} />
|
||||
<BoutonTel libelle="Rejeter" variante="gris" surAppui={() => setPanneau('rejeter')} />
|
||||
</View>
|
||||
</View>
|
||||
) : panneau === 'approuver' ? (
|
||||
<View style={{ gap: 6, marginTop: 4 }}>
|
||||
<ChoixTel
|
||||
libelle="Assigner à"
|
||||
valeur={technicien ? { id: technicien.id, label: technicien.displayName } : null}
|
||||
options={techniciens.map((tt) => ({ id: tt.id, label: tt.displayName }))}
|
||||
surChoix={setAssigneId}
|
||||
/>
|
||||
<View style={{ flexDirection: 'row', gap: 8 }}>
|
||||
<View style={{ flex: 1 }}>
|
||||
<BoutonTel
|
||||
libelle="Annuler"
|
||||
variante="contour"
|
||||
surAppui={() => {
|
||||
setPanneau(null);
|
||||
setAssigneId(null);
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
<View style={{ flex: 1 }}>
|
||||
<BoutonTel
|
||||
libelle="Confirmer l'approbation"
|
||||
variante="vert"
|
||||
desactive={approbation.isPending}
|
||||
surAppui={() =>
|
||||
approbation.mutate(
|
||||
{
|
||||
id: r.id,
|
||||
priority: r.isPersonTrapped ? 'PERSON_TRAPPED' : 'HIGH',
|
||||
assigneeIds: assigneId ? [assigneId] : undefined,
|
||||
},
|
||||
{ onSuccess: () => setPanneau(null) },
|
||||
)
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
) : (
|
||||
@@ -167,13 +208,25 @@ function CarteDemande({ demande: r, peutTraiter }: { demande: RequestSummary; pe
|
||||
/>
|
||||
<View style={{ flexDirection: 'row', gap: 8 }}>
|
||||
<View style={{ flex: 1 }}>
|
||||
<BoutonTel libelle="Annuler" variante="contour" surAppui={() => setMotif(null)} />
|
||||
<BoutonTel
|
||||
libelle="Annuler"
|
||||
variante="contour"
|
||||
surAppui={() => {
|
||||
setPanneau(null);
|
||||
setMotif('');
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
<View style={{ flex: 1 }}>
|
||||
<BoutonTel
|
||||
libelle="Confirmer le rejet"
|
||||
desactive={motif.trim().length < 3}
|
||||
surAppui={() => rejet.mutate({ id: r.id, reason: motif.trim() }, { onSuccess: () => setMotif(null) })}
|
||||
surAppui={() =>
|
||||
rejet.mutate(
|
||||
{ id: r.id, reason: motif.trim() },
|
||||
{ onSuccess: () => setPanneau(null) },
|
||||
)
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
Reference in New Issue
Block a user