Загрузка...

DashboardPro: UI-компонент для создания интерактивных панелей управления. Организуйте данные и визуализации.
# DashboardPro
You are given a task to integrate an existing React component in the codebase
~~~/README.md
# Modern Dashboard
A comprehensive, production-ready dashboard component featuring:
- **Interactive Sidebar**: Collapsible, responsive, role-based.
- **Data Visualization**: Integrated charts using `recharts`.
- **Calendar**: Full monthly calendar view with event details.
- **Grid Layouts**: Responsive cards for items like Clubs and Users.
- **Theme Support**: Fully compatible with the project's slate theme.
## Architecture
This component is built as a Single Page Application (SPA) shell within the component.
- `Component.tsx`: The main Layout shell (Sidebar + Content Area).
- `App.tsx`: The Router configuration connecting pages to the layout.
- `/pages`: Individual page components (Home, Clubs, Users, etc.).
## Usage
```tsx
import Dashboard from '@/sd-components/202919f0-430a-4b81-8707-c0240c9927b7';
export default function Page() {
return <Dashboard />;
}
```
~~~
~~~/src/App.tsx
import React from 'react';
import { DashboardLayout } from './Component';
import { HashRouter, Routes, Route, Navigate } from 'react-router-dom';
import Home from './pages/Home';
import Clubs from './pages/Clubs';
import UsersPage from './pages/Users';
import Activities from './pages/Activities';
import Finance from './pages/Finance';
import Documents from './pages/Documents';
import Children from './pages/Children';
// Placeholder components for remaining pages
const Help = () => <div className="p-10 text-center text-muted-foreground">Page Aide (En construction)</div>;
export default function App() {
return (
<HashRouter>
<Routes>
<Route path="/" element={<DashboardLayout><Home /></DashboardLayout>} />
<Route path="/clubs" element={<DashboardLayout><Clubs /></DashboardLayout>} />
<Route path="/users" element={<DashboardLayout><UsersPage /></DashboardLayout>} />
<Route path="/children" element={<DashboardLayout><Children /></DashboardLayout>} />
<Route path="/activities" element={<DashboardLayout><Activities /></DashboardLayout>} />
<Route path="/admin/finance" element={<DashboardLayout><Finance /></DashboardLayout>} />
<Route path="/admin/docs" element={<DashboardLayout><Documents /></DashboardLayout>} />
<Route path="/resources/docs" element={<DashboardLayout><Documents /></DashboardLayout>} />
<Route path="/help" element={<DashboardLayout><Help /></DashboardLayout>} />
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
</HashRouter>
);
}
~~~
~~~/package.json
{
"name": "dashboard",
"description": "A fully functional modern dashboard with role-based navigation and interactive pages",
"dependencies": {
"lucide-react": "^0.344.0",
"clsx": "^2.1.0",
"tailwind-merge": "^2.2.1",
"react-router-dom": "^6.22.2",
"recharts": "^2.12.2",
"date-fns": "^3.3.1",
"framer-motion": "^11.0.8",
"react-day-picker": "^8.10.0"
}
}
~~~
~~~/src/Component.tsx
import React, { useState, useEffect } from 'react';
import {
Home, Trophy, Users, Baby, Calendar, Briefcase, BookOpen, Settings,
LogOut, ChevronsLeft, Menu, Bell, Search, CreditCard, Shield,
FileText, LifeBuoy, Plus
} from 'lucide-react';
import { useNavigate, useLocation, Link, Outlet } from 'react-router-dom';
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
// Utility for merging tailwind classes
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
// Types
export type UserRole = 'user' | 'parent' | 'owner';
export interface NavItem {
title: string;
icon: React.ReactNode;
href: string;
description?: string;
badge?: string;
}
export interface NavGroup {
title: string;
items: NavItem[];
roles: UserRole[];
}
interface DashboardLayoutProps {
children?: React.ReactNode;
role?: UserRole;
user?: {
name: string;
email: string;
avatar?: string;
};
}
// Navigation Configuration
const NAV_CONFIG: NavGroup[] = [
{
title: "Général",
roles: ['user', 'parent', 'owner'],
items: [
{
title: "Accueil",
icon: <Home className="w-5 h-5" />,
href: "/",
description: "Vue d'ensemble"
},
{
title: "Clubs",
icon: <Trophy className="w-5 h-5" />,
href: "/clubs",
description: "Mes clubs et adhésions"
}
]
},
{
title: "Communauté",
roles: ['parent', 'owner'],
items: [
{
title: "Utilisateurs",
icon: <Users className="w-5 h-5" />,
href: "/users",
description: "Membres et profils"
}
]
},
{
title: "Famille",
roles: ['parent'],
items: [
{
title: "Enfants",
icon: <Baby className="w-5 h-5" />,
href: "/children",
description: "Profils et activités enfants"
}
]
},
{
title: "Planification",
roles: ['user', 'parent', 'owner'],
items: [
{
title: "Activités",
icon: <Calendar className="w-5 h-5" />,
href: "/activities",
description: "Calendrier et événements"
}
]
},
{
title: "Administration",
roles: ['owner'],
items: [
{
title: "Finances",
icon: <CreditCard className="w-5 h-5" />,
href: "/admin/finance",
description: "Gestion des abonnements"
},
{
title: "Documents",
icon: <Shield className="w-5 h-5" />,
href: "/admin/docs",
description: "Contrats et légal"
}
]
},
{
title: "Ressources",
roles: ['user', 'parent', 'owner'],
items: [
{
title: "Documents",
icon: <FileText className="w-5 h-5" />,
href: "/resources/docs",
description: "Règlements et guides"
},
{
title: "Aide",
icon: <LifeBuoy className="w-5 h-5" />,
href: "/help",
description: "Support et FAQ"
}
]
}
];
export function DashboardLayout({
children,
role = 'owner',
user = { name: "Thomas Anderson", email: "neo@matrix.com" }
}: DashboardLayoutProps) {
const [collapsed, setCollapsed] = useState(false);
const location = useLocation();
const navigate = useNavigate();
const [isMobileOpen, setIsMobileOpen] = useState(false);
// Filter groups based on role
const visibleGroups = NAV_CONFIG.filter(group => group.roles.includes(role));
return (
<div className="flex h-screen bg-background overflow-hidden font-sans text-foreground">
{/* Mobile Overlay */}
{isMobileOpen && (
<div
className="fixed inset-0 bg-background/80 backdrop-blur-sm z-40 lg:hidden"
onClick={() => setIsMobileOpen(false)}
/>
)}
{/* Sidebar */}
<aside
className={cn(
"fixed lg:static inset-y-0 left-0 z-50 flex flex-col h-full bg-card border-r border-border transition-all duration-300 ease-in-out shadow-sm",
collapsed ? "lg:w-[80px]" : "lg:w-[280px]",
isMobileOpen ? "w-[280px] translate-x-0" : "w-[280px] -translate-x-full lg:translate-x-0"
)}
>
{/* Header */}
<div className="flex items-center h-16 px-6 border-b border-border">
<div className="flex items-center gap-3 font-bold text-xl text-foreground">
<div className="flex items-center justify-center w-9 h-9 rounded-xl bg-primary text-primary-foreground shadow-sm">
<Trophy className="w-5 h-5" />
</div>
{(!collapsed || isMobileOpen) && (
<span className="truncate bg-gradient-to-r from-foreground to-foreground/70 bg-clip-text text-transparent">
SportManager
</span>
)}
</div>
</div>
{/* Navigation */}
<div className="flex-1 overflow-y-auto py-6 px-4 space-y-8 scrollbar-thin scrollbar-thumb-border">
{visibleGroups.map((group, groupIndex) => (
<div key={group.title + groupIndex}>
{(!collapsed || isMobileOpen) && (
<h3 className="mb-3 px-2 text-xs font-bold uppercase tracking-widest text-muted-foreground/50">
{group.title}
</h3>
)}
<div className="space-y-1">
{group.items.map((item) => {
const isActive = location.pathname === item.href;
return (
<button
key={item.href}
onClick={() => {
navigate(item.href);
setIsMobileOpen(false);
}}
className={cn(
"flex items-center w-full p-2.5 text-sm font-medium rounded-xl transition-all duration-200 group relative overflow-hidden",
isActive
? "bg-primary text-primary-foreground shadow-md"
: "text-muted-foreground hover:bg-muted hover:text-foreground",
collapsed && !isMobileOpen ? "justify-center" : ""
)}
title={collapsed ? item.title : undefined}
>
<span className={cn(
"flex items-center justify-center transition-transform duration-200",
isActive ? "" : "group-hover:scale-110"
)}>
{item.icon}
</span>
{(!collapsed || isMobileOpen) && (
<span className="ml-3 truncate font-medium">{item.title}</span>
)}
{/* Active Indicator Dot for Collapsed State */}
{collapsed && !isMobileOpen && isActive && (
<div className="absolute right-1.5 top-1.5 w-2 h-2 rounded-full bg-white animate-pulse" />
)}
</button>
);
})}
</div>
</div>
))}
</div>
{/* Footer */}
<div className="p-4 border-t border-border bg-card/50 backdrop-blur-sm">
<div className={cn(
"flex items-center gap-3 rounded-xl p-2.5 transition-colors hover:bg-muted cursor-pointer group",
collapsed && !isMobileOpen ? "justify-center" : ""
)}>
<div className="relative">
<div className="w-10 h-10 rounded-full bg-secondary flex items-center justify-center overflow-hidden border-2 border-background shadow-sm group-hover:border-border transition-colors">
{user.avatar ? (
<img src={user.avatar} alt={user.name} className="w-full h-full object-cover" />
) : (
<span className="font-semibold text-sm text-foreground">
{user.name.charAt(0)}
</span>
)}
</div>
<div className="absolute bottom-0 right-0 w-3 h-3 bg-emerald-500 border-2 border-background rounded-full"></div>
</div>
{(!collapsed || isMobileOpen) && (
<div className="flex-1 min-w-0">
<p className="text-sm font-semibold text-foreground truncate">
{user.name}
</p>
<p className="text-xs text-muted-foreground truncate font-medium">
{role === 'owner' ? 'Propriétaire' : role === 'parent' ? 'Parent' : 'Membre'}
</p>
</div>
)}
{(!collapsed || isMobileOpen) && (
<Settings className="w-4 h-4 text-muted-foreground group-hover:text-foreground transition-colors" />
)}
</div>
</div>
</aside>
{/* Main Content Wrapper */}
<div className="flex-1 flex flex-col min-w-0 bg-secondary/30">
{/* Top Navbar */}
<header className="h-16 border-b border-border bg-background/80 backdrop-blur-md px-6 flex items-center justify-between sticky top-0 z-30">
<div className="flex items-center gap-4">
<button
onClick={() => setIsMobileOpen(!isMobileOpen)}
className="lg:hidden p-2 -ml-2 text-muted-foreground hover:text-foreground"
>
<Menu className="w-6 h-6" />
</button>
<button
onClick={() => setCollapsed(!collapsed)}
className="hidden lg:flex p-2 -ml-2 text-muted-foreground hover:text-foreground hover:bg-muted rounded-lg transition-colors"
>
<ChevronsLeft className={cn("w-5 h-5 transition-transform duration-300", collapsed && "rotate-180")} />
</button>
{/* Breadcrumb-ish or Page Title */}
<h1 className="text-lg font-semibold text-foreground hidden sm:block">
{NAV_CONFIG.flatMap(g => g.items).find(i => i.href === location.pathname)?.title || "Tableau de bord"}
</h1>
</div>
<div className="flex items-center gap-4">
<div className="relative hidden md:block">
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
<input
type="search"
placeholder="Rechercher..."
className="h-9 w-64 rounded-full border border-input bg-muted/50 pl-9 pr-4 text-sm outline-none focus:bg-background focus:ring-2 focus:ring-ring transition-all"
/>
</div>
<button className="relative p-2 text-muted-foreground hover:text-foreground hover:bg-muted rounded-full transition-colors">
<Bell className="w-5 h-5" />
<span className="absolute top-1.5 right-1.5 w-2 h-2 bg-destructive rounded-full ring-2 ring-background" />
</button>
</div>
</header>
{/* Page Content */}
<main className="flex-1 overflow-y-auto p-4 md:p-8 scroll-smooth">
<div className="mx-auto max-w-7xl animate-in fade-in slide-in-from-bottom-4 duration-500">
{children || <Outlet />}
</div>
</main>
</div>
</div>
);
}
export default DashboardLayout;
~~~
~~~/src/pages/Home.tsx
import React from 'react';
import {
Users, DollarSign, TrendingUp, Activity,
ArrowUpRight, ArrowDownRight, Calendar, UserPlus
} from 'lucide-react';
import {
AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, BarChart, Bar
} from 'recharts';
const data = [
{ name: 'Lun', visite: 400, adh: 240 },
{ name: 'Mar', visite: 300, adh: 139 },
{ name: 'Mer', visite: 200, adh: 980 },
{ name: 'Jeu', visite: 278, adh: 390 },
{ name: 'Ven', visite: 189, adh: 480 },
{ name: 'Sam', visite: 239, adh: 380 },
{ name: 'Dim', visite: 349, adh: 430 },
];
const StatCard = ({ title, value, change, trend, icon: Icon, color }: any) => (
<div className="p-6 rounded-2xl bg-card border border-border shadow-sm hover:shadow-md transition-shadow">
<div className="flex items-center justify-between mb-4">
<div className={`p-3 rounded-xl ${color} bg-opacity-10`}>
<Icon className={`w-6 h-6 ${color.replace('bg-', 'text-')}`} />
</div>
<div className={`flex items-center gap-1 text-sm font-medium ${trend === 'up' ? 'text-emerald-500' : 'text-red-500'}`}>
{change}
{trend === 'up' ? <ArrowUpRight className="w-4 h-4" /> : <ArrowDownRight className="w-4 h-4" />}
</div>
</div>
<h3 className="text-sm font-medium text-muted-foreground">{title}</h3>
<p className="text-3xl font-bold text-foreground mt-1">{value}</p>
</div>
);
export default function Home() {
return (
<div className="space-y-8">
{/* Stats Grid */}
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-4">
<StatCard
title="Revenu Total"
value="45,231.89 €"
change="+20.1%"
trend="up"
icon={DollarSign}
color="bg-primary text-primary"
/>
<StatCard
title="Nouveaux Membres"
value="+2350"
change="+180.1%"
trend="up"
icon={Users}
color="bg-indigo-500 text-indigo-500"
/>
<StatCard
title="Taux de Rétention"
value="92.6%"
change="+19%"
trend="up"
icon={Activity}
color="bg-emerald-500 text-emerald-500"
/>
<StatCard
title="Événements Actifs"
value="12"
change="-4%"
trend="down"
icon={Calendar}
color="bg-orange-500 text-orange-500"
/>
</div>
<div className="grid gap-6 md:grid-cols-7">
{/* Main Chart */}
<div className="md:col-span-4 p-6 rounded-2xl bg-card border border-border shadow-sm">
<div className="flex items-center justify-between mb-6">
<div>
<h3 className="text-lg font-bold text-foreground">Vue d'ensemble</h3>
<p className="text-sm text-muted-foreground">Performances des adhésions cette semaine</p>
</div>
<button className="px-3 py-1 text-xs font-medium bg-muted text-foreground rounded-lg hover:bg-muted/80">
Voir détails
</button>
</div>
<div className="h-[300px] w-full">
<ResponsiveContainer width="100%" height="100%">
<AreaChart data={data}>
<defs>
<linearGradient id="colorAdh" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="hsl(var(--primary))" stopOpacity={0.3}/>
<stop offset="95%" stopColor="hsl(var(--primary))" stopOpacity={0}/>
</linearGradient>
</defs>
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke="hsl(var(--border))" />
<XAxis dataKey="name" axisLine={false} tickLine={false} tick={{fill: 'hsl(var(--muted-foreground))', fontSize: 12}} dy={10} />
<YAxis axisLine={false} tickLine={false} tick={{fill: 'hsl(var(--muted-foreground))', fontSize: 12}} />
<Tooltip
contentStyle={{ backgroundColor: 'hsl(var(--card))', borderColor: 'hsl(var(--border))', borderRadius: '8px' }}
itemStyle={{ color: 'hsl(var(--foreground))' }}
/>
<Area type="monotone" dataKey="adh" stroke="hsl(var(--primary))" strokeWidth={3} fillOpacity={1} fill="url(#colorAdh)" />
</AreaChart>
</ResponsiveContainer>
</div>
</div>
{/* Recent Activity / Side Chart */}
<div className="md:col-span-3 p-6 rounded-2xl bg-card border border-border shadow-sm flex flex-col">
<h3 className="text-lg font-bold text-foreground mb-1">Activité Récente</h3>
<p className="text-sm text-muted-foreground mb-6">Nouveaux inscrits par catégorie</p>
<div className="flex-1 h-[250px] w-full">
<ResponsiveContainer width="100%" height="100%">
<BarChart data={data}>
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke="hsl(var(--border))" />
<XAxis dataKey="name" axisLine={false} tickLine={false} tick={{fill: 'hsl(var(--muted-foreground))', fontSize: 12}} dy={10} />
<Tooltip
cursor={{fill: 'hsl(var(--muted)/0.5)'}}
contentStyle={{ backgroundColor: 'hsl(var(--card))', borderColor: 'hsl(var(--border))', borderRadius: '8px' }}
/>
<Bar dataKey="visite" fill="hsl(var(--primary))" radius={[4, 4, 0, 0]} />
</BarChart>
</ResponsiveContainer>
</div>
<div className="mt-6 space-y-4">
{[1, 2, 3].map((_, i) => (
<div key={i} className="flex items-center gap-3 p-3 rounded-lg bg-muted/50 hover:bg-muted transition-colors">
<div className="w-8 h-8 rounded-full bg-background border border-border flex items-center justify-center">
<UserPlus className="w-4 h-4 text-primary" />
</div>
<div className="flex-1">
<p className="text-sm font-medium text-foreground">Nouvelle inscription</p>
<p className="text-xs text-muted-foreground">Il y a 2 heures</p>
</div>
<span className="text-xs font-bold text-emerald-500">+45€</span>
</div>
))}
</div>
</div>
</div>
</div>
);
}
~~~
~~~/src/pages/Clubs.tsx
import React from 'react';
import { Search, Filter, MapPin, Users, Trophy, ExternalLink, Star } from 'lucide-react';
const CLUBS = [
{
id: 1,
name: "Tennis Club Paris",
category: "Tennis",
members: 1240,
rating: 4.8,
location: "Paris 16e",
image: "https://images.unsplash.com/photo-1622279457486-62dcc4a431d6?q=80&w=2070&auto=format&fit=crop",
status: "Premium"
},
{
id: 2,
name: "Olympique Natation",
category: "Natation",
members: 850,
rating: 4.5,
location: "Lyon",
image: "https://images.unsplash.com/photo-1576013551627-0cc20b96c2a7?q=80&w=2070&auto=format&fit=crop",
status: "Standard"
},
{
id: 3,
name: "Dojo Central",
category: "Judo",
members: 420,
rating: 4.9,
location: "Marseille",
image: "https://images.unsplash.com/photo-1599058945522-28d584b6f0ff?q=80&w=2069&auto=format&fit=crop",
status: "Elite"
},
{
id: 4,
name: "Basket Future",
category: "Basketball",
members: 630,
rating: 4.6,
location: "Bordeaux",
image: "https://images.unsplash.com/photo-1546519638-68e109498ee2?q=80&w=2070&auto=format&fit=crop",
status: "Standard"
},
{
id: 5,
name: "FC United",
category: "Football",
members: 2100,
rating: 4.7,
location: "Lille",
image: "https://images.unsplash.com/photo-1574629810360-7efbbe436cd7?q=80&w=2071&auto=format&fit=crop",
status: "Elite"
},
{
id: 6,
name: "Golf & Country",
category: "Golf",
members: 320,
rating: 4.9,
location: "Nice",
image: "https://images.unsplash.com/photo-1587174486073-ae5e5cff23aa?q=80&w=2070&auto=format&fit=crop",
status: "Premium"
}
];
export default function Clubs() {
return (
<div className="space-y-6">
<div className="flex flex-col sm:flex-row items-center justify-between gap-4">
<div>
<h2 className="text-2xl font-bold text-foreground">Mes Clubs</h2>
<p className="text-muted-foreground">Gérez vos adhésions et découvrez de nouveaux clubs.</p>
</div>
<div className="flex items-center gap-2 w-full sm:w-auto">
<div className="relative flex-1 sm:w-64">
<Search className="absolute left-3 top-2.5 h-4 w-4 text-muted-foreground" />
<input
placeholder="Rechercher un club..."
className="w-full h-10 pl-9 pr-4 rounded-xl border border-border bg-card focus:outline-none focus:ring-2 focus:ring-primary"
/>
</div>
<button className="h-10 px-4 rounded-xl border border-border bg-card hover:bg-muted flex items-center gap-2 text-foreground font-medium transition-colors">
<Filter className="w-4 h-4" />
<span className="hidden sm:inline">Filtres</span>
</button>
</div>
</div>
<div className="grid gap-6 md:grid-cols-2 xl:grid-cols-3">
{CLUBS.map((club) => (
<div key={club.id} className="group relative bg-card border border-border rounded-2xl overflow-hidden hover:shadow-lg transition-all duration-300 hover:-translate-y-1">
<div className="relative h-48 w-full overflow-hidden">
<img
src={club.image}
alt={club.name}
className="w-full h-full object-cover transition-transform duration-500 group-hover:scale-110"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent" />
<div className="absolute top-3 right-3 px-2 py-1 bg-background/90 backdrop-blur-sm rounded-lg text-xs font-bold text-foreground border border-border/50">
{club.category}
</div>
<div className="absolute bottom-3 left-3 text-white">
<h3 className="text-lg font-bold">{club.name}</h3>
<div className="flex items-center gap-2 text-xs text-white/80">
<MapPin className="w-3 h-3" /> {club.location}
</div>
</div>
</div>
<div className="p-5">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-4 text-sm text-muted-foreground">
<div className="flex items-center gap-1">
<Users className="w-4 h-4" />
<span>{club.members}</span>
</div>
<div className="flex items-center gap-1">
<Star className="w-4 h-4 text-yellow-500 fill-yellow-500" />
<span>{club.rating}</span>
</div>
</div>
<span className={`text-xs font-bold px-2 py-1 rounded-full ${
club.status === 'Elite' ? 'bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-300' :
club.status === 'Premium' ? 'bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-300' :
'bg-slate-100 text-slate-700 dark:bg-slate-800 dark:text-slate-300'
}`}>
{club.status}
</span>
</div>
<div className="flex items-center gap-2 mt-4 pt-4 border-t border-border">
<button className="flex-1 h-9 rounded-lg bg-primary text-primary-foreground font-medium text-sm hover:bg-primary/90 transition-colors">
Gérer
</button>
<button className="h-9 w-9 rounded-lg border border-border flex items-center justify-center hover:bg-muted text-muted-foreground hover:text-foreground transition-colors">
<ExternalLink className="w-4 h-4" />
</button>
</div>
</div>
</div>
))}
</div>
</div>
);
}
~~~
~~~/src/pages/Users.tsx
import React, { useState } from 'react';
import {
MoreHorizontal, Search, UserPlus, Filter, Download, Mail,
CheckCircle2, XCircle, Clock
} from 'lucide-react';
const USERS = [
{ id: 1, name: "Sarah Connor", email: "sarah@resistance.com", role: "Parent", status: "Active", lastActive: "Il y a 2 min", avatar: "https://i.pravatar.cc/150?u=1" },
{ id: 2, name: "John Wick", email: "babayaga@continental.com", role: "Membre", status: "Inactive", lastActive: "Il y a 3 jours", avatar: "https://i.pravatar.cc/150?u=2" },
{ id: 3, name: "Tony Stark", email: "ironman@stark.com", role: "Propriétaire", status: "Active", lastActive: "En ligne", avatar: "https://i.pravatar.cc/150?u=3" },
{ id: 4, name: "Bruce Wayne", email: "batman@wayne.com", role: "Parent", status: "Active", lastActive: "Il y a 5 heures", avatar: "https://i.pravatar.cc/150?u=4" },
{ id: 5, name: "Diana Prince", email: "wonder@woman.com", role: "Membre", status: "Pending", lastActive: "Jamais", avatar: "https://i.pravatar.cc/150?u=5" },
{ id: 6, name: "Peter Parker", email: "spidey@dailybugle.com", role: "Membre", status: "Active", lastActive: "Il y a 10 min", avatar: "https://i.pravatar.cc/150?u=6" },
{ id: 7, name: "Natasha Romanoff", email: "blackwidow@avengers.com", role: "Coach", status: "Active", lastActive: "En ligne", avatar: "https://i.pravatar.cc/150?u=7" },
];
export default function UsersPage() {
const [selectedUsers, setSelectedUsers] = useState<number[]>([]);
const toggleUser = (id: number) => {
setSelectedUsers(prev =>
prev.includes(id) ? prev.filter(u => u !== id) : [...prev, id]
);
};
const selectAll = () => {
if (selectedUsers.length === USERS.length) {
setSelectedUsers([]);
} else {
setSelectedUsers(USERS.map(u => u.id));
}
};
return (
<div className="space-y-6">
<div className="flex flex-col sm:flex-row items-center justify-between gap-4">
<div>
<h2 className="text-2xl font-bold text-foreground">Utilisateurs</h2>
<p className="text-muted-foreground">Gérez les membres, parents et le staff.</p>
</div>
<div className="flex items-center gap-2">
<button className="h-10 px-4 rounded-xl border border-border bg-card hover:bg-muted flex items-center gap-2 text-foreground font-medium transition-colors">
<Download className="w-4 h-4" />
<span className="hidden sm:inline">Exporter</span>
</button>
<button className="h-10 px-4 rounded-xl bg-primary text-primary-foreground hover:bg-primary/90 flex items-center gap-2 font-medium transition-colors shadow-lg shadow-primary/20">
<UserPlus className="w-4 h-4" />
<span>Inviter</span>
</button>
</div>
</div>
<div className="bg-card border border-border rounded-2xl shadow-sm overflow-hidden">
{/* Table Header Controls */}
<div className="p-4 border-b border-border flex flex-col sm:flex-row items-center justify-between gap-4">
<div className="relative w-full sm:w-72">
<Search className="absolute left-3 top-2.5 h-4 w-4 text-muted-foreground" />
<input
placeholder="Rechercher par nom ou email..."
className="w-full h-10 pl-9 pr-4 rounded-xl border border-border bg-background focus:outline-none focus:ring-2 focus:ring-primary"
/>
</div>
<div className="flex items-center gap-2 w-full sm:w-auto">
<button className="flex-1 sm:flex-none h-10 px-3 rounded-lg border border-border hover:bg-muted text-sm font-medium transition-colors">
Tous les rôles
</button>
<button className="flex-1 sm:flex-none h-10 px-3 rounded-lg border border-border hover:bg-muted text-sm font-medium transition-colors">
Statut: Actif
</button>
</div>
</div>
{/* Table */}
<div className="overflow-x-auto">
<table className="w-full text-sm text-left">
<thead className="text-xs text-muted-foreground uppercase bg-muted/50">
<tr>
<th className="p-4 w-10">
<input
type="checkbox"
className="rounded border-gray-300 text-primary focus:ring-primary"
checked={selectedUsers.length === USERS.length}
onChange={selectAll}
/>
</th>
<th className="p-4">Utilisateur</th>
<th className="p-4">Rôle</th>
<th className="p-4">Statut</th>
<th className="p-4">Dernière activité</th>
<th className="p-4 text-right">Actions</th>
</tr>
</thead>
<tbody className="divide-y divide-border">
{USERS.map((user) => (
<tr key={user.id} className="hover:bg-muted/30 transition-colors group">
<td className="p-4">
<input
type="checkbox"
className="rounded border-gray-300 text-primary focus:ring-primary"
checked={selectedUsers.includes(user.id)}
onChange={() => toggleUser(user.id)}
/>
</td>
<td className="p-4">
<div className="flex items-center gap-3">
<img src={user.avatar} alt={user.name} className="w-10 h-10 rounded-full bg-muted object-cover" />
<div>
<div className="font-medium text-foreground">{user.name}</div>
<div className="text-muted-foreground text-xs">{user.email}</div>
</div>
</div>
</td>
<td className="p-4">
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-secondary text-secondary-foreground">
{user.role}
</span>
</td>
<td className="p-4">
<div className="flex items-center gap-2">
{user.status === 'Active' ? <CheckCircle2 className="w-4 h-4 text-emerald-500" /> :
user.status === 'Inactive' ? <XCircle className="w-4 h-4 text-red-500" /> :
<Clock className="w-4 h-4 text-amber-500" />}
<span className={
user.status === 'Active' ? 'text-emerald-600' :
user.status === 'Inactive' ? 'text-red-600' : 'text-amber-600'
}>
{user.status}
</span>
</div>
</td>
<td className="p-4 text-muted-foreground">
{user.lastActive}
</td>
<td className="p-4 text-right">
<div className="flex items-center justify-end gap-2 opacity-0 group-hover:opacity-100 transition-opacity">
<button className="p-2 hover:bg-muted rounded-lg text-muted-foreground hover:text-foreground">
<Mail className="w-4 h-4" />
</button>
<button className="p-2 hover:bg-muted rounded-lg text-muted-foreground hover:text-foreground">
<MoreHorizontal className="w-4 h-4" />
</button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
{/* Pagination */}
<div className="p-4 border-t border-border flex items-center justify-between text-sm text-muted-foreground">
<div>Affichage de 1 à 7 sur 24 résultats</div>
<div className="flex gap-2">
<button className="px-3 py-1 border border-border rounded-lg hover:bg-muted disabled:opacity-50">Précédent</button>
<button className="px-3 py-1 border border-border rounded-lg hover:bg-muted disabled:opacity-50">Suivant</button>
</div>
</div>
</div>
</div>
);
}
~~~
~~~/src/pages/Finance.tsx
import React from 'react';
import {
CreditCard, DollarSign, Download, TrendingUp, TrendingDown,
FileText, Plus, MoreHorizontal
} from 'lucide-react';
import {
BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, PieChart, Pie, Cell
} from 'recharts';
const data = [
{ name: 'Jan', revenu: 4000, depense: 2400 },
{ name: 'Fév', revenu: 3000, depense: 1398 },
{ name: 'Mar', revenu: 2000, depense: 9800 },
{ name: 'Avr', revenu: 2780, depense: 3908 },
{ name: 'Mai', revenu: 1890, depense: 4800 },
{ name: 'Juin', revenu: 2390, depense: 3800 },
{ name: 'Juil', revenu: 3490, depense: 4300 },
];
const pieData = [
{ name: 'Abonnements', value: 400 },
{ name: 'Événements', value: 300 },
{ name: 'Boutique', value: 300 },
{ name: 'Sponsoring', value: 200 },
];
const COLORS = ['hsl(var(--primary))', '#10b981', '#f59e0b', '#6366f1'];
const TRANSACTIONS = [
{ id: 1, desc: "Abonnement Annuel - Famille Martin", date: "15 Mars 2024", amount: "+450.00 €", type: "income", status: "Confirmé" },
{ id: 2, desc: "Achat Équipement Tennis", date: "14 Mars 2024", amount: "-1,250.00 €", type: "expense", status: "En attente" },
{ id: 3, desc: "Inscription Tournoi - Thomas", date: "12 Mars 2024", amount: "+45.00 €", type: "income", status: "Confirmé" },
{ id: 4, desc: "Maintenance Piscine", date: "10 Mars 2024", amount: "-320.00 €", type: "expense", status: "Confirmé" },
{ id: 5, desc: "Subvention Mairie", date: "01 Mars 2024", amount: "+5,000.00 €", type: "income", status: "Confirmé" },
];
export default function Finance() {
return (
<div className="space-y-6">
<div className="flex flex-col sm:flex-row items-center justify-between gap-4">
<div>
<h2 className="text-2xl font-bold text-foreground">Finances</h2>
<p className="text-muted-foreground">Suivi des revenus, dépenses et abonnements.</p>
</div>
<div className="flex items-center gap-2">
<button className="h-10 px-4 rounded-xl border border-border bg-card hover:bg-muted flex items-center gap-2 text-foreground font-medium transition-colors">
<Download className="w-4 h-4" />
<span className="hidden sm:inline">Rapport</span>
</button>
<button className="h-10 px-4 rounded-xl bg-primary text-primary-foreground hover:bg-primary/90 flex items-center gap-2 font-medium transition-colors shadow-lg shadow-primary/20">
<Plus className="w-4 h-4" />
<span>Nouvelle transaction</span>
</button>
</div>
</div>
{/* Cards */}
<div className="grid gap-6 md:grid-cols-3">
<div className="p-6 rounded-2xl bg-card border border-border shadow-sm relative overflow-hidden">
<div className="flex items-center justify-between mb-4">
<div className="p-3 rounded-xl bg-emerald-500/10 text-emerald-500">
<TrendingUp className="w-6 h-6" />
</div>
<span className="text-xs font-bold px-2 py-1 rounded-full bg-emerald-500/10 text-emerald-600">+12%</span>
</div>
<h3 className="text-sm font-medium text-muted-foreground">Revenus ce mois</h3>
<p className="text-3xl font-bold text-foreground mt-1">12,450.00 €</p>
</div>
<div className="p-6 rounded-2xl bg-card border border-border shadow-sm relative overflow-hidden">
<div className="flex items-center justify-between mb-4">
<div className="p-3 rounded-xl bg-red-500/10 text-red-500">
<TrendingDown className="w-6 h-6" />
</div>
<span className="text-xs font-bold px-2 py-1 rounded-full bg-red-500/10 text-red-600">+5%</span>
</div>
<h3 className="text-sm font-medium text-muted-foreground">Dépenses ce mois</h3>
<p className="text-3xl font-bold text-foreground mt-1">4,230.50 €</p>
</div>
<div className="p-6 rounded-2xl bg-card border border-border shadow-sm relative overflow-hidden">
<div className="flex items-center justify-between mb-4">
<div className="p-3 rounded-xl bg-blue-500/10 text-blue-500">
<CreditCard className="w-6 h-6" />
</div>
<span className="text-xs font-bold px-2 py-1 rounded-full bg-blue-500/10 text-blue-600">Active</span>
</div>
<h3 className="text-sm font-medium text-muted-foreground">Solde actuel</h3>
<p className="text-3xl font-bold text-foreground mt-1">8,219.50 €</p>
</div>
</div>
{/* Charts Section */}
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
<div className="lg:col-span-2 p-6 rounded-2xl bg-card border border-border shadow-sm">
<h3 className="text-lg font-bold text-foreground mb-6">Flux Financier</h3>
<div className="h-[300px] w-full">
<ResponsiveContainer width="100%" height="100%">
<BarChart data={data}>
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke="hsl(var(--border))" />
<XAxis dataKey="name" axisLine={false} tickLine={false} tick={{fill: 'hsl(var(--muted-foreground))', fontSize: 12}} dy={10} />
<YAxis axisLine={false} tickLine={false} tick={{fill: 'hsl(var(--muted-foreground))', fontSize: 12}} />
<Tooltip
cursor={{fill: 'hsl(var(--muted)/0.5)'}}
contentStyle={{ backgroundColor: 'hsl(var(--card))', borderColor: 'hsl(var(--border))', borderRadius: '8px' }}
/>
<Bar dataKey="revenu" fill="hsl(var(--primary))" radius={[4, 4, 0, 0]} />
<Bar dataKey="depense" fill="#ef4444" radius={[4, 4, 0, 0]} />
</BarChart>
</ResponsiveContainer>
</div>
</div>
<div className="p-6 rounded-2xl bg-card border border-border shadow-sm flex flex-col">
<h3 className="text-lg font-bold text-foreground mb-2">Répartition</h3>
<div className="flex-1 min-h-[250px] relative">
<ResponsiveContainer width="100%" height="100%">
<PieChart>
<Pie
data={pieData}
cx="50%"
cy="50%"
innerRadius={60}
outerRadius={80}
paddingAngle={5}
dataKey="value"
>
{pieData.map((entry, index) => (
<Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
))}
</Pie>
<Tooltip />
</PieChart>
</ResponsiveContainer>
<div className="absolute inset-0 flex items-center justify-center pointer-events-none">
<div className="text-center">
<span className="text-2xl font-bold text-foreground">100%</span>
</div>
</div>
</div>
<div className="grid grid-cols-2 gap-2 mt-4">
{pieData.map((item, index) => (
<div key={index} className="flex items-center gap-2 text-xs text-muted-foreground">
<div className="w-2 h-2 rounded-full" style={{ backgroundColor: COLORS[index] }} />
<span>{item.name}</span>
</div>
))}
</div>
</div>
</div>
{/* Transactions Table */}
<div className="bg-card border border-border rounded-2xl shadow-sm overflow-hidden">
<div className="p-6 border-b border-border">
<h3 className="text-lg font-bold text-foreground">Dernières Transactions</h3>
</div>
<div className="overflow-x-auto">
<table className="w-full text-sm text-left">
<thead className="text-xs text-muted-foreground uppercase bg-muted/50">
<tr>
<th className="p-4">Description</th>
<th className="p-4">Date</th>
<th className="p-4">Statut</th>
<th className="p-4 text-right">Montant</th>
<th className="p-4 w-10"></th>
</tr>
</thead>
<tbody className="divide-y divide-border">
{TRANSACTIONS.map((tx) => (
<tr key={tx.id} className="hover:bg-muted/30 transition-colors">
<td className="p-4 font-medium">
<div className="flex items-center gap-3">
<div className={`p-2 rounded-lg ${tx.type === 'income' ? 'bg-emerald-500/10 text-emerald-500' : 'bg-red-500/10 text-red-500'}`}>
{tx.type === 'income' ? <TrendingUp className="w-4 h-4" /> : <TrendingDown className="w-4 h-4" />}
</div>
{tx.desc}
</div>
</td>
<td className="p-4 text-muted-foreground">{tx.date}</td>
<td className="p-4">
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
tx.status === 'Confirmé' ? 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-300' :
'bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-300'
}`}>
{tx.status}
</span>
</td>
<td className={`p-4 text-right font-bold ${tx.type === 'income' ? 'text-emerald-600' : 'text-foreground'}`}>
{tx.amount}
</td>
<td className="p-4">
<button className="p-2 hover:bg-muted rounded-lg text-muted-foreground hover:text-foreground">
<MoreHorizontal className="w-4 h-4" />
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
);
}
~~~
~~~/src/pages/Children.tsx
import React from 'react';
import {
Baby, Calendar, Award, Heart, MapPin, ChevronRight, Plus,
TrendingUp, Activity, AlertCircle, Phone
} from 'lucide-react';
import {
RadarChart, PolarGrid, PolarAngleAxis, PolarRadiusAxis, Radar, ResponsiveContainer,
LineChart, Line, XAxis, Tooltip
} from 'recharts';
const CHILDREN = [
{
id: 1,
name: "Léo Martin",
age: "8 ans",
group: "Poussins",
sport: "Football",
nextEvent: "Match Samedi 14h",
attendance: "95%",
avatar: "https://images.unsplash.com/photo-1596870230751-ebdfce98ec42?q=80&w=2000&auto=format&fit=crop",
color: "bg-blue-500",
stats: [
{ subject: 'Vitesse', A: 120, fullMark: 150 },
{ subject: 'Technique', A: 98, fullMark: 150 },
{ subject: 'Physique', A: 86, fullMark: 150 },
{ subject: 'Mental', A: 99, fullMark: 150 },
{ subject: 'Tactique', A: 85, fullMark: 150 },
{ subject: 'Collectif', A: 65, fullMark: 150 },
],
progression: [
{ month: 'Sep', score: 60 }, { month: 'Oct', score: 65 }, { month: 'Nov', score: 70 },
{ month: 'Déc', score: 68 }, { month: 'Jan', score: 75 }, { month: 'Fév', score: 82 }
]
},
{
id: 2,
name: "Emma Martin",
age: "12 ans",
group: "Benjamins",
sport: "Natation",
nextEvent: "Entraînement Mercredi 17h",
attendance: "100%",
avatar: "https://images.unsplash.com/photo-1610473068565-38c62c3e449c?q=80&w=1953&auto=format&fit=crop",
color: "bg-cyan-500",
stats: [
{ subject: 'Vitesse', A: 110, fullMark: 150 },
{ subject: 'Technique', A: 130, fullMark: 150 },
{ subject: 'Physique', A: 110, fullMark: 150 },
{ subject: 'Mental', A: 100, fullMark: 150 },
{ subject: 'Tactique', A: 90, fullMark: 150 },
{ subject: 'Collectif', A: 85, fullMark: 150 },
],
progression: [
{ month: 'Sep', score: 70 }, { month: 'Oct', score: 72 }, { month: 'Nov', score: 75 },
{ month: 'Déc', score: 80 }, { month: 'Jan', score: 85 }, { month: 'Fév', score: 90 }
]
}
];
export default function Children() {
return (
<div className="space-y-8 pb-8">
{/* Header */}
<div className="flex flex-col sm:flex-row items-center justify-between gap-4">
<div>
<h2 className="text-2xl font-bold text-foreground">Espace Famille</h2>
<p className="text-muted-foreground">Suivi des performances et activités de vos enfants.</p>
</div>
<button className="h-10 px-4 rounded-xl bg-primary text-primary-foreground hover:bg-primary/90 flex items-center gap-2 font-medium transition-colors shadow-lg shadow-primary/20">
<Plus className="w-4 h-4" />
<span>Inscrire un enfant</span>
</button>
</div>
<div className="grid gap-8 lg:grid-cols-2">
{CHILDREN.map((child) => (
<div key={child.id} className="bg-card border border-border rounded-3xl shadow-sm overflow-hidden hover:shadow-lg transition-all duration-300 flex flex-col">
{/* Header / Cover */}
<div className={`h-32 ${child.color} relative overflow-hidden`}>
<div className="absolute inset-0 bg-black/10" />
<div className="absolute inset-0 bg-gradient-to-t from-black/50 to-transparent" />
<div className="absolute top-4 right-4 bg-white/20 backdrop-blur-md border border-white/30 rounded-lg px-3 py-1 text-white text-xs font-bold">
{child.group}
</div>
<div className="absolute -bottom-12 left-8">
<div className="w-24 h-24 rounded-2xl border-4 border-card overflow-hidden shadow-lg bg-muted">
<img src={child.avatar} alt={child.name} className="w-full h-full object-cover" />
</div>
</div>
</div>
<div className="pt-14 px-8 pb-8 flex-1 flex flex-col">
{/* Identity */}
<div className="flex justify-between items-start mb-6">
<div>
<h3 className="text-2xl font-bold text-foreground flex items-center gap-2">
{child.name}
</h3>
<div className="flex items-center gap-2 text-sm text-muted-foreground mt-1">
<span className="bg-muted px-2 py-0.5 rounded text-xs font-medium text-foreground">{child.age}</span>
<span>•</span>
<span className="flex items-center gap-1"><Award className="w-3.5 h-3.5" /> {child.sport}</span>
</div>
</div>
<div className="text-right">
<div className="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-emerald-500/10 text-emerald-600 border border-emerald-200 text-sm font-bold">
<TrendingUp className="w-3.5 h-3.5" />
{child.attendance}
</div>
</div>
</div>
{/* Data Grid */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-6 mb-6">
{/* Radar Chart */}
<div className="bg-muted/30 rounded-2xl p-4 border border-border/50">
<h4 className="text-xs font-bold text-muted-foreground uppercase tracking-wider mb-2 text-center">Compétences</h4>
<div className="h-[180px]">
<ResponsiveContainer width="100%" height="100%">
<RadarChart cx="50%" cy="50%" outerRadius="70%" data={child.stats}>
<PolarGrid stroke="hsl(var(--border))" />
<PolarAngleAxis dataKey="subject" tick={{ fill: 'hsl(var(--muted-foreground))', fontSize: 10 }} />
<PolarRadiusAxis angle={30} domain={[0, 150]} tick={false} axisLine={false} />
<Radar
name={child.name}
dataKey="A"
stroke={child.color.replace('bg-', 'text-').replace('500', '600')} // Simple hack for color
fill={child.color.replace('bg-', 'text-').replace('500', '500')}
fillOpacity={0.3}
/>
</RadarChart>
</ResponsiveContainer>
</div>
</div>
{/* Info List */}
<div className="space-y-4">
<div className="bg-card border border-border rounded-xl p-3 flex items-start gap-3">
<div className="p-2 bg-primary/10 rounded-lg text-primary shrink-0">
<Calendar className="w-4 h-4" />
</div>
<div>
<p className="text-xs text-muted-foreground font-medium uppercase">Prochain Événement</p>
<p className="font-semibold text-sm">{child.nextEvent}</p>
</div>
</div>
<div className="bg-card border border-border rounded-xl p-3 flex items-start gap-3">
<div className="p-2 bg-red-500/10 rounded-lg text-red-500 shrink-0">
<Heart className="w-4 h-4" />
</div>
<div>
<p className="text-xs text-muted-foreground font-medium uppercase">Santé</p>
<p className="font-semibold text-sm">Certificat Valide (2025)</p>
</div>
</div>
<div className="bg-card border border-border rounded-xl p-3 flex items-start gap-3">
<div className="p-2 bg-purple-500/10 rounded-lg text-purple-500 shrink-0">
<Activity className="w-4 h-4" />
</div>
<div>
<p className="text-xs text-muted-foreground font-medium uppercase">Progression</p>
<div className="h-8 w-24">
<ResponsiveContainer width="100%" height="100%">
<LineChart data={child.progression}>
<Line type="monotone" dataKey="score" stroke="currentColor" strokeWidth={2} dot={false} className="text-purple-500" />
</LineChart>
</ResponsiveContainer>
</div>
</div>
</div>
</div>
</div>
{/* Actions Footer */}
<div className="mt-auto pt-6 border-t border-border flex items-center gap-3">
<button className="flex-1 py-2.5 rounded-xl bg-primary text-primary-foreground font-medium text-sm hover:bg-primary/90 transition-colors shadow-sm">
Planning complet
</button>
<button className="flex-1 py-2.5 rounded-xl border border-border hover:bg-muted font-medium text-sm transition-colors text-foreground flex items-center justify-center gap-2">
<Phone className="w-4 h-4" />
Contacter Coach
</button>
</div>
</div>
</div>
))}
{/* Add New Placeholder */}
<button className="group border-2 border-dashed border-border rounded-3xl p-8 flex flex-col items-center justify-center text-muted-foreground hover:border-primary hover:text-primary hover:bg-primary/5 transition-all min-h-[400px]">
<div className="w-20 h-20 rounded-full bg-muted flex items-center justify-center mb-6 group-hover:bg-primary/10 group-hover:scale-110 transition-all duration-300 shadow-sm">
<Plus className="w-10 h-10" />
</div>
<h3 className="font-bold text-xl mb-2">Ajouter un profil</h3>
<p className="text-sm opacity-70 max-w-xs text-center">Gérez les activités de toute la famille au même endroit.</p>
</button>
</div>
</div>
);
}
~~~
~~~/src/pages/Documents.tsx
import React from 'react';
import {
Folder, FileText, Download, Upload, Search, MoreVertical,
Grid, List, Clock, Star, Share2, Trash2, Cloud, FileImage, FileSpreadsheet, AlertCircle
} from 'lucide-react';
import { PieChart, Pie, Cell, ResponsiveContainer, Tooltip } from 'recharts';
const FILES = [
{ id: 1, name: "Contrat_Adhésion_2024.pdf", type: "pdf", size: "2.4 MB", date: "15 Mar 2024", starred: true, owner: "Admin" },
{ id: 2, name: "Règlement_Intérieur.pdf", type: "pdf", size: "1.1 MB", date: "10 Jan 2024", starred: false, owner: "Admin" },
{ id: 3, name: "Budget_Prévisionnel_v2.xlsx", type: "excel", size: "850 KB", date: "28 Fév 2024", starred: true, owner: "Finance" },
{ id: 4, name: "Photos_Tournoi_Hiver", type: "folder", size: "125 MB", date: "20 Fév 2024", starred: false, owner: "Comms" },
{ id: 5, name: "Certificats_Médicaux", type: "folder", size: "45 MB", date: "05 Mar 2024", starred: false, owner: "Secrétariat" },
{ id: 6, name: "Logo_Club_Vector.ai", type: "image", size: "4.2 MB", date: "01 Jan 2024", starred: false, owner: "Comms" },
{ id: 7, name: "Facture_Matériel_Mars.pdf", type: "pdf", size: "156 KB", date: "14 Mar 2024", starred: false, owner: "Finance" },
{ id: 8, name: "Planning_Saison_2024.xlsx", type: "excel", size: "420 KB", date: "12 Mar 2024", starred: true, owner: "Sportif" },
];
const storageData = [
{ name: 'Documents', value: 45, color: '#3b82f6' },
{ name: 'Images', value: 30, color: '#10b981' },
{ name: 'Vidéos', value: 15, color: '#f59e0b' },
{ name: 'Autres', value: 10, color: '#64748b' },
];
export default function Documents() {
return (
<div className="space-y-6 h-[calc(100vh-8rem)] flex flex-col">
{/* Header Controls */}
<div className="flex flex-col sm:flex-row items-center justify-between gap-4">
<div>
<h2 className="text-2xl font-bold text-foreground">Gestion Documentaire</h2>
<p className="text-muted-foreground">Centralisez et partagez vos ressources.</p>
</div>
<div className="flex items-center gap-2 w-full sm:w-auto">
<button className="h-10 px-4 rounded-xl border border-border bg-card hover:bg-muted flex items-center gap-2 text-foreground font-medium transition-colors">
<Cloud className="w-4 h-4" />
<span className="hidden sm:inline">Connecter Drive</span>
</button>
<button className="h-10 px-4 rounded-xl bg-primary text-primary-foreground hover:bg-primary/90 flex items-center gap-2 font-medium transition-colors shadow-lg shadow-primary/20">
<Upload className="w-4 h-4" />
<span>Importer fichier</span>
</button>
</div>
</div>
<div className="flex-1 flex gap-6 overflow-hidden">
{/* Left Sidebar */}
<div className="w-64 flex-col gap-6 hidden md:flex">
{/* Storage Widget */}
<div className="p-4 bg-card border border-border rounded-2xl shadow-sm">
<h3 className="font-bold text-sm mb-4">Stockage</h3>
<div className="h-32 relative">
<ResponsiveContainer width="100%" height="100%">
<PieChart>
<Pie
data={storageData}
innerRadius={40}
outerRadius={55}
dataKey="value"
stroke="none"
>
{storageData.map((entry, index) => <Cell key={`cell-${index}`} fill={entry.color} />)}
</Pie>
</PieChart>
</ResponsiveContainer>
<div className="absolute inset-0 flex items-center justify-center flex-col pointer-events-none">
<span className="text-xl font-bold">75%</span>
<span className="text-[10px] text-muted-foreground">Utilisé</span>
</div>
</div>
<div className="space-y-2 mt-2">
<div className="flex justify-between text-xs">
<span className="text-muted-foreground">7.5 GB utilisés</span>
<span className="font-medium">10 GB</span>
</div>
<div className="h-1.5 w-full bg-muted rounded-full overflow-hidden">
<div className="h-full bg-primary w-[75%] rounded-full" />
</div>
<button className="w-full py-1.5 mt-2 text-xs font-medium text-primary hover:bg-primary/5 rounded-lg border border-primary/20 transition-colors">
Augmenter stockage
</button>
</div>
</div>
{/* Quick Filters */}
<div className="flex-1 bg-card border border-border rounded-2xl shadow-sm p-2">
<div className="space-y-1">
<button className="w-full flex items-center gap-3 px-3 py-2 text-sm font-medium rounded-lg bg-primary/10 text-primary">
<Folder className="w-4 h-4" />
Tous les fichiers
</button>
<button className="w-full flex items-center gap-3 px-3 py-2 text-sm font-medium rounded-lg text-muted-foreground hover:bg-muted hover:text-foreground transition-colors">
<Clock className="w-4 h-4" />
Récents
</button>
<button className="w-full flex items-center gap-3 px-3 py-2 text-sm font-medium rounded-lg text-muted-foreground hover:bg-muted hover:text-foreground transition-colors">
<Star className="w-4 h-4" />
Favoris
</button>
<button className="w-full flex items-center gap-3 px-3 py-2 text-sm font-medium rounded-lg text-muted-foreground hover:bg-muted hover:text-foreground transition-colors">
<Share2 className="w-4 h-4" />
Partagés
</button>
<div className="h-px bg-border my-2 mx-3" />
<button className="w-full flex items-center gap-3 px-3 py-2 text-sm font-medium rounded-lg text-muted-foreground hover:bg-muted hover:text-foreground transition-colors">
<Trash2 className="w-4 h-4" />
Corbeille
</button>
</div>
</div>
</div>
{/* Main Content */}
<div className="flex-1 flex flex-col bg-card border border-border rounded-2xl shadow-sm overflow-hidden">
{/* Toolbar */}
<div className="h-16 border-b border-border flex items-center justify-between px-6 gap-4">
<div className="relative flex-1 max-w-lg">
<Search className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
<input
placeholder="Rechercher..."
className="w-full h-10 pl-10 pr-4 rounded-xl border border-border bg-muted/30 focus:bg-background focus:outline-none focus:ring-2 focus:ring-primary transition-colors text-sm"
/>
</div>
<div className="flex items-center gap-2 border-l border-border pl-4">
<button className="p-2 rounded-lg bg-muted text-foreground shadow-sm">
<List className="w-4 h-4" />
</button>
<button className="p-2 rounded-lg text-muted-foreground hover:text-foreground hover:bg-muted transition-colors">
<Grid className="w-4 h-4" />
</button>
</div>
</div>
{/* Recent/Pinned Section */}
<div className="p-6 pb-2">
<h3 className="text-sm font-bold text-foreground mb-4">Accès Rapide</h3>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
{FILES.filter(f => f.type === 'folder').map(folder => (
<div key={folder.id} className="p-4 rounded-xl border border-border bg-muted/20 hover:bg-muted/50 transition-colors cursor-pointer group">
<div className="flex justify-between items-start mb-2">
<Folder className="w-8 h-8 text-blue-500 fill-blue-500/20" />
<MoreVertical className="w-4 h-4 text-muted-foreground opacity-0 group-hover:opacity-100 transition-opacity" />
</div>
<p className="font-medium text-sm truncate">{folder.name}</p>
<p className="text-xs text-muted-foreground">{folder.size}</p>
</div>
))}
<button className="p-4 rounded-xl border-2 border-dashed border-border hover:border-primary/50 hover:bg-primary/5 transition-colors flex flex-col items-center justify-center gap-2 text-muted-foreground hover:text-primary">
<Upload className="w-6 h-6" />
<span className="text-xs font-medium">Déposer fichier</span>
</button>
</div>
</div>
{/* File List */}
<div className="flex-1 overflow-y-auto">
<div className="px-6 pb-4">
<h3 className="text-sm font-bold text-foreground mb-2 mt-4">Tous les fichiers</h3>
</div>
<table className="w-full text-sm text-left">
<thead className="text-xs text-muted-foreground uppercase bg-muted/30 sticky top-0 z-10 backdrop-blur-sm">
<tr>
<th className="px-6 py-3 font-medium">Nom</th>
<th className="px-6 py-3 font-medium hidden sm:table-cell">Propriétaire</th>
<th className="px-6 py-3 font-medium hidden md:table-cell">Modifié le</th>
<th className="px-6 py-3 font-medium hidden lg:table-cell">Taille</th>
<th className="px-6 py-3 w-10"></th>
</tr>
</thead>
<tbody className="divide-y divide-border">
{FILES.filter(f => f.type !== 'folder').map((file) => (
<tr key={file.id} className="hover:bg-muted/40 transition-colors group cursor-pointer">
<td className="px-6 py-3">
<div className="flex items-center gap-3">
<div className={`p-2 rounded-lg shrink-0 ${
file.type === 'pdf' ? 'bg-red-500/10 text-red-500' :
file.type === 'excel' ? 'bg-emerald-500/10 text-emerald-500' :
file.type === 'image' ? 'bg-purple-500/10 text-purple-500' :
'bg-gray-500/10 text-gray-500'
}`}>
{file.type === 'pdf' ? <FileText className="w-5 h-5" /> :
file.type === 'excel' ? <FileSpreadsheet className="w-5 h-5" /> :
file.type === 'image' ? <FileImage className="w-5 h-5" /> :
<FileText className="w-5 h-5" />}
</div>
<div className="min-w-0">
<p className="font-medium text-foreground truncate max-w-[200px] sm:max-w-xs">{file.name}</p>
<div className="flex items-center gap-2 sm:hidden text-xs text-muted-foreground">
<span>{file.size}</span>
<span>•</span>
<span>{file.date}</span>
</div>
</div>
{file.starred && <Star className="w-3.5 h-3.5 text-yellow-500 fill-yellow-500 shrink-0" />}
</div>
</td>
<td className="px-6 py-3 hidden sm:table-cell">
<div className="flex items-center gap-2">
<div className="w-6 h-6 rounded-full bg-primary/10 text-primary text-xs flex items-center justify-center font-bold">
{file.owner.charAt(0)}
</div>
<span className="text-muted-foreground">{file.owner}</span>
</div>
</td>
<td className="px-6 py-3 hidden md:table-cell text-muted-foreground whitespace-nowrap">{file.date}</td>
<td className="px-6 py-3 hidden lg:table-cell text-muted-foreground font-mono text-xs">{file.size}</td>
<td className="px-6 py-3 text-right">
<div className="flex items-center justify-end gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
<button className="p-2 hover:bg-background rounded-lg text-muted-foreground hover:text-foreground border border-transparent hover:border-border transition-all shadow-sm" title="Télécharger">
<Download className="w-4 h-4" />
</button>
<button className="p-2 hover:bg-background rounded-lg text-muted-foreground hover:text-foreground border border-transparent hover:border-border transition-all shadow-sm">
<MoreVertical className="w-4 h-4" />
</button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
</div>
);
}
~~~
~~~/src/pages/Activities.tsx
import React, { useState } from 'react';
import { ChevronLeft, ChevronRight, Clock, MapPin, Users, Plus } from 'lucide-react';
import { format, addMonths, subMonths, startOfMonth, endOfMonth, eachDayOfInterval, isSameMonth, isSameDay, isToday } from 'date-fns';
import { fr } from 'date-fns/locale';
import { cn } from '../Component';
const EVENTS = [
{ id: 1, title: "Entraînement Football", date: new Date(2024, 2, 15), time: "18:00 - 20:00", type: "sport", color: "bg-emerald-500", location: "Stade Municipal" },
{ id: 2, title: "Réunion Parents", date: new Date(2024, 2, 18), time: "19:00 - 20:30", type: "meeting", color: "bg-blue-500", location: "Salle Polyvalente" },
{ id: 3, title: "Tournoi Tennis", date: new Date(2024, 2, 22), time: "09:00 - 16:00", type: "competition", color: "bg-purple-500", location: "Club House" },
{ id: 4, title: "Cours Natation", date: new Date(2024, 2, 15), time: "14:00 - 15:30", type: "sport", color: "bg-cyan-500", location: "Piscine Olympique" },
];
export default function Activities() {
const [currentDate, setCurrentDate] = useState(new Date());
const [selectedDate, setSelectedDate] = useState(new Date());
const days = eachDayOfInterval({
start: startOfMonth(currentDate),
end: endOfMonth(currentDate),
});
const nextMonth = () => setCurrentDate(addMonths(currentDate, 1));
const prevMonth = () => setCurrentDate(subMonths(currentDate, 1));
const selectedEvents = EVENTS.filter(event => isSameDay(event.date, selectedDate)); // In a real app, this would match actual dates
return (
<div className="grid lg:grid-cols-3 gap-8 h-[calc(100vh-8rem)]">
{/* Calendar Section */}
<div className="lg:col-span-2 flex flex-col h-full bg-card border border-border rounded-2xl shadow-sm overflow-hidden">
<div className="p-6 border-b border-border flex items-center justify-between">
<div>
<h2 className="text-2xl font-bold text-foreground capitalize">
{format(currentDate, 'MMMM yyyy', { locale: fr })}
</h2>
<p className="text-muted-foreground">Gérez votre emploi du temps</p>
</div>
<div className="flex items-center gap-2">
<button onClick={prevMonth} className="p-2 hover:bg-muted rounded-full transition-colors">
<ChevronLeft className="w-5 h-5" />
</button>
<button onClick={() => setCurrentDate(new Date())} className="px-3 py-1 text-sm font-medium border border-border rounded-lg hover:bg-muted">
Aujourd'hui
</button>
<button onClick={nextMonth} className="p-2 hover:bg-muted rounded-full transition-colors">
<ChevronRight className="w-5 h-5" />
</button>
</div>
</div>
<div className="grid grid-cols-7 flex-1">
{['Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam', 'Dim'].map(day => (
<div key={day} className="py-4 text-center text-sm font-medium text-muted-foreground border-b border-border bg-muted/20">
{day}
</div>
))}
{days.map((day, dayIdx) => {
const hasEvents = dayIdx % 5 === 0; // Mock event indicators
return (
<button
key={day.toString()}
onClick={() => setSelectedDate(day)}
className={cn(
"relative flex flex-col items-center justify-start py-4 transition-colors border-r border-b border-border/50 hover:bg-muted/30",
!isSameMonth(day, currentDate) && "text-muted-foreground/30 bg-muted/10",
isSameDay(day, selectedDate) && "bg-primary/5 shadow-inner",
isToday(day) && "bg-accent/30"
)}
>
<span className={cn(
"flex items-center justify-center w-8 h-8 text-sm font-medium rounded-full mb-1",
isToday(day) ? "bg-primary text-primary-foreground" : "text-foreground",
isSameDay(day, selectedDate) && !isToday(day) && "bg-foreground text-background"
)}>
{format(day, 'd')}
</span>
{hasEvents && (
<div className="flex gap-1 mt-1">
<div className="w-1.5 h-1.5 rounded-full bg-emerald-500"></div>
{dayIdx % 7 === 0 && <div className="w-1.5 h-1.5 rounded-full bg-blue-500"></div>}
</div>
)}
</button>
);
})}
</div>
</div>
{/* Sidebar / Agenda */}
<div className="bg-card border border-border rounded-2xl shadow-sm p-6 flex flex-col">
<div className="flex items-center justify-between mb-6">
<h3 className="text-lg font-bold text-foreground">
{isToday(selectedDate) ? "Aujourd'hui" : format(selectedDate, 'd MMMM yyyy', { locale: fr })}
</h3>
<button className="p-2 bg-primary text-primary-foreground rounded-full hover:bg-primary/90 transition-colors shadow-lg shadow-primary/20">
<Plus className="w-5 h-5" />
</button>
</div>
<div className="flex-1 overflow-y-auto space-y-4 pr-2">
{/* Mock events for the selected date - in real app filter properly */}
{[1, 2, 3].map((_, i) => (
<div key={i} className="group p-4 rounded-xl border border-border bg-muted/20 hover:bg-muted/50 transition-colors cursor-pointer border-l-4 border-l-primary">
<div className="flex justify-between items-start mb-2">
<h4 className="font-semibold text-foreground">Entraînement Football</h4>
<span className="text-xs font-medium px-2 py-1 rounded bg-background border border-border">Sport</span>
</div>
<div className="space-y-2 text-sm text-muted-foreground">
<div className="flex items-center gap-2">
<Clock className="w-4 h-4" />
<span>18:00 - 20:00</span>
</div>
<div className="flex items-center gap-2">
<MapPin className="w-4 h-4" />
<span>Stade Municipal</span>
</div>
<div className="flex items-center gap-2">
<Users className="w-4 h-4" />
<span>12 Participants</span>
</div>
</div>
</div>
))}
<div className="p-4 rounded-xl border border-dashed border-border flex items-center justify-center text-muted-foreground text-sm hover:bg-muted/20 cursor-pointer transition-colors">
Aucun autre événement
</div>
</div>
</div>
</div>
);
}
~~~
Implementation Guidelines
1. Analyze the component structure, styling, animation implementations
2. Review the component's arguments and state
3. Think through what is the best place to adopt this component/style into the design we are doing
4. Then adopt the component/design to our current system
Help me integrate this into my design