diff --git a/.github/instructions/instructions.instructions.md b/.github/instructions/instructions.instructions.md index e69de29..2fb3499 100644 --- a/.github/instructions/instructions.instructions.md +++ b/.github/instructions/instructions.instructions.md @@ -0,0 +1,135 @@ +--- +applyTo: '**' +--- + +# Instructions pour les IA Copilot - Espace Paie Odentas + +## 📋 Context du Projet + +**Nom du projet** : Nouvel Espace Paie Odentas +**Type** : Application Web Next.js 14 (Full-Stack) +**Langage principal** : TypeScript + React +**Base de donnĂ©es** : Supabase (PostgreSQL) +**Authentification** : Supabase Auth + 2FA TOTP +**HĂ©bergement** : Vercel (rĂ©gion cdg1 - Paris) + +## 🎯 Architecture du Projet + +### Stack Technologique +- **Frontend** : Next.js 14, React 18, TypeScript, Tailwind CSS +- **Backend** : Next.js API Routes, TypeScript +- **Authentification** : Supabase Auth, TOTP 2FA +- **Base de donnĂ©es** : Supabase PostgreSQL +- **Stockage** : AWS S3, Supabase Storage +- **Signatures Ă©lectroniques** : Docuseal +- **Analytics** : PostHog +- **PDF** : PDFMonkey + +### Structure des Dossiers +``` +/app → Next.js App Router (pages et layouts) +/app/api → API Routes (devraient ĂȘtre sur cdg1 dans vercel.json) +/components → Composants React rĂ©utilisables +/lib → Utilitaires et helpers +/hooks → Hooks React personnalisĂ©s +/templates-mails → Templates d'emails HTML +/public → Assets statiques +``` + +## 🔑 Points Importants + +### 1. Authentification & SĂ©curitĂ© +- Toujours utiliser `createRouteHandlerClient` pour les routes API +- Le 2FA TOTP est activable mais optionnel +- Les statuts MFA sont : "verified" (activĂ©) et autres (dĂ©sactivĂ©) +- **Important** : Comparer avec `!== "verified"` au lieu de `=== "unverified"` (le type n'existe pas) + +### 2. Configuration Vercel +- **RĂ©gion des Functions API** : cdg1 (Paris) - À MAINTENIR dans vercel.json +- Les functions ne doivent PAS ĂȘtre sur iad1 (risque de panne) +- Configuration dans `vercel.json` : + ```json + "regions": ["cdg1"] + ``` + +### 3. Base de DonnĂ©es +- Tables principales : + - `profiles` → Utilisateurs + - `organizations` → Entreprises/clients + - `employees` → SalariĂ©s + - `contracts` → Contrats CDDU et RG + - `payslips` → Fiches de paie + - `cotisations` → Cotisations mensuelles + - `salary_transfers` → Virements salaires + +### 4. RĂ©gimes de Contrats +- **CDDU** : CDD d'usage (intermittents du spectacle) + - `CDDU_MONO` : Mono-mois + - `CDDU_MULTI` : Multi-mois +- **RG** : RĂ©gime GĂ©nĂ©ral (salaires classiques) + +### 5. Composants ClĂ©s + +#### Formulaire CDDU +- Fichier : `components/contrats/NouveauCDDUForm.tsx` +- Contient un **bouton calculatrice** pour saisir les montants +- Utilise le composant `Calculator` pour les calculs +- Support des deux rĂ©gimes (CDDU et RG) + +#### Calculatrice +- Fichier : `components/Calculator.tsx` +- Modale draggable avec focus +- **Important** : VĂ©rifier que le focus ne capture pas les autres champs +- Utiliser `calculatorRef.current.contains(document.activeElement)` pour vĂ©rifier le focus + +### 6. Hooks PersonnalisĂ©s +- `useDemoMode()` → Mode dĂ©mo activĂ©/dĂ©sactivĂ© +- `usePageTitle()` → DĂ©finir le titre de la page +- `usePostHog()` → Analytics PostHog + +## ✅ Standards de Code + +### TypeScript +- Toujours dĂ©finir les types explicitement +- Utiliser les enums pour les statuts +- Valider les types de statuts MFA avec `!==` plutĂŽt que `===` +- **JAMAIS d'emojis dans le code** (commentaires, messages, etc.) + +### Composants React +- Utiliser "use client" pour les composants interactifs +- PrĂ©fĂ©rer les fonctions pures +- Gestion du focus : vĂ©rifier que le focus est bien sur l'Ă©lĂ©ment avant de capturer les Ă©vĂ©nements +- **Pour l'UI/UX** : Uniquement des icĂŽnes **Lucide React** (depuis `lucide-react`) +- **JAMAIS d'emojis dans l'interface utilisateur** + +### Styling +- Utiliser Tailwind CSS avec les utilitaires de base +- Palette de couleurs : slate, indigo, orange, green, red +- RĂ©utiliser les classes composables (flex, gap, etc.) + +## 🐛 Corrections RĂ©centes + +- ✅ Ajout du bouton calculatrice au formulaire CDDU +- ✅ Correction des comparaisons de statut MFA (unverified → !== "verified") +- ✅ Correction du focus trap de la calculatrice +- ✅ Migration des API Functions de iad1 vers cdg1 + +## 📝 Conventions de Commit + +- `feat:` → Nouvelle fonctionnalitĂ© +- `fix:` → Correction de bug +- `chore:` → TĂąche de maintenance +- `style:` → Changements de style uniquement +- `refactor:` → Refactorisation de code + +Exemple : `fix: Corriger focus trap de la calculatrice` + +- Toujours proposer un git commit et un git push Ă  la fin d'une modification. + +## ⚠ Points d'Attention + +1. **RĂ©gion Vercel** : Toujours vĂ©rifier que cdg1 est dĂ©fini dans vercel.json +2. **Authentification** : Ne jamais exposer les tokens en client-side +3. **Focus management** : Toujours vĂ©rifier le focus avant de capturer les Ă©vĂ©nements clavier +4. **Typage MFA** : Utiliser `!== "verified"` pour les comparaisons, jamais `=== "unverified"` +5. **Build local** : Tester avec `npm run build` avant de pousser diff --git a/app/(app)/contrats/page.tsx b/app/(app)/contrats/page.tsx index 760e5b8..a935154 100644 --- a/app/(app)/contrats/page.tsx +++ b/app/(app)/contrats/page.tsx @@ -27,8 +27,8 @@ type ClientInfo = { } | null; // --- Hook d'accĂšs API - MODIFIÉ pour rĂ©cupĂ©rer clientInfo dynamiquement -function useContrats(params: { regime: "CDDU" | "RG"; status: "en_cours" | "termines"; page: number; limit: number; q?: string; month?: number; year?: number; period?: string; org?: string | null }){ - const { regime, status, page, limit, q, month, year, period, org } = params; +function useContrats(params: { regime: "CDDU" | "RG"; status: "en_cours" | "termines"; page: number; limit: number; q?: string; month?: number; year?: number; period?: string; org?: string | null; sortField?: 'date_debut' | 'date_fin'; sortOrder?: 'asc' | 'desc' }){ + const { regime, status, page, limit, q, month, year, period, org, sortField = 'date_fin', sortOrder = 'desc' } = params; // RĂ©cupĂ©ration dynamique des infos client via /api/me const { data: clientInfo } = useQuery({ @@ -66,6 +66,8 @@ function useContrats(params: { regime: "CDDU" | "RG"; status: "en_cours" | "term status === "termines" ? year : undefined, clientInfo?.id, org, + sortField, + sortOrder, ], queryFn: () => { const base = `/contrats?regime=${encodeURIComponent(regime)}&status=${status}&page=${page}&limit=${limit}`; @@ -89,6 +91,9 @@ function useContrats(params: { regime: "CDDU" | "RG"; status: "en_cours" | "term if (!Number.isNaN(sv)) parts.push(`semester=${sv}`); } } + // Ajouter les paramĂštres de tri + if (sortField) parts.push(`sort=${encodeURIComponent(sortField)}`); + if (sortOrder) parts.push(`order=${encodeURIComponent(sortOrder)}`); const qs = parts.length ? `&${parts.join("&")}` : ""; // Build final clientInfo to pass to api(): if UI provided explicit org filter, override id @@ -218,6 +223,8 @@ export default function PageContrats(){ const [limit, setLimit] = useState(10); const [q, setQ] = useState(""); const [regime, setRegime] = useState<"CDDU" | "RG">("CDDU"); + const [sortField, setSortField] = useState<'date_debut' | 'date_fin'>('date_fin'); // Tri par dĂ©faut: date de fin + const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc'); // Ordre par dĂ©faut: dĂ©croissant const router = useRouter(); // 🎭 DĂ©tection du mode dĂ©mo @@ -283,6 +290,8 @@ export default function PageContrats(){ year: status === "termines" ? year : undefined, period: status === "termines" ? period : undefined, org: selectedOrg || null, + sortField, + sortOrder, }); const items = data?.items ?? []; const hasMore = data?.hasMore ?? false; @@ -467,8 +476,12 @@ export default function PageContrats(){ Structure {regime === 'RG' ? 'Analytique' : 'Production'} Profession - DĂ©but - Fin + { setSortField('date_debut'); setSortOrder((o) => o === 'asc' ? 'desc' : 'asc'); }}> + DĂ©but {sortField === 'date_debut' ? (sortOrder === 'asc' ? 'â–Č' : 'â–Œ') : ''} + + { setSortField('date_fin'); setSortOrder((o) => o === 'asc' ? 'desc' : 'asc'); }}> + Fin {sortField === 'date_fin' ? (sortOrder === 'asc' ? 'â–Č' : 'â–Œ') : ''} + Actions diff --git a/app/api/contrats/route.ts b/app/api/contrats/route.ts index 6ed071c..0fd481c 100644 --- a/app/api/contrats/route.ts +++ b/app/api/contrats/route.ts @@ -94,6 +94,9 @@ export async function GET(req: Request) { const offset = (page - 1) * limit; // Recherche const q = url.searchParams.get("q")?.trim(); + // Tri + const sort = url.searchParams.get("sort") || "date_fin"; + const order = url.searchParams.get("order") || "desc"; // Support explicit org filter via query param `org_id` (sent by client UI when staff selects a structure). // Resolution priority: // 1) If `org_id` query param is present, allow it only for staff or if it matches resolved orgId for non-staff. @@ -256,11 +259,24 @@ export async function GET(req: Request) { }); } // Si regime === null/undefined, on garde tous les contrats (pas de filtrage) - // Tri dĂ©croissant par date de fin (plus rĂ©cent d'abord) + // Tri basĂ© sur les paramĂštres sort/order filtered.sort((a: any, b: any) => { - const aDate = new Date(a.end_date || a.date_fin); - const bDate = new Date(b.end_date || b.date_fin); - return bDate.getTime() - aDate.getTime(); + let aValue: any; + let bValue: any; + + // DĂ©terminer le champ Ă  trier + if (sort === 'date_debut' || sort === 'start_date') { + aValue = new Date(a.start_date || a.date_debut || 0); + bValue = new Date(b.start_date || b.date_debut || 0); + } else { + // DĂ©faut: date_fin + aValue = new Date(a.end_date || a.date_fin || 0); + bValue = new Date(b.end_date || b.date_fin || 0); + } + + // Appliquer l'ordre + const comparison = aValue.getTime() - bValue.getTime(); + return order === 'asc' ? comparison : -comparison; }); // Pagination JS aprĂšs filtrage/tri const paged = filtered.slice(offset, offset + limit);