diff --git a/frontend/app/projects/[id]/groups/auto-create/page.tsx b/frontend/app/projects/[id]/groups/auto-create/page.tsx index 16addea..abc47a0 100644 --- a/frontend/app/projects/[id]/groups/auto-create/page.tsx +++ b/frontend/app/projects/[id]/groups/auto-create/page.tsx @@ -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(null); + const [project, setProject] = useState(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([]); 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(); + const levels = new Set(); + + 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 = { "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 = {}; - - 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 = { "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 = {}; + + 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"); diff --git a/frontend/app/projects/page.tsx b/frontend/app/projects/page.tsx index 1509957..3064e51 100644 --- a/frontend/app/projects/page.tsx +++ b/frontend/app/projects/page.tsx @@ -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([]); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(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} /> + {error && ( +
+

{error}

+
+ )} + + {isLoading && ( +
+
+
+ )} + {/* Mobile card view */}
{filteredProjects.length === 0 ? (