146 lines
4.7 KiB
TypeScript
146 lines
4.7 KiB
TypeScript
import { formatNumber } from "~/lib/utils";
|
|
|
|
export interface BarChartData {
|
|
label: string;
|
|
value: number;
|
|
maxValue?: number;
|
|
color?: string;
|
|
labelColor?: string;
|
|
valuePrefix?: string;
|
|
valueSuffix?: string;
|
|
}
|
|
|
|
interface CustomBarChartProps {
|
|
data: BarChartData[];
|
|
title?: string;
|
|
height?: string;
|
|
barHeight?: string;
|
|
showAxisLabels?: boolean;
|
|
className?: string;
|
|
loading?: boolean;
|
|
}
|
|
|
|
export function CustomBarChart({
|
|
data,
|
|
title,
|
|
height = "auto",
|
|
barHeight = "h-6",
|
|
showAxisLabels = true,
|
|
className = "",
|
|
loading = false,
|
|
}: CustomBarChartProps) {
|
|
// Calculate the maximum value across all data points for consistent scaling
|
|
const globalMaxValue = Math.max(
|
|
...data.map((item) => item.maxValue || item.value)
|
|
);
|
|
|
|
// Loading skeleton
|
|
if (loading) {
|
|
return (
|
|
<div className={`space-y-6 p-4 ${className}`} style={{ height }}>
|
|
{title && (
|
|
<div className="h-7 bg-gray-600 rounded animate-pulse mb-4 w-1/2"></div>
|
|
)}
|
|
|
|
<div className="space-y-4 flex flex-col gap-4">
|
|
{Array.from({ length: 4 }).map((_, index) => (
|
|
<div key={index} className="flex items-center gap-3">
|
|
{/* Label skeleton */}
|
|
<div className="h-4 bg-gray-600 rounded animate-pulse min-w-[160px]"></div>
|
|
|
|
{/* Bar skeleton */}
|
|
<div className="flex-1 bg-gray-700 rounded-full h-6">
|
|
<div
|
|
className="h-6 bg-gray-600 rounded-full animate-pulse"
|
|
style={{ width: `${Math.random() * 60 + 20}%` }}
|
|
></div>
|
|
</div>
|
|
|
|
{/* Value skeleton */}
|
|
<div className="h-4 bg-gray-600 rounded animate-pulse min-w-[60px]"></div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className={`space-y-6 ${className}`} style={{ height }}>
|
|
{title && (
|
|
<div className="border-b-[#3F415A] border-b-2">
|
|
<h3 className="text-sm font-semibold text-white font-persian text-right p-4">
|
|
{title}
|
|
</h3>
|
|
</div>
|
|
)}
|
|
|
|
<div className="space-y-4 px-4 pb-4">
|
|
{data.map((item, index) => {
|
|
const percentage =
|
|
globalMaxValue > 0 ? (item.value / globalMaxValue) * 100 : 0;
|
|
const displayValue: any = item.value;
|
|
return (
|
|
<div key={index} className="flex items-center gap-3">
|
|
<span
|
|
className={`font-persian text-sm font-normal min-w-[120px] text-right ${
|
|
item.labelColor || "text-white"
|
|
}`}
|
|
>
|
|
{item.label}
|
|
</span>
|
|
<div
|
|
className={`${showAxisLabels && "bg-pr-gray"} flex-1 flex items-center gap-1 justify-start rounded-full overflow-hidden ${barHeight}`}
|
|
>
|
|
<div
|
|
className={`${barHeight} rounded-full transition-all duration-700 ease-out ${
|
|
item.color || "bg-pr-green"
|
|
}`}
|
|
style={{
|
|
width: `${Math.min(percentage, 100)}%`,
|
|
}}
|
|
>
|
|
<div className="inset-0 bg-gradient-to-r from-transparent to-white/10 rounded-full"></div>
|
|
</div>
|
|
{displayValue != 100 && (
|
|
<span
|
|
className={`text-base font-normal text-left text-white`}
|
|
>
|
|
{item.valuePrefix || ""}
|
|
|
|
{formatNumber(parseFloat(displayValue))}
|
|
{item.valueSuffix || ""}
|
|
</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
|
|
{/* Axis Labels */}
|
|
{showAxisLabels && globalMaxValue > 0 && (
|
|
<div className="flex w-full items-center gap-3 mt-6">
|
|
<span className="min-w-[120px]"></span>
|
|
<div className="flex-1 flex justify-between pt-2 border-t border-gray-700">
|
|
<span className="text-gray-400 text-xs">{formatNumber(0)}</span>
|
|
<span className="text-gray-400 text-xs">
|
|
{formatNumber(Math.round(globalMaxValue / 4))}
|
|
</span>
|
|
<span className="text-gray-400 text-xs">
|
|
{formatNumber(Math.round(globalMaxValue / 2))}
|
|
</span>
|
|
<span className="text-gray-400 text-xs">
|
|
{formatNumber(Math.round((globalMaxValue * 3) / 4))}
|
|
</span>
|
|
<span className="text-gray-400 text-xs">
|
|
{formatNumber(Math.round(globalMaxValue))}
|
|
</span>
|
|
</div>
|
|
<span className="min-w-[0px]"></span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|