JavaScript 56.7%
TypeScript 42.6%
1"use client";23import { useEffect, useState } from "react";4import Link from "next/link";5import { motion } from "framer-motion";6import { LayoutDashboard, HardDrive, Clock, ArrowRight, Users, Share2, Camera, Key, Settings, Activity, Database } from "lucide-react";7import AppShell from "@/components/layout/AppShell";8import StatsCards from "@/components/admin/StatsCards";9import ActivityLog from "@/components/admin/ActivityLog";10import StorageChart from "@/components/admin/StorageChart";11import ActivityBars, { type DayActivity } from "@/components/dashboard/ActivityBars";12import type { CategoryStat } from "@/components/dashboard/categories";13import PageHeader from "@/components/ui/PageHeader";14import Card, { CardTitle } from "@/components/ui/Card";15import Skeleton, { SkeletonStat, SkeletonActivity } from "@/components/ui/Skeleton";1617interface Stats {18 fileCount: number;19 folderCount: number;20 shareCount: number;21 storageUsed: number;22 diskFileCount: number;23 recentActivity: Array<{ id: string; action: string; details: string | null; createdAt: string; file: { name: string } | null }>;24 typeBreakdown: Array<{ type: string; count: number; size: number }>;25 byCategory?: CategoryStat[];26 activityByDay?: DayActivity[];27 favoriteCount?: number;28 trashCount?: number;29 versionCount?: number;30}3132const links = [33 { href: "/admin/users", label: "Utilisateurs", icon: Users },34 { href: "/admin/shared-spaces", label: "Espaces partagés", icon: Users },35 { href: "/admin/shares", label: "Liens partagés", icon: Share2 },36 { href: "/admin/activity", label: "Journal complet", icon: Activity },37 { href: "/admin/snapshots", label: "Snapshots", icon: Camera },38 { href: "/admin/backup", label: "Backups", icon: Database },39 { href: "/admin/api-keys", label: "Clés API", icon: Key },40 { href: "/admin/settings", label: "Paramètres", icon: Settings },41];4243export default function AdminPage() {44 const [stats, setStats] = useState<Stats | null>(null);45 const [loading, setLoading] = useState(true);4647 useEffect(() => {48 fetch("/api/admin/stats")49 .then((res) => res.json())50 .then((data) => { setStats(data); setLoading(false); })51 .catch(() => setLoading(false));52 }, []);5354 return (55 <AppShell>56 <div className="p-3 sm:p-5 lg:p-6 space-y-4 sm:space-y-5 max-w-[1400px] mx-auto">57 <PageHeader58 title="Administration"59 subtitle="Vue d'ensemble de votre cloud : stockage, activité et outils."60 icon={LayoutDashboard}61 compact62 />6364 {loading ? (65 <div className="space-y-4 sm:space-y-5" aria-busy="true">66 <div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-6 gap-2 sm:gap-3">{[...Array(6)].map((_, i) => <SkeletonStat key={i} />)}</div>67 <div className="grid grid-cols-1 lg:grid-cols-12 gap-4 sm:gap-5">68 <div className="card p-5 lg:col-span-5"><Skeleton variant="text" className="h-3 w-24 mb-4" /><Skeleton variant="circular" className="w-[184px] h-[184px] mx-auto" /></div>69 <div className="card p-5 lg:col-span-7"><Skeleton variant="text" className="h-3 w-24 mb-4" /><Skeleton className="h-[150px]" /></div>70 </div>71 <div className="card p-5">{[...Array(6)].map((_, i) => <SkeletonActivity key={i} />)}</div>72 </div>73 ) : stats ? (74 <>75 <StatsCards stats={stats} />7677 <div className="grid grid-cols-1 lg:grid-cols-12 gap-4 sm:gap-5">78 <motion.div initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: 0.1 }} className="lg:col-span-5">79 <Card className="h-full">80 <CardTitle><HardDrive className="w-3.5 h-3.5" /> Répartition du stockage</CardTitle>81 <StorageChart data={stats.typeBreakdown} totalSize={stats.storageUsed} byCategory={stats.byCategory} />82 <p className="mt-4 pt-3 border-t border-line dark:border-white/[0.07] text-[11.5px] text-gray-400">83 {(stats.diskFileCount ?? 0).toLocaleString("fr-CA")} objets sur disque · {(stats.typeBreakdown ?? []).length} types MIME84 </p>85 </Card>86 </motion.div>87 <motion.div initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: 0.15 }} className="lg:col-span-7">88 <Card className="h-full">89 <CardTitle><Clock className="w-3.5 h-3.5" /> Activité · 14 jours</CardTitle>90 {stats.activityByDay && stats.activityByDay.length > 0 ? (91 <ActivityBars data={stats.activityByDay} height={150} />92 ) : (93 <p className="text-sm text-gray-400 py-8 text-center">Aucune activité récente</p>94 )}95 </Card>96 </motion.div>97 </div>9899 <div className="grid grid-cols-1 lg:grid-cols-12 gap-4 sm:gap-5">100 <motion.div initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: 0.2 }} className="lg:col-span-7">101 <Card>102 <CardTitle action={<Link href="/admin/activity" className="text-[12px] text-blue-600 dark:text-blue-300 hover:underline flex items-center gap-1">Tout le journal <ArrowRight className="w-3 h-3" /></Link>}>103 <Clock className="w-3.5 h-3.5" /> Activité récente104 </CardTitle>105 <div className="max-h-[520px] overflow-y-auto -mx-1 px-1 scrollbar-thin">106 <ActivityLog activities={stats.recentActivity} />107 </div>108 </Card>109 </motion.div>110 <motion.div initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: 0.25 }} className="lg:col-span-5">111 <Card>112 <CardTitle><Settings className="w-3.5 h-3.5" /> Outils d'administration</CardTitle>113 <ul className="grid grid-cols-2 gap-2">114 {links.map((l) => (115 <li key={l.href}>116 <Link href={l.href} className="group flex items-center gap-2.5 p-3 rounded-xl border border-line dark:border-white/[0.08] hover:border-blue-500/40 hover:bg-blue-500/5 transition-colors min-h-[52px]">117 <span className="w-8 h-8 rounded-lg bg-gray-100 dark:bg-white/[0.06] group-hover:bg-blue-500/10 flex items-center justify-center shrink-0 text-gray-500 dark:text-gray-400 group-hover:text-blue-600 dark:group-hover:text-blue-300 transition-colors"><l.icon className="w-4 h-4" /></span>118 <span className="text-[13px] font-medium text-gray-800 dark:text-gray-100 truncate">{l.label}</span>119 </Link>120 </li>121 ))}122 </ul>123 </Card>124 </motion.div>125 </div>126 </>127 ) : (128 <Card><p className="text-gray-500 text-sm text-center py-8">Impossible de charger les statistiques.</p></Card>129 )}130 </div>131 </AppShell>132 );133}134