154 lines
4.4 KiB
TypeScript
154 lines
4.4 KiB
TypeScript
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;
|