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>
131 lines
5.0 KiB
TypeScript
131 lines
5.0 KiB
TypeScript
import { useState } from 'react';
|
|
import { Pressable, ScrollView, Text, View } from 'react-native';
|
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
import { useAnalyticsSummary } from '@/api/pilotage';
|
|
import { Carte, EnteteFiche, LigneInfo } from '@/composants/ui';
|
|
import { useTokens } from '@/theme/tokens';
|
|
|
|
const fmtMAD = new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'MAD', maximumFractionDigits: 0 });
|
|
const PERIODES = [3, 6, 12] as const;
|
|
|
|
/** Statistiques — R6.4 (Pilotage). Maquette écran 7 : les mêmes chiffres
|
|
* que `/analytics/summary` (web), en cartes plutôt qu'en graphes denses
|
|
* (D4) — coût du mois, taux préventif, pannes par organe. */
|
|
export default function PageStatistiques() {
|
|
const t = useTokens();
|
|
const [periode, setPeriode] = useState<(typeof PERIODES)[number]>(12);
|
|
const { data } = useAnalyticsSummary(periode);
|
|
|
|
return (
|
|
<SafeAreaView style={{ flex: 1, backgroundColor: t.fond }} edges={['top']}>
|
|
<ScrollView contentContainerStyle={{ padding: 14, gap: 10 }}>
|
|
<EnteteFiche titre="Statistiques" />
|
|
<View style={{ flexDirection: 'row', gap: 8 }}>
|
|
{PERIODES.map((p) => (
|
|
<Pressable
|
|
key={p}
|
|
accessibilityRole="button"
|
|
onPress={() => setPeriode(p)}
|
|
style={{
|
|
flex: 1,
|
|
paddingVertical: 8,
|
|
borderRadius: 8,
|
|
alignItems: 'center',
|
|
backgroundColor: periode === p ? t.primaire : t.surface,
|
|
borderWidth: 1,
|
|
borderColor: periode === p ? t.primaire : t.bordure,
|
|
}}
|
|
>
|
|
<Text
|
|
style={{
|
|
fontFamily: 'Manrope_700Bold',
|
|
fontSize: 12.5,
|
|
color: periode === p ? '#fff' : t.encre2,
|
|
}}
|
|
>
|
|
{p} mois
|
|
</Text>
|
|
</Pressable>
|
|
))}
|
|
</View>
|
|
|
|
{data ? (
|
|
<>
|
|
<View style={{ flexDirection: 'row', gap: 8 }}>
|
|
<View
|
|
style={{
|
|
flex: 1,
|
|
backgroundColor: t.surface,
|
|
borderColor: t.bordure,
|
|
borderWidth: 1,
|
|
borderRadius: 10,
|
|
padding: 10,
|
|
}}
|
|
>
|
|
<Text style={{ fontFamily: 'Manrope_800ExtraBold', fontSize: 17, color: t.primaire }}>
|
|
{fmtMAD.format(data.monthCost)}
|
|
</Text>
|
|
<Text style={{ fontFamily: 'Manrope_600SemiBold', fontSize: 10.5, color: t.encre2 }}>
|
|
Coût du mois
|
|
</Text>
|
|
</View>
|
|
<View
|
|
style={{
|
|
flex: 1,
|
|
backgroundColor: t.surface,
|
|
borderColor: t.bordure,
|
|
borderWidth: 1,
|
|
borderRadius: 10,
|
|
padding: 10,
|
|
}}
|
|
>
|
|
<Text style={{ fontFamily: 'Manrope_800ExtraBold', fontSize: 17, color: t.encre }}>
|
|
{data.preventiveRate != null ? `${Math.round(data.preventiveRate * 100)} %` : '—'}
|
|
</Text>
|
|
<Text style={{ fontFamily: 'Manrope_600SemiBold', fontSize: 10.5, color: t.encre2 }}>
|
|
Taux préventif
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
|
|
<Carte titre={`OT clôturés · ${data.months} mois`}>
|
|
<LigneInfo nom="Total" valeur={String(data.closed.total)} />
|
|
<LigneInfo nom="Dont préventif" valeur={String(data.closed.preventive)} />
|
|
<LigneInfo
|
|
nom="Délai moyen"
|
|
valeur={data.avgResolutionDays != null ? `${data.avgResolutionDays.toFixed(1)} j` : '—'}
|
|
/>
|
|
</Carte>
|
|
|
|
<Carte titre={`Pannes par organe · ${data.months} mois`}>
|
|
{data.failuresByComponent.slice(0, 5).map((f) => (
|
|
<LigneInfo key={f.label} nom={f.label} valeur={String(f.count)} />
|
|
))}
|
|
{!data.failuresByComponent.length ? (
|
|
<Text style={{ color: t.encre3, fontFamily: 'Manrope_400Regular', fontSize: 12.5 }}>
|
|
Aucune panne codée sur la période.
|
|
</Text>
|
|
) : null}
|
|
</Carte>
|
|
|
|
<Carte titre="Top équipements en coût">
|
|
{data.topAssets.slice(0, 5).map((a) => (
|
|
<LigneInfo
|
|
key={a.reference}
|
|
nom={`Asc. ${a.reference} — ${a.siteName}`}
|
|
valeur={fmtMAD.format(a.total)}
|
|
/>
|
|
))}
|
|
{!data.topAssets.length ? (
|
|
<Text style={{ color: t.encre3, fontFamily: 'Manrope_400Regular', fontSize: 12.5 }}>
|
|
Aucune donnée sur la période.
|
|
</Text>
|
|
) : null}
|
|
</Carte>
|
|
</>
|
|
) : null}
|
|
</ScrollView>
|
|
</SafeAreaView>
|
|
);
|
|
}
|