From 49548fa86d51acf45868559c16ce895b3e2c689b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Poncin?= Date: Wed, 19 Aug 2026 00:05:35 +0200 Subject: [PATCH 1/3] feat: team members export --- backend/server.ts | 1 + .../src/controllers/im_export.controller.ts | 9 +++ backend/src/routes/im_export.routes.ts | 23 ++++-- backend/src/services/im_export.service.ts | 77 ++++++++++++++----- .../components/Admin/adminExportImport.tsx | 49 +++++++++++- frontend/src/pages/admin/adminExport.tsx | 9 ++- .../services/requests/im_export.service.ts | 9 ++- 7 files changed, 143 insertions(+), 34 deletions(-) diff --git a/backend/server.ts b/backend/server.ts index 7e7399e..2530fe2 100644 --- a/backend/server.ts +++ b/backend/server.ts @@ -74,6 +74,7 @@ async function startServer() { app.use('/api/uploads/foodmenu', express.static(path.join(__dirname, '/uploads/foodmenu'))); app.use('/api/uploads/plannings', express.static(path.join(__dirname, '/uploads/plannings'))); app.use('/api/exports/bus', express.static(path.join(__dirname, '/exports/bus'))); + app.use('/api/exports/teammembers', express.static(path.join(__dirname, '/exports/teammembers'))); // Démarrage du serveur app.listen(server_port, () => { diff --git a/backend/src/controllers/im_export.controller.ts b/backend/src/controllers/im_export.controller.ts index 979e6d1..668c40f 100644 --- a/backend/src/controllers/im_export.controller.ts +++ b/backend/src/controllers/im_export.controller.ts @@ -172,6 +172,15 @@ export const exportUsersCSV: AppRequestHandler = async (_req, res) => { } }; +export const exportTeamMembersCSV: AppRequestHandler = async (_req, res) => { + try { + await export_service.exportTeamMembersToCSV(); + Ok(res, { msg: "CSV des membres de l'équipe généré" }); + } catch (error) { + console.error(error); + Error(res, { msg: "Erreur lors de l'export CSV" }); + } +}; export const getUploadedDocumentStatus: AppRequestHandler = async ( req, res, diff --git a/backend/src/routes/im_export.routes.ts b/backend/src/routes/im_export.routes.ts index 38077cd..7dbb04a 100644 --- a/backend/src/routes/im_export.routes.ts +++ b/backend/src/routes/im_export.routes.ts @@ -1,20 +1,27 @@ import express from 'express'; import * as imexportController from '../controllers/im_export.controller'; -import { createUploadMiddleware } from "../middlewares/multer.middleware"; +import { createUploadMiddleware } from '../middlewares/multer.middleware'; import { checkRole } from '../middlewares/user.middleware'; const uploadMiddleware = createUploadMiddleware(); const imexportRouter = express.Router(); imexportRouter.post( - '/admin/import/:category/:item', checkRole("Admin", []), - uploadMiddleware.multerUpload.single("file"), + '/admin/import/:category/:item', + checkRole('Admin', []), + uploadMiddleware.multerUpload.single('file'), uploadMiddleware.verifyAndSave, - imexportController.updateFoodMenu); + imexportController.updateFoodMenu, +); -imexportRouter.post('/admin/exportgsheet', checkRole("Admin", []), imexportController.exportAllDataToSheets); -imexportRouter.get('/admin/exportbus', checkRole("Admin", []), imexportController.exportUsersCSV); -imexportRouter.get('/admin/document/:category/:item', checkRole("Admin", []), imexportController.getUploadedDocumentStatus); -imexportRouter.delete('/admin/document/:category/:item', checkRole("Admin", []), imexportController.deleteDocument); +imexportRouter.post('/admin/exportgsheet', checkRole('Admin', []), imexportController.exportAllDataToSheets); +imexportRouter.get('/admin/exportbus', checkRole('Admin', []), imexportController.exportUsersCSV); +imexportRouter.get('/admin/exportteammembers', checkRole('Admin', []), imexportController.exportTeamMembersCSV); +imexportRouter.get( + '/admin/document/:category/:item', + checkRole('Admin', []), + imexportController.getUploadedDocumentStatus, +); +imexportRouter.delete('/admin/document/:category/:item', checkRole('Admin', []), imexportController.deleteDocument); export default imexportRouter; diff --git a/backend/src/services/im_export.service.ts b/backend/src/services/im_export.service.ts index ba7b756..8f46705 100644 --- a/backend/src/services/im_export.service.ts +++ b/backend/src/services/im_export.service.ts @@ -1,6 +1,6 @@ -import { existsSync, mkdirSync, writeFileSync } from "fs"; +import { existsSync, mkdirSync, writeFileSync } from 'fs'; import { google } from 'googleapis'; -import { parse } from "json2csv"; +import { parse } from 'json2csv'; import * as user_service from './user.service'; import path from 'path'; @@ -14,11 +14,7 @@ const jwtClient = new google.auth.JWT({ }); // Fonction pour écrire dans Google Sheets -export const writeToGoogleSheet = async ( - spreadsheetId: string, - range: string, - values: any[][] -) => { +export const writeToGoogleSheet = async (spreadsheetId: string, range: string, values: any[][]) => { try { // Crée un client Sheets en utilisant JWT pour l'authentification const sheets = google.sheets({ version: 'v4', auth: jwtClient }); @@ -35,7 +31,7 @@ export const writeToGoogleSheet = async ( console.log(`Données envoyées à Google Sheets dans la plage ${range}`); } catch (error) { - console.error('Erreur lors de l\'écriture dans Google Sheets:', error); + console.error("Erreur lors de l'écriture dans Google Sheets:", error); throw error; } }; @@ -43,18 +39,18 @@ export const writeToGoogleSheet = async ( export const exportUsersToCSV = async (): Promise => { const users = await user_service.getUsersAll(); - const formattedUsers = users.map(u => { + const formattedUsers = users.map((u) => { const isOrga = u.roles && u.roles.length > 0; - const isCE = u.permission === "Student" && u.teamId !== null; - const isBenevole = u.roles.some(role => role.roleName === "Bénévole"); + const isCE = u.permission === 'Student' && u.teamId !== null; + const isBenevole = u.roles.some((role) => role.roleName === 'Bénévole'); return { id: u.id, - prenom: u.first_name ?? "", - nom: u.last_name ?? "", - mail: u.email ?? "", - telephone: u.contact ?? "", - nouveau: u.permission === "Nouveau", + prenom: u.first_name ?? '', + nom: u.last_name ?? '', + mail: u.email ?? '', + telephone: u.contact ?? '', + nouveau: u.permission === 'Nouveau', ce: isCE, num_equipe: u.teamId ?? null, benevole: isBenevole, @@ -65,13 +61,23 @@ export const exportUsersToCSV = async (): Promise => { const csv = parse(formattedUsers, { fields: [ - "id", "prenom", "nom", "mail", "telephone", - "nouveau", "ce", "num_equipe", "benevole", "orga", "majeur", "bus_manual" - ] + 'id', + 'prenom', + 'nom', + 'mail', + 'telephone', + 'nouveau', + 'ce', + 'num_equipe', + 'benevole', + 'orga', + 'majeur', + 'bus_manual', + ], }); - const exportDir = path.join(__dirname, "../../exports/bus"); - const filePath = path.join(exportDir, "bus.csv"); + const exportDir = path.join(__dirname, '../../exports/bus'); + const filePath = path.join(exportDir, 'bus.csv'); if (!existsSync(exportDir)) { mkdirSync(exportDir, { recursive: true }); @@ -80,5 +86,34 @@ export const exportUsersToCSV = async (): Promise => { writeFileSync(filePath, csv); return filePath; +}; + +export const exportTeamMembersToCSV = async (): Promise => { + const users = await user_service.getUsersAll(); + + const formattedUsers = users.map((u) => { + const isCE = u.permission === 'Student' && u.teamId !== null; + return { + prenom: u.first_name ?? '', + nom: u.last_name ?? '', + ce: isCE, + equipe: u.teamName, + }; + }); + const csv = parse(formattedUsers, { + fields: ['prenom', 'nom', 'ce', 'equipe'], + }); + + const exportDir = path.join(__dirname, '../../exports/teammembers'); + const filePath = path.join(exportDir, 'teammembers.csv'); + console.log('Exporting team members to:', filePath); + + if (!existsSync(exportDir)) { + mkdirSync(exportDir, { recursive: true }); + } + + writeFileSync(filePath, csv); + + return filePath; }; diff --git a/frontend/src/components/Admin/adminExportImport.tsx b/frontend/src/components/Admin/adminExportImport.tsx index 33a01e5..d048770 100644 --- a/frontend/src/components/Admin/adminExportImport.tsx +++ b/frontend/src/components/Admin/adminExportImport.tsx @@ -1,6 +1,6 @@ import { useState } from 'react'; -import { exportBus, exportDb } from '../../services/requests/im_export.service'; +import { exportBus, exportDb, exportTeamMembers } from '../../services/requests/im_export.service'; import { Button } from '../ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '../ui/card'; import { AdminFileImport } from './adminFileImport'; @@ -81,6 +81,53 @@ export const AdminExportConnect = () => { ); }; +export const AdminExportTeamMembers = () => { + const [showTeamMembersExport, setShowTeamMembersExport] = useState(false); + const [loading, setLoading] = useState(false); + + const teamMembersUrl = 'http://integration.utt.fr/api/exports/teammembers/teammembers.csv'; + + const handleExport = async () => { + try { + setLoading(true); + await exportTeamMembers(); + setShowTeamMembersExport(true); + } catch (error) { + console.error('Erreur export Team Members :', error); + } finally { + setLoading(false); + } + }; + return ( + + + + Exporter les membres des équipes + + + + + + {showTeamMembersExport && ( + + + + ⬇️ Télécharger le csv + + + + )} + + ); +}; + export const AdminImportFoodMenu = () => { return ( diff --git a/frontend/src/pages/admin/adminExport.tsx b/frontend/src/pages/admin/adminExport.tsx index a44d62c..e7c300d 100644 --- a/frontend/src/pages/admin/adminExport.tsx +++ b/frontend/src/pages/admin/adminExport.tsx @@ -1,5 +1,6 @@ import { AdminExportConnect, + AdminExportTeamMembers, AdminImportFoodMenu, AdminImportNotebooks, AdminImportPlannings, @@ -15,14 +16,18 @@ const AdminPageExport: React.FC = () => ( - + - + + + + + diff --git a/frontend/src/services/requests/im_export.service.ts b/frontend/src/services/requests/im_export.service.ts index dd0c4da..5de586b 100644 --- a/frontend/src/services/requests/im_export.service.ts +++ b/frontend/src/services/requests/im_export.service.ts @@ -1,4 +1,4 @@ -import api from "../api"; +import api from '../api'; type ExistingDocumentStatus = { exists: boolean; @@ -16,7 +16,7 @@ export const exportDb = async () => { // Fonction import export const importFile = async (formData: FormData, category: string, item: string) => { const response = await api.post(`/imexport/admin/import/${category}/${item}`, formData, { - headers: { "Content-Type": "multipart/form-data" }, + headers: { 'Content-Type': 'multipart/form-data' }, }); return response.data; }; @@ -26,6 +26,11 @@ export const exportBus = async () => { return response.data; }; +export const exportTeamMembers = async () => { + const response = await api.get('/imexport/admin/exportteammembers'); + return response.data; +}; + export const checkIfExistingDocument = async (category: string, item: string) => { const response = await api.get('/imexport/admin/document/' + category + '/' + item); return response.data.data as ExistingDocumentStatus; From f6b90c16200cd2fa110d8216edf0bf38c3b3cf72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Poncin?= Date: Wed, 19 Aug 2026 00:12:16 +0200 Subject: [PATCH 2/3] fix: api url --- frontend/src/components/Admin/adminExportImport.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/Admin/adminExportImport.tsx b/frontend/src/components/Admin/adminExportImport.tsx index d048770..faa5089 100644 --- a/frontend/src/components/Admin/adminExportImport.tsx +++ b/frontend/src/components/Admin/adminExportImport.tsx @@ -14,7 +14,7 @@ export const AdminExportConnect = () => { const [message, setMessage] = useState(''); const [showBusExport, setShowBusExport] = useState(false); - const busUrl = 'https://integration.utt.fr/api/exports/bus/bus.csv'; + const busUrl = `${import.meta.env.VITE_API_URL}/exports/bus/bus.csv`; const handleExport = async (type: 'db' | 'bus', exportFn: () => Promise<{ message: string }>) => { setLoading((prev) => ({ ...prev, [type]: true })); @@ -85,7 +85,7 @@ export const AdminExportTeamMembers = () => { const [showTeamMembersExport, setShowTeamMembersExport] = useState(false); const [loading, setLoading] = useState(false); - const teamMembersUrl = 'http://integration.utt.fr/api/exports/teammembers/teammembers.csv'; + const teamMembersUrl = `${import.meta.env.VITE_API_URL}/exports/teammembers/teammembers.csv`; const handleExport = async () => { try { From df9caae8972f6580ddce60aa468d4dab86185023 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Poncin?= Date: Wed, 19 Aug 2026 08:59:14 +0200 Subject: [PATCH 3/3] fix: only use one card for team export --- .../components/Admin/adminExportImport.tsx | 73 +++++++------------ frontend/src/pages/admin/adminExport.tsx | 9 +-- 2 files changed, 27 insertions(+), 55 deletions(-) diff --git a/frontend/src/components/Admin/adminExportImport.tsx b/frontend/src/components/Admin/adminExportImport.tsx index faa5089..c2cd0eb 100644 --- a/frontend/src/components/Admin/adminExportImport.tsx +++ b/frontend/src/components/Admin/adminExportImport.tsx @@ -6,17 +6,21 @@ import { Card, CardContent, CardHeader, CardTitle } from '../ui/card'; import { AdminFileImport } from './adminFileImport'; export const AdminExportConnect = () => { - const [loading, setLoading] = useState<{ db: boolean; bus: boolean }>({ + const [loading, setLoading] = useState<{ db: boolean; bus: boolean; teamMembers: boolean }>({ db: false, bus: false, + teamMembers: false, }); const [error, setError] = useState(null); const [message, setMessage] = useState(''); const [showBusExport, setShowBusExport] = useState(false); + const [showTeamMembersExport, setShowTeamMembersExport] = useState(false); const busUrl = `${import.meta.env.VITE_API_URL}/exports/bus/bus.csv`; - const handleExport = async (type: 'db' | 'bus', exportFn: () => Promise<{ message: string }>) => { + const teamMembersUrl = `${import.meta.env.VITE_API_URL}/exports/teammembers/teammembers.csv`; + + const handleExport = async (type: 'db' | 'bus' | 'teamMembers', exportFn: () => Promise<{ message: string }>) => { setLoading((prev) => ({ ...prev, [type]: true })); setError(null); setMessage(''); @@ -24,6 +28,7 @@ export const AdminExportConnect = () => { const response = await exportFn(); setMessage(response.message); if (type === 'bus') setShowBusExport(true); + if (type === 'teamMembers') setShowTeamMembersExport(true); } catch (err) { console.error(`Erreur export ${type}`, err); setError( @@ -56,6 +61,11 @@ export const AdminExportConnect = () => { className="w-full sm:w-auto bg-gradient-to-r from-green-600 to-green-700 hover:from-green-700 hover:to-green-800 text-white py-2 px-6 rounded-xl shadow-md transition-all duration-200"> {loading.bus ? '⏳ Export en cours...' : 'Exporter les bus'} + {error &&

{error}

} @@ -76,54 +86,21 @@ export const AdminExportConnect = () => {
)} - - - ); -}; - -export const AdminExportTeamMembers = () => { - const [showTeamMembersExport, setShowTeamMembersExport] = useState(false); - const [loading, setLoading] = useState(false); - - const teamMembersUrl = `${import.meta.env.VITE_API_URL}/exports/teammembers/teammembers.csv`; - const handleExport = async () => { - try { - setLoading(true); - await exportTeamMembers(); - setShowTeamMembersExport(true); - } catch (error) { - console.error('Erreur export Team Members :', error); - } finally { - setLoading(false); - } - }; - return ( - - - - Exporter les membres des équipes - - - - + {showTeamMembersExport && ( + + +

📄 Télécharger le csv des équipes

+ + ⬇️ Télécharger le csv + +
+
+ )}
- {showTeamMembersExport && ( - - - - ⬇️ Télécharger le csv - - - - )}
); }; diff --git a/frontend/src/pages/admin/adminExport.tsx b/frontend/src/pages/admin/adminExport.tsx index e7c300d..a44d62c 100644 --- a/frontend/src/pages/admin/adminExport.tsx +++ b/frontend/src/pages/admin/adminExport.tsx @@ -1,6 +1,5 @@ import { AdminExportConnect, - AdminExportTeamMembers, AdminImportFoodMenu, AdminImportNotebooks, AdminImportPlannings, @@ -16,18 +15,14 @@ const AdminPageExport: React.FC = () => ( - - - - - + - +