Reviewed-on: https://git.pelekan.org/Saeed0920/inogen/pulls/14 Co-authored-by: saeed0920 <sd.eed1381@gmail.com> Co-committed-by: saeed0920 <sd.eed1381@gmail.com>
96 lines
3.9 KiB
TypeScript
96 lines
3.9 KiB
TypeScript
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>
|
||
);
|
||
}
|