SPB Git forge

spb/spb-cloud

Public
10commits 1branches 0releases
3.2 MBsize
maindefault branch
2 h agolast push
JavaScript 56.7% TypeScript 42.6%
7.0 KB · 134 lines tsx
Raw Blame History
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&apos;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