inogen/app/components/ui/custom-bar-chart.tsx
Saeed AB 20ee1f54bb feat/digital-innovation (#1)
Co-authored-by: MehrdadAdabi <126083584+mehrdadAdabi@users.noreply.github.com>
Co-authored-by: mehrdad <admehrdad148113@gmail.com>
Reviewed-on: https://git.pelekan.org/Saeed0920/inogen/pulls/1
2025-08-27 13:36:31 +03:30

160 lines
5.2 KiB
TypeScript

import * as React from "react";
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">
{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 }}>
<div className="border-b">
{title && (
<h3 className="text-xl font-bold 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 = item.value.toFixed(1);
return (
<div key={index} className="flex items-center gap-3">
{/* Label */}
<span
className={`font-persian text-sm min-w-[160px] text-right ${item.labelColor || "text-white"
}`}
>
{item.label}
</span>
{/* Bar Container */}
<div
className={`flex-1 flex items-center bg-gray-700 rounded-full relative overflow-hidden ${barHeight}`}
>
<div
className={`${barHeight} rounded-full transition-all duration-700 ease-out relative ${item.color || "bg-emerald-400"
}`}
style={{
width: `${Math.min(percentage, 100)}%`,
}}
>
{/* Add a subtle gradient effect for better visual appeal */}
<div className="absolute inset-0 bg-gradient-to-r from-transparent to-white/10 rounded-full"></div>
</div>
</div>
{/* Value Label */}
<span
className={`font-bold text-sm min-w-[60px] text-left ${item.color?.includes("emerald")
? "text-emerald-400"
: item.color?.includes("blue")
? "text-blue-400"
: item.color?.includes("purple")
? "text-purple-400"
: item.color?.includes("red")
? "text-red-400"
: item.color?.includes("yellow")
? "text-yellow-400"
: "text-emerald-400"
}`}
>
{item.valuePrefix || ""}
{formatNumber(parseFloat(displayValue))}
{item.valueSuffix || ""}
</span>
</div>
);
})}
{/* Axis Labels */}
{showAxisLabels && globalMaxValue > 0 && (
<div className="flex items-center gap-3 mt-6">
<span className="min-w-[160px]"></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-[60px]"></span>
</div>
)}
</div>
</div>
);
}