JavaScript 56.7%
TypeScript 42.6%
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