Added `swr@2.3.3` with peer and regular dependencies (`react@19.1.0`, `dequal@2.0.3`, `use-sync-external-store@1.5.0`). Updated lock file to reflect changes.
295 lines
10 KiB
TypeScript
295 lines
10 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useEffect } from "react";
|
|
import Link from "next/link";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Input } from "@/components/ui/input";
|
|
import {
|
|
Table,
|
|
TableBody,
|
|
TableCell,
|
|
TableHead,
|
|
TableHeader,
|
|
TableRow
|
|
} from "@/components/ui/table";
|
|
import {
|
|
Card,
|
|
CardHeader,
|
|
CardTitle,
|
|
CardDescription,
|
|
CardContent,
|
|
CardFooter
|
|
} from "@/components/ui/card";
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuLabel,
|
|
DropdownMenuSeparator,
|
|
DropdownMenuTrigger
|
|
} from "@/components/ui/dropdown-menu";
|
|
import {
|
|
PlusCircle,
|
|
Search,
|
|
MoreHorizontal,
|
|
Pencil,
|
|
Trash2,
|
|
Users,
|
|
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("");
|
|
|
|
// 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(
|
|
(project) =>
|
|
project.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
|
project.description.toLowerCase().includes(searchQuery.toLowerCase())
|
|
);
|
|
|
|
return (
|
|
<div className="flex flex-col gap-6">
|
|
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
|
<h1 className="text-2xl sm:text-3xl font-bold">Projets</h1>
|
|
<Button asChild className="w-full sm:w-auto">
|
|
<Link href="/projects/new">
|
|
<PlusCircle className="mr-2 h-4 w-4" />
|
|
Nouveau projet
|
|
</Link>
|
|
</Button>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-2">
|
|
<div className="relative flex-1">
|
|
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
|
|
<Input
|
|
type="search"
|
|
placeholder="Rechercher des projets..."
|
|
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 ? (
|
|
<div className="rounded-md border p-6 text-center text-muted-foreground">
|
|
Aucun projet trouvé.
|
|
</div>
|
|
) : (
|
|
filteredProjects.map((project) => (
|
|
<Card key={project.id}>
|
|
<CardHeader className="pb-2">
|
|
<CardTitle className="text-lg">{project.name}</CardTitle>
|
|
<CardDescription>{project.description}</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="pb-2">
|
|
<div className="grid grid-cols-2 gap-2 text-sm">
|
|
<div className="flex flex-col">
|
|
<span className="text-muted-foreground">Date</span>
|
|
<span>{new Date(project.date).toLocaleDateString("fr-FR")}</span>
|
|
</div>
|
|
<div className="flex flex-col">
|
|
<span className="text-muted-foreground">Groupes</span>
|
|
<span>{project.groups}</span>
|
|
</div>
|
|
<div className="flex flex-col">
|
|
<span className="text-muted-foreground">Personnes</span>
|
|
<span>{project.persons}</span>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
<CardFooter className="flex justify-between pt-0">
|
|
<Button variant="outline" size="sm" asChild>
|
|
<Link href={`/projects/${project.id}`}>
|
|
<Eye className="mr-2 h-4 w-4" />
|
|
Voir
|
|
</Link>
|
|
</Button>
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
<Button variant="ghost" size="sm">
|
|
<MoreHorizontal className="h-4 w-4" />
|
|
<span className="sr-only">Actions</span>
|
|
</Button>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align="end">
|
|
<DropdownMenuLabel>Actions</DropdownMenuLabel>
|
|
<DropdownMenuSeparator />
|
|
<DropdownMenuItem asChild>
|
|
<Link href={`/projects/${project.id}/groups`} className="flex items-center">
|
|
<Users className="mr-2 h-4 w-4" />
|
|
<span>Gérer les groupes</span>
|
|
</Link>
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem asChild>
|
|
<Link href={`/projects/${project.id}/edit`} className="flex items-center">
|
|
<Pencil className="mr-2 h-4 w-4" />
|
|
<span>Modifier</span>
|
|
</Link>
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem className="text-destructive focus:text-destructive">
|
|
<Trash2 className="mr-2 h-4 w-4" />
|
|
<span>Supprimer</span>
|
|
</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
</CardFooter>
|
|
</Card>
|
|
))
|
|
)}
|
|
</div>
|
|
|
|
{/* Desktop table view */}
|
|
<div className="rounded-md border hidden sm:block overflow-auto">
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead>Nom</TableHead>
|
|
<TableHead>Description</TableHead>
|
|
<TableHead>Date de création</TableHead>
|
|
<TableHead>Groupes</TableHead>
|
|
<TableHead>Personnes</TableHead>
|
|
<TableHead className="w-[100px]">Actions</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{filteredProjects.length === 0 ? (
|
|
<TableRow>
|
|
<TableCell colSpan={6} className="h-24 text-center">
|
|
Aucun projet trouvé.
|
|
</TableCell>
|
|
</TableRow>
|
|
) : (
|
|
filteredProjects.map((project) => (
|
|
<TableRow key={project.id}>
|
|
<TableCell className="font-medium">{project.name}</TableCell>
|
|
<TableCell>{project.description}</TableCell>
|
|
<TableCell>{new Date(project.date).toLocaleDateString("fr-FR")}</TableCell>
|
|
<TableCell>{project.groups}</TableCell>
|
|
<TableCell>{project.persons}</TableCell>
|
|
<TableCell>
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
<Button variant="ghost" size="icon">
|
|
<MoreHorizontal className="h-4 w-4" />
|
|
<span className="sr-only">Actions</span>
|
|
</Button>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align="end">
|
|
<DropdownMenuLabel>Actions</DropdownMenuLabel>
|
|
<DropdownMenuSeparator />
|
|
<DropdownMenuItem asChild>
|
|
<Link href={`/projects/${project.id}`} className="flex items-center">
|
|
<Eye className="mr-2 h-4 w-4" />
|
|
<span>Voir</span>
|
|
</Link>
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem asChild>
|
|
<Link href={`/projects/${project.id}/groups`} className="flex items-center">
|
|
<Users className="mr-2 h-4 w-4" />
|
|
<span>Gérer les groupes</span>
|
|
</Link>
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem asChild>
|
|
<Link href={`/projects/${project.id}/edit`} className="flex items-center">
|
|
<Pencil className="mr-2 h-4 w-4" />
|
|
<span>Modifier</span>
|
|
</Link>
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem className="text-destructive focus:text-destructive">
|
|
<Trash2 className="mr-2 h-4 w-4" />
|
|
<span>Supprimer</span>
|
|
</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
</TableCell>
|
|
</TableRow>
|
|
))
|
|
)}
|
|
</TableBody>
|
|
</Table>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|