Docs menu
Phase 3 — Mode ÉTABLISSEMENT mobile (brief d'implémentation)#
⚠️ Document historique#
Brief d'implémentation du prototype, conservé pour la trace des décisions. Il ne décrit pas la plateforme actuelle. Pour l'état réel, voir
ARCHITECTURE.mdetAPI.md.
Brief pour Claude Code (modèle
fable). Implémente la Phase 3 du SPEC Staff : app mobile, mode ÉTABLISSEMENT, sans casser Phase 1 (prestataire) ni Phase 2 (web).
Principes obligatoires#
- NE CASSER RIEN : ne modifie ni
app/(tabs)/*, niapp/onboarding/*(prestataire), niapp/role.tsxau-delà du branchement du bouton établissement, niapps/web/*, nipackages/ui/src/tokens.ts(les valeurs existantes). - Design system unique :
apps/mobile/src/lib/theme.ts+packages/ui(tokens sémantiques). Aucune couleur brute dans les écrans. Vert#059669. - Textes légaux EXACTS : SEPA (SPEC §6.1) et engagement annuel (SPEC §6.2) — recopiés au caractère près, non modifiables.
- Français partout, TypeScript strict,
tabular-numspour tous les montants/ distances/horaires/chronos. - Zéro texte blanc-sur-blanc, zéro bouton/carte sans fond. Chaque écran COMPLET et peuplé de données mockées crédibles Martinique.
- Données mockées cohérentes avec le web : mêmes établissements, mêmes
prestataires, mêmes missions (voir
apps/web/src/data/*).
1. Types partagés (packages/ui/src/types.ts — AJOUTS seulement, ne rien casser)#
Ajouter en fin de fichier :
/** Régions DOM-TOM et leur taux de TVA (SPEC §8). */
export interface RegionTva {
id: string;
label: string;
/** Taux de TVA applicable, en pourcentage (8.5 ou 0). */
tva: 8.5 | 0;
}
/** Formule d'abonnement établissement (SPEC §6.2). */
export type FormuleAbonnement = "1_site" | "3_sites" | "5_plus";
/** Établissement connecté (espace mobile + backup web). */
export interface Etablissement {
id: string;
nom: string;
enseigne: string;
adresse: string;
ville: string;
siret: string; // SIRET du site
secteur: SectorId;
region: string; // id region (martinique, ...)
responsable: string;
email: string; // email de facturation
telephone: string;
abonnement: {
formule: FormuleAbonnement;
montantHT: number; // €/mois HT
};
sepa: {
iban: string;
bic: string;
titulaire: string;
mandateSigned: boolean;
signatureHorodatage?: string; // ISO, simulé
};
missionsTotal: number;
depensesMois: number; // € HT dépensés en missions ce mois
prochaineEcheance: string; // ISO
cguAccepted: boolean;
engagementSigned: boolean;
engagementSignatureHorodatage?: string; // ISO, simulé
}
/** Candidat à une mission (côté établissement). Liste capée 5–8. */
export interface CandidatMission {
id: string;
prenom: string;
nom: string;
noteGlobale: number; // 1–5
niveau: "standard" | "or" | "platine" | "diamant";
missionsTotal: number;
distanceKm: number;
}
/** Statut de pointage d'une mission (côté établissement, suivi). */
export type PointageEtabState = "en_attente" | "en_cours" | "termine";
export interface PointageEtab {
state: PointageEtabState;
startedAt?: string; // "HH:mm"
endedAt?: string; // "HH:mm"
dureeH?: number; // heures validées
gpsConfirme: boolean;
}
/** Mission enrichie côté établissement (suivi + notation). */
export interface MissionEtab {
id: string;
etablissementId: string; // rattachée à l'établissement connecté
secteur: SectorId;
fonction: string;
date: string; // ISO
heureDebut: string; // "HH:mm"
dureeH: number;
remuneration: number; // €/h
rayonKm: number; // 10 ou 25 (extension simulée)
ville: string;
candidats: CandidatMission[]; // capé 5–8
prestataireRetenuId?: string;
pointage: PointageEtab;
statut: "a_venir" | "en_cours" | "terminee";
prestataireNote?: { noteGlobale: number; critères: NotationEtab; commentaire: string; notePrive?: string };
notifiees?: number; // nb prestataires notifiés autour (écran de confirmation)
}
/** Notation d'un prestataire par un établissement (SPEC §7). 4 critères + 5★. */
export interface NotationEtab {
ponctualite: number; // 1–5
presentation: number; // 1–5
attitude: number; // 1–5
competences: number; // 1–5
}
/** Facture d'abonnement établissement (PDF simulé). */
export interface FactureEtab {
id: string;
date: string; // ISO
montantHT: number;
tva: number; // 8.5 ou 0 selon région
montantTTC: number;
statut: "payee" | "a_payer";
url: string; // URL simulée du PDF
}Ajouter aussi un export de la liste des régions avec TVA dans tokens.ts (à la suite de
l'export regions existant — ne pas modifier regions) :
/** Régions DOM-TOM avec leur taux de TVA (SPEC §8 TVA). */
export const regionsTva: RegionTva[] = [
{ id: "martinique", label: "Martinique", tva: 8.5 },
{ id: "guadeloupe", label: "Guadeloupe", tva: 8.5 },
{ id: "reunion", label: "La Réunion", tva: 8.5 },
{ id: "guyane", label: "Guyane", tva: 0 },
{ id: "saint-martin", label: "Saint-Martin", tva: 0 },
{ id: "saint-barthelemy", label: "Saint-Barthélemy", tva: 0 },
];
/** Calcule le taux de TVA d'une région (par id). 8,5 % DOM / 0 % Guyane & collectivités. */
export function tvaPourRegion(regionId: string): 8.5 | 0 {
for (const r of regionsTva) {
if (r.id === regionId) return r.tva;
}
return 0;
}Et exporter regionsTva et tvaPourRegion depuis packages/ui/src/index.ts.
2. Textes légaux EXACTS — module partagé#
Créer packages/ui/src/legal.ts (nouveau) :
/** Mandat SEPA — SPEC §6.1 (texte EXACT, non modifiable). */
export const MANDAT_SEPA_TEXTE =
"En confirmant ce mandat, vous autorisez (A) Staff SAS à envoyer des instructions à votre banque " +
"pour débiter votre compte, et (B) votre banque à débiter votre compte conformément aux " +
"instructions de Staff SAS. Vous bénéficiez d'un droit à remboursement par votre banque selon les " +
"conditions décrites dans la convention que vous avez passée avec elle. Toute demande de " +
"remboursement doit être présentée dans les 8 semaines suivant la date de débit de votre compte. " +
"Pour les débits non autorisés : 13 mois maximum.";
/** Engagement annuel — SPEC §6.2 (texte EXACT, case à cocher, non modifiable). */
export const ENGAGEMENT_ANNUEL_TEXTE =
"En cochant cette case, vous souscrivez un abonnement Staff d'une durée minimale de 12 mois " +
"(valeur totale : 358,80 € HT pour la formule 1 site, soit 29,90 € × 12 mois). À l'issue de cette " +
"période, l'abonnement se renouvelle automatiquement par tacite reconduction pour des périodes " +
"successives de 12 mois. Vous serez informé du renouvellement 3 mois avant l'échéance. En cas de " +
"résiliation anticipée, le solde des mois restants sera facturé à hauteur de 50 %. ";Exporter depuis index.ts.
3. Données mockées mobile (apps/mobile/src/data/)#
etablissement.ts (nouveau)#
L'établissement connecté = etablissements[0] du web (Station Total Energies — Génipa,
responsable fictif, Ducos, Martinique, formule 3 sites à 79,90 € HT, SEPA signé).
Reprendre exactement les valeurs du web (apps/web/src/data/etablissements.ts élément e1)
pour l'objet Etablissement (mobile). Exporter etablissementCourant: Etablissement.
missions-etab.ts (nouveau)#
Missions de l'établissement connecté (e1 = Génipa + Aimé Césaire, ce sont e1 et e7 du web).
Reprendre ~6 missions à partir du web (apps/web/src/data/missions.ts où
etablissement contient "Génipa" ou "Aimé Césaire"), mappées en MissionEtab avec
etablissementId: "e1", candidats, pointage, statut. Inclure : 1 en cours, 1 a_venir, 2 terminees,
1 en attente de notation (terminee sans prestataireNote), 1 a_venir à publier. Cohérent avec
le web (mêmes prestataires p1..p12, mêmes notes/niveaux).
factures-etab.ts (nouveau)#
4–6 factures d'abonnement (mensuelles) de l'établissement connecté, montantHT=79.90,
tva=8.5 (Martinique), TTC=86,69 €, statut payee/a_payer, URL simulée https://staff.jaikin.dev/factures/F-XXXX.pdf.
4. TVA — test automatisé#
Créer apps/mobile/tests/tva.test.ts (vitest) testant tvaPourRegion :
- Martinique/Guadeloupe/Réunion → 8.5
- Guyane/Saint-Martin/Saint-Barthélemy → 0
- région inconnue → 0 Et un test calculant le TTC d'une facture 79,90 € HT en Martinique → 86,69 € TTC (arrondi 2).
5. Branchement du rôle (app/role.tsx)#
Le bouton « Je suis établissement » doit pousser vers /etablissement/onboarding/step-1
au lieu de /onboarding/step-1. Le bouton prestataire reste sur /onboarding/step-1.
Un seul changement de onPress sur la carte établissement.
6. Onboarding établissement (3 étapes)#
app/etablissement/onboarding/_layout.tsx (Stack, headerShown: false)#
Group etablissement/onboarding avec un layout Stack neutre.
app/etablissement/onboarding/step-1.tsx — Établissement#
Réutiliser le gabarit onboarding (progress 1/3, retour, eyebrow, title, footer primaire).
Champs : Nom de l'établissement, Adresse, SIRET du site, Secteur (picker parmi les secteurs
actifs : Station-service, Netttoyage — réutiliser sectors filtré active), Région (picker
parmi 6 — réutiliser regions), TVA calculée dynamiquement affichée en lecture seule sous
la région (8,5 % ou 0 % — utiliser tvaPourRegion), Email de facturation, Responsable.
Validation : tous champs renseignés + SIRET 14 chiffres + email valide → push step-2.
app/etablissement/onboarding/step-2.tsx — Abonnement + SEPA + Engagement#
- Choix formule : 3 cartes (1 site 29,90 € HT/mois · 3 sites 79,90 € HT/mois · 5+ sites « Devis »). La 3 sites est présélectionnée ( cohérent avec l'établissement connecté mocké).
- Bloc Mandat SEPA : champs Titulaire, IBAN, BIC (BIC auto-déduit de l'IBAN, lecture seule,
simulé — afficher un BIC mocké). Puis le texte légal SEPA EXACT (
MANDAT_SEPA_TEXTE) dans un encadrésurfaceSubtle, encaption,textSecondary(texte long lisible, jamais blanc sur blanc). Case à cocher « Je signe ce mandat SEPA » + bouton « Signer » = clic horodaté (simulé : affiche « Signé le JJ/MM/AAAA à HH:mm »). - Bloc Engagement 12 mois : case à cocher avec le texte EXACT
(
ENGAGEMENT_ANNUEL_TEXTE) en regard. Signature = clic horodaté simulé. - Validation : formule choisie + SEPA signé + engagement coché+signé → push step-3.
app/etablissement/onboarding/step-3.tsx — Activation#
Récap complet : établissement (nom, adresse, SIRET, secteur, région + TVA, email, responsable),
abonnement (formule + montant HT/mois), SEPA (titulaire, IBAN masqué **** dernière partie, signé
le …), engagement (signé le …). Case CGU obligatoire. Bouton « Accéder au tableau de bord » →
router.replace("/etablissement/missions") (tab bar établissement, onboarding consommé).
7. Tab bar établissement#
app/(etablissement)/_layout.tsx (nouveau)#
Group route (etablissement) avec Tabs. 4 onglets :
missions→ « Missions » (dashboard) — icôneLayoutGridpublier→ « Publier » — icônePlus(ouSquarePen)suivi→ « Suivi » — icôneClipboardList(ouListChecks)profil→ « Profil » — icôneUser
Mêmes screenOptions que le prestataire (glass, activeTintColor primary, height 68) —
réutiliser le style exact de app/(tabs)/_layout.tsx.
app/(etablissement)/missions.tsx — Dashboard#
- En-tête
Screen« Missions ». - Hero card (dégradé
hero) : « Bonjour {responsable} », missions actives (nb), prestataires en mission ce mois (nb), dépenses du mois (€ HT, tabular-nums), prochaine échéance (date). - Section « Missions actives » : cartes MissionEtab (fonction, secteur chip, date/heure, durée,
rémunération, rayon, nb candidats) → tap ouvre
/etablissement/suivi/[id]. - Section « À venir » : missions a_venir.
- États : loading skeleton (300ms), empty « Aucune mission active — publiez-en une ».
- Données :
etablissementCourant+missionsEtab.
app/(etablissement)/publier.tsx — Publier une mission#
Form : secteur (picker actifs), fonction (input), date (input JJ/MM/AAAA ou picker natif
DatePicker simulé — input texte suffit), heure début (HH:mm), durée h (stepper 2–8, min 2 h),
rémunération €/h (input numérique, défaut 12), rayon (segmented 10 km / 25 km extension
simulée). Bouton « Publier la mission » → écran de confirmation publier-confirmation.tsx
« X prestataires notifiés autour » (X simulé selon rayon : 10 km → 12, 25 km → 28). Bouton
« Voir le suivi » → /(etablissement)/suivi/[id] ou retour missions.
app/(etablissement)/suivi.tsx — Liste de suivi#
Toutes les missions (actives + passées) avec statut pointage (en attente → en cours → terminé).
Tap → /etablissement/suivi/[id].
app/(etablissement)/suivi/[id].tsx — Suivi mission détail#
- En-tête : fonction + établissement + statut.
- Bloc candidats : liste capée 5–8 (afficher
mission.candidats), chaque candidat = carte (prénom nom, niveau badge Or/Platine/Diamant, note 5★, missions total, distance) + bouton « Sélectionner » (état pressed). Le prestataire retenu est mis en avant (liseré vert + check). - Bloc QR code à afficher sur place : généré (faux QR — réutiliser le composant web
FauxQRs'il existe, sinon un grid SVG/View de 21×21 modules deterministe depuis l'id mission). Bouton « Afficher le QR en grand » (plein écran modal). Légende « À scanner par le prestataire sur place ». - Bloc pointage : statut (en attente → en cours → terminé), heures (startedAt → endedAt, dureeH).
Badge GPS « Position confirmée » si
gpsConfirme. - Si terminée et non notée → bouton « Noter le prestataire » →
/etablissement/noter/[id]. - Si terminée et notée → récap note (5★ + 4 critères + commentaire + note privée masquée « Note privée enregistrée — interne Staff »).
app/(etablissement)/noter.tsx — Liste missions à noter#
Missions terminees sans note → tap /etablissement/noter/[id].
app/(etablissement)/noter/[id].tsx — Noter le prestataire#
- 5★ global (tap étoiles 1–5, grande taille).
- 4 critères : Ponctualité / Présentation / Attitude / Compétences — chacun 5★ (plus petit).
- Commentaire public (≤ 200 car, compteur live, textarea).
- Note privée établissement (interne Staff, jamais publique) — textarea séparé, label « Note privée — visible par Staff uniquement, jamais par le prestataire. »
- Bouton « Publier la note » → confirmation + retour suivi.
- États : disabled tant que note globale < 1 ou commentaire vide.
app/(etablissement)/profil.tsx — Abonnement / SEPA / factures#
- Carte établissement (nom, SIRET, secteur, région, responsable).
- Bloc abonnement : formule + montant HT/mois + prochaine échéance.
- Bloc SEPA : titulaire, IBAN masqué, BIC, « Mandat signé le … ».
- Bloc Factures : liste
facturesEtab(date, montant HT, TVA, TTC, statut). Tap → ouvrefacture/[id]. - Lien « Modifier mon établissement » (simulé, alerte).
app/(etablissement)/facture/[id].tsx — Facture PDF simulé#
Récap facture + « Télécharger le PDF » (simulé, alerte/lien url). Bouton retour.
8. Composants partagés à réutiliser / créer#
- Réutiliser
@/components/screen,@/components/ui(Card, Chip, PrimaryButton, SectionTitle, FieldLabel),@/components/press-scale,@/components/app-header,@/components/onboarding-shell. - Créer
@/components/faux-qr.tsx: génère un QR visuel (grid 21×21 deViewcarrés, déterministe depuis un hash de l'id mission). Noir/blanc, bord arrondi2xl, liseré blanc. - Créer
@/components/etoiles.tsx: composant 5★ tappable (props: value, onChange, size, disabled). Étoile pleine =colors.primary(ougoldpour les badges niveau), vide = bordsemantic.textTertiary. - Créer
@/components/badge-niveau.tsx: badge Or/Platine/Diamant avec dégradé noble (gradients.rewardpour Or, slate pour Platine,diamondpour Diamant) — petit chip. - Créer
@/components/checkbox.tsx: case à cocher (carré arrondism, checkChecklucide, état pressed). Couleur check =colors.primary.
9. Validation (DoD)#
cd apps/mobile && npx tsc --noEmit: 0 erreur.cd apps/mobile && npx expo export --platform web: réussit, routes(etablissement)/*présentes dansdist.pnpm --filter @staff/mobile test: vert (onboarding + tva tests).pnpm --filter @staff/web build: toujours OK (rien cassé côté web — ne pas le modifier).git add -A && git commit -m "feat(mobile): mode ÉTABLISSEMENT Phase 3 ..."(français). Branch DEV. NE TOUCHE PAS PREVIEW/PRODUCTION. JAMAISvercel deploy.
10. Garde-fous (rappels)#
- Zéro texte blanc-sur-blanc. Sur fond vert/dégradé → blanc ; sur surface blanche → encre.
- Chaque écran COMPLET et peuplé (données mockées, états loading/empty couverts).
- Ne change pas les textes légaux/exacts. Ne casse pas Phase 1/2.
- Les types
@staff/uine sont qu'en AJOUTS (ne rien supprimer/renommer).