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
This commit is contained in:
parent
49f0204a74
commit
c148c46796
2 changed files with 136 additions and 2 deletions
126
FIX_UPLOAD_MANUEL_CONTRAT_SIGNE.md
Normal file
126
FIX_UPLOAD_MANUEL_CONTRAT_SIGNE.md
Normal file
|
|
@ -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<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é
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in a new issue