Compare commits
2 Commits
bb16aaee40
...
ce7e89d339
Author | SHA1 | Date | |
---|---|---|---|
ce7e89d339 | |||
bd522743af |
@ -27,11 +27,11 @@ Nous avons élaboré un plan de bataille complet pour l'implémentation du backe
|
||||
- ✅ Système de migrations de base de données avec DrizzleORM
|
||||
|
||||
#### Composants Non Implémentés
|
||||
- ⏳ Module d'authentification avec GitHub OAuth
|
||||
- ⏳ Stratégies JWT pour la gestion des sessions
|
||||
- ✅ Module d'authentification avec GitHub OAuth
|
||||
- ✅ Stratégies JWT pour la gestion des sessions
|
||||
- ✅ Guards et décorateurs pour la protection des routes
|
||||
- ❌ Module groupes
|
||||
- ❌ Module tags
|
||||
- ✅ Module groupes
|
||||
- ✅ Module tags
|
||||
- ❌ Communication en temps réel avec Socket.IO
|
||||
- ❌ Fonctionnalités de conformité RGPD
|
||||
- ⏳ Tests unitaires et e2e
|
||||
@ -43,15 +43,20 @@ Nous avons élaboré un plan de bataille complet pour l'implémentation du backe
|
||||
- ✅ Structure de base du projet Next.js
|
||||
- ✅ Configuration de ShadcnUI pour les composants UI
|
||||
- ✅ Configuration Docker pour le déploiement
|
||||
- ✅ Pages d'authentification (login, callback, logout)
|
||||
- ✅ Système d'authentification avec GitHub OAuth
|
||||
- ✅ Page d'accueil et tableau de bord
|
||||
- ✅ Pages de gestion de projets (liste, création, édition)
|
||||
- ✅ Pages de gestion de personnes (liste, création, édition)
|
||||
- ✅ Pages de création et gestion de groupes (manuelle et automatique)
|
||||
- ✅ Pages d'administration (utilisateurs, tags, statistiques)
|
||||
|
||||
#### Composants En Cours
|
||||
- ✅ Intégration avec l'API backend (avec fallback aux données mock)
|
||||
- ⏳ Fonctionnalités de collaboration en temps réel
|
||||
|
||||
#### Composants Non Implémentés
|
||||
- ❌ Pages d'authentification (login, callback)
|
||||
- ❌ Page d'accueil et tableau de bord
|
||||
- ❌ Pages de gestion de projets
|
||||
- ❌ Pages de gestion de personnes
|
||||
- ❌ Pages de création et gestion de groupes
|
||||
- ❌ Fonctionnalités de collaboration en temps réel
|
||||
- ❌ Optimisations de performance et d'expérience utilisateur
|
||||
- ❌ Optimisations de performance et d'expérience utilisateur avancées
|
||||
|
||||
## Tâches Restantes
|
||||
|
||||
@ -72,9 +77,9 @@ Nous avons élaboré un plan de bataille complet pour l'implémentation du backe
|
||||
- [x] Implémenter le refresh token
|
||||
|
||||
##### Modules Manquants
|
||||
- [ ] Implémenter le module groupes (contrôleurs, services, DTOs)
|
||||
- [ ] Implémenter le module tags (contrôleurs, services, DTOs)
|
||||
- [ ] Compléter les relations entre les modules existants
|
||||
- [x] Implémenter le module groupes (contrôleurs, services, DTOs)
|
||||
- [x] Implémenter le module tags (contrôleurs, services, DTOs)
|
||||
- [x] Compléter les relations entre les modules existants
|
||||
|
||||
#### Priorité Moyenne
|
||||
|
||||
@ -106,18 +111,18 @@ Nous avons élaboré un plan de bataille complet pour l'implémentation du backe
|
||||
#### Priorité Haute
|
||||
|
||||
##### Authentification
|
||||
- [ ] Créer la page de login avec le bouton "Login with GitHub"
|
||||
- [ ] Implémenter la page de callback OAuth
|
||||
- [ ] Configurer le stockage sécurisé des tokens JWT
|
||||
- [ ] Implémenter la logique de refresh token
|
||||
- [ ] Créer les composants de protection des routes authentifiées
|
||||
- [x] Créer la page de login avec le bouton "Login with GitHub"
|
||||
- [x] Implémenter la page de callback OAuth
|
||||
- [x] Configurer le stockage sécurisé des tokens JWT
|
||||
- [x] Implémenter la logique de refresh token
|
||||
- [x] Créer les composants de protection des routes authentifiées
|
||||
|
||||
##### Pages Principales
|
||||
- [ ] Implémenter la page d'accueil
|
||||
- [ ] Créer le tableau de bord utilisateur
|
||||
- [ ] Développer les pages de gestion de projets (liste, création, détail, édition)
|
||||
- [ ] Développer les pages de gestion de personnes (liste, création, détail, édition)
|
||||
- [ ] Implémenter les pages de création et gestion de groupes
|
||||
- [x] Implémenter la page d'accueil
|
||||
- [x] Créer le tableau de bord utilisateur
|
||||
- [x] Développer les pages de gestion de projets (liste, création, détail, édition)
|
||||
- [x] Développer les pages de gestion de personnes (liste, création, détail, édition)
|
||||
- [x] Implémenter les pages de création et gestion de groupes
|
||||
|
||||
#### Priorité Moyenne
|
||||
|
||||
@ -169,57 +174,66 @@ Nous avons élaboré un plan de bataille complet pour l'implémentation du backe
|
||||
- Configurer les stratégies JWT pour la gestion des sessions ✅
|
||||
- Créer les guards et décorateurs pour la protection des routes ✅
|
||||
|
||||
2. **Modules Manquants**
|
||||
- Implémenter le module groupes
|
||||
- Implémenter le module tags
|
||||
- Compléter les relations entre les modules existants
|
||||
2. **Modules Manquants** ✅
|
||||
- Implémenter le module groupes ✅
|
||||
- Implémenter le module tags ✅
|
||||
- Compléter les relations entre les modules existants ✅
|
||||
|
||||
### Frontend (Priorité Haute)
|
||||
1. **Authentification**
|
||||
- Créer la page de login avec le bouton "Login with GitHub"
|
||||
- Implémenter la page de callback OAuth
|
||||
- Configurer le stockage sécurisé des tokens JWT
|
||||
1. **Authentification** ✅
|
||||
- Créer la page de login avec le bouton "Login with GitHub" ✅
|
||||
- Implémenter la page de callback OAuth ✅
|
||||
- Configurer le stockage sécurisé des tokens JWT ✅
|
||||
|
||||
2. **Pages Principales**
|
||||
- Implémenter la page d'accueil
|
||||
- Créer le tableau de bord utilisateur
|
||||
- Développer les pages de gestion de projets et de personnes
|
||||
2. **Pages Principales** ✅
|
||||
- Implémenter la page d'accueil ✅
|
||||
- Créer le tableau de bord utilisateur ✅
|
||||
- Développer les pages de gestion de projets et de personnes ✅
|
||||
|
||||
3. **Intégration avec le Backend** ✅
|
||||
- Remplacer les données mock par des appels API réels ✅
|
||||
- Implémenter la gestion des erreurs API ✅
|
||||
- Ajouter des indicateurs de chargement ✅
|
||||
|
||||
## Progression Globale
|
||||
|
||||
| Composant | Progression |
|
||||
|-----------|-------------|
|
||||
| Backend - Structure de Base | 90% |
|
||||
| Backend - Structure de Base | 100% |
|
||||
| Backend - Base de Données | 100% |
|
||||
| Backend - Modules Fonctionnels | 60% |
|
||||
| Backend - Authentification | 90% |
|
||||
| Backend - Modules Fonctionnels | 100% |
|
||||
| Backend - Authentification | 100% |
|
||||
| Backend - WebSockets | 0% |
|
||||
| Backend - Tests et Documentation | 20% |
|
||||
| Frontend - Structure de Base | 70% |
|
||||
| Frontend - Pages et Composants | 10% |
|
||||
| Frontend - Authentification | 0% |
|
||||
| Frontend - Fonctionnalités Avancées | 0% |
|
||||
| Frontend - Structure de Base | 100% |
|
||||
| Frontend - Pages et Composants | 100% |
|
||||
| Frontend - Authentification | 100% |
|
||||
| Frontend - Intégration API | 80% |
|
||||
| Frontend - Fonctionnalités Avancées | 30% |
|
||||
| Déploiement | 70% |
|
||||
|
||||
## Estimation du Temps Restant
|
||||
|
||||
Basé sur l'état d'avancement actuel et les tâches restantes, l'estimation du temps nécessaire pour compléter le projet est la suivante:
|
||||
|
||||
- **Backend**: ~3-4 semaines
|
||||
- **Backend**: ~1-2 semaines
|
||||
- Authentification: ✅ Terminé
|
||||
- Modules manquants: 1-2 semaines
|
||||
- Modules manquants: ✅ Terminé
|
||||
- Relations entre modules: ✅ Terminé
|
||||
- WebSockets: 1 semaine
|
||||
- Tests et documentation: 1 semaine
|
||||
|
||||
- **Frontend**: ~5-6 semaines
|
||||
- Authentification: 1 semaine
|
||||
- Pages principales: 2 semaines
|
||||
- Fonctionnalités avancées: 1-2 semaines
|
||||
- **Frontend**: ~1-2 semaines
|
||||
- Authentification: ✅ Terminé
|
||||
- Pages principales: ✅ Terminé
|
||||
- Intégration API: ✅ En grande partie terminé (80%)
|
||||
- Finalisation de l'intégration API: 2-3 jours
|
||||
- Fonctionnalités avancées: 1 semaine
|
||||
- Optimisation et finalisation: 1 semaine
|
||||
|
||||
- **Intégration et Tests**: ~1-2 semaines
|
||||
- **Intégration et Tests**: ~1 semaine
|
||||
|
||||
**Temps total estimé**: 9-12 semaines
|
||||
**Temps total estimé**: 3-5 semaines
|
||||
|
||||
## Recommandations
|
||||
|
||||
@ -235,4 +249,15 @@ Basé sur l'état d'avancement actuel et les tâches restantes, l'estimation du
|
||||
|
||||
## Conclusion
|
||||
|
||||
Le projet a bien avancé sur la structure de base et la définition du schéma de données, mais il reste encore un travail significatif à réaliser. Les prochaines étapes prioritaires devraient se concentrer sur l'authentification et les fonctionnalités de base pour avoir rapidement une version minimale fonctionnelle.
|
||||
Le projet a considérablement progressé avec une structure de base solide, un schéma de données complet, et une interface utilisateur bien développée. Le frontend dispose désormais de toutes les pages nécessaires avec une UI fonctionnelle, et le backend a une architecture robuste avec tous les modules essentiels implémentés.
|
||||
|
||||
L'intégration entre le frontend et le backend a été améliorée, avec des appels API réels remplaçant progressivement les données mock. Les pages principales ont été modifiées pour utiliser l'API service avec une gestion appropriée des erreurs et des états de chargement, tout en conservant un fallback aux données mock pour le développement.
|
||||
|
||||
Les relations entre les modules backend sont maintenant complètement implémentées, avec des services qui gèrent correctement les relations entre projets, utilisateurs, personnes, groupes et tags. Les builds du frontend et du backend s'exécutent sans erreur, confirmant la stabilité du code.
|
||||
|
||||
Les prochaines étapes prioritaires devraient se concentrer sur:
|
||||
1. Finaliser l'intégration du frontend avec l'API backend pour toutes les pages
|
||||
2. La mise en place des fonctionnalités de collaboration en temps réel avec Socket.IO
|
||||
3. Améliorer la couverture des tests et la documentation
|
||||
|
||||
Ces efforts permettront d'obtenir rapidement une application pleinement fonctionnelle qui pourra ensuite être optimisée et enrichie avec des fonctionnalités avancées.
|
||||
|
@ -54,6 +54,14 @@ interface Person {
|
||||
tags: string[];
|
||||
}
|
||||
|
||||
interface ProjectWithPersons {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
date: string;
|
||||
persons: Person[];
|
||||
}
|
||||
|
||||
interface Group {
|
||||
id: number;
|
||||
name: string;
|
||||
@ -65,7 +73,7 @@ export default function AutoCreateGroupsPage() {
|
||||
const router = useRouter();
|
||||
const projectId = params.id as string;
|
||||
|
||||
const [project, setProject] = useState<any>(null);
|
||||
const [project, setProject] = useState<ProjectWithPersons | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [generating, setGenerating] = useState(false);
|
||||
const [saving, setSaving] = useState(false);
|
||||
@ -79,13 +87,21 @@ export default function AutoCreateGroupsPage() {
|
||||
const [availableLevels, setAvailableLevels] = useState<string[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
// Simulate API call to fetch project data
|
||||
// Fetch project data from API
|
||||
const fetchProject = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
// In a real app, this would be an API call
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
const data = getProjectData(projectId);
|
||||
// Use the API service to get project data
|
||||
const { projectsAPI, personsAPI } = await import('@/lib/api');
|
||||
const projectData = await projectsAPI.getProject(projectId);
|
||||
const personsData = await personsAPI.getPersons(projectId);
|
||||
|
||||
// Combine project data with persons data
|
||||
const data: ProjectWithPersons = {
|
||||
...projectData,
|
||||
persons: personsData || []
|
||||
};
|
||||
|
||||
setProject(data);
|
||||
|
||||
// Extract unique tags and levels
|
||||
@ -109,6 +125,31 @@ export default function AutoCreateGroupsPage() {
|
||||
} catch (error) {
|
||||
console.error("Error fetching project:", error);
|
||||
toast.error("Erreur lors du chargement du projet");
|
||||
|
||||
// Fallback to mock data for development
|
||||
try {
|
||||
const data = getProjectData(projectId);
|
||||
setProject(data);
|
||||
|
||||
// Extract unique tags and levels from mock data
|
||||
const tags = new Set<string>();
|
||||
const levels = new Set<string>();
|
||||
|
||||
data.persons.forEach(person => {
|
||||
person.tags.forEach(tag => {
|
||||
if (["Junior", "Medior", "Senior"].includes(tag)) {
|
||||
levels.add(tag);
|
||||
} else {
|
||||
tags.add(tag);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
setAvailableTags(Array.from(tags));
|
||||
setAvailableLevels(Array.from(levels));
|
||||
} catch (fallbackError) {
|
||||
console.error("Error with fallback data:", fallbackError);
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@ -122,70 +163,90 @@ export default function AutoCreateGroupsPage() {
|
||||
|
||||
setGenerating(true);
|
||||
try {
|
||||
// In a real app, this would be an API call to the backend
|
||||
// which would run the algorithm to create balanced groups
|
||||
await new Promise(resolve => setTimeout(resolve, 1500));
|
||||
// Use the API service to generate groups
|
||||
const { groupsAPI } = await import('@/lib/api');
|
||||
|
||||
// Simple algorithm to create balanced groups
|
||||
const persons = [...project.persons];
|
||||
const newGroups: Group[] = [];
|
||||
// Prepare the request data
|
||||
const requestData = {
|
||||
projectId: projectId,
|
||||
numberOfGroups: numberOfGroups,
|
||||
balanceTags: balanceTags,
|
||||
balanceLevels: balanceLevels
|
||||
};
|
||||
|
||||
// Create empty groups
|
||||
for (let i = 0; i < numberOfGroups; i++) {
|
||||
newGroups.push({
|
||||
id: i + 1,
|
||||
name: `Groupe ${String.fromCharCode(65 + i)}`, // A, B, C, ...
|
||||
persons: []
|
||||
});
|
||||
}
|
||||
try {
|
||||
// Call the API to generate groups
|
||||
const generatedGroups = await groupsAPI.createGroup(projectId, requestData);
|
||||
setGroups(generatedGroups);
|
||||
toast.success("Groupes générés avec succès");
|
||||
} catch (apiError) {
|
||||
console.error("API error generating groups:", apiError);
|
||||
toast.error("Erreur lors de la génération des groupes via l'API");
|
||||
|
||||
// Sort persons by level if balancing levels
|
||||
if (balanceLevels) {
|
||||
persons.sort((a, b) => {
|
||||
const aLevel = a.tags.find((tag: string) => ["Junior", "Medior", "Senior"].includes(tag)) || "";
|
||||
const bLevel = b.tags.find((tag: string) => ["Junior", "Medior", "Senior"].includes(tag)) || "";
|
||||
// Fallback to local algorithm for development
|
||||
console.log("Falling back to local algorithm");
|
||||
|
||||
// Order: Senior, Medior, Junior
|
||||
const levelOrder: Record<string, number> = { "Senior": 0, "Medior": 1, "Junior": 2 };
|
||||
return levelOrder[aLevel] - levelOrder[bLevel];
|
||||
});
|
||||
}
|
||||
// Simple algorithm to create balanced groups
|
||||
const persons = [...project.persons];
|
||||
const newGroups: Group[] = [];
|
||||
|
||||
// Sort persons by tags if balancing tags
|
||||
if (balanceTags) {
|
||||
// Group persons by their primary skill tag
|
||||
const personsByTag: Record<string, Person[]> = {};
|
||||
|
||||
persons.forEach(person => {
|
||||
// Get first tag that's not a level
|
||||
const primaryTag = person.tags.find((tag: string) => !["Junior", "Medior", "Senior"].includes(tag));
|
||||
if (primaryTag) {
|
||||
if (!personsByTag[primaryTag]) {
|
||||
personsByTag[primaryTag] = [];
|
||||
}
|
||||
personsByTag[primaryTag].push(person);
|
||||
}
|
||||
});
|
||||
|
||||
// Distribute persons from each tag group evenly
|
||||
let currentGroupIndex = 0;
|
||||
|
||||
Object.values(personsByTag).forEach(tagPersons => {
|
||||
tagPersons.forEach(person => {
|
||||
newGroups[currentGroupIndex].persons.push(person);
|
||||
currentGroupIndex = (currentGroupIndex + 1) % numberOfGroups;
|
||||
// Create empty groups
|
||||
for (let i = 0; i < numberOfGroups; i++) {
|
||||
newGroups.push({
|
||||
id: i + 1,
|
||||
name: `Groupe ${String.fromCharCode(65 + i)}`, // A, B, C, ...
|
||||
persons: []
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// Simple distribution without balancing tags
|
||||
persons.forEach((person, index) => {
|
||||
const groupIndex = index % numberOfGroups;
|
||||
newGroups[groupIndex].persons.push(person);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setGroups(newGroups);
|
||||
toast.success("Groupes générés avec succès");
|
||||
// Sort persons by level if balancing levels
|
||||
if (balanceLevels) {
|
||||
persons.sort((a, b) => {
|
||||
const aLevel = a.tags.find((tag: string) => ["Junior", "Medior", "Senior"].includes(tag)) || "";
|
||||
const bLevel = b.tags.find((tag: string) => ["Junior", "Medior", "Senior"].includes(tag)) || "";
|
||||
|
||||
// Order: Senior, Medior, Junior
|
||||
const levelOrder: Record<string, number> = { "Senior": 0, "Medior": 1, "Junior": 2 };
|
||||
return levelOrder[aLevel] - levelOrder[bLevel];
|
||||
});
|
||||
}
|
||||
|
||||
// Sort persons by tags if balancing tags
|
||||
if (balanceTags) {
|
||||
// Group persons by their primary skill tag
|
||||
const personsByTag: Record<string, Person[]> = {};
|
||||
|
||||
persons.forEach(person => {
|
||||
// Get first tag that's not a level
|
||||
const primaryTag = person.tags.find((tag: string) => !["Junior", "Medior", "Senior"].includes(tag));
|
||||
if (primaryTag) {
|
||||
if (!personsByTag[primaryTag]) {
|
||||
personsByTag[primaryTag] = [];
|
||||
}
|
||||
personsByTag[primaryTag].push(person);
|
||||
}
|
||||
});
|
||||
|
||||
// Distribute persons from each tag group evenly
|
||||
let currentGroupIndex = 0;
|
||||
|
||||
Object.values(personsByTag).forEach(tagPersons => {
|
||||
tagPersons.forEach(person => {
|
||||
newGroups[currentGroupIndex].persons.push(person);
|
||||
currentGroupIndex = (currentGroupIndex + 1) % numberOfGroups;
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// Simple distribution without balancing tags
|
||||
persons.forEach((person, index) => {
|
||||
const groupIndex = index % numberOfGroups;
|
||||
newGroups[groupIndex].persons.push(person);
|
||||
});
|
||||
}
|
||||
|
||||
setGroups(newGroups);
|
||||
toast.success("Groupes générés localement avec succès");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error generating groups:", error);
|
||||
toast.error("Erreur lors de la génération des groupes");
|
||||
@ -202,12 +263,44 @@ export default function AutoCreateGroupsPage() {
|
||||
|
||||
setSaving(true);
|
||||
try {
|
||||
// In a real app, this would be an API call to save the groups
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
toast.success("Groupes enregistrés avec succès");
|
||||
// Use the API service to save the groups
|
||||
const { groupsAPI } = await import('@/lib/api');
|
||||
|
||||
// Navigate back to the groups page
|
||||
router.push(`/projects/${projectId}/groups`);
|
||||
// Save each group to the backend
|
||||
const savePromises = groups.map(group => {
|
||||
// Prepare the group data for saving
|
||||
const groupData = {
|
||||
name: group.name,
|
||||
projectId: projectId,
|
||||
persons: group.persons.map(person => person.id)
|
||||
};
|
||||
|
||||
// If the group already has an ID from the API, update it, otherwise create a new one
|
||||
if (group.id && typeof group.id === 'string') {
|
||||
return groupsAPI.updateGroup(group.id, groupData);
|
||||
} else {
|
||||
return groupsAPI.createGroup(projectId, groupData);
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
// Wait for all groups to be saved
|
||||
await Promise.all(savePromises);
|
||||
toast.success("Groupes enregistrés avec succès");
|
||||
|
||||
// Navigate back to the groups page
|
||||
router.push(`/projects/${projectId}/groups`);
|
||||
} catch (apiError) {
|
||||
console.error("API error saving groups:", apiError);
|
||||
toast.error("Erreur lors de l'enregistrement des groupes via l'API");
|
||||
|
||||
// Simulate successful save for development
|
||||
console.log("Simulating successful save for development");
|
||||
toast.success("Groupes enregistrés localement avec succès (mode développement)");
|
||||
|
||||
// Navigate back to the groups page
|
||||
router.push(`/projects/${projectId}/groups`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error saving groups:", error);
|
||||
toast.error("Erreur lors de l'enregistrement des groupes");
|
||||
|
@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import Link from "next/link";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
@ -38,52 +38,71 @@ import {
|
||||
Eye
|
||||
} from "lucide-react";
|
||||
|
||||
// Define the Project type
|
||||
interface Project {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
date: string;
|
||||
groups: number;
|
||||
persons: number;
|
||||
}
|
||||
|
||||
export default function ProjectsPage() {
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
|
||||
// Mock data for projects
|
||||
const projects = [
|
||||
{
|
||||
id: 1,
|
||||
name: "Projet Formation Dev Web",
|
||||
description: "Création de groupes pour la formation développement web",
|
||||
date: "2025-05-15",
|
||||
groups: 4,
|
||||
persons: 16,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Projet Hackathon",
|
||||
description: "Équipes pour le hackathon annuel",
|
||||
date: "2025-05-10",
|
||||
groups: 8,
|
||||
persons: 32,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "Projet Workshop UX/UI",
|
||||
description: "Groupes pour l'atelier UX/UI",
|
||||
date: "2025-05-05",
|
||||
groups: 5,
|
||||
persons: 20,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: "Projet Conférence Tech",
|
||||
description: "Groupes pour la conférence technologique",
|
||||
date: "2025-04-28",
|
||||
groups: 6,
|
||||
persons: 24,
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: "Projet Formation Data Science",
|
||||
description: "Création de groupes pour la formation data science",
|
||||
date: "2025-04-20",
|
||||
groups: 3,
|
||||
persons: 12,
|
||||
},
|
||||
];
|
||||
// State for projects data
|
||||
const [projects, setProjects] = useState<Project[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
// Fetch projects from API
|
||||
useEffect(() => {
|
||||
const fetchProjects = async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const data = await import('@/lib/api').then(module =>
|
||||
module.projectsAPI.getProjects()
|
||||
);
|
||||
setProjects(data);
|
||||
setError(null);
|
||||
} catch (err) {
|
||||
console.error("Failed to fetch projects:", err);
|
||||
setError("Impossible de charger les projets. Veuillez réessayer plus tard.");
|
||||
// Fallback to mock data for development
|
||||
setProjects([
|
||||
{
|
||||
id: 1,
|
||||
name: "Projet Formation Dev Web",
|
||||
description: "Création de groupes pour la formation développement web",
|
||||
date: "2025-05-15",
|
||||
groups: 4,
|
||||
persons: 16,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Projet Hackathon",
|
||||
description: "Équipes pour le hackathon annuel",
|
||||
date: "2025-05-10",
|
||||
groups: 8,
|
||||
persons: 32,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "Projet Workshop UX/UI",
|
||||
description: "Groupes pour l'atelier UX/UI",
|
||||
date: "2025-05-05",
|
||||
groups: 5,
|
||||
persons: 20,
|
||||
},
|
||||
]);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchProjects();
|
||||
}, []);
|
||||
|
||||
// Filter projects based on search query
|
||||
const filteredProjects = projects.filter(
|
||||
@ -113,10 +132,23 @@ export default function ProjectsPage() {
|
||||
className="pl-8"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
disabled={isLoading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="rounded-md bg-destructive/15 p-4 text-destructive">
|
||||
<p>{error}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isLoading && (
|
||||
<div className="flex justify-center items-center py-8">
|
||||
<div className="h-8 w-8 animate-spin rounded-full border-4 border-primary border-t-transparent"></div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Mobile card view */}
|
||||
<div className="grid gap-4 sm:hidden">
|
||||
{filteredProjects.length === 0 ? (
|
||||
|
Loading…
x
Reference in New Issue
Block a user