inogen/app/components/ui/funnel-chart.tsx
2025-09-11 06:46:59 +03:30

101 lines
4.0 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 React from "react";
import { formatNumber } from "~/lib/utils";
interface FunnelData {
name: string;
value: number;
label: string;
percentage?: string;
}
interface FunnelChartProps {
data: FunnelData[];
title?: string;
className?: string;
}
export function FunnelChart({ data, title, className = "" }: FunnelChartProps) {
const maxValue = Math.max(...data.map(d => d.value));
const toPercent = (value: number) => {
if (!maxValue || maxValue <= 0) return 0;
return Math.round((value / maxValue) * 100);
};
return (
<div className={`w-full ${className}`}>
{title && (
<h3 className="text-lg font-semibold text-white mb-4 py-2 text-right border-b-2 border-gray-400/20">
{title}
</h3>
)}
<div className="flex flex-col items-center gap-2 space-y-2">
{/* Start Process Line */}
<div className="flex items-center w-full gap-10 mt-6">
<div className="text-lg text-gray-600 min-w-[max-content]">ابتدا فرآیند</div>
<div className="flex items-center w-full gap-4">
<div className="w-full h-0.5 bg-gray-600 relative">
<div className="text-2xl text-white absolute left-1/2 -translate-x-1/2 top-[-1rem] -translate-y-1/2">۱۰۰%</div>
<div className="absolute -top-1 left-0 w-1 h-3 bg-gray-600"></div>
<div className="absolute -top-1 right-0 w-1 h-3 bg-gray-600"></div>
</div>
</div>
</div>
{/* Funnel Bars */}
<div className="flex flex-col items-center space-y-1 gap-2 w-full max-w-md">
{data.map((item, index) => {
const widthPercentage = toPercent(item.value);
const barWidth = Math.max(20, widthPercentage); // Minimum 20% width
return (
<div key={index} className="flex items-center justify-center w-full">
<div className="flex items-center gap-10 w-full">
{/* Right side label (RTL) */}
<div className="text-lg text-white font-thin min-w-[max-content] text-center">
{item.label}
</div>
{/* Centered bar with equal side spacers so that numbers align on a vertical axis */}
<div className="flex items-center w-full">
{/* Left spacer */}
<div style={{ width: `${(100 - barWidth) / 2}%` }} />
{/* Bar */}
<div
className="bg-[#3BC47A] h-8 rounded-2xl flex items-center justify-center text-lg relative"
style={{ width: `${barWidth}%` }}
>
<span className="text-gray-900 text-[#3F415A] font-semibold">
{item.value.toLocaleString('fa-IR')}
</span>
</div>
{/* Right spacer */}
<div style={{ width: `${(100 - barWidth) / 2}%` }} />
</div>
</div>
</div>
);
})}
</div>
{/* End Process Line */}
<div className="flex items-center w-full gap-10 mt-6">
<div className="text-lg text-gray-600 min-w-[max-content]">انتها فرآیند</div>
<div className="flex items-center w-full gap-4">
{(() => {
const lastValue = data[data.length - 1]?.value ?? 0;
const percent = toPercent(lastValue);
return (
<div style={{ width: `${percent}%` }} className={`mx-auto h-0.5 bg-gray-600 relative ${percent === 0 ? "hidden" : ""}`}>
<div className="text-2xl text-white absolute left-1/2 -translate-x-1/2 bottom-[-2.5rem] -translate-y-1">{formatNumber(percent)}%</div>
<div className="absolute -top-1 left-0 w-1 h-3 bg-gray-600"></div>
<div className="absolute -top-1 right-0 w-1 h-3 bg-gray-600"></div>
</div>
);
})()}
</div>
</div>
</div>
</div>
);
}