inogen/app/components/ui/funnel-chart.tsx

96 lines
3.9 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-sm px-4 font-semibold text-white mb-4 py-2 text-right border-b-2 border-gray-400/20">
{title}
</h3>
)}
<div className="flex px-4 flex-col items-center gap-2 space-y-2">
{/* Start Process Line */}
<div className="flex items-center w-full gap-10 mt-6 px-4">
<div className="text-sm font-normal text-[#5F6284] 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-base text-white font-semibold 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="grid grid-cols-[6rem_1fr] gap-2 w-full">
<div className="text-sm font-light text-white cols-start-1 justify-self-start font-thin min-w-[max-content] text-center">
{item.label}
</div>
<div className="flex items-center gap-10 w-full cols-start-2 flex items-center justify-center w-full">
<div className="flex items-center w-full">
<div style={{ width: `${(100 - barWidth) / 2}%` }} />
<div
className="bg-[#3BC47A] h-8 rounded-2xl flex items-center justify-center text-lg relative"
style={{ width: `${barWidth}%` }}
>
<span className="text-pr-gray text-base font-semibold">
{item.value.toLocaleString('fa-IR')}
</span>
</div>
<div style={{ width: `${(100 - barWidth) / 2}%` }} />
</div>
</div>
</div>
);
})}
</div>
{/* End Process Line */}
<div className="flex items-center w-full gap-10 px-4">
<div className="text-sm text-[#5F6284] 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-base font-semibold 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>
);
}