Vendaweb-portal/components/ArcGauge.tsx

154 lines
4.4 KiB
TypeScript
Raw Normal View History

2026-01-08 12:09:16 +00:00
import React from "react";
import GaugeComponent from "react-gauge-component";
interface ArcGaugeProps {
value: number;
max?: number;
colors: Array<{
from?: number;
to?: number;
color: string;
}>;
label?: string;
title?: string;
}
const ArcGauge: React.FC<ArcGaugeProps> = ({
value,
max = 100,
colors,
label,
title,
}) => {
// Normalizar valor (pode ser maior que max para exibir acima de 100%)
const normalizedValue = Math.max(value, 0);
// Para o gauge, usar o valor normalizado (limitado ao max visualmente)
const gaugeValue = Math.min(normalizedValue, max);
// Converter faixas de cores para o formato do GaugeComponent
// O GaugeComponent usa subArcs com limites e colorArray
const sortedColors = [...colors].sort((a, b) => {
const aFrom = a.from ?? 0;
const bFrom = b.from ?? 0;
return aFrom - bFrom;
});
const colorArray = sortedColors.map((c) => c.color);
// Criar subArcs baseado nas faixas de cores
// O GaugeComponent espera limites em valores absolutos entre minValue e maxValue
// As cores vêm em porcentagem (0-100), então precisamos converter para o range correto
const subArcs = sortedColors.map((colorRange) => {
const to = colorRange.to ?? 100; // Assume 100% se não especificado
// Converter porcentagem (0-100) para o valor absoluto no range (0-max)
const limit = (to / 100) * max;
return { limit };
});
// Garantir que o último subArc vá até maxValue
if (subArcs.length > 0) {
subArcs[subArcs.length - 1].limit = max;
}
// Determinar a cor atual baseada no valor
const getCurrentColor = (): string => {
for (const colorRange of sortedColors) {
const from = colorRange.from ?? 0;
const to = colorRange.to ?? max;
if (normalizedValue >= from && normalizedValue <= to) {
return colorRange.color;
}
}
return sortedColors[sortedColors.length - 1]?.color || "#0aac25";
};
const currentColor = getCurrentColor();
return (
<div className="w-full flex flex-col items-center mt-[-30px]">
{title && (
<h2 className="text-base font-bold text-slate-700 mb-3">{title}</h2>
)}
<div
className="w-full max-w-[230px] relative"
style={{ height: "140px", marginTop: "-30px" }}
>
<GaugeComponent
value={gaugeValue}
type="radial"
style={{ width: "100%", height: "100%" }}
labels={{
tickLabels: {
type: "inner",
ticks: [
{ value: 0 },
{ value: (20 / 100) * max },
{ value: (40 / 100) * max },
{ value: (60 / 100) * max },
{ value: (80 / 100) * max },
{ value: max },
],
defaultTickValueConfig: {
formatTextValue: (value) => {
// Converter valor absoluto para porcentagem para exibição
const percentage = (value / max) * 100;
return Math.round(percentage).toString();
},
style: {
fill: "#0066cc",
fontSize: "10px",
fontWeight: "600",
},
},
},
valueLabel: {
formatTextValue: () => "",
style: {
fontSize: "0px",
fill: "transparent",
},
},
}}
arc={{
colorArray: colorArray,
subArcs: subArcs,
padding: 0.02,
width: 0.3,
}}
pointer={{
elastic: true,
animationDelay: 0,
}}
minValue={0}
maxValue={max}
/>
{/* Valor centralizado customizado */}
<div
className="absolute"
style={{
left: "50%",
top: "50%",
transform: "translate(-50%, -50%)",
marginTop: "88px",
pointerEvents: "none",
}}
>
<span
className="text-xl font-black leading-tight"
style={{ color: currentColor }}
>
{normalizedValue.toFixed(2).replace(".", ",")}%
</span>
</div>
</div>
{label && (
<span className="text-[9px] font-bold text-slate-400 uppercase mt-2">
{label}
</span>
)}
</div>
);
};
export default ArcGauge;