diff --git a/FIX_UPLOAD_MANUEL_CONTRAT_SIGNE.md b/FIX_UPLOAD_MANUEL_CONTRAT_SIGNE.md new file mode 100644 index 0000000..b66a07a --- /dev/null +++ b/FIX_UPLOAD_MANUEL_CONTRAT_SIGNE.md @@ -0,0 +1,126 @@ +# Fix : Rafraîchissement des documents après upload manuel du contrat signé + +## Problème identifié + +Lorsqu'un contrat de travail était uploadé manuellement depuis la page staff (`staff/contrats/[id]`), il n'apparaissait pas immédiatement sur la page client (`contrats/[id]`). + +### Cause racine + +Le composant `DocumentsCard` utilisé par la page client effectuait des appels `fetch` directs dans des `useEffect` avec uniquement `contractId` comme dépendance. Ces appels n'étaient donc déclenchés qu'au montage du composant, et pas lors d'un upload manuel effectué depuis une autre page. + +Côté staff, le système fonctionnait correctement car il utilisait React Query avec invalidation de cache. + +## Solution implémentée + +### 1. Ajout d'un mécanisme de refresh dans DocumentsCard + +**Fichier** : `components/contrats/DocumentsCard.tsx` + +- Ajout d'une prop `refreshTrigger?: number` +- Ajout de `refreshTrigger` comme dépendance dans tous les `useEffect` qui récupèrent les données +- Ajout de `setLoadingSignedPdf(true)` au début de `fetchSignedPdf` pour afficher un loader lors du rechargement + +```typescript +interface DocumentsCardProps { + contractId: string; + contractNumber?: string; + contractData?: { ... }; + showPayslips?: boolean; + refreshTrigger?: number; // ← Nouvelle prop +} + +useEffect(() => { + const fetchSignedPdf = async () => { + setLoadingSignedPdf(true); // ← Ajouté + // ... code de récupération + }; + fetchSignedPdf(); +}, [contractId, refreshTrigger]); // ← refreshTrigger ajouté +``` + +### 2. Système d'événements personnalisés + +**Fichier** : `components/staff/contracts/ManualSignedContractUpload.tsx` + +Après un upload réussi, émission d'un événement personnalisé : + +```typescript +// Émettre un événement personnalisé pour rafraîchir les documents côté client +if (typeof window !== 'undefined') { + window.dispatchEvent(new CustomEvent('refreshContractDocuments')); +} +``` + +### 3. Écoute de l'événement dans la page client + +**Fichier** : `app/(app)/contrats/[id]/page.tsx` + +- Ajout d'un state `documentsRefreshTrigger` +- Ajout d'un listener pour l'événement `refreshContractDocuments` +- Passage de `refreshTrigger` au composant `DocumentsCard` + +```typescript +const [documentsRefreshTrigger, setDocumentsRefreshTrigger] = useState(0); + +useEffect(() => { + const handleRefreshDocuments = () => { + console.log('🔄 Rafraîchissement des documents demandé'); + setDocumentsRefreshTrigger(prev => prev + 1); + }; + + window.addEventListener('refreshContractDocuments', handleRefreshDocuments); + + return () => { + window.removeEventListener('refreshContractDocuments', handleRefreshDocuments); + }; +}, []); + +// Dans le JSX + +``` + +## Flux de données + +1. **Upload manuel** : L'utilisateur staff upload un contrat signé via `ManualSignedContractUpload` +2. **Upload S3** : Le fichier est uploadé sur S3 et `contract_pdf_s3_key` est mis à jour dans la base de données +3. **Invalidation React Query** : La query `["signed-contract-pdf", contract.id]` est invalidée côté staff +4. **Événement custom** : Un événement `refreshContractDocuments` est émis globalement +5. **Réception côté client** : La page client écoute cet événement et incrémente `documentsRefreshTrigger` +6. **Rechargement** : `DocumentsCard` détecte le changement de `refreshTrigger` et re-déclenche ses `useEffect` +7. **Affichage** : Le contrat signé apparaît dans la liste des documents + +## Avantages de cette approche + +- **Compatibilité** : Fonctionne même si la page client n'est pas en React Query +- **Découplage** : Les composants ne sont pas fortement couplés +- **Extensible** : D'autres composants peuvent écouter le même événement si nécessaire +- **Performance** : Pas de polling inutile, rechargement uniquement quand nécessaire + +## Tests à effectuer + +- [ ] Upload manuel d'un contrat depuis la page staff +- [ ] Vérifier que le contrat apparaît sur la page client (après F5 ou event) +- [ ] Vérifier que l'upload fonctionne toujours côté staff (avec React Query) +- [ ] Vérifier les logs console pour confirmer le déclenchement de l'événement + +## Commit + +``` +fix: Rafraîchir les documents côté client après upload manuel du contrat signé + +- Ajout d'une prop refreshTrigger à DocumentsCard pour forcer le rechargement +- Ajout d'un listener d'événement custom 'refreshContractDocuments' dans la page client +- Émission de l'événement après l'upload réussi dans ManualSignedContractUpload +- Fix: Le contrat signé apparaît maintenant sur la page client après upload manuel depuis staff +``` + +## Notes + +- Cette solution est temporaire en attendant une migration complète vers React Query dans `DocumentsCard` +- À terme, il serait préférable de centraliser toutes les requêtes de documents dans React Query +- L'événement `refreshContractDocuments` pourrait être typé avec TypeScript pour plus de sécurité diff --git a/app/api/staff/contrats/[id]/upload-signed-pdf/route.ts b/app/api/staff/contrats/[id]/upload-signed-pdf/route.ts index 2499240..31114c4 100644 --- a/app/api/staff/contrats/[id]/upload-signed-pdf/route.ts +++ b/app/api/staff/contrats/[id]/upload-signed-pdf/route.ts @@ -1,6 +1,7 @@ // app/api/staff/contrats/[id]/upload-signed-pdf/route.ts import { NextRequest, NextResponse } from "next/server"; import { createSbServer } from "@/lib/supabaseServer"; +import { createClient } from "@supabase/supabase-js"; import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3"; const REGION = process.env.AWS_REGION || "eu-west-3"; @@ -156,8 +157,15 @@ export async function POST( ); } - // Mise à jour du contrat avec la clé S3 - const { error: updateError } = await sb + // Mise à jour du contrat avec la clé S3 - utiliser le client admin pour bypasser RLS + console.log("📝 [UPLOAD] Mise à jour du contrat avec le client admin..."); + + const adminClient = createClient( + process.env.NEXT_PUBLIC_SUPABASE_URL || "", + process.env.SUPABASE_SERVICE_ROLE_KEY || "" + ); + + const { error: updateError } = await adminClient .from("cddu_contracts") .update({ contract_pdf_s3_key: s3Key,