espace-paie-odentas/FIX_UPLOAD_MANUEL_CONTRAT_SIGNE.md
odentas c148c46796 fix: Utiliser le client admin pour bypasser RLS lors de l'upload manuel du contrat signé
Le problème était que l'update du champ contract_pdf_s3_key utilisait le client Supabase normal (avec RLS) au lieu du client admin (service role). Cela empêchait potentiellement la mise à jour du contrat ou l'accès côté client.

Changements:
- Import de createClient depuis @supabase/supabase-js
- Création d'un adminClient avec SUPABASE_SERVICE_ROLE_KEY
- Utilisation de adminClient pour l'update au lieu de sb
- Ajout de logs pour le debug
2025-12-19 17:46:27 +01:00

126 lines
4.9 KiB
Markdown

# 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<number>(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
<DocumentsCard
contractId={id}
contractNumber={data.numero}
contractData={{ ... }}
refreshTrigger={documentsRefreshTrigger}
/>
```
## 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é