01 - Technologies & architecture
1. Stack technique (réel, vérifié dans le dépôt)
ℹ️ La demande mentionnait « React, Java, déploiement… ». Il n'y a aucun Java dans ce projet, ni backend applicatif. La stack est 100 % JavaScript + Supabase.
Frontend
| Techno | Version | Rôle |
|---|---|---|
| React | ^18.3.1 |
UI (composants fonctionnels + hooks) |
| Vite | ^5.3.1 |
Build & dev server |
| Tailwind CSS | ^3.4.4 |
Styling utilitaire (tout le style est inline en classes) |
| lucide-react | ^0.383.0 |
Icônes |
@types/react |
^18.3.3 |
Types présents… mais le code est en JSX pur, pas TypeScript |
| PostCSS + autoprefixer | - | Pipeline CSS |
Backend / plateforme
| Techno | Rôle |
|---|---|
Supabase (@supabase/supabase-js ^2.49.1) |
Base PostgreSQL, Auth (email/mot de passe), Storage (bucket privé employee-docs) |
| PostgreSQL + RLS | Stockage relationnel, Row Level Security (⚠️ permissive, voir ADR-0003) |
Déploiement
| Techno | Rôle |
|---|---|
| Netlify | Hébergement statique du SPA ; netlify.toml fait le fallback SPA (/* → /index.html) |
| Variables d'env | VITE_SUPABASE_URL, VITE_SUPABASE_ANON_KEY (injectées au build) |
Il n'y a donc pas de couche serveur : le navigateur parle directement à
Supabase avec la clé publique anon. Toute la logique métier (paie, droits,
périmètres) s'exécute côté client.
2. Schéma de la base de données
2.1 Tables documentées (dans les fichiers supabase-*.sql)
app_users (id, auth_id→auth.users, email, role[admin|manager], scope_type?, scope_id?, created_at)
entities (id, name, siret, address, created_at) ← sociétés / entités juridiques
locations (id, entity_id→entities, name, address, manager, created_at) ← établissements / boutiques
employees (id, first_name, last_name, birth_date, email, phone, address,
contract_type[CDI|CDD|Alternance|Stage], start_date, end_date, position,
work_time, gross_salary, entity_id, location_id,
trial_start/duration/renewal/end, documents(JSONB), status[actif|sorti],
nationality, sex, qualification, notes, cp_balance,
-- via migrations contrats : alternance_rhythm, cdd_reason, cdd_replaced_name,
-- cdd_renewable, cdd_renewal_end, stage_gratification, stage_hours_max, stage_convention
-- via migration paie : weekly_hours, sale_bonus_amount, transport_amount, transport_justified)
leave_requests (id, employee_id, type, start_date, end_date, days,
status[en_attente|validé|refusé], note, created_at)
monthly_payroll (id, employee_id, month, hours_worked, hours_overtime, sunday_hours,
sale_count, sale_bonus, transport_reimbursement, notes, UNIQUE(employee_id,month))
schedule_templates (id, name, entity_id, slots(JSONB), ...) ← planning type récurrent
weekly_schedules (id, entity_id, week_start, status[brouillon|publié], slots(JSONB), published_at, UNIQUE(entity_id,week_start))
2.2 ⚠️ Tables/objets utilisés par le code mais ABSENTS des migrations
Le code appelle ces objets, qui n'existent dans aucun fichier supabase-*.sql :
| Objet | Utilisé où | Preuve |
|---|---|---|
transport_receipts |
dépôt/validation des justificatifs transport | App.jsx supabase.from("transport_receipts") |
planning_notes |
notes sur le planning | App.jsx supabase.from("planning_notes") |
contract_history |
historique/versions de contrat + documents | App.jsx supabase.from("contract_history") |
app_users.scope_type / scope_id |
périmètre manager | lu/écrit dans App.jsx, absent de supabase-schema.sql |
app_users.role = 'employee' |
rôle salarié | isEmployee, alors que le CHECK SQL n'autorise que `admin |
bucket Storage employee-docs |
documents & justificatifs | storage.from("employee-docs") |
Conséquence : on ne peut pas recréer une base fonctionnelle à partir du dépôt. C'est une dette bloquante (P0) - voir backlog.
2.3 Modèle de données (relations)
entities (société) 1───* locations (boutique)
│ │
│ └──* employees.location_id
└──────────────────────────* employees.entity_id
employees 1───* leave_requests
employees 1───* monthly_payroll (1 ligne / mois)
employees 1───* transport_receipts (1 justificatif / mois)
employees 1───* contract_history (versions successives du contrat)
entities 1───* weekly_schedules / schedule_templates (planning par société, filtré par ville côté app)
app_users *───1 auth.users (compte d'authentification)
3. Architecture applicative
3.1 Vue d'ensemble
┌─────────────────────────────────────────────────────────┐
│ Navigateur (SPA React servi par Netlify) │
│ │
│ App.jsx ── AuthScreen | HRApp │
│ ├─ Pages : Dashboard, Planning, Salariés, Congés, │
│ │ Paie, Transport, Registre, Paramètres, │
│ │ Utilisateurs, (+ vues Employé) │
│ ├─ Logique métier : alertes, calcul paie, périmètres │
│ └─ UI primitives : Btn, Modal, Badge, Card, Input… │
│ │
│ │ @supabase/supabase-js (clé anon publique) │
└─────────┼────────────────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────┐
│ Supabase │
│ ├─ Auth (email/mot de passe) │
│ ├─ PostgreSQL + RLS (⚠️ permissive) │
│ └─ Storage : bucket privé employee-docs (URLs signées)│
└─────────────────────────────────────────────────────────┘
3.2 Chargement des données
Toutes les données sont chargées d'un bloc au montage via un Promise.all
de 11 requêtes select("*") (loadData, App.jsx:363), puis stockées dans des
useState au niveau de HRApp. Il n'y a pas de cache, de pagination, ni de
rechargement ciblé : chaque écran travaille sur les tableaux en mémoire, et les
mutations mettent à jour l'état local en parallèle de l'écriture Supabase
(optimistic-like, mais sans rollback en cas d'échec réseau).
Implications :
- ✅ Simplicité, réactivité immédiate entre écrans.
- ⚠️ Passage à l'échelle :
select("*")sans filtre ni pagination - se dégrade avec le nombre de salariés/plannings ; charge tout côté client. - ⚠️ Sécurité : puisque la RLS est ouverte,
select("*")renvoie toutes les lignes, y compris hors périmètre du manager/salarié (le filtrage est purement visuel,visibleEmployees).
3.3 Rôles & périmètres (uniquement applicatifs)
- admin : accès à tout, gestion des utilisateurs.
- manager : pages Dashboard, Planning, Congés, Transport ; périmètre limité
par
scope_type(city|entity|location) +scope_id. Filtrage calculé en mémoire (visibleEmployees,isEmployeeInScope). - employee : self-service (Ma fiche, Mon planning, Mon transport).
⚠️ Ce cloisonnement n'existe que dans le JS. La base ne le connaît pas (ADR-0003).
3.4 Sécurité & confidentialité - synthèse
| Sujet | État | Risque |
|---|---|---|
| RLS | Permissive (tout authentifié = accès total) | 🔴 Élevé |
| Contrôle des rôles | Côté client uniquement | 🔴 Élevé |
| Clé Supabase | anon publique dans le bundle (normal), mais sans RLS stricte derrière |
🔴 Élevé |
| Stockage documents | Bucket privé + URLs signées 120 s | 🟢 Correct |
| Mots de passe | Gérés par Supabase Auth | 🟢 Correct |
| Confirmation email | Désactivée (procédure README) | 🟡 Acceptable en interne |
| RGPD (données perso, salaires, RIB) | Pas de minimisation/cloisonnement DB | 🟠 À traiter |
Détails et remédiation : ADR-0003 et prompts/05-durcir-securite-rls.
4. Points techniques notables
- Dates : le code mélange calculs locaux (
getMonday,addDays, commentés pour éviter le décalage UTC) et calculs UTC (toISOString().slice(0,10)danscomputeHoursFromPlanning,alerts). Risque de décalage d'un jour selon le fuseau/l'heure - à uniformiser (tout en local). - Calcul de paie : heures issues du planning publié, heures sup = au-delà des
heures contractuelles du mois (
weekly_hours × semaines), bonus dimanche (+7 h/dimanche travaillé), prime de vente (sale_count × sale_bonus_amount), transport remboursé à 50 % si justificatif. Logique correcte mais non testée. - Gestion d'erreurs : essentiellement
console.error+toast. Pas d'Error Boundary React, pas de remontée structurée. - Accessibilité / i18n : UI en français en dur, pas d'a11y explicite.
- Performance build : un seul chunk (pas de code-splitting) - acceptable vu la taille, à surveiller si l'app grandit.
Voir aussi 02 - Conventions & organisation du code.
