inogen/app/components/ui/custom-bar-chart.tsx
Saeed 699548c674
Refactor process impacts chart to use new CustomBarChart component (#3)
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2025-08-13 18:15:02 +03:30

144 lines
5.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import * as React from "react";
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));
// Function to format numbers with Persian digits
const formatNumber = (value: number): string => {
const str = String(value);
const map: Record<string, string> = {
"0": "۰", "1": "۱", "2": "۲", "3": "۳", "4": "۴",
"5": "۵", "6": "۶", "7": "۷", "8": "۸", "9": "۹"
};
return str.replace(/[0-9]/g, (d) => map[d] ?? d);
};
// Loading skeleton
if (loading) {
return (
<div className={`space-y-6 ${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 }}>
{title && (
<h3 className="text-xl font-bold text-white font-persian text-right mb-4">
{title}
</h3>
)}
<div className="space-y-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>
);
}