ultisuite-client/components/admin/settings/sections/overview-section.tsx
2026-06-07 21:55:42 +02:00

165 lines
5.6 KiB
TypeScript

"use client"
import { SettingsSectionHeader } from "@/components/gmail/settings/settings-section-header"
import { SettingsSyncBanner } from "@/components/gmail/settings/settings-sync-banner"
import { AdminRuntimePanel } from "@/components/admin/settings/admin-runtime-panel"
import { useAdminStats } from "@/lib/api/hooks/use-admin-queries"
import { formatBytes } from "@/lib/admin/format-bytes"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
export function OverviewSection() {
const { data, isFetching, isError, refetch } = useAdminStats()
const storage = data?.storage
return (
<>
<SettingsSectionHeader
title="Vue d'ensemble"
description="Activité de la plateforme, stockage et configuration runtime."
/>
<SettingsSyncBanner isFetching={isFetching} isError={isError} onRetry={() => refetch()} />
<AdminRuntimePanel />
{data ? (
<div className="space-y-6">
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
<StatCard label="Utilisateurs" value={data.users.total} hint={`${data.users.active} actifs`} />
<StatCard label="Invitations" value={data.users.invited} hint={`${data.users.disabled} désactivés`} />
<StatCard label="Comptes mail" value={data.services.mail_accounts_total} />
<StatCard label="Messages" value={data.services.messages_total} />
</div>
{storage ? (
<div className="grid gap-4 lg:grid-cols-3">
<StorageCard
label="Mail (plateforme)"
used={storage.mail.used_bytes}
allocated={storage.mail.allocated_bytes}
/>
<StorageCard
label="Drive (plateforme)"
used={storage.drive.used_bytes}
allocated={storage.drive.allocated_bytes}
tracked={storage.drive.tracked}
/>
<StorageCard
label="Photos (plateforme)"
used={storage.photos.used_bytes}
allocated={storage.photos.allocated_bytes}
tracked={storage.photos.tracked}
/>
</div>
) : null}
<div className="grid gap-4 lg:grid-cols-2">
<Card>
<CardHeader className="pb-2">
<CardTitle className="text-sm font-medium">Audit (24 h)</CardTitle>
</CardHeader>
<CardContent>
<p className="text-2xl font-semibold">{data.services.audit_events_24h}</p>
<p className="mt-1 text-xs text-muted-foreground">événements enregistrés</p>
</CardContent>
</Card>
<Card>
<CardHeader className="pb-2">
<CardTitle className="text-sm font-medium">Quotas mail</CardTitle>
</CardHeader>
<CardContent>
<p className="text-2xl font-semibold">{data.quotas.users_near_mail_quota_90pct}</p>
<p className="mt-1 text-xs text-muted-foreground">
utilisateurs à plus de 90 % du quota
</p>
</CardContent>
</Card>
</div>
{data.audit.top_actors_7d.length > 0 ? (
<Card>
<CardHeader className="pb-2">
<CardTitle className="text-sm font-medium">Top acteurs (7 j)</CardTitle>
</CardHeader>
<CardContent className="space-y-2">
{data.audit.top_actors_7d.map((item) => (
<div
key={item.actor}
className="flex items-center justify-between text-sm"
>
<span className="truncate font-mono text-xs">{item.actor}</span>
<span className="text-muted-foreground">{item.count}</span>
</div>
))}
</CardContent>
</Card>
) : null}
</div>
) : isFetching ? (
<p className="text-sm text-muted-foreground">Chargement des statistiques</p>
) : null}
</>
)
}
function StatCard({
label,
value,
hint,
}: {
label: string
value: number
hint?: string
}) {
return (
<Card>
<CardHeader className="pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground">{label}</CardTitle>
</CardHeader>
<CardContent>
<p className="text-2xl font-semibold">{value.toLocaleString("fr-FR")}</p>
{hint ? <p className="mt-1 text-xs text-muted-foreground">{hint}</p> : null}
</CardContent>
</Card>
)
}
function StorageCard({
label,
used,
allocated,
tracked = true,
}: {
label: string
used: number
allocated: number
tracked?: boolean
}) {
const pct = allocated > 0 ? Math.min(100, Math.round((used / allocated) * 100)) : 0
return (
<Card>
<CardHeader className="pb-2">
<CardTitle className="text-sm font-medium">{label}</CardTitle>
</CardHeader>
<CardContent className="space-y-2">
<p className="text-lg font-semibold">
{formatBytes(used)}
<span className="text-sm font-normal text-muted-foreground">
{" "}
/ {formatBytes(allocated)}
</span>
</p>
<div className="h-1.5 overflow-hidden rounded-full bg-muted">
<div
className="h-full rounded-full bg-primary transition-all"
style={{ width: `${pct}%` }}
/>
</div>
<p className="text-xs text-muted-foreground">
{tracked ? `${pct} % des quotas alloués` : "Mesure non disponible"}
</p>
</CardContent>
</Card>
)
}