diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md new file mode 100644 index 0000000..c6661e4 --- /dev/null +++ b/DOCUMENTATION.md @@ -0,0 +1,577 @@ +# EquiTask — Documentation technique complète + +> Application PWA de mesure et de répartition des tâches domestiques +> Version actuelle : **1.0.0** — Live sur **https://equitask.domench.fr** +> Dernière mise à jour : avril 2026 + +--- + +## Table des matières + +1. [Vision et concept](#1-vision-et-concept) +2. [Architecture globale](#2-architecture-globale) +3. [Stack technique](#3-stack-technique) +4. [Structure des fichiers](#4-structure-des-fichiers) +5. [Base de données](#5-base-de-données) +6. [API REST — référence complète](#6-api-rest--référence-complète) +7. [Frontend — pages et composants](#7-frontend--pages-et-composants) +8. [Système de score](#8-système-de-score) +9. [Mode offline / PWA](#9-mode-offline--pwa) +10. [Déploiement et infrastructure](#10-déploiement-et-infrastructure) +11. [Variables d'environnement](#11-variables-denvironnement) +12. [Seed automatique](#12-seed-automatique) +13. [Limites connues de la V1](#13-limites-connues-de-la-v1) + +--- + +## 1. Vision et concept + +EquiTask permet à un foyer de **mesurer objectivement qui fait quoi** dans les tâches du quotidien. Chaque tâche est quantifiée par un **score = durée × coefficient de pénibilité**, ce qui permet de comparer l'investissement réel de chaque membre plutôt que le simple nombre de tâches. + +**Flux utilisateur principal :** +1. Première visite → wizard Setup (nom du foyer + membres) +2. Sélection du profil (qui effectue la tâche ?) +3. Page Saisie → clic sur une tâche → modal de confirmation → score enregistré +4. Dashboard → visualisation de la répartition + indicateur d'équilibre + +**Concept clé : le score** +``` +score_final = durée_minutes × coefficient_pénibilité (1–5) +``` +Exemples : Nettoyage WC (10 min × 4) = 40 pts ; Préparation repas (45 min × 3) = 135 pts + +--- + +## 2. Architecture globale + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Navigateur │ +│ React PWA (Vite + Tailwind) │ +│ ┌──────────┐ ┌──────────┐ ┌───────────┐ ┌──────────────┐ │ +│ │ Setup │ │ Saisie │ │ Dashboard │ │ Paramètres │ │ +│ └──────────┘ └──────────┘ └───────────┘ └──────────────┘ │ +│ IndexedDB (queue offline) Service Worker (cache) │ +└──────────────────────┬──────────────────────────────────────┘ + │ HTTP /api/* +┌──────────────────────▼──────────────────────────────────────┐ +│ Express.js (Node 20) │ +│ /api/foyer /api/membres /api/categories │ +│ /api/taches /api/saisies /api/saisies/stats │ +│ Fichiers statiques frontend servis depuis /public │ +└──────────────────────┬──────────────────────────────────────┘ + │ better-sqlite3 +┌──────────────────────▼──────────────────────────────────────┐ +│ SQLite — /data/equitask.db │ +│ foyer | membres | categories | taches_recurrentes | saisies│ +└─────────────────────────────────────────────────────────────┘ +``` + +Tout tourne dans **un seul conteneur Docker** : l'Express sert à la fois l'API et les fichiers statiques buildés du frontend. La base SQLite est persistée dans un **volume Docker** (`equitask-data:/data`). + +--- + +## 3. Stack technique + +### Frontend +| Technologie | Version | Rôle | +|---|---|---| +| React | 18.2 | UI | +| Vite | 5.2 | Build + dev server | +| TypeScript | 5.2 | Typage | +| Tailwind CSS | 3.4 | Styles utilitaires | +| React Router | 6.22 | Navigation SPA | +| TanStack Query | 5.28 | Fetching + cache serveur | +| Recharts | 2.12 | Graphiques (bar, area, pie) | +| date-fns | 3.6 | Manipulation de dates | +| idb | 8.0 | IndexedDB (queue offline) | +| vite-plugin-pwa | 0.19 | Service Worker + manifest | + +### Backend +| Technologie | Version | Rôle | +|---|---|---| +| Node.js | 20 (Alpine) | Runtime | +| Express | 4.18 | HTTP server | +| TypeScript | 5.4 | Typage | +| Drizzle ORM | 0.30 | ORM SQLite | +| better-sqlite3 | 9.4 | Driver SQLite synchrone | +| helmet | 7.1 | Headers sécurité | +| cors | 2.8 | CORS (dev uniquement) | +| morgan | 1.10 | Logs HTTP | + +### Infrastructure +| Composant | Détail | +|---|---| +| Hébergement | VPS OVH — 57.131.33.182 | +| Orchestration | Coolify 4.0.0-beta.474 | +| Reverse proxy | Traefik v3.6 | +| DNS | OVH — wildcard `*.domench.fr` → IP VPS | +| Source code | Gitea — https://git.domench.fr/gael/equitask | +| HTTPS | Let's Encrypt via Traefik | +| Registry | Pas de registry externe — build local sur le VPS | + +--- + +## 4. Structure des fichiers + +``` +equitask/ +├── Dockerfile # Multi-stage build (3 stages) +├── docker-compose.yml # Pour dev local +├── .dockerignore +├── .gitignore +│ +├── backend/ +│ ├── package.json +│ ├── tsconfig.json +│ └── src/ +│ ├── index.ts # Point d'entrée Express + seed auto +│ ├── db/ +│ │ ├── index.ts # Connexion SQLite + initDb() + CREATE TABLE +│ │ ├── schema.ts # Schéma Drizzle (tables + types TS) +│ │ ├── seed.ts # Seed async (non utilisé en prod) +│ │ └── seed-sync.ts # Seed synchrone +│ └── routes/ +│ ├── foyer.ts # CRUD foyer +│ ├── membres.ts # CRUD membres +│ ├── categories.ts # CRUD catégories +│ ├── taches.ts # CRUD tâches récurrentes +│ └── saisies.ts # CRUD saisies + stats + export +│ +└── frontend/ + ├── package.json + ├── tsconfig.json # "types": ["vite/client"] requis ! + ├── tsconfig.node.json + ├── vite.config.ts # PWA + proxy /api → :3001 + ├── tailwind.config.js + ├── postcss.config.js + ├── index.html + ├── public/ + │ ├── favicon.svg + │ └── icons/ + │ ├── icon-192.png + │ └── icon-512.png + └── src/ + ├── main.tsx + ├── App.tsx # Router + QueryClient + AppProvider + ├── index.css + ├── types/ + │ └── index.ts # Tous les types TS de l'app + ├── api/ + │ ├── client.ts # fetch wrapper typé + │ └── index.ts # foyerApi, membresApi, tachesApi, saisiesApi, dashboardApi + ├── context/ + │ └── AppContext.tsx # foyer, membreActif, isOnline, queueCount + ├── hooks/ + │ ├── useOfflineSync.ts # Sync auto IndexedDB → API au retour en ligne + │ └── useToast.ts + ├── offline/ + │ └── queue.ts # IndexedDB : enqueue / dequeue / getQueue / countQueue + ├── pages/ + │ ├── Setup.tsx # Wizard première config (foyer + membres) + │ ├── SelectionProfil.tsx # Écran de sélection du membre + │ ├── Saisie.tsx # Grille tâches + modals (récurrente + one-shot) + │ ├── Dashboard.tsx # Graphiques + indicateur équilibre + historique + │ └── Parametres.tsx # Gestion membres / catégories / tâches + └── components/ + └── ui/ + ├── Layout.tsx # Shell avec nav bas mobile + ├── Modal.tsx # Modale générique + ├── PenibiliteSelector.tsx # 5 boutons 1–5 + ├── ScoreBadge.tsx # Badge score coloré + └── Toast.tsx # Notifications +``` + +--- + +## 5. Base de données + +### Schéma SQLite + +```sql +-- Un seul enregistrement — configuration du foyer +CREATE TABLE foyer ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + nom TEXT NOT NULL, + cree_le TEXT DEFAULT (datetime('now', 'localtime')) +); + +-- Membres du foyer (max 7 recommandé) +CREATE TABLE membres ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + nom TEXT NOT NULL, + role TEXT NOT NULL CHECK(role IN ('adulte', 'enfant')), + couleur TEXT NOT NULL, -- ex: #ef4444 + actif INTEGER DEFAULT 1, -- soft delete + ordre INTEGER DEFAULT 0, + cree_le TEXT DEFAULT (datetime('now', 'localtime')) +); + +-- Catégories de tâches (éditables) +CREATE TABLE categories ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + nom TEXT NOT NULL, + icone TEXT NOT NULL, -- emoji + couleur TEXT NOT NULL, + ordre INTEGER DEFAULT 0, + actif INTEGER DEFAULT 1 +); + +-- Catalogue des tâches habituelles +CREATE TABLE taches_recurrentes ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + nom TEXT NOT NULL, + categorie_id INTEGER NOT NULL REFERENCES categories(id), + duree_moyenne_min INTEGER NOT NULL, + coefficient_penibilite INTEGER NOT NULL CHECK(coefficient_penibilite BETWEEN 1 AND 5), + actif INTEGER DEFAULT 1 -- soft delete +); + +-- Historique de toutes les saisies +CREATE TABLE saisies ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + tache_recurrente_id INTEGER REFERENCES taches_recurrentes(id), -- nullable + nom_tache_oneshot TEXT, -- nullable (tâche ponctuelle) + categorie_id INTEGER NOT NULL REFERENCES categories(id), + membre_id INTEGER NOT NULL REFERENCES membres(id), + date_heure TEXT NOT NULL DEFAULT (datetime('now', 'localtime')), + duree_reelle_min INTEGER, -- override de la durée par défaut + coefficient_penibilite INTEGER NOT NULL CHECK(coefficient_penibilite BETWEEN 1 AND 5), + score_final INTEGER NOT NULL, -- snapshot = duree * coef + notes TEXT, + synced INTEGER DEFAULT 1, + cree_le TEXT DEFAULT (datetime('now', 'localtime')), + CHECK (tache_recurrente_id IS NOT NULL OR nom_tache_oneshot IS NOT NULL) +); + +-- Index de performance +CREATE INDEX idx_saisies_membre ON saisies(membre_id); +CREATE INDEX idx_saisies_date ON saisies(date_heure); +CREATE INDEX idx_saisies_categorie ON saisies(categorie_id); +``` + +### Règles importantes +- **Soft delete** partout : `actif = 0` pour membres, catégories et tâches (jamais de DELETE réel) +- **score_final est un snapshot** : stocké au moment de la saisie, non recalculé en cas de modification ultérieure de la tâche +- Une saisie doit avoir soit `tache_recurrente_id` soit `nom_tache_oneshot` (contrainte CHECK) +- `duree_reelle_min` est nullable : si null, la durée d'affichage est celle de la tâche référencée + +--- + +## 6. API REST — référence complète + +Base URL : `/api` +Content-Type : `application/json` + +### Foyer + +| Méthode | Route | Description | +|---|---|---| +| GET | `/foyer` | Récupère le foyer (`{ foyer: Foyer \| null }`) | +| POST | `/foyer` | Crée le foyer (`{ nom }`) | +| PUT | `/foyer/:id` | Modifie le nom du foyer | + +### Membres + +| Méthode | Route | Description | +|---|---|---| +| GET | `/membres` | Liste les membres actifs | +| POST | `/membres` | Crée un membre (`{ nom, role, couleur, ordre? }`) | +| PUT | `/membres/:id` | Modifie un membre | +| DELETE | `/membres/:id` | Soft delete (actif = 0) | + +### Catégories + +| Méthode | Route | Description | +|---|---|---| +| GET | `/categories` | Liste les catégories actives, triées par `ordre` | +| POST | `/categories` | Crée (`{ nom, icone, couleur, ordre? }`) | +| PUT | `/categories/:id` | Modifie | +| DELETE | `/categories/:id` | Soft delete | + +### Tâches récurrentes + +| Méthode | Route | Description | +|---|---|---| +| GET | `/taches` | Liste + `score_calcule` joint, triées catégorie/nom | +| POST | `/taches` | Crée (`{ nom, categorie_id, duree_moyenne_min, coefficient_penibilite }`) | +| PUT | `/taches/:id` | Modifie | +| DELETE | `/taches/:id` | Soft delete | + +### Saisies + +| Méthode | Route | Body / Query | Description | +|---|---|---|---| +| GET | `/saisies` | `?debut&fin&membre_id&categorie_id&type&page&limit` | Liste paginée (max 200/page) avec jointures | +| POST | `/saisies` | `{ tache_recurrente_id?, nom_tache_oneshot?, categorie_id, membre_id, date_heure?, duree_reelle_min?, coefficient_penibilite, score_final, notes? }` | Crée une saisie | +| POST | `/saisies/batch` | `{ saisiesData: [...] }` | Sync offline (ignore les erreurs individuelles) | +| PUT | `/saisies/:id` | champs modifiables | Modifie une saisie | +| DELETE | `/saisies/:id` | — | Suppression réelle (pas de soft delete) | +| GET | `/saisies/stats` | `?debut&fin&inclure_enfants&categorie_id` | Stats dashboard (voir ci-dessous) | +| GET | `/saisies/export` | `?format=json\|csv` | Télécharge toutes les saisies | + +### Réponse `/saisies/stats` + +```json +{ + "scores_par_membre": [ + { "membre_id": 1, "nom": "Alice", "couleur": "#ef4444", "role": "adulte", + "score_total": 3420, "pourcentage": 62, "nb_saisies": 28 } + ], + "scores_par_categorie": [ + { "categorie_id": 1, "nom": "Cuisine", "icone": "🍳", "couleur": "#ef4444", + "scores_membres": [ + { "membre_id": 1, "nom": "Alice", "couleur": "#ef4444", "score": 1200 } + ] + } + ], + "evolution_temporelle": [ + { "date": "2026-04-20", + "scores": [{ "membre_id": 1, "nom": "Alice", "couleur": "#ef4444", "score": 315 }] } + ], + "indicateur_equilibre": { + "adulte1": { "membre_id": 1, "nom": "Alice", "couleur": "#ef4444", "score": 3420, "pourcentage": 62 }, + "adulte2": { "membre_id": 2, "nom": "Bob", "couleur": "#3b82f6", "score": 2100, "pourcentage": 38 }, + "ecart_pct": 24, + "statut": "orange" // "vert" ≤10%, "orange" ≤25%, "rouge" >25% + } +} +``` + +### Santé +``` +GET /api/health → { "ok": true, "version": "1.0.0", "timestamp": "..." } +``` + +--- + +## 7. Frontend — pages et composants + +### Flux de navigation + +``` +/ (SelectionProfil) + ├── [pas de foyer] → /setup (Setup) + └── [foyer OK] → sélection membre → mémorisation dans sessionStorage + ├── /saisie (Layout > Saisie) + ├── /dashboard (Layout > Dashboard) + └── /parametres (Layout > Parametres) +``` + +### Pages + +**Setup** — Wizard en 2 étapes : nom du foyer, puis ajout des membres (min 1 adulte). Initialise le foyer via POST `/api/foyer` puis POST `/api/membres`. + +**SelectionProfil** — Grille de cartes membres. Le clic stocke le membre dans `sessionStorage` (via `AppContext.setMembreActif`) et redirige vers `/saisie`. + +**Saisie** — Grille de cartes tâches groupées par catégorie (tabs horizontaux). Deux modals : +- `ModalConfirmTache` : confirme une tâche récurrente, permet d'override durée/pénibilité +- `ModalOneShot` : saisie libre avec option "ajouter au catalogue" + +**Dashboard** — 4 blocs de visualisation : +1. Scores cumulés par membre (barres de progression) +2. Indicateur d'équilibre couple (barre bicolore + statut vert/orange/rouge) +3. Répartition par catégorie (BarChart Recharts, grouped) +4. Évolution temporelle (AreaChart Recharts) +Plus un tableau historique paginé avec suppression. + +Filtres : période (semaine/mois/7j/30j/personnalisé), catégorie, toggle enfants. Export CSV/JSON. + +**Parametres** — Trois onglets : +- Membres : CRUD + couleur + rôle +- Catégories : CRUD + icône emoji + couleur +- Tâches : CRUD filtré par catégorie + durée + pénibilité + +### Composants UI + +| Composant | Rôle | +|---|---| +| `Layout` | Shell : contenu + barre de navigation bas (mobile-first) avec icônes + badge offline | +| `Modal` | Modale générique avec backdrop, animation, gestion focus | +| `PenibiliteSelector` | 5 boutons numérotés 1–5 avec couleurs graduées | +| `ScoreBadge` | Badge score avec couleur selon valeur (vert → rouge) | +| `Toast` | Notification éphémère (succès/erreur/info) avec animation | + +### AppContext + +```typescript +interface AppContextType { + foyer: Foyer | null; + setFoyer: (f: Foyer | null) => void; + membreActif: Membre | null; // persisté dans sessionStorage + setMembreActif: (m: Membre | null) => void; + isOnline: boolean; // window online/offline events + queueCount: number; // nb saisies en attente de sync + setQueueCount: (n: number) => void; +} +``` + +--- + +## 8. Système de score + +Le score est la métrique centrale de l'app. Il cherche à rendre comparables des tâches de nature très différente. + +``` +score = durée_minutes × coefficient_pénibilité +``` + +**Coefficient de pénibilité (1–5) :** +- 1 = tâche agréable ou automatique +- 2 = tâche neutre +- 3 = tâche un peu contraignante +- 4 = tâche pénible (WC, impôts...) +- 5 = réservé aux tâches exceptionnellement pénibles + +**Exemples issus du seed :** +| Tâche | Durée | Coef | Score | +|---|---|---|---| +| Nettoyage WC | 10 min | 4 | 40 pts | +| Préparation repas | 45 min | 3 | 135 pts | +| Batch cooking | 90 min | 3 | 270 pts | +| Déclarations impôts | 60 min | 4 | 240 pts | + +**Indicateur d'équilibre :** +- Calculé uniquement sur les adultes (les enfants sont exclus par défaut) +- `écart_pct = |score_adulte1 - score_adulte2| / score_total × 100` +- Vert ≤ 10% — Orange ≤ 25% — Rouge > 25% + +--- + +## 9. Mode offline / PWA + +### Service Worker (Workbox) +Configuré dans `vite.config.ts` via `vite-plugin-pwa` : +- **Assets statiques** : précachés au build (JS, CSS, HTML, PNG, SVG) +- **Appels API** (`/api/*`) : stratégie `NetworkFirst` avec fallback cache, TTL 24h, max 100 entrées + +### Queue offline (IndexedDB) +Quand `isOnline === false`, les saisies ne sont pas envoyées au serveur mais stockées dans IndexedDB via `src/offline/queue.ts` (base `equitask-offline`, store `saisies-queue`). + +Au retour en ligne (`useOfflineSync.ts`), toutes les saisies en queue sont envoyées une par une via `POST /api/saisies`. En cas d'erreur réseau, la synchronisation s'arrête (les saisies restent en queue). + +### Manifest PWA +```json +{ + "name": "EquiTask - Répartition tâches", + "short_name": "EquiTask", + "display": "standalone", + "orientation": "portrait-primary", + "theme_color": "#1e1b4b", + "background_color": "#0f172a" +} +``` +Icônes : 192×192 et 512×512 (maskable). + +--- + +## 10. Déploiement et infrastructure + +### Dockerfile (multi-stage) + +``` +Stage 1 — frontend-builder (node:20-alpine) + npm install --include=dev ← OBLIGATOIRE (sinon vite/tsc pas installés) + npm run build → dist/ + +Stage 2 — backend-builder (node:20-alpine) + apk add python3 make g++ ← pour compiler better-sqlite3 (module natif) + npm install --include=dev ← OBLIGATOIRE (sinon tsc pas installé) + npm run build → dist/ + +Stage 3 — runtime (node:20-alpine) + apk add libstdc++ ← runtime better-sqlite3 + Copie node_modules du builder (incluant better-sqlite3 natif) + Copie dist/ backend + Copie dist/ frontend → public/ + ENV NODE_ENV=production PORT=3001 DATABASE_PATH=/data/equitask.db + VOLUME ["/data"] + CMD ["node", "dist/index.js"] +``` + +**Points critiques Docker :** +- Utiliser `npm install --include=dev` et non `npm ci` (pas de lockfile commité) +- `NODE_ENV=production` supprime les devDependencies → toujours passer `--include=dev` dans les stages builder +- `better-sqlite3` compile un module natif → outils de compilation nécessaires au build, `libstdc++` au runtime + +### Infrastructure Coolify + +``` +Coolify URL : http://100.94.204.91:8000 +App UUID : bunmdt8jmb2i594zuhzvdhfy +Gitea repo : https://git.domench.fr/gael/equitask.git +Branche : main +Build pack : dockerfile +Port exposé : 3001 +Domaine : https://equitask.domench.fr +Volume : equitask-data → /data +``` + +### Script de déploiement + +`deploy.py` (à la racine de `Applications VPS/`) automatise la création d'une nouvelle app : +```bash +# Depuis Windows cmd (pas PowerShell) +py -X utf8 deploy.py equitask ./equitask +``` +Lit `config-deploy.json`, crée le repo Gitea, pousse le code, crée l'app Coolify, configure le volume, déclenche le build. + +**Particularités Coolify API v1 découvertes :** +- `fqdn` en lecture seule → utiliser `domains` dans PATCH +- `POST /applications/public` tronque le hostname du `git_repository` → re-PATCH après création +- Storage API : utiliser `custom_docker_run_options: "--mount type=volume,..."` (pas l'API `/storages`) +- Déploiement : `GET /api/v1/deploy?uuid={app_uuid}&force=false` (pas POST) + +--- + +## 11. Variables d'environnement + +| Variable | Défaut | Description | +|---|---|---| +| `NODE_ENV` | `production` | Désactive CORS en production | +| `PORT` | `3001` | Port d'écoute Express | +| `DATABASE_PATH` | `/data/equitask.db` | Chemin absolu de la base SQLite | + +En développement local, créer un `.env` à la racine de `backend/` : +```env +NODE_ENV=development +PORT=3001 +DATABASE_PATH=./data/equitask.db +``` + +--- + +## 12. Seed automatique + +Au démarrage, `backend/src/index.ts` vérifie si la table `categories` est vide. Si oui, il insère automatiquement : + +**7 catégories :** Cuisine 🍳, Ménage 🧹, Courses 🛒, Enfants 👶, Administratif 📋, Entretien maison 🔧, Charge mentale 🧠 + +**29 tâches récurrentes** couvrant les activités domestiques courantes, avec durées et coefficients préréglés. + +Ce seed ne s'exécute qu'une seule fois (première instance vierge). Les données sont ensuite modifiables depuis la page Paramètres. + +--- + +## 13. Limites connues de la V1 + +**Fonctionnelles :** +- Pas d'authentification — une seule instance par déploiement (un seul foyer) +- Pas de gestion multi-foyers +- Pas de notifications push (rappels, suggestions) +- Pas d'objectifs ou de gamification +- Les statistiques ne couvrent pas les tendances long terme (ex: évolution mensuelle sur 6 mois) +- Pas de vue "récapitulatif hebdomadaire" envoyé par email/notification +- Le tableau historique n'a pas de tri par colonne +- Pas de possibilité de modifier une saisie existante depuis l'historique (seulement supprimer) +- Pas de saisie rapide ("j'ai fait X aujourd'hui à 8h30" sans passer par la grille) + +**Techniques :** +- Pas de migrations Drizzle — schéma recréé via `CREATE TABLE IF NOT EXISTS` (fragile pour les évolutions) +- Pas de tests automatisés (ni backend ni frontend) +- Pas de CI/CD : le déploiement est déclenché manuellement via `deploy.py` +- `score_final` est un snapshot immuable — pas de recalcul si on modifie le coefficient d'une tâche +- Pas de rate limiting sur l'API +- Pas d'authentification API +- `sessionStorage` pour le membre actif → perdu si on ferme l'onglet +- Offline sync séquentielle (une saisie à la fois) → lente si beaucoup de saisies en queue diff --git a/PROMPT_REPRISE_V2.md b/PROMPT_REPRISE_V2.md new file mode 100644 index 0000000..cf3c58e --- /dev/null +++ b/PROMPT_REPRISE_V2.md @@ -0,0 +1,127 @@ +# Prompt de reprise — EquiTask V2 + +> À coller tel quel en début de nouvelle session pour démarrer le développement de la V2. + +--- + +## Contexte du projet + +Tu vas m'aider à développer **EquiTask V2**, l'évolution d'une application PWA de mesure de répartition des tâches domestiques. + +**L'application V1 est déjà en production** à l'adresse `https://equitask.domench.fr`. Le code source se trouve dans mon dossier de travail `equitask/` (dossier sélectionné dans Cowork). + +Avant de commencer, lis les deux fichiers de référence : +1. `equitask/DOCUMENTATION.md` — documentation technique complète de la V1 (architecture, stack, BDD, API, composants, limites) +2. `equitask/PROMPT_REPRISE_V2.md` — ce fichier (contexte V2) + +--- + +## Ce qui existe en V1 (à ne pas recoder) + +- **Backend** : Express + TypeScript + SQLite (Drizzle ORM) + better-sqlite3 + - Routes : `/api/foyer`, `/api/membres`, `/api/categories`, `/api/taches`, `/api/saisies` + - Stats dashboard : `GET /api/saisies/stats?debut&fin&inclure_enfants&categorie_id` + - Export CSV/JSON : `GET /api/saisies/export?format=json|csv` + +- **Frontend** : React 18 + Vite + Tailwind + TanStack Query + Recharts + - Pages : Setup (wizard), SelectionProfil, Saisie (grille tâches), Dashboard (4 graphiques), Paramètres (3 onglets) + - Offline : queue IndexedDB + sync automatique au retour en ligne + - PWA : Service Worker Workbox, manifest, icônes + +- **Infra** : Docker multi-stage → Coolify → https://equitask.domench.fr + - Déploiement via `deploy.py` depuis le dossier parent + - Volume SQLite persisté : `equitask-data:/data` + +--- + +## Système de score (inchangé en V2) + +``` +score_final = durée_minutes × coefficient_pénibilité (1–5) +``` +L'indicateur d'équilibre mesure l'écart en % entre les deux adultes du foyer. +Seuils : vert ≤ 10%, orange ≤ 25%, rouge > 25%. + +--- + +## Objectifs de la V2 + +> **À compléter par Gaël avant de démarrer** — noter ici les fonctionnalités souhaitées. +> Exemples de pistes identifiées lors du développement de la V1 : + +### Fonctionnalités prioritaires (à valider) +- [ ] **Authentification simple** — code PIN par membre ou mot de passe foyer pour éviter que n'importe qui modifie les données +- [ ] **Notifications / rappels** — push notification ou email hebdomadaire avec le récap de répartition +- [ ] **Vue récapitulatif hebdomadaire** — résumé automatique chaque dimanche soir +- [ ] **Objectifs** — définir un objectif d'équilibre cible et suivre la progression +- [ ] **Modification d'une saisie** — pouvoir éditer une saisie depuis l'historique (actuellement suppression seulement) +- [ ] **Saisie rapide** — ajouter une tâche passée sans passer par la grille (ex: "j'ai fait X hier") +- [ ] **Tri du tableau historique** — tri par colonne (date, membre, score...) +- [ ] **Tendances long terme** — graphique mensuel sur 6/12 mois +- [ ] **Badges/gamification** — récompenses pour les périodes bien équilibrées + +### Améliorations techniques (à valider) +- [ ] **Migrations Drizzle** — remplacer les `CREATE TABLE IF NOT EXISTS` par un vrai système de migrations +- [ ] **Tests** — au moins les routes API critiques (saisies, stats) +- [ ] **CI/CD automatique** — déclencher le déploiement sur push Gitea (webhook → Coolify) +- [ ] **Rate limiting** — protéger l'API des abus +- [ ] **Recalcul des scores** — option pour recalculer les scores historiques si les paramètres d'une tâche changent + +--- + +## Contraintes techniques à respecter + +1. **Rester dans la même stack** : Express/Node 20, React 18, SQLite, TypeScript, Tailwind +2. **Compatibilité Docker** : le Dockerfile multi-stage doit continuer à fonctionner + - Toujours `npm install --include=dev` (pas `npm ci`, pas de lockfile) + - `NODE_ENV=production` coupe les devDependencies — le `--include=dev` est obligatoire dans les builder stages +3. **Volume SQLite** : les données sont dans `/data/equitask.db` — les migrations doivent être non-destructives +4. **Frontend `tsconfig.json`** : doit avoir `"types": ["vite/client"]` pour `import.meta.env` +5. **API** : rester rétrocompatible avec V1 (mêmes routes, mêmes structures de réponse) +6. **Design** : dark theme slate-950/slate-900, accents indigo-600, cards rounded-2xl — maintenir la cohérence visuelle + +--- + +## Infra de déploiement + +``` +VPS OVH : 57.131.33.182 +Coolify : http://100.94.204.91:8000 +Gitea : https://git.domench.fr/gael/equitask +Branche : main +App Coolify : bunmdt8jmb2i594zuhzvdhfy +URL prod : https://equitask.domench.fr +``` + +Pour redéployer après modifications : +```bash +# Depuis Windows — cmd (pas PowerShell) +cd C:\Users\GALWAL~1\Documents\Claude\Projects\APPLIC~1 +py -X utf8 deploy.py equitask ./equitask +``` +Ou : pusher sur `main` et déclencher manuellement dans Coolify. + +--- + +## Points de vigilance hérités de la V1 + +Ces bugs/pièges ont déjà été rencontrés — à ne pas reproduire : + +- **`npm ci` échoue** → pas de `package-lock.json` commité, toujours utiliser `npm install` +- **`tsc: not found` pendant le build Docker** → `NODE_ENV=production` coupe les devDependencies, toujours `--include=dev` +- **TypeScript `import.meta.env` introuvable** → vérifier `"types": ["vite/client"]` dans `frontend/tsconfig.json` +- **Type `role: string` trop large** → dans les mutations TanStack Query, typer explicitement `role: 'adulte' | 'enfant'` +- **Coolify API `fqdn` read-only** → utiliser le champ `domains` dans les PATCH +- **Coolify API git_repository tronqué** → re-PATCH après création pour rétablir l'URL complète +- **`py` vs `python`** → sur ce Windows, `py` fonctionne en cmd mais pas `python3` +- **Chemin avec `ë`** → utiliser le chemin court Windows `GALWAL~1` dans cmd si nécessaire + +--- + +## Pour démarrer la session + +1. Lis `equitask/DOCUMENTATION.md` (documentation technique complète) +2. Explore le code existant si besoin : `equitask/backend/src/` et `equitask/frontend/src/` +3. Demande-moi quelles fonctionnalités V2 je veux prioriser +4. Propose un plan de développement incrémental (backend d'abord, puis frontend) +5. Travaille fichier par fichier en préservant la compatibilité avec l'existant diff --git a/backend/src/index.ts b/backend/src/index.ts index 9591832..2c0cbd3 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -108,19 +108,20 @@ app.get('/api/health', (_req, res) => { // ─── Frontend statique (en production) ────────────────────────────────────── const frontendPath = path.join(__dirname, '..', 'public'); if (fs.existsSync(frontendPath)) { - app.use(express.static(frontendPath)); - // SPA fallback - toutes les routes non-API servent index.html - app.get('*', (req, res) => { - if (!req.path.startsWith('/api')) { - res.sendFile(path.join(frontendPath, 'index.html')); - } + // Service worker — jamais mis en cache (sinon l'ancien SW continue après déploiement) + app.get('/sw.js', (_req, res) => { + res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate'); + res.setHeader('Pragma', 'no-cache'); + res.setHeader('Expires', '0'); + res.sendFile(path.join(frontendPath, 'sw.js')); }); -} -// ─── Démarrage ─────────────────────────────────────────────────────────────── -app.listen(PORT, () => { - console.log(`🚀 EquiTask API démarrée sur le port ${PORT}`); - console.log(` Mode : ${process.env.NODE_ENV || 'development'}`); -}); + // Manifeste PWA — pas de cache long + app.get(/\.webmanifest$/, (_req, res, next) => { + res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate'); + res.setHeader('Expires', '0'); + next(); + }); -export default app; + app.use(express.static(frontendPath)); + // SPA fallback - toutes les routes non-API ser \ No newline at end of file diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index c94cfb0..0a8c86d 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -25,12 +25,13 @@ export default defineConfig({ }, workbox: { globPatterns: ['**/*.{js,css,html,ico,png,svg,woff2}'], + cleanupOutdatedCaches: true, runtimeCaching: [ { urlPattern: /^\/api\/.*/i, handler: 'NetworkFirst', options: { - cacheName: 'api-cache', + cacheName: 'equitask-api-cache', expiration: { maxEntries: 100, maxAgeSeconds: 86400 }, networkTimeoutSeconds: 10, }, @@ -45,7 +46,4 @@ export default defineConfig({ server: { port: 5173, proxy: { - '/api': { target: 'http://localhost:3001', changeOrigin: true }, - }, - }, -}); + '/api': { target: 'http://localhost \ No newline at end of file