mirror of
https://github.com/siop-spelev/siop2.git
synced 2026-08-08 12:41:54 +00:00
EXPO_PUBLIC_WEB_URL/WEB_URL pour le lien d'activation (pointe la page web /activation?token=…, aucun équivalent mobile). api/pilotage.ts (useAnalyticsSummary, useUsers, useRoles, useInviteUser). Écran Statistiques (maquette écran 7) : période 3/6/12 mois, coût du mois, taux préventif, OT clôturés, pannes par organe, top équipements — cartes plutôt que graphes denses (D4). Écran Personnes & équipes : liste + statut, Inviter (nom/e-mail/rôle), lien d'activation en texte sélectionnable (Text selectable, RN natif) — expo-clipboard est une dépendance native absente, différée comme expo-sharing en R6.3 plutôt qu'ajoutée à la légère. Taux horaire/rôles/ équipes restent gérés au web (D4). Assistant reste "à venir" dans le Menu : un chat sourcé est un nouveau patron d'écran jamais maquetté sur mobile, contrairement aux autres familles qui réutilisaient des patrons déjà validés — mérite son propre tour de design-first plutôt qu'être exécuté par réflexe en fin de release. R6 quasi close (Exploitation/Parc/Ressources/Pilotage livrés pour tous les rôles, hors Assistant) — reste la confirmation du référent sur iPhone (R6.1 + correctif déconnexion) avant clôture réelle. Typecheck propre, 17 tests Jest, lint 5/5 paquets. Contrat non touché. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
95 lines
3.1 KiB
TypeScript
95 lines
3.1 KiB
TypeScript
import { router } from 'expo-router';
|
|
import { FlatList, Text, View } from 'react-native';
|
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
import { useUsers } from '@/api/pilotage';
|
|
import { usePermissions } from '@/auth/use-permissions';
|
|
import { BoutonTel, EnteteFiche } from '@/composants/ui';
|
|
import { useTokens, type Tokens } from '@/theme/tokens';
|
|
|
|
const LABEL_STATUT: Record<'active' | 'invited' | 'disabled', string> = {
|
|
active: 'Actif',
|
|
invited: 'Invitation envoyée',
|
|
disabled: 'Désactivé',
|
|
};
|
|
|
|
/** Personnes & équipes — R6.4 (Pilotage). Consultation + « Inviter » (D4) —
|
|
* taux horaire, équipes, rôles restent gérés depuis le web pour l'instant. */
|
|
export default function PagePersonnes() {
|
|
const t = useTokens();
|
|
const { data: users } = useUsers();
|
|
const { can } = usePermissions();
|
|
const personnes = [...(users ?? [])].sort((a, b) => a.displayName.localeCompare(b.displayName));
|
|
|
|
return (
|
|
<SafeAreaView style={{ flex: 1, backgroundColor: t.fond }} edges={['top']}>
|
|
<View style={{ flex: 1, padding: 14, gap: 10 }}>
|
|
<EnteteFiche titre="Personnes" />
|
|
{can('PEOPLE_TEAMS', 'create') ? (
|
|
<BoutonTel libelle="+ Inviter" surAppui={() => router.push('/personnes/inviter')} />
|
|
) : null}
|
|
<FlatList
|
|
data={personnes}
|
|
keyExtractor={(u) => u.id}
|
|
contentContainerStyle={{ gap: 8, paddingBottom: 12 }}
|
|
ListEmptyComponent={
|
|
<Text style={{ color: t.encre3, fontFamily: 'Manrope_600SemiBold', padding: 16, textAlign: 'center' }}>
|
|
Personne accessible.
|
|
</Text>
|
|
}
|
|
renderItem={({ item: u }) => <LignePersonne utilisateur={u} t={t} />}
|
|
/>
|
|
</View>
|
|
</SafeAreaView>
|
|
);
|
|
}
|
|
|
|
function LignePersonne({
|
|
utilisateur: u,
|
|
t,
|
|
}: {
|
|
utilisateur: { id: string; displayName: string; email: string; role: { name: string }; teams: { name: string }[]; status: 'active' | 'invited' | 'disabled' };
|
|
t: Tokens;
|
|
}) {
|
|
const [enc, fond] =
|
|
u.status === 'active'
|
|
? [t.stTermine, t.stTermineFond]
|
|
: u.status === 'invited'
|
|
? [t.stAttente, t.stAttenteFond]
|
|
: [t.stAnnule, t.stAnnuleFond];
|
|
return (
|
|
<View
|
|
style={{
|
|
backgroundColor: t.surface,
|
|
borderColor: t.bordure,
|
|
borderWidth: 1,
|
|
borderRadius: 12,
|
|
padding: 12,
|
|
gap: 2,
|
|
}}
|
|
>
|
|
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
|
|
<Text style={{ flex: 1, fontFamily: 'Manrope_700Bold', fontSize: 14, color: t.encre }}>
|
|
{u.displayName}
|
|
</Text>
|
|
<Text
|
|
style={{
|
|
fontFamily: 'Manrope_700Bold',
|
|
fontSize: 10.5,
|
|
color: enc,
|
|
backgroundColor: fond,
|
|
paddingHorizontal: 8,
|
|
paddingVertical: 2,
|
|
borderRadius: 999,
|
|
}}
|
|
>
|
|
{LABEL_STATUT[u.status]}
|
|
</Text>
|
|
</View>
|
|
<Text style={{ fontFamily: 'Manrope_400Regular', fontSize: 12, color: t.encre2 }}>
|
|
{u.role.name}
|
|
{u.teams.length ? ` · ${u.teams.map((eq) => eq.name).join(', ')}` : ''}
|
|
</Text>
|
|
</View>
|
|
);
|
|
}
|