Skip to content

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 :

ts
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 :

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 :

tsx
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 + AppBar avec back : 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 :

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 :

ts
'my-absences': 'account',

6. Ouvrir l'écran ​

Dans src/app/(tabs)/account.tsx :

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 :

bash
npx expo start

8. Vérifier ​

bash
npx tsc --noEmit
npx expo lint

Puis 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.ts et fr.ts
  • [ ] Le fichier est dans src/app/(tabs)/ avec un export default
  • [ ] L'écran est déclaré dans (tabs)/_layout.tsx
  • [ ] Il est dans parentTab
  • [ ] Un écran existant l'ouvre
  • [ ] npx tsc --noEmit et npx expo lint passent
  • [ ] Testé avec chaque rôle