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%
3.5 KB · 81 lines tsx
Raw Blame History
1"use client";23import { motion } from "framer-motion";4import { HardDrive, FileText, Share2, Folder, Star, Trash2, Layers, type LucideIcon } from "lucide-react";5import { formatFileSize } from "@/lib/utils";6import { STORAGE_QUOTA_BYTES } from "@/lib/prefs";78interface StatsData {9  fileCount: number;10  folderCount: number;11  shareCount: number;12  storageUsed: number;13  favoriteCount?: number;14  trashCount?: number;15  versionCount?: number;16}1718interface StatsCardsProps {19  stats: StatsData;20}2122interface CardDef {23  key: keyof StatsData;24  label: string;25  icon: LucideIcon;26  format: (v: number) => string;27  tone: string;28  sub?: (s: StatsData) => string | undefined;29}3031const cards: CardDef[] = [32  {33    key: "storageUsed", label: "Stockage utilisé", icon: HardDrive, format: (v) => formatFileSize(v),34    tone: "text-blue-600 bg-blue-500/10 dark:text-blue-300",35    sub: (s) => `${Math.min(100, (s.storageUsed / STORAGE_QUOTA_BYTES) * 100).toFixed(1)} % de ${formatFileSize(STORAGE_QUOTA_BYTES)}`,36  },37  { key: "fileCount", label: "Fichiers", icon: FileText, format: (v) => (v ?? 0).toLocaleString("fr-CA"), tone: "text-emerald-600 bg-emerald-500/10 dark:text-emerald-300", sub: (s) => (typeof s.versionCount === "number" ? `${s.versionCount.toLocaleString("fr-CA")} versions` : undefined) },38  { key: "folderCount", label: "Dossiers", icon: Folder, format: (v) => (v ?? 0).toLocaleString("fr-CA"), tone: "text-amber-600 bg-amber-500/10 dark:text-amber-300" },39  { key: "shareCount", label: "Liens actifs", icon: Share2, format: (v) => (v ?? 0).toLocaleString("fr-CA"), tone: "text-purple-600 bg-purple-500/10 dark:text-purple-300" },40  { key: "favoriteCount", label: "Favoris", icon: Star, format: (v) => (v ?? 0).toLocaleString("fr-CA"), tone: "text-amber-600 bg-amber-500/10 dark:text-amber-300" },41  { key: "trashCount", label: "Corbeille", icon: Trash2, format: (v) => (v ?? 0).toLocaleString("fr-CA"), tone: "text-rose-600 bg-rose-500/10 dark:text-rose-300" },42];4344export default function StatsCards({ stats }: StatsCardsProps) {45  const visible = cards.filter((c) => typeof stats[c.key] === "number");46  return (47    <div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-6 gap-2 sm:gap-3">48      {visible.map((card, i) => {49        const value = stats[card.key] as number;50        const sub = card.sub?.(stats);51        const Icon = card.icon;52        return (53          <motion.div54            key={card.key}55            initial={{ opacity: 0, y: 10 }}56            animate={{ opacity: 1, y: 0 }}57            transition={{ delay: i * 0.05, duration: 0.3 }}58            className="card card-hover p-3.5 sm:p-4"59          >60            <div className="flex items-center gap-3">61              <span className={`w-10 h-10 rounded-xl flex items-center justify-center shrink-0 ${card.tone}`}>62                <Icon className="w-[18px] h-[18px]" />63              </span>64              <div className="min-w-0">65                <p className="text-[20px] sm:text-[22px] font-semibold tracking-tight text-gray-900 dark:text-white tabular-nums leading-none truncate">{card.format(value)}</p>66                <p className="text-[11.5px] text-gray-500 dark:text-gray-400 mt-1 truncate">{card.label}</p>67              </div>68            </div>69            {sub && (70              <p className="mt-2.5 text-[11px] text-gray-400 flex items-center gap-1 truncate">71                {card.key === "fileCount" && <Layers className="w-3 h-3 shrink-0" />}72                {sub}73              </p>74            )}75          </motion.div>76        );77      })}78    </div>79  );80}81