hit-server-manager/frontend/src/components/ServerCardMetrics.tsx

85 lines
3.3 KiB
TypeScript
Raw Normal View History

import { useEffect, useState } from "react";
import api from "../Api";
import { ServerTypeLabels } from "../types/enums";
import { Code, Database, FlaskConical, Server } from "lucide-icons-react";
interface ServerCount {
type: string;
total: number;
}
export const ServerCardMetrics = () => {
const [counts, setCounts] = useState<ServerCount[]>([]);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchCounts = async () => {
try {
const { data } = await api.get<Record<string, number>>("/api/servers/type");
const normalized = Object.entries(data).map(([type, total]) => ({
type,
total,
}));
setCounts(normalized);
} catch (err: any) {
const message = err?.response?.data?.message || "Falha ao carregar o total de servidores.";
setError(message);
}
};
fetchCounts();
}, []);
if (error) {
return <div className={Styles.error}>{error}</div>;
}
if (counts.length === 0) {
return <div className={Styles.placeholder}>Carregando métricas...</div>;
}
const handleCardIcon = (type: string) => {
const iconProps = { size: 32, strokeWidth: 1.5 };
switch (type) {
case "DATABASE":
return <Database {...iconProps} />;
case "HOMOLOGATION":
return <FlaskConical {...iconProps} />;
case "PRODUCTION":
return <Server {...iconProps} />;
default:
return <Code {...iconProps} />;
}
};
const formatTotal = (value: number) => value.toLocaleString("pt-BR");
return (
<div className={Styles.grid}>
{counts.map(({ type, total }) => (
<div key={type} className={Styles.card}>
<div className={Styles.iconWrapper}>
{handleCardIcon(type)}
</div>
<div className={Styles.textGroup}>
<p className={Styles.value}>{formatTotal(total)}</p>
<p className={Styles.label}>{ServerTypeLabels[type] ?? type}</p>
</div>
</div>
))}
</div>
);
};
const Styles = {
grid: "grid gap-4 sm:grid-cols-2 lg:grid-cols-3",
card: "flex items-center gap-4 rounded-xl border border-cardBorder bg-gradient-to-br from-white/90 to-card p-5 shadow-sm dark:border-slate-800 dark:from-slate-900 dark:to-slate-950 dark:shadow-slate-900/40",
iconWrapper: "flex h-14 w-14 items-center justify-center rounded-lg border border-accent/20 bg-accent/10 text-accent dark:border-slate-800 dark:bg-slate-800/60",
textGroup: "flex flex-col",
label: "text-xs font-medium uppercase tracking-wide text-text-secondary dark:text-slate-400",
value: "text-3xl font-semibold leading-tight text-text dark:text-white",
placeholder: "rounded-lg border border-cardBorder bg-card p-4 text-sm text-text-secondary dark:border-slate-800 dark:bg-slate-900 dark:text-slate-400",
error: "rounded-lg border border-red-200 bg-red-50 p-4 text-sm text-red-600 dark:border-red-400/30 dark:bg-red-950/40 dark:text-red-300",
};