DOC
Accueil/ Projets/ MJY Task Platform/ Audit Technique Complet - Plateforme MJY Holding

Audit Technique Complet - Plateforme MJY Holding

Date : 7 juillet 2026 Périmètre : src/, server.js, netlify/functions/, seed-db.js, configuration, documentation, historique Git Version auditée : commit f00ce48 (branche main)


1. Résumé exécutif

Le projet a réussi sa migration d'un monolithe index.html (~4 200 lignes) vers une architecture React 18 + Vite modulaire, avec persistance Supabase (PostgreSQL + Realtime + RLS) et authentification par PIN signée en JWT côté serveur. La séparation en pages / components / services / utils est globalement saine et lisible.

Cependant l'audit relève :

Gravité Nombre Exemples
🔴 Critique 4 setState pendant le rendu (Error #310), incohérence de champ ts/date sur les notes, PIN en clair, secret JWT par défaut
🟠 Majeur 6 Duplication server.jsnetlify/functions, dossiers hooks/ et context/ vides, listes d'admins incohérentes, workflow dev cassé
🟡 Mineur 8 Textes obsolètes, cloudClient fantôme, absence d'ESLint, tests minimaux, requêtes N+1, etc.

Note globale de structure : conforme aux conventions React à ~70 %. Les fondations sont bonnes, mais la logique métier (auth, CRUD, synchro) n'a jamais été extraite dans hooks/ / context/ comme prévu par audit_refactoring.md - elle reste dispersée dans les pages et dans un singleton module (storage._cache).


2. Conformité à la structure d'un projet React

2.1 Arborescence observée

src/
├── App.jsx                 # Racine + routage par rôle + cycle d'hydratation
├── main.jsx                # Point d'entrée ReactDOM
├── index.css
├── assets/                 # Logos + .gitkeep
├── components/
│   ├── common/             # Button, Modal, Tag, BackupButtons, SyncMenu
│   ├── tasks/              # TaskCard, TaskDetail, TaskForm, StatsBar,
│   │                       #   GroupedListView, PhaseGroupView, VocalCreator
│   ├── projects/           # ProjectModal, PhasesManager
│   ├── members/            # MemberModal
│   └── import/             # ImportModal
├── pages/                  # Auth, AdminDashboard, SuperManagerDashboard, EmployeeDashboard
├── services/               # supabaseClient.js, storage.js
├── utils/                  # taskHelpers.js
├── hooks/                  # ⚠️ VIDE (.gitkeep seulement)
├── context/                # ⚠️ ABSENT (jamais créé)
└── __tests__/              # App.test.jsx

2.2 Verdict par convention

Convention attendue État Commentaire
pages/ (écrans) ✅ Conforme 4 écrans bien séparés. (« pages » ≈ « screens », convention valide.)
components/ par domaine ✅ Conforme Découpage common / tasks / projects / members / import exemplaire.
services/ ✅ Conforme Accès Supabase + couche de cache isolés.
utils/ ✅ Conforme Helpers purs.
hooks/ 🟠 Non respecté Dossier vide. useAuth, useTasks, useRealtime planifiés dans audit_refactoring.md jamais implémentés. La logique vit dans les pages.
context/ 🟠 Absent Aucun React Context. L'état partagé transite par props et par un singleton module _cache + le hack global window.__forceAppRerender.

2.3 Y a-t-il des éléments « au mauvais endroit » ?

Oui, quelques-uns :

  1. Logique métier dans les pages plutôt que dans hooks/. AdminDashboard.jsx (622 lignes) et SuperManagerDashboard.jsx (433 lignes) contiennent chacun la logique complète de CRUD tâches/projets/membres, la synchro et les filtres. saveMember, deleteProject, handleImportCreate, changeRole… devraient être des hooks réutilisables (useMembers, useProjects). Le code de saveMember/deleteMember/saveProject/deleteProject est quasi dupliqué à l'identique entre AdminDashboard et SuperManagerDashboard.

  2. Re-export parasite dans un composant. components/common/SyncMenu.jsx:151 re-exporte forcePullFromCloud, forcePushToCloud, wipeCloud. Un composant UI ne doit pas ré-exporter des fonctions de service : c'est le rôle de services/storage.js.

  3. window.__forceAppRerender (App.jsx:16) : mécanisme de rafraîchissement global via window. C'est un contournement de l'absence de state manager/Context - à remplacer par un Context ou un store.

  4. Constantes de sécurité dans utils/. ADMIN_PIN, EMPLOYEE_PIN, SUPER_MANAGER_PIN, SUPER_ADMIN_EMAILS sont dans utils/taskHelpers.js - donc livrés dans le bundle client. Ces valeurs sensibles n'ont rien à faire côté front (voir §4).


3. Vérification demandée : imports en début de fichier

Tous les import statiques sont bien placés en tête de fichier. Aucun import statique indenté / au milieu d'un fichier détecté (grep sur src/, netlify/, server.js, seed-db.js).

⚠️ Réserve - imports dynamiques redondants. App.jsx et Auth.jsx utilisent import('./services/...') à l'intérieur de fonctions, sur des modules déjà chargés statiquement ailleurs :

Fichier Ligne(s) Module importé dynamiquement Déjà importé statiquement ?
App.jsx 24, 35, 141 ./services/supabaseClient (setSessionToken) Oui, indirectement via storage.js
App.jsx 52, 58, 112 ./services/storage (subscribeToRealtimeChanges, pullFromSupabase) Oui - App.jsx:7 importe déjà storage
Auth.jsx 79 ../services/supabaseClient (setSessionToken) -

Ces import() n'apportent aucun code-splitting utile (le module est déjà dans le graphe). Ils compliquent la lecture et pourraient masquer des erreurs. Recommandation : remplacer par des imports statiques en tête de fichier (ex. App.jsx:7 → ajouter subscribeToRealtimeChanges, pullFromSupabase).


4. Problèmes 🔴 CRITIQUES

C1 - setState pendant le rendu (source de l'« Error #310 » récurrente)

Fichier : src/App.jsx

Plusieurs mutations d'état sont déclenchées pendant le rendu de App, ce qui est interdit par React et provoque exactement l'erreur que les 4 derniers commits tentent de corriger :

  • App.jsx:166setEmployeeMode(false) appelé dans le corps de rendu (branche admin).
  • App.jsx:181if (!ws) { onLogout(); return null; } : onLogout() appelle setSession(null) pendant le rendu.

Impact : re-rendus en boucle possibles, avertissements React, comportement instable au changement de rôle. Ces corrections doivent passer par un useEffect (déplacer la logique de garde hors du rendu).

C2 - Champ de date des notes incohérent (ts vs date) → « Invalid Date »

Fichiers : src/components/tasks/TaskDetail.jsx, src/services/storage.js, docs/supabase_schema.sql

  • TaskDetail.jsx:78 crée une note avec le champ ts : { id, text, author, role, ts }.
  • TaskDetail.jsx:249 affiche n.ts.
  • Mais storage.js (lecture :173, écriture :246) et la table task_notes (schéma :77) utilisent la colonne date.

Conséquence : une note s'affiche correctement juste après sa création (objet en mémoire avec ts), puis après un aller-retour cloud / rafraîchissement Realtime, n.ts devient undefined → « Invalid Date ». Le champ ts n'est jamais persisté. Correctif : uniformiser sur date (créer la note avec date:, afficher n.date).

C3 - Codes PIN et e-mails admin en clair côté client

Fichier : src/utils/taskHelpers.js (lignes 1–4)

export const ADMIN_PIN = "1052";
export const EMPLOYEE_PIN = "4011";
export const SUPER_MANAGER_PIN = "9999";
export const SUPER_ADMIN_EMAILS = [
  "j.gros1052@gmail.com",
  "sanceymarco@hotmail.com",
];

Ces valeurs sont compilées dans le bundle JS public et même affichées à l'écran dans Auth.jsx:156 et :161 (« Code manager partagé (1052) », « (4011) »). De plus, la colonne members.pin est stockée en clair dans PostgreSQL (supabase_schema.sql:46), alors que audit_refactoring.md prévoyait un pin_hash. Impact : n'importe quel visiteur peut se connecter en tant que manager/collaborateur. La sécurité repose entièrement sur des secrets publics. Correctif : ne jamais exposer de PIN côté client ; hacher les PIN en base (bcrypt) ; supprimer les PIN partagés par défaut.

C4 - Secret JWT par défaut 'secret'

Fichiers : server.js:17, netlify/functions/auth.js:6

const jwtSecret = process.env.SUPABASE_JWT_SECRET || "secret";

Si SUPABASE_JWT_SECRET n'est pas défini en production, tous les JWT sont signés avec 'secret' → n'importe qui peut forger un token super_manager et contourner le RLS Supabase. Correctif : faire échouer le démarrage si le secret est absent (pas de fallback silencieux).


5. Problèmes 🟠 MAJEURS

M1 - Backend dupliqué : server.js (Express) ↔ netlify/functions/*

Les trois endpoints (/api/auth, /api/public_data, /api/import) sont implémentés deux fois : dans server.js (hébergement Node/Hostinger) et dans netlify/functions/{auth,public_data,import}.js (serverless). Toute correction doit être répliquée manuellement. La dérive a déjà commencé (voir M2). Recommandation : extraire la logique commune (signJWT, requêtes membres) dans un module partagé importé par les deux cibles.

M2 - Listes d'administrateurs incohérentes entre client et serveur

  • Client (taskHelpers.js:4) : super-admins = j.gros1052@gmail.com, sanceymarco@hotmail.com.
  • Serveur (server.js:105 et netlify/functions/auth.js:130) : admins globaux = marco@myj.com, julien@myj.com.

Ces domaines ne correspondent pas. Selon le point d'entrée, un utilisateur peut être admin côté UI mais pas côté token (ou l'inverse). Source de bugs de permissions.

M3 - Dossiers hooks/ et context/ vides malgré le plan d'architecture

audit_refactoring.md (§3) planifie useAuth, useTasks, useRealtime et une couche services étoffée. Rien n'a été extrait : hooks/ ne contient qu'un .gitkeep, context/ n'existe pas. La logique reste couplée aux composants (voir §2.3). Dette d'architecture : réutilisation et testabilité fortement limitées.

M4 - Workflow de développement npm run dev cassé pour l'API

vite.config.js ne définit aucun proxy pour /api/*. En dev pur (vite), les appels fetch("/api/public_data") (Auth.jsx:13) et fetch("/api/auth") (Auth.jsx:64) échouent (pas de backend). Le routage /api/* n'existe que via netlify.toml (prod Netlify) ou server.js (qui sert le dist déjà buildé). Impact : impossible de tester l'auth en npm run dev sans lancer netlify dev ou builder + node server.js. À documenter et/ou ajouter un server.proxy dans vite.config.js.

M5 - Requêtes Supabase N+1 et pull global systématique

Fichier : src/services/storage.js

  • writeToSupabase upsert une ligne à la fois dans des boucles for (tâches :225, notes :248, phases :274, membres :298). Pour un projet de 50 tâches → 50+ requêtes réseau par sauvegarde.
  • subscribeToRealtimeChanges (:339) déclenche un pullFromSupabase() complet (toutes les tables, tous les projets) à chaque changement Realtime.

Recommandation : utiliser des upserts batch (upsert([...])) et un pull ciblé par table/projet.

M6 - Dérive documentaire (docs ≠ code réel)

  • audit_refactoring.md (§4) décrit un schéma avec title, position, pin_hash, table de liaison project_members (M2M). Le schéma réel (supabase_schema.sql) utilise label, order, pin (clair), et un project_id en FK directe (1-N).
  • README.md mentionne un projet « KOBIKE » inexistant dans INIT_WS, et une synchro « toutes les ~4 secondes » alors que le code est passé au Realtime WebSocket.
  • SyncMenu.jsx (:44, :110) parle encore de la « Netlify Function storage » et du polling 4 s - vestiges de l'ancienne architecture Blobs.

6. Problèmes 🟡 MINEURS / dette technique

  • MI1 - cloudClient fantôme. storage.js:434 : cloudClient.delete() est un no-op (return { success: true }) mais est appelé lors des suppressions de projet (AdminDashboard.jsx:209, SuperManagerDashboard.jsx:128) en laissant croire à un nettoyage cloud. La suppression réelle repose sur la logique de diff de writeToSupabase. À supprimer ou implémenter.
  • MI2 - Aucun outil de qualité. Pas d'ESLint, Prettier ni .editorconfig. Le .gitignore référence pourtant .eslintcache. Aucun garde-fou sur les hooks React (aurait détecté C1).
  • MI3 - Couverture de tests minimale. Un seul fichier (App.test.jsx, 3 assertions de montage). Le fetch de Auth.jsx n'est pas mocké → la suite crache un TypeError: Invalid URL /api/public_data bruyant (test passe quand même). Aucune couverture des services, du workflow de validation, ni des permissions.
  • MI4 - Styles inline massifs. 100 % du style est en style={{…}} inline (aucun CSS module / classe). Rendu correct mais duplication énorme (dizaines de répétitions de fontFamily:"inherit", palettes hex copiées-collées) et pas de thème centralisé.
  • MI5 - Binaire versionné. BIREF PLATEFORME MJY.pdf (68 Ko) est suivi par Git. Les documents binaires alourdissent le dépôt ; à externaliser ou déplacer hors du versionnement source.
  • MI6 - console.* résiduels (10 occurrences dans src/) laissés en production (storage.js, App.jsx).
  • MI7 - VITE_ANTHROPIC_API_KEY (.env.example) : le préfixe VITE_ est exposé au bundle client par Vite. La clé n'est utilisée que côté serveur (server.js:215) - la renommer ANTHROPIC_API_KEY pour éviter toute fuite accidentelle si elle était lue côté front.
  • MI8 - pollCloud mort. storage.js:355 : fonction vidée « conservée pour compatibilité », toujours importée dans App.jsx:7. Import inutile.

7. Inventaire documenté du code (fichier par fichier, fonction par fonction)

Objectif : documenter le rôle de chaque module et de chaque fonction/action de l'application (demande explicite « documentes tout le code et chaque action et fonction »).

7.1 Racine & configuration

Fichier Rôle
index.html Gabarit HTML Vite, monte #root, charge src/main.jsx.
vite.config.js Config Vite + Vitest (jsdom, setupFiles). Manque un proxy /api.
netlify.toml Publie dist, mappe /api/*/.netlify/functions/:splat.
playwright.config.js Config E2E Playwright (e2e/).
package.json Scripts : dev, build, preview, test, test:watch, test:e2e, start.
seed-db.js Script d'amorçage de la base Supabase (projets/membres/PIN de démo).

7.2 server.js (backend Express - hébergement Node)

Fonction Rôle
signJWT(payload, secret) Signe un JWT HS256 en pur Web Crypto (Node 18+).
POST /api/auth Authentifie par e-mail + PIN (cas spécial super@manager/9999), lit members via clé Service Role, renvoie { token, session }. Calcule le rôle effectif.
GET /api/public_data Retourne projets + membres (sans PIN) + phases, pour peupler l'écran de login sans authentification.
getFallbackStructuredProject(...) Parseur heuristique de brief projet si aucune clé Anthropic.
POST /api/import Appelle l'API Anthropic pour structurer un brief en JSON (projet/phases/membres/tâches), sinon fallback.
GET * Fallback SPA → dist/index.html.

7.3 netlify/functions/ (équivalents serverless - dupliquent 7.2)

Fichier Rôle
auth.js handler = équivalent de POST /api/auth (gère CORS/OPTIONS).
public_data.js Équivalent de GET /api/public_data.
import.js Équivalent de POST /api/import.

7.4 src/App.jsx - racine applicative

Élément Rôle
État hydrated / tick / syncStatus Cycle d'hydratation initial + compteur de re-rendu forcé + statut de synchro.
useEffect (hydratation) Charge le token de session, hydrateFromCloud(), positionne le token Supabase.
useEffect (Realtime) Abonnement onCloudUpdate + subscribeToRealtimeChanges + refresh sur visibilitychange.
useEffect (expiration 1 h) Déconnexion auto après 1 h (session_created_at).
useEffect (pull post-login) pullFromSupabase() dès qu'une session existe.
onLogin / onLogout Ouverture / fermeture de session (efface token + cache local).
Bloc de routage Aiguillage par rôle : Auth / SuperManagerDashboard / AdminDashboard / EmployeeDashboard. ⚠️ contient les setState-en-rendu C1.

7.5 src/services/

storage.js - fonction Rôle
loadS(k, fb) / saveS(k, v) Lecture/écriture dans le cache mémoire _cache ; localStorage réservé à la session ; déclenche la synchro cloud différée.
_flushSoon / _flush / flushNow Débounce (300 ms) et vidage des écritures en attente vers Supabase.
withWriteLock(fn) Verrou anti-course pendant les écritures massives (imports).
pullFromSupabase() Charge projets/phases/membres/tâches/notes et reconstruit _cache (mapping snake_case → camelCase).
writeToSupabase(k, v) Route une clé ws_* vers la bonne table + upsert + suppression par diff. ⚠️ N+1 (M5).
hydrateFromCloud() Hydratation initiale (token + pull).
subscribeToRealtimeChanges() Abonnement Realtime Postgres → pull global (M5).
onCloudUpdate / pollCloud Listeners de mise à jour ; pollCloud = mort (MI8).
exportData / importData Sauvegarde/restauration JSON locale.
forcePullFromCloud / forcePushToCloud / wipeCloud Actions manuelles de SyncMenu.
cloudClient.list/delete Vestige ; delete = no-op (MI1).
supabaseClient.js - setSessionToken(jwt) Recrée le client Supabase avec l'en-tête Authorization: Bearer (pour le RLS).

7.6 src/utils/taskHelpers.js

Constantes de sécurité (⚠️ C3), isSuperAdmin, validateMemberPin, daysLeft, fmtDate, dueBadge, PRIORITY_CFG, getPriority, getStatus, getPhaseProgress, uid, seeds PILLARS_DEFAULT / INIT_WS / INIT_MEMBERS.

7.7 src/pages/

Page Actions principales
Auth.jsx Sélection de compte + saisie PIN → POST /api/auth ; pré-charge public_data.
AdminDashboard.jsx reloadAll, saveTask, updateTask, deleteTask, saveProject, deleteProject, handleImportCreate, handleImportAppend, saveMember, deleteMember, filtres par projet/axe/statut, bascule Liste/Phases.
SuperManagerDashboard.jsx Vue globale (stats par manager), gestion collaborateurs (changeRole, getEffectiveRole), gestion projets ; saveProject/deleteProject/saveMember/deleteMember dupliqués depuis Admin.
EmployeeDashboard.jsx Tâches assignées multi-projets, onglets todo/en cours/à valider/faites, création de tâche perso, workflow de soumission (pending_validation).

7.8 src/components/

Composant Rôle
common/Button (Btn) Bouton stylé (variants primary/danger/ghost/success).
common/Modal Boîte de dialogue générique.
common/Tag Badge coloré.
common/BackupButtons Export/import JSON local.
common/SyncMenu Panneau de synchro cloud manuel (⚠️ textes obsolètes MI, re-export §2.3).
tasks/TaskCard Carte tâche + changement rapide de statut.
tasks/TaskDetail Détail tâche : statut, priorité, assignation (super manager), workflow validation, journal de notes (⚠️ C2).
tasks/TaskForm Formulaire création/édition (mode selfMode collaborateur).
tasks/StatsBar Compteurs cliquables = filtre.
tasks/GroupedListView / PhaseGroupView Regroupement par pilier/responsable ou par phase.
tasks/VocalCreator Dictée vocale (SpeechRecognition) + parsing d'échéance.
projects/ProjectModal / PhasesManager Édition projet ; gestion des phases (ordre, J1→Jn).
members/MemberModal Édition collaborateur (email, rôle, PIN, projets).
import/ImportModal Assistant d'import IA (analyse → complétion emails → prévisualisation).

8. Plan d'action priorisé

Priorité 1 - Corriger sans attendre (critiques)

  1. C1 : sortir setEmployeeMode(false) / onLogout() du rendu de App.jsx (les placer dans un useEffect).
  2. C2 : uniformiser le champ de note sur date (créer et afficher date, supprimer ts).
  3. C3/C4 : retirer les PIN du client et de l'UI, hacher les PIN en base, imposer SUPABASE_JWT_SECRET (échec si absent).

Priorité 2 - Consolider (majeurs)

  1. M2 : unifier une seule source de vérité pour les admins (idéalement en base, pas en dur).
  2. M1 : factoriser la logique backend commune entre server.js et netlify/functions.
  3. M4 : ajouter un proxy /api dans vite.config.js (ou documenter netlify dev).
  4. M3 : extraire useAuth, useTasks, useProjects/useMembers et un AppContext ; dédupliquer Admin ↔ SuperManager.
  5. M5 : upserts batch + pull ciblé.

Priorité 3 - Hygiène (mineurs)

  1. Ajouter ESLint (eslint-plugin-react-hooks - aurait détecté C1) + Prettier.
  2. Mettre à jour README.md, audit_refactoring.md, SyncMenu (retirer KOBIKE, polling 4 s, Netlify Blobs).
  3. Nettoyer cloudClient, pollCloud, console.*, renommer VITE_ANTHROPIC_API_KEY.
  4. Étoffer les tests (services, workflow de validation, permissions) et mocker fetch dans les tests.

Fin du rapport - audit réalisé le 7 juillet 2026.