Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions backend/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, () => {
Expand Down
9 changes: 9 additions & 0 deletions backend/src/controllers/im_export.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<unknown, unknown, UploadedDocumentParams> = async (
req,
res,
Expand Down
23 changes: 15 additions & 8 deletions backend/src/routes/im_export.routes.ts
Original file line number Diff line number Diff line change
@@ -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;
77 changes: 56 additions & 21 deletions backend/src/services/im_export.service.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -14,11 +14,7 @@
});

// 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[][]) => {

Check warning on line 17 in backend/src/services/im_export.service.ts

View workflow job for this annotation

GitHub Actions / lint-api

Unexpected any. Specify a different type
try {
// Crée un client Sheets en utilisant JWT pour l'authentification
const sheets = google.sheets({ version: 'v4', auth: jwtClient });
Expand All @@ -35,26 +31,26 @@

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;
}
};

export const exportUsersToCSV = async (): Promise<string> => {
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,
Expand All @@ -65,13 +61,23 @@

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',
Comment thread
tuturd marked this conversation as resolved.
],
});

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 });
Expand All @@ -80,5 +86,34 @@
writeFileSync(filePath, csv);

return filePath;
};

export const exportTeamMembersToCSV = async (): Promise<string> => {
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;
};
32 changes: 28 additions & 4 deletions frontend/src/components/Admin/adminExportImport.tsx
Comment thread
tuturd marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -1,29 +1,34 @@
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';

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<string | null>(null);
const [message, setMessage] = useState<string>('');
const [showBusExport, setShowBusExport] = useState(false);
const [showTeamMembersExport, setShowTeamMembersExport] = 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 }>) => {
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('');
try {
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(
Expand Down Expand Up @@ -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'}
</Button>
<Button
onClick={() => handleExport('teamMembers', exportTeamMembers)}
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.teamMembers ? '⏳ Export en cours...' : 'Exporter les équipes'}
</Button>
</div>

{error && <p className="text-center text-sm text-red-500 font-medium animate-fade-in">{error}</p>}
Expand All @@ -76,6 +86,20 @@ export const AdminExportConnect = () => {
</CardContent>
</Card>
)}

{showTeamMembersExport && (
<Card className="animate-fade-in">
<CardContent className="p-6 space-y-4 text-center">
<h3 className="text-xl font-semibold text-gray-800">📄 Télécharger le csv des équipes</h3>
<a
href={teamMembersUrl}
download
className="inline-block mt-4 px-6 py-2 bg-blue-600 text-white font-medium rounded-lg shadow hover:bg-blue-700 transition">
⬇️ Télécharger le csv
</a>
</CardContent>
</Card>
)}
</CardContent>
</Card>
);
Expand Down
9 changes: 7 additions & 2 deletions frontend/src/services/requests/im_export.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import api from "../api";
import api from '../api';

type ExistingDocumentStatus = {
exists: boolean;
Expand All @@ -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;
};
Expand All @@ -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;
Expand Down
Loading