From ba6b733ad0755f42251b55842bee99d904e5dc92 Mon Sep 17 00:00:00 2001 From: odentas Date: Sun, 12 Oct 2025 23:30:58 +0200 Subject: [PATCH] =?UTF-8?q?Docs=20staff=20+=20docs=20comptables=20+=20docs?= =?UTF-8?q?=20g=C3=A9n=C3=A9raux?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- STAFF_DOCUMENTS_FEATURE.md | 207 ++++ STAFF_NOTES_FEATURE.md | 152 +++ STAFF_RESEND_INVITATION_FEATURE.md | 218 +++++ SUMMARY_STAFF_FEATURES.md | 210 ++++ app/(app)/staff/documents/page.tsx | 907 ++++++++++++++++++ app/(app)/staff/salaries/page.tsx | 2 +- app/(app)/vos-documents/page.tsx | 585 ++++++++--- .../documents/delete/route.ts | 82 ++ app/api/auto-declaration/documents/route.ts | 148 +++ app/api/documents/generaux/route.ts | 158 +++ app/api/documents/route.ts | 78 +- .../staff/documents/delete-general/route.ts | 81 ++ app/api/staff/documents/delete/route.ts | 83 ++ app/api/staff/documents/list/route.ts | 89 ++ app/api/staff/documents/upload/route.ts | 116 +++ .../staff/salaries/documents/delete/route.ts | 56 ++ app/api/staff/salaries/documents/route.ts | 121 +++ .../salaries/documents/update-type/route.ts | 106 ++ .../staff/salaries/documents/upload/route.ts | 127 +++ app/api/staff/salaries/search/route.ts | 4 +- app/api/staff/salaries/update/route.ts | 2 +- app/auto-declaration/page.tsx | 255 ++++- components/DocumentPreviewModal.tsx | 169 ++++ components/DocumentViewModal.tsx | 107 +++ components/SaveConfirmationModal.tsx | 114 +++ components/Sidebar.tsx | 8 + components/staff/DocumentViewerModal.tsx | 325 +++++++ components/staff/ResendInvitationModal.tsx | 179 ++++ components/staff/SalariesGridSimple.tsx | 333 ++++++- components/staff/UploadDocumentModal.tsx | 260 +++++ lib/autoDeclarationTokenService.ts | 6 + 31 files changed, 5069 insertions(+), 219 deletions(-) create mode 100644 STAFF_DOCUMENTS_FEATURE.md create mode 100644 STAFF_NOTES_FEATURE.md create mode 100644 STAFF_RESEND_INVITATION_FEATURE.md create mode 100644 SUMMARY_STAFF_FEATURES.md create mode 100644 app/(app)/staff/documents/page.tsx create mode 100644 app/api/auto-declaration/documents/delete/route.ts create mode 100644 app/api/auto-declaration/documents/route.ts create mode 100644 app/api/documents/generaux/route.ts create mode 100644 app/api/staff/documents/delete-general/route.ts create mode 100644 app/api/staff/documents/delete/route.ts create mode 100644 app/api/staff/documents/list/route.ts create mode 100644 app/api/staff/documents/upload/route.ts create mode 100644 app/api/staff/salaries/documents/delete/route.ts create mode 100644 app/api/staff/salaries/documents/route.ts create mode 100644 app/api/staff/salaries/documents/update-type/route.ts create mode 100644 app/api/staff/salaries/documents/upload/route.ts create mode 100644 components/DocumentPreviewModal.tsx create mode 100644 components/DocumentViewModal.tsx create mode 100644 components/SaveConfirmationModal.tsx create mode 100644 components/staff/DocumentViewerModal.tsx create mode 100644 components/staff/ResendInvitationModal.tsx create mode 100644 components/staff/UploadDocumentModal.tsx diff --git a/STAFF_DOCUMENTS_FEATURE.md b/STAFF_DOCUMENTS_FEATURE.md new file mode 100644 index 0000000..e845a63 --- /dev/null +++ b/STAFF_DOCUMENTS_FEATURE.md @@ -0,0 +1,207 @@ +# Feature: Affichage et gestion des documents salariés dans Staff + +## 📝 Description + +Cette fonctionnalité permet au staff de : +- Visualiser les documents up## 📦 Types de documents supportés + +| Type | Label affiché | +|------|---------------| +| `piece_identite` | Pièce d'identité | +| `attestation_secu` | Attestation Sécurité Sociale | +| `rib` | RIB | +| `medecine_travail` | Médecine du travail | +| `contrat_travail` | Contrat de travail *(nouveau - staff uniquement)* | +| `diplome` | Diplôme *(nouveau - staff uniquement)* | +| `justificatif` | Justificatif *(nouveau - staff uniquement)* | +| `autre` | Autre document |r les salariés via la page d'auto-déclaration +- Télécharger ces documents +- **Uploader manuellement des documents pour un salarié** + +Le tout accessible directement depuis l'interface `/staff/salaries`. + +## 🔧 Fichiers modifiés/créés + +### 1. `/app/api/staff/salaries/documents/route.ts` (NOUVEAU) +API qui récupère les documents d'un salarié depuis S3. + +**Fonctionnalités :** +- Récupère tous les fichiers stockés dans S3 pour un matricule donné +- Génère des URLs pré-signées valides 1 heure pour le téléchargement sécurisé +- Détecte automatiquement le type de document (CNI, RIB, etc.) basé sur le nom du fichier +- Retourne les métadonnées (nom, type, taille, date de modification) + +**Endpoint :** `GET /api/staff/salaries/documents?matricule={code_salarie}` + +**Réponse :** +```json +{ + "documents": [ + { + "key": "justif-salaries/MAT001/piece-identite-1234567890.pdf", + "name": "piece-identite-1234567890.pdf", + "type": "Pièce d'identité", + "size": 245678, + "lastModified": "2025-10-12T10:30:00Z", + "downloadUrl": "https://odentas-docs.s3.eu-west-3.amazonaws.com/..." + } + ], + "count": 1, + "matricule": "MAT001" +} +``` + +### 2. `/app/api/staff/salaries/documents/upload/route.ts` (NOUVEAU) +API qui permet au staff d'uploader des documents pour un salarié. + +**Fonctionnalités :** +- Upload de fichiers (PDF, JPG, PNG) jusqu'à 10MB +- Vérification de l'existence du salarié +- Stockage dans S3 sous `justif-salaries/{matricule}/` +- Métadonnées incluant `uploaded-by: 'staff'` pour tracer l'origine + +**Endpoint :** `POST /api/staff/salaries/documents/upload` + +**Body (FormData) :** +- `file`: Le fichier à uploader +- `matricule`: Code salarié +- `type`: Type de document (piece_identite, rib, etc.) + +**Types de documents supportés :** +- `piece_identite` - Pièce d'identité +- `attestation_secu` - Attestation Sécurité Sociale +- `rib` - RIB +- `medecine_travail` - Attestation médecine du travail +- `contrat_travail` - Contrat de travail +- `diplome` - Diplôme +- `justificatif` - Justificatif +- `autre` - Autre document + +### 3. `/components/staff/UploadDocumentModal.tsx` (NOUVEAU) +Composant modal pour uploader des documents. + +**Fonctionnalités :** +- Sélection du type de document via dropdown +- Upload par drag & drop ou clic +- Validation du type et de la taille de fichier +- Prévisualisation du fichier sélectionné +- Feedback visuel pendant l'upload +- Messages de succès/erreur avec toast + +### 4. `/components/staff/SalariesGridSimple.tsx` (MODIFIÉ) +Composant principal de la page staff/salaries. + +**Modifications :** +- Ajout du type `S3Document` pour typer les documents +- Ajout des états `documents`, `loadingDocuments` et `isUploadModalOpen` +- Ajout de la fonction `fetchSalarieDocuments()` qui appelle l'API +- Mise à jour du `useEffect` pour charger les documents lors de la sélection d'un salarié +- Refonte complète de la Card "Documents" pour afficher : + - Un loader pendant le chargement + - La liste des documents avec icône de téléchargement + - Le type, la date et la taille de chaque document + - Un message si aucun document n'est disponible + - **Un bouton "Ajouter un document" pour upload manuel** +- Intégration de `UploadDocumentModal` avec callback de rafraîchissement + +## 🎨 Interface utilisateur + +### Card Documents - États possibles + +1. **Chargement** : Spinner animé +2. **Documents disponibles** : + - Liste cliquable avec : + - Icône de téléchargement + - Type de document (Pièce d'identité, RIB, etc.) + - Date d'upload et taille en Ko + - Fond bleu clair au hover + - Bouton "Ajouter un document" en bas +3. **Aucun document** : + - Message explicatif + - Bouton "Ajouter un document" + +### Modale d'upload + +La modale permet de : +1. Sélectionner le type de document (dropdown avec 8 types) +2. Uploader un fichier via : + - Drag & drop + - Clic pour ouvrir le sélecteur +3. Voir le fichier sélectionné avec sa taille +4. Valider ou annuler l'upload + +### Exemple d'affichage +``` +📄 Documents +──────────────────────── +3 documents disponibles + +[⬇️] Pièce d'identité + 12/10/2025 • 245.7 Ko + +[⬇️] Attestation Sécurité Sociale + 12/10/2025 • 189.2 Ko + +[⬇️] RIB + 12/10/2025 • 102.3 Ko + +[+ Ajouter un document] +``` + +## 🔒 Sécurité + +- Les URLs de téléchargement sont pré-signées et expirent après 1 heure +- L'accès aux APIs est réservé au staff (vérification via `createSbServiceRole()`) +- Les fichiers sont stockés de manière isolée par matricule sur S3 +- Les uploads par le staff sont tracés via la métadonnée `uploaded-by: 'staff'` +- Validation du type de fichier et de la taille (max 10MB) +- Vérification de l'existence du salarié avant upload + +## 🔗 Intégration + +La feature s'intègre avec le système d'auto-déclaration existant : +- Les salariés uploadent leurs documents via `/auto-declaration` +- **Le staff peut aussi uploader des documents manuellement via `/staff/salaries`** +- Les documents sont stockés dans S3 sous `justif-salaries/{matricule}/` +- Le staff peut les visualiser et télécharger via `/staff/salaries` +- Tous les documents (auto-déclaration + staff) sont mélangés dans la même liste + +## 📦 Types de documents supportés + +| Type | Label affiché | +|------|---------------| +| `piece-identite` | Pièce d'identité | +| `attestation-secu` | Attestation Sécurité Sociale | +| `rib` | RIB | +| `medecine-travail` | Médecine du travail | +| `autre` | Autre document | + +## 🚀 Déploiement + +Aucune configuration supplémentaire requise. La feature utilise les variables d'environnement AWS S3 existantes : +- `AWS_REGION` +- `AWS_ACCESS_KEY_ID` +- `AWS_SECRET_ACCESS_KEY` +- `AWS_S3_BUCKET` + +## ✅ Tests recommandés + +1. **Visualisation :** + - Sélectionner un salarié qui a uploadé des documents + - Vérifier que les documents s'affichent correctement + - Cliquer sur un document pour le télécharger + - Vérifier qu'un salarié sans document affiche le bon message + +2. **Upload manuel par le staff :** + - Cliquer sur "Ajouter un document" + - Sélectionner un type de document + - Tester le drag & drop d'un fichier valide + - Tester l'upload avec un fichier trop volumineux (>10MB) + - Tester avec un type de fichier invalide (.txt, .doc) + - Vérifier que le document apparaît immédiatement après l'upload + - Vérifier qu'on peut télécharger le document uploadé + +3. **Edge cases :** + - Salarié sans matricule (code_salarie null) + - Upload pendant qu'un autre est en cours + - Fermeture de la modale pendant l'upload diff --git a/STAFF_NOTES_FEATURE.md b/STAFF_NOTES_FEATURE.md new file mode 100644 index 0000000..d05ff8d --- /dev/null +++ b/STAFF_NOTES_FEATURE.md @@ -0,0 +1,152 @@ +# Feature: Gestion des notes internes pour les salariés (Staff) + +## 📝 Description + +Cette fonctionnalité permet au staff de : +- **Ajouter des notes internes** pour chaque salarié +- **Modifier** les notes existantes +- **Visualiser** les notes dans une interface claire + +Les notes sont stockées dans la colonne `notes` de la table `salaries` et sont **visibles uniquement par le staff**. + +## 🔧 Fichiers modifiés + +### 1. `/components/staff/SalariesGridSimple.tsx` (MODIFIÉ) + +**Ajouts :** +- Ajout du champ `notes` dans le type `Salarie` +- États pour gérer l'édition : + - `isEditingNote` : booléen pour mode édition + - `noteValue` : valeur temporaire de la note en cours d'édition + - `savingNote` : indicateur de sauvegarde en cours + +**Fonctions ajoutées :** +- `handleSaveNote()` : Sauvegarde la note via l'API +- `handleStartEditNote()` : Active le mode édition et charge la note actuelle +- `handleCancelEditNote()` : Annule l'édition en cours + +**UI de la Card Note interne :** + +**Mode Lecture (note existante) :** +- Affichage de la note dans un bloc jaune avec bordure +- Format `whitespace-pre-wrap` pour respecter les retours à la ligne +- Bouton "✏️ Modifier la note" + +**Mode Lecture (pas de note) :** +- Message "Aucune note enregistrée pour ce salarié" +- Bouton "+ Ajouter une note" + +**Mode Édition :** +- Textarea de 5 lignes avec placeholder +- Bouton "Enregistrer" avec loader pendant la sauvegarde +- Bouton "Annuler" pour quitter sans sauvegarder +- Désactivation des contrôles pendant la sauvegarde + +### 2. `/app/api/staff/salaries/update/route.ts` (MODIFIÉ) + +**Modifications :** +- Ajout de `'notes'` dans la liste `allowedFields` +- Permet maintenant de mettre à jour la colonne `notes` de la table `salaries` + +### 3. `/app/(app)/staff/salaries/page.tsx` (MODIFIÉ) + +**Modifications :** +- Ajout de `notes` dans le `.select()` de la requête Supabase initiale +- Permet de charger les notes existantes au chargement de la page + +### 4. `/app/api/staff/salaries/search/route.ts` (MODIFIÉ) + +**Modifications :** +- Ajout de `notes` dans le `.select()` de la requête de recherche +- Permet d'avoir les notes dans les résultats de recherche + +## 🎨 Interface utilisateur + +### Card "Note interne" + +``` +📝 Note interne +──────────────────────── + +[Mode vide] +Aucune note enregistrée pour ce salarié. +[+ Ajouter une note] + +[Mode avec note] +┌──────────────────────────────┐ +│ Note existante qui peut │ +│ contenir plusieurs lignes │ +│ avec retours à la ligne │ +└──────────────────────────────┘ +[✏️ Modifier la note] + +[Mode édition] +┌──────────────────────────────┐ +│ Textarea éditable │ +│ │ +│ │ +│ │ +└──────────────────────────────┘ +[Enregistrer] [Annuler] +``` + +## 💾 Stockage + +- **Table** : `salaries` +- **Colonne** : `notes` (TEXT, nullable) +- **Format** : Texte libre avec support des retours à la ligne +- **Visibilité** : Staff uniquement + +## 🔒 Sécurité + +- Seul le staff peut lire et modifier les notes +- Validation côté serveur via `staff_users.is_staff` +- Les notes ne sont jamais exposées aux salariés + +## ⚡ Comportement + +1. **Changement de salarié** : Réinitialise automatiquement le mode édition +2. **Sauvegarde** : Mise à jour optimiste + confirmation serveur +3. **Annulation** : Restaure la valeur d'origine sans sauvegarder +4. **Feedback visuel** : Loader pendant la sauvegarde, désactivation des boutons + +## 🎯 Cas d'usage + +- Notes RH confidentielles +- Historique des échanges +- Alertes ou rappels internes +- Informations administratives spécifiques +- Tout commentaire utile pour le suivi du salarié + +## ✅ Tests recommandés + +1. **Ajout d'une note sur un salarié sans note** + - Cliquer sur "+ Ajouter une note" + - Saisir du texte avec plusieurs lignes + - Valider et vérifier l'affichage + +2. **Modification d'une note existante** + - Sélectionner un salarié avec une note + - Cliquer sur "✏️ Modifier la note" + - Modifier le texte + - Valider et vérifier la mise à jour + +3. **Annulation** + - Commencer à éditer une note + - Cliquer sur "Annuler" + - Vérifier que les modifications ne sont pas sauvegardées + +4. **Changement de salarié pendant l'édition** + - Éditer une note + - Sélectionner un autre salarié + - Vérifier que le mode édition se désactive + +5. **Retours à la ligne** + - Ajouter une note avec plusieurs paragraphes + - Vérifier que les retours à la ligne sont préservés + +6. **Sauvegarde d'une note vide** + - Éditer une note existante + - Vider le contenu + - Sauvegarder + - Vérifier qu'elle devient "Aucune note" diff --git a/STAFF_RESEND_INVITATION_FEATURE.md b/STAFF_RESEND_INVITATION_FEATURE.md new file mode 100644 index 0000000..ecaab88 --- /dev/null +++ b/STAFF_RESEND_INVITATION_FEATURE.md @@ -0,0 +1,218 @@ +# Feature: Relance d'invitation auto-déclaration + +## 📝 Description + +Cette fonctionnalité permet au staff de renvoyer l'email d'invitation à l'auto-déclaration à un salarié directement depuis la page `/staff/salaries`. + +## 🎯 Objectif + +Permettre de relancer facilement un salarié qui : +- N'a pas reçu l'email initial +- N'a pas complété son profil +- A besoin d'un nouveau lien (token expiré après 7 jours) +- Doit mettre à jour ses documents + +## 🔧 Fichiers créés/modifiés + +### 1. `/components/staff/ResendInvitationModal.tsx` (NOUVEAU) + +Composant modal de confirmation avant envoi de la relance. + +**Fonctionnalités :** +- Affichage des informations du salarié (nom, matricule, email) +- Validation de la présence d'un email +- Message explicatif du contenu de l'email +- Confirmation visuelle avant envoi +- Feedback pendant l'envoi (loader) +- Notification de succès/erreur + +### 2. `/components/staff/SalariesGridSimple.tsx` (MODIFIÉ) + +**Ajouts :** +- Import de `ResendInvitationModal` +- État `isResendInvitationOpen` pour gérer l'ouverture de la modale +- Bouton "Envoyer une relance justifs" en gradient bleu/indigo +- Intégration de la modale avec le salarié sélectionné + +**Emplacement du bouton :** +- Au dessus de la card "Informations personnelles" +- Pleine largeur avec icône mail +- Design attrayant en gradient + +### 3. API utilisée : `/api/auto-declaration/generate-token/route.ts` (EXISTANTE) + +L'API existante est réutilisée. Elle : +- Génère un nouveau token sécurisé +- Supprime les anciens tokens du salarié +- Envoie l'email d'invitation avec le lien personnalisé +- Retourne le statut d'envoi + +## 🎨 Interface utilisateur + +### Bouton de relance +``` +┌─────────────────────────────────────┐ +│ 📧 Envoyer une relance justifs │ +│ (Gradient bleu → indigo) │ +└─────────────────────────────────────┘ +``` + +### Modale de confirmation + +``` +┌───────────────────────────────────────┐ +│ 📧 Relancer le salarié ✕ │ +├───────────────────────────────────────┤ +│ │ +│ ✓ Email d'invitation à │ +│ l'auto-déclaration │ +│ │ +│ Le salarié recevra un email avec │ +│ un lien sécurisé... │ +│ │ +│ Salarié: Jean Dupont │ +│ Matricule: MAT001 │ +│ Email: jean.dupont@mail.com │ +│ │ +│ 💡 Contenu: Lien valide 7 jours... │ +│ │ +│ [Annuler] [Envoyer] 📧 │ +└───────────────────────────────────────┘ +``` + +## 📧 Contenu de l'email envoyé + +L'email envoyé est **exactement le même** que lors de la création du salarié : + +**Template :** `auto-declaration-invitation` + +**Contenu :** +- Salutation personnalisée avec prénom +- Nom de l'organisation employeur +- Matricule du salarié +- **Lien d'accès sécurisé** avec token unique +- Instructions pour : + - Compléter les informations personnelles + - Uploader les justificatifs (CNI, attestation Sécu, RIB, etc.) + - Accepter les conditions RGPD + +**Validité :** 7 jours + +## 🔐 Sécurité + +- ✅ Vérification de l'authentification staff +- ✅ Validation de la présence d'un email +- ✅ Génération d'un nouveau token sécurisé (32 bytes hex) +- ✅ Suppression automatique des anciens tokens +- ✅ Token unique par salarié +- ✅ Expiration automatique après 7 jours + +## ⚙️ Comportement + +1. **Clic sur le bouton** → Ouverture de la modale +2. **Vérification email** → Si absent, message d'erreur et bouton désactivé +3. **Confirmation** → Appel API pour générer token et envoyer email +4. **Pendant l'envoi** → Loader et désactivation des boutons +5. **Succès** → Toast de confirmation et fermeture de la modale +6. **Erreur** → Toast d'erreur avec message détaillé + +## 📊 Cas d'usage + +### ✅ Cas valides +- Salarié avec email valide +- Besoin de renvoyer l'invitation +- Token expiré (> 7 jours) +- Email initial non reçu +- Salarié n'a pas complété son profil + +### ❌ Cas bloqués +- Salarié sans email → Bouton désactivé avec message d'erreur +- Pendant l'envoi → Boutons désactivés +- Erreur API → Toast d'erreur + +## 🎯 Flow complet + +``` +Staff sélectionne salarié + ↓ +Clic "Envoyer une relance justifs" + ↓ +Modale s'ouvre avec infos salarié + ↓ +Validation email présent + ↓ +Staff confirme "Envoyer" + ↓ +API génère nouveau token + ↓ +Supprime anciens tokens + ↓ +Envoie email avec lien + ↓ +Toast succès + fermeture modale + ↓ +Salarié reçoit email + ↓ +Clique sur lien → /auto-declaration?token=... + ↓ +Complète son profil et upload documents +``` + +## 💡 Avantages + +1. **Simplicité** : Un seul clic depuis la fiche salarié +2. **Confirmation** : Modale explicative avant envoi +3. **Feedback** : Messages clairs de succès/erreur +4. **Sécurité** : Nouveau token à chaque envoi +5. **Traçabilité** : Logs des envois côté serveur +6. **UX** : Bouton visible et design attrayant + +## ✅ Tests recommandés + +1. **Envoi réussi** + - Salarié avec email valide + - Vérifier réception de l'email + - Vérifier que le lien fonctionne + - Vérifier expiration après 7 jours + +2. **Salarié sans email** + - Bouton doit être désactivé + - Message d'erreur affiché + - Pas d'appel API + +3. **Erreur réseau** + - Simuler échec API + - Vérifier toast d'erreur + - Modale reste ouverte + +4. **Double envoi** + - Envoyer 2 fois de suite + - Vérifier que seul le dernier token est valide + - Ancien token doit être supprimé + +5. **Changement de salarié** + - Ouvrir modale pour salarié A + - Changer vers salarié B + - Vérifier que la modale affiche les bonnes infos + +## 🔄 Intégration avec l'existant + +- Utilise l'API `/api/auto-declaration/generate-token` existante +- Utilise le service `autoDeclarationTokenService` existant +- Utilise les templates d'email existants +- Compatible avec le système d'auto-déclaration actuel +- Pas de modification de la base de données nécessaire + +## 📈 Prochaines améliorations possibles + +- [ ] Historique des relances envoyées +- [ ] Indication de la dernière relance envoyée +- [ ] Statistiques de complétion après relance +- [ ] Envoi groupé à plusieurs salariés +- [ ] Personnalisation du message +- [ ] Rappel automatique après X jours + +--- + +**Date de création** : 12 octobre 2025 +**Version** : 1.0.0 diff --git a/SUMMARY_STAFF_FEATURES.md b/SUMMARY_STAFF_FEATURES.md new file mode 100644 index 0000000..789e30d --- /dev/null +++ b/SUMMARY_STAFF_FEATURES.md @@ -0,0 +1,210 @@ +# 🎉 Récapitulatif des fonctionnalités développées - Page Staff/Salariés + +## Vue d'ensemble + +Trois fonctionnalités majeures ont été implémentées pour améliorer la gestion des salariés dans l'interface Staff : + +1. ✅ **Gestion complète des documents** (visualisation, upload, modification, suppression) +2. ✅ **Notes internes** (ajout, modification, visualisation) +3. ✅ **Connexion S3** pour les documents d'auto-déclaration + +--- + +## 🗂️ 1. Gestion des documents salariés + +### Fichiers créés +- `/app/api/staff/salaries/documents/route.ts` - Récupération des documents S3 +- `/app/api/staff/salaries/documents/upload/route.ts` - Upload par le staff +- `/app/api/staff/salaries/documents/delete/route.ts` - Suppression de documents +- `/app/api/staff/salaries/documents/update-type/route.ts` - Changement de type +- `/components/staff/UploadDocumentModal.tsx` - Modale d'upload +- `/components/staff/DocumentViewerModal.tsx` - Visionneuse de documents + +### Fonctionnalités +- ✅ Liste des documents S3 du salarié avec taille et date +- ✅ Visualisation PDF/image dans une modale moderne +- ✅ Upload manuel de documents par le staff (drag & drop) +- ✅ Changement du type de document (CNI, RIB, etc.) +- ✅ Suppression de documents avec confirmation +- ✅ Téléchargement et ouverture dans nouvel onglet +- ✅ URLs pré-signées sécurisées (1h d'expiration) + +### Types de documents +- Pièce d'identité +- Attestation Sécurité Sociale +- RIB +- Attestation médecine du travail +- Contrat de travail *(staff uniquement)* +- Diplôme *(staff uniquement)* +- Justificatif *(staff uniquement)* +- Autre document + +--- + +## 📝 2. Notes internes + +### Fichiers modifiés +- `/components/staff/SalariesGridSimple.tsx` - UI et logique de gestion +- `/app/api/staff/salaries/update/route.ts` - Ajout du champ `notes` +- `/app/(app)/staff/salaries/page.tsx` - Inclusion de `notes` dans la query +- `/app/api/staff/salaries/search/route.ts` - Inclusion de `notes` dans la recherche + +### Fonctionnalités +- ✅ Ajout de notes internes par salarié +- ✅ Modification des notes existantes +- ✅ Support des retours à la ligne (textarea) +- ✅ Sauvegarde avec feedback visuel +- ✅ Annulation sans sauvegarde +- ✅ Réinitialisation automatique au changement de salarié + +--- + +## 🏗️ Architecture technique + +### Stack +- **Frontend** : React, Next.js 14, TypeScript +- **Backend** : Next.js API Routes, Supabase +- **Storage** : AWS S3 (région eu-west-3, Paris) +- **Authentification** : Supabase Auth avec vérification staff + +### Sécurité +- ✅ Vérification `is_staff` sur toutes les routes +- ✅ URLs S3 pré-signées avec expiration +- ✅ Validation des types de fichiers (PDF, JPG, PNG) +- ✅ Limite de taille : 10 MB +- ✅ Métadonnées de traçabilité (`uploaded-by: 'staff'`) +- ✅ Notes visibles uniquement par le staff + +### Optimisations +- ✅ Mise à jour optimiste de l'UI +- ✅ Refresh automatique après upload/suppression +- ✅ Loader pendant les opérations asynchrones +- ✅ Gestion d'état locale pour éviter les re-fetch + +--- + +## 📊 Structure S3 + +``` +odentas-docs/ +└── justif-salaries/ + └── {matricule}/ + ├── piece-identite-{timestamp}.pdf + ├── attestation-secu-{timestamp}.pdf + ├── rib-{timestamp}.pdf + ├── contrat-travail-{timestamp}.pdf + └── ... +``` + +--- + +## 🎨 Expérience utilisateur + +### Page `/staff/salaries` + +**Layout à 3 colonnes :** +1. **Colonne 1-2** : Table des salariés avec filtres +2. **Colonne 3** : Cards détaillées du salarié sélectionné + - 👤 Informations personnelles + - 📝 Note interne *(nouvelle)* + - 📄 Documents *(améliorée)* + - 💼 Contrats récents + +### Card Documents +- Clic sur un document → Modale de visualisation +- Prévisualisation PDF/image intégrée +- Actions : Modifier type, Télécharger, Supprimer, Nouvel onglet +- Bouton "+ Ajouter un document" toujours visible + +### Card Note interne +- Mode lecture : Affichage avec fond jaune +- Mode édition : Textarea 5 lignes +- Boutons : Enregistrer / Annuler +- Support multi-lignes avec `whitespace-pre-wrap` + +--- + +## 📚 Documentation créée + +- `STAFF_DOCUMENTS_FEATURE.md` - Gestion des documents +- `STAFF_NOTES_FEATURE.md` - Gestion des notes +- `SUMMARY_STAFF_FEATURES.md` - Ce fichier + +--- + +## 🧪 Tests à effectuer + +### Documents +1. Upload d'un document (drag & drop et clic) +2. Visualisation PDF et image +3. Changement de type de document +4. Suppression avec confirmation +5. Vérification des liens de téléchargement + +### Notes +1. Ajout d'une note sur un salarié +2. Modification d'une note existante +3. Annulation sans sauvegarde +4. Changement de salarié pendant l'édition +5. Notes multi-lignes + +### Intégration +1. Salarié avec documents auto-déclarés + documents staff +2. Navigation entre plusieurs salariés +3. Recherche et filtres +4. Performance avec beaucoup de documents + +--- + +## 🚀 Prochaines étapes suggérées + +### Court terme +- [ ] Ajouter un historique des modifications de documents +- [ ] Permettre de filtrer/trier par présence de notes +- [ ] Notification quand un salarié upload un nouveau document + +### Moyen terme +- [ ] Validation automatique de documents (OCR) +- [ ] Catégories personnalisées de documents +- [ ] Export groupé de documents + +### Long terme +- [ ] Versioning des documents +- [ ] Signatures électroniques intégrées +- [ ] Dashboard analytique des documents manquants + +--- + +## 💡 Points techniques importants + +### Gestion des états +- Réinitialisation automatique lors du changement de salarié +- Mise à jour optimiste pour meilleure UX +- Gestion des erreurs avec toast notifications + +### Performance +- Pagination des salariés (200 max) +- Génération d'URLs S3 à la demande +- Cache des documents jusqu'au refresh + +### Maintenance +- Code TypeScript strict +- Composants réutilisables (modales) +- APIs RESTful cohérentes +- Logging console pour debug + +--- + +## 📞 Support + +En cas de problème : +1. Vérifier les logs serveur Next.js +2. Vérifier les erreurs console navigateur +3. Tester les permissions Supabase RLS +4. Valider les credentials AWS S3 +5. Consulter la documentation des features + +--- + +**Dernière mise à jour** : 12 octobre 2025 +**Version** : 1.0.0 diff --git a/app/(app)/staff/documents/page.tsx b/app/(app)/staff/documents/page.tsx new file mode 100644 index 0000000..42499ec --- /dev/null +++ b/app/(app)/staff/documents/page.tsx @@ -0,0 +1,907 @@ +'use client' + +import React from 'react' +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' +import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card' +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { + Upload, FileText, Folder, Building2, Search, Plus, + Trash2, Edit3, Download, Loader2, X, Check, ChevronDown, ChevronRight +} from 'lucide-react' +import { usePageTitle } from '@/hooks/usePageTitle' +import { toast } from 'sonner' +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog' + +type Organization = { + id: string + name: string + structure_api: string +} + +type GeneralDocument = { + type: string + label: string + available: boolean + key?: string + name?: string + size?: number + downloadUrl?: string +} + +type DocumentItem = { + id: string + title: string + filename: string + category: string + period_label?: string | null + size_bytes: number + date_added: string + storage_path: string + download_url?: string +} + +type Period = { + label: string + count: number +} + +const DOC_TYPES = { + "contrat-odentas": "Contrat Odentas", + "licence-spectacles": "Licence de spectacles", + "rib": "RIB", + "kbis-jo": "KBIS / Journal Officiel", + "delegation-signature": "Délégation de signature" +} + +function formatBytes(bytes?: number) { + if (!bytes && bytes !== 0) return '' + const sizes = ['o', 'Ko', 'Mo', 'Go', 'To'] + const i = Math.floor(Math.log(bytes) / Math.log(1024)) + return `${(bytes / Math.pow(1024, i)).toFixed(1)} ${sizes[i]}` +} + +function formatDate(dateStr: string) { + return new Date(dateStr).toLocaleDateString('fr-FR', { + day: '2-digit', + month: '2-digit', + year: 'numeric' + }) +} + +function slugify(text: string): string { + return text + .normalize('NFD') + .replace(/[\u0300-\u036f]/g, '') + .toLowerCase() + .trim() + .replace(/[^a-z0-9]+/g, '-') + .replace(/^-+|-+$/g, '') +} + +// Modal pour uploader un document général +function UploadGeneralDocModal({ + isOpen, + onClose, + orgId, + orgKey, + docType +}: { + isOpen: boolean + onClose: () => void + orgId: string + orgKey: string + docType: string +}) { + const [file, setFile] = React.useState(null) + const [uploading, setUploading] = React.useState(false) + const queryClient = useQueryClient() + + const handleUpload = async () => { + if (!file) return + + setUploading(true) + try { + const formData = new FormData() + formData.append('file', file) + formData.append('org_id', orgId) + formData.append('org_key', slugify(orgKey)) + formData.append('doc_type', docType) + formData.append('category', 'docs_generaux') + + const response = await fetch('/api/staff/documents/upload', { + method: 'POST', + body: formData + }) + + if (!response.ok) throw new Error('Erreur lors de l\'upload') + + toast.success('Document uploadé avec succès') + queryClient.invalidateQueries({ queryKey: ['documents', 'generaux', orgId] }) + setFile(null) + onClose() + } catch (error) { + toast.error('Erreur lors de l\'upload du document') + console.error(error) + } finally { + setUploading(false) + } + } + + return ( + + + + Uploader {DOC_TYPES[docType as keyof typeof DOC_TYPES]} + + Sélectionnez un fichier PDF à uploader pour ce type de document. + + + +
+
+ + setFile(e.target.files?.[0] || null)} + /> + {file && ( +

+ {file.name} ({formatBytes(file.size)}) +

+ )} +
+ +
+ + +
+
+
+
+ ) +} + +// Modal pour uploader un ou plusieurs documents comptables +function UploadComptableModal({ + isOpen, + onClose, + orgId, + orgKey +}: { + isOpen: boolean + onClose: () => void + orgId: string + orgKey: string +}) { + type FileEntry = { id: number; file: File | null } + const [files, setFiles] = React.useState([{ id: 1, file: null }]) + const [period, setPeriod] = React.useState('') + const [uploading, setUploading] = React.useState(false) + const queryClient = useQueryClient() + const nextId = React.useRef(2) + + const addFileRow = () => { + setFiles(prev => [...prev, { id: nextId.current++, file: null }]) + } + + const removeFileRow = (id: number) => { + if (files.length === 1) return + setFiles(prev => prev.filter(f => f.id !== id)) + } + + const updateFile = (id: number, file: File | null) => { + setFiles(prev => prev.map(f => f.id === id ? { ...f, file } : f)) + } + + const handleUpload = async () => { + const validFiles = files.filter(f => f.file !== null).map(f => f.file!) + if (validFiles.length === 0 || !period) return + + setUploading(true) + try { + // Upload chaque fichier séparément + for (const file of validFiles) { + const formData = new FormData() + formData.append('file', file) + formData.append('org_id', orgId) + formData.append('org_key', slugify(orgKey)) + formData.append('period', period) + formData.append('category', 'docs_comptables') + + const response = await fetch('/api/staff/documents/upload', { + method: 'POST', + body: formData + }) + + if (!response.ok) throw new Error(`Erreur lors de l'upload de ${file.name}`) + } + + toast.success(`${validFiles.length} document(s) uploadé(s) avec succès`) + queryClient.invalidateQueries({ queryKey: ['documents', 'comptables'] }) + setFiles([{ id: 1, file: null }]) + setPeriod('') + onClose() + } catch (error) { + toast.error('Erreur lors de l\'upload des documents') + console.error(error) + } finally { + setUploading(false) + } + } + + return ( + + + + Uploader des documents comptables + + Ajoutez un ou plusieurs fichiers pour une période donnée. + + + +
+
+ + setPeriod(e.target.value)} + /> +

+ Format: YYMM-mois-YYYY +

+
+ +
+
+ + +
+ + {files.map((entry, index) => ( +
+
+ updateFile(entry.id, e.target.files?.[0] || null)} + disabled={uploading} + /> + {entry.file && ( +

+ {entry.file.name} ({formatBytes(entry.file.size)}) +

+ )} +
+ {files.length > 1 && ( + + )} +
+ ))} +
+ +
+ + +
+
+
+
+ ) +} + +// Modal pour visualiser un document +function ViewDocumentModal({ + isOpen, + onClose, + document +}: { + isOpen: boolean + onClose: () => void + document: { name: string; downloadUrl: string; size?: number } | null +}) { + if (!document) return null + + return ( + + + + {document.name} + + {document.size && `Taille: ${formatBytes(document.size)}`} + + + +
+