2025-12-16 15:52:32 +00:00
|
|
|
import { useEffect, useState } from "react";
|
|
|
|
|
import api from "../Api";
|
|
|
|
|
import { ServerTypeLabels, type ServersType } from "../types/enums";
|
2025-12-16 16:17:23 +00:00
|
|
|
import { Code, Database, FlaskConical, Server } from "lucide-icons-react";
|
2025-12-16 15:52:32 +00:00
|
|
|
|
|
|
|
|
interface ServerCount {
|
|
|
|
|
type: ServersType;
|
|
|
|
|
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<ServersType, number>>("/api/servers/type");
|
|
|
|
|
const normalized = Object.entries(data).map(([type, total]) => ({
|
|
|
|
|
type: type as ServersType,
|
|
|
|
|
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>;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-16 16:17:23 +00:00
|
|
|
const handleCardIcon = (type: ServersType) => {
|
|
|
|
|
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");
|
|
|
|
|
|
2025-12-16 15:52:32 +00:00
|
|
|
return (
|
|
|
|
|
<div className={Styles.grid}>
|
|
|
|
|
{counts.map(({ type, total }) => (
|
|
|
|
|
<div key={type} className={Styles.card}>
|
2025-12-16 16:17:23 +00:00
|
|
|
<div className={Styles.iconWrapper}>
|
|
|
|
|
{handleCardIcon(type)}
|
|
|
|
|
</div>
|
|
|
|
|
<div className={Styles.textGroup}>
|
|
|
|
|
<p className={Styles.value}>{formatTotal(total)}</p>
|
|
|
|
|
<p className={Styles.label}>{ServerTypeLabels[type]}</p>
|
|
|
|
|
</div>
|
2025-12-16 15:52:32 +00:00
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const Styles = {
|
|
|
|
|
grid: "grid gap-4 sm:grid-cols-2 lg:grid-cols-3",
|
2025-12-16 16:17:23 +00:00
|
|
|
card: "flex items-center gap-4 rounded-xl border border-cardBorder bg-gradient-to-br from-white/90 to-card p-5 shadow-sm",
|
|
|
|
|
iconWrapper: "flex h-14 w-14 items-center justify-center rounded-lg border border-accent/20 bg-accent/10 text-accent",
|
|
|
|
|
textGroup: "flex flex-col",
|
|
|
|
|
label: "text-xs font-medium uppercase tracking-wide text-text-secondary",
|
|
|
|
|
value: "text-3xl font-semibold text-text leading-tight",
|
2025-12-16 15:52:32 +00:00
|
|
|
placeholder: "p-4 rounded-lg border border-cardBorder bg-card text-text-secondary text-sm",
|
|
|
|
|
error: "p-4 rounded-lg border border-red-200 bg-red-50 text-red-600 text-sm",
|
|
|
|
|
};
|