Ajouter un écran
Un pas à pas complet, sur un exemple : un écran « Mes absences » qui liste les absences de l'utilisateur connecté, ouvert depuis Compte.
1. Vérifier l'API
Avant tout, il faut savoir quelle requête donne les données. Ici, GET /users/:id/absences?start&end existe déjà, et src/api/absences.ts a la fonction :
export async function fetchAbsences(userId: number, from: Date, to: Date) { ... }Si la fonction n'existait pas, on l'ajouterait dans le fichier src/api/ de la ressource, en convertissant les dates avec fromApiDateTime ou fromDateKey.
Pas de nouvelle route API sans l'équipe backend
Si l'endpoint n'existe pas, il faut en parler à l'équipe backend. On ne contourne pas l'API.
2. Ajouter les textes
Dans src/i18n/en.ts :
myAbsences: {
title: 'My absences',
none: 'No absence recorded.',
},Puis la même chose dans src/i18n/fr.ts. TypeScript le réclame de toute façon.
3. Créer le fichier de l'écran
Le fichier src/app/(tabs)/my-absences.tsx crée la route /my-absences :
import { StyleSheet } from 'react-native';
import { useTranslation } from 'react-i18next';
import { addDays, startOfDay } from '@/api/dates';
import { fetchAbsences } from '@/api/absences';
import { Screen } from '@/components/screen';
import { AppBar } from '@/components/ui/app-bar';
import { Group, ValueRow } from '@/components/ui/group';
import { Text } from '@/components/ui/text';
import { useQuery } from '@/hooks/use-query';
import { useCurrentUser } from '@/state/session';
import { groupAbsences } from '@/utils/absences';
export default function MyAbsences() {
const { t } = useTranslation();
const user = useCurrentUser();
const today = startOfDay(new Date());
const { data, error } = useQuery(
() => fetchAbsences(user.id, addDays(today, -90), addDays(today, 90)),
[user.id],
);
const periods = groupAbsences(data ?? []);
return (
<Screen
background="surfacePage"
contentStyle={styles.body}
header={<AppBar title={t('myAbsences.title')} back bordered />}>
{error ? (
<Text color="feedbackDangerText" align="center">{t('common.unreachable')}</Text>
) : null}
<Group>
{periods.length === 0 ? (
<ValueRow label={t('myAbsences.none')} value="" />
) : (
periods.map((period) => (
<ValueRow key={period.ids[0]} label={period.label} value={t(`absences.kinds.${period.kind}`)} />
))
)}
</Group>
</Screen>
);
}
const styles = StyleSheet.create({
body: {
gap: 24,
paddingTop: 16,
paddingHorizontal: 16,
},
});Ce qu'il faut remarquer :
export default: expo-router prend l'export par défaut comme écran ;useCurrentUser(): on est dans(tabs), l'utilisateur existe forcément ;useQuery: les données se rechargent à chaque affichage ;Screen+AppBaravecback: le cadre et la flèche retour ;- les styles en bas, avec les mêmes espacements que les autres écrans de réglages.
4. Déclarer l'écran dans les onglets
Dans src/app/(tabs)/_layout.tsx :
<Tabs.Screen name="my-absences" />expo-router trouverait l'écran tout seul, mais tous les écrans sont listés ici pour que le layout reste la liste complète et lisible des écrans connectés. La barre d'onglets, elle, n'affiche que les onglets de tabsByRole : un nouvel écran n'y apparaît jamais par accident.
5. Allumer le bon onglet
Dans src/components/navigation/tab-bar.tsx, ajoute l'écran à parentTab :
'my-absences': 'account',6. Ouvrir l'écran
Dans src/app/(tabs)/account.tsx :
<NavRow label={t('myAbsences.title')} onPress={() => router.push('/my-absences')} />7. Régénérer les types de routes
router.push('/my-absences') est souligné en rouge tant que les types ne connaissent pas la nouvelle route. Relance une fois :
npx expo start8. Vérifier
npx tsc --noEmit
npx expo lintPuis teste sur le téléphone avec un compte de chaque rôle concerné.
La checklist
- [ ] La fonction existe dans
src/api/ - [ ] Les textes sont dans
en.tsetfr.ts - [ ] Le fichier est dans
src/app/(tabs)/avec unexport default - [ ] L'écran est déclaré dans
(tabs)/_layout.tsx - [ ] Il est dans
parentTab - [ ] Un écran existant l'ouvre
- [ ]
npx tsc --noEmitetnpx expo lintpassent - [ ] Testé avec chaque rôle