inogen/app/components/ui/custom-bar-chart.tsx
saeed0920 aed286660a refactor_#2 (#13)
Reviewed-on: https://git.pelekan.org/Saeed0920/inogen/pulls/13
Co-authored-by: saeed0920 <sd.eed1381@gmail.com>
Co-committed-by: saeed0920 <sd.eed1381@gmail.com>
2025-09-18 10:57:50 +03:30

142 lines
4.6 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>
<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>
);
}