Aller au contenu

Feature : unitPreference

C'est l'implémentation de référence de l'architecture par module de feature. Copiez cette structure pour construire une nouvelle feature.

Objectif

Un écran par projet où l'utilisateur choisit l'unité d'affichage de chaque catégorie de mesure. Ce n'est pas une bascule globale métrique/impérial — c'est granulaire par grandeur.

Catégorie Unités sélectionnables
movement mm, cm, inch
tilt deg, mm/m
acceleration mg, mm/s²
ppv mm/s
waterLevel cm, m
temperature °C, °F
humidity %
photoresistor %

Les catégories se regroupent en groupes (deformationTilt, vibration, temperature, humidity, photoresistor, waterLevel) pour l'affichage. Le mapping backend↔frontend est un 1→N : Vibration côté backend se déploie en acceleration + ppv ; Humidity côté backend se déploie en humidity + photoresistor (géré dans adapter/).

API publique

import {
  UnitPreferenceScreen,   // l'écran de navigation
  useUnitPreferences,     // le hook d'orchestration
  getUnitsForCategory,    // validateurs de domaine
  // + types de domaine : CategoryId, UnitId, Unit, categories, units
} from 'src/features/unitPreference';

Architecture

unitPreference/
├── index.ts        API publique (re-exporte domaine + hook + écran)
├── domain/         logique PURE, sans React :
│                     category.ts, unit.ts, group.ts, measurement.ts, catalog.ts,
│                     groupIndex.ts (getCategoriesByGroup), validators.ts, schemas.ts
├── adapter/        unitPreference.mapper.ts — categoryToBackend / unitToBackend / backendToUnit,
│                     mapBackendToFrontend (le déploiement 1→N)
├── api/queries/    GetUnitPreference.graphl.ts (sic : faute dans le nom de fichier), UpdateUnitPreference.graphql.ts
├── graphql/        types.ts — enums (Unit, UnitCategory) + types requête/réponse
├── hooks/
│   ├── unitPreference.store.ts   store zustand : preferences, setPreference, hydrate, reset
│   └── useUnitPreferences.ts     query Apollo→hydrate ; updatePreference avec update optimiste + rollback
├── components/     CategoryItem/ (ligne mémoïsée), UnitSelector/ (sélecteur d'unité segmenté)
├── screens/        UnitPreferenceScreen.tsx — SectionList groupée par groupe
└── test-utils/     mockUnitPreferenceState.ts, mockUseUnitPreferences.ts

Les objets de domaine sont as const avec des types de clés dérivés : type CategoryId = keyof typeof categories, type UnitId = keyof typeof units.

Décisions clés

  • En couches / quasi-hexagonal : domain/ (pur) ← adapter/ (mapping) ← hooks/ (orchestration) ← components//screens/ (UI). Le domaine n'a aucune dépendance framework et est testé unitairement de façon isolée.
  • Zustand pour l'état local à la feature (pas un duck Redux) — voir ADR-003.
  • Updates optimistes avec rollback : updatePreference écrit dans le store immédiatement et revient en arrière si la mutation échoue.

Utilisation

// UnitPreferenceScreen est enregistré dans ProjectsStackScreen (route : UnitPreference),
// atteint depuis l'écran de paramètres d'un projet.

// Consommer le hook directement (nécessite un id de projet) :
const { preferences, updatePreference, loading } = useUnitPreferences({ id: projectId });

preferences.temperature; // → 'degC'
updatePreference('temperature', 'degF'); // optimiste ; rollback auto en cas d'erreur

Namespace i18n : unitPreference (src/i18n/<lang>/features/unitPreference.json).

Tests

Tests co-localisés extensifs : domaine pur (domain/*.test.ts), mapper (adapter/*.test.ts), hook (hooks/useUnitPreferences.test.ts — mocke Apollo, réinitialise le store zustand dans beforeEach), composants (components/**/*.test.tsx). Les mocks réutilisables vivent dans test-utils/.

yarn jest src/features/unitPreference