inogen/app/components/ui/funnel-chart.tsx
saeed0920 ef2cf01495 add new page (#9)
Reviewed-on: https://git.pelekan.org/Saeed0920/inogen/pulls/9
Co-authored-by: saeed0920 <sd.eed1381@gmail.com>
Co-committed-by: saeed0920 <sd.eed1381@gmail.com>
2025-09-11 11:03:27 +03:30

96 lines
3.8 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="grid grid-cols-[6rem_1fr] gap-2 w-full">
<div className="text-lg 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-[#3F415A] 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">
<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>
);
}