Files
stats/apps/dashboard/src/components/report/funnel/Funnel.tsx
Carl-Gerhard Lindesvärd a1fa48da75 dashboard: add dark mode
2024-03-28 06:47:04 +01:00

176 lines
5.5 KiB
TypeScript

'use client';
import type { RouterOutputs } from '@/app/_trpc/client';
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from '@/components/ui/carousel';
import { cn } from '@/utils/cn';
import { round } from '@/utils/math';
import { ArrowRight, ArrowRightIcon } from 'lucide-react';
import { useChartContext } from '../chart/ChartProvider';
function FunnelChart({ from, to }: { from: number; to: number }) {
const fromY = 100 - from;
const toY = 100 - to;
const steps = [
`M0,${fromY}`,
'L0,100',
'L100,100',
`L100,${toY}`,
`L0,${fromY}`,
];
return (
<svg viewBox="0 0 100 100">
<defs>
<linearGradient
id="blue"
x1="50"
y1="100"
x2="50"
y2="0"
gradientUnits="userSpaceOnUse"
>
{/* bottom */}
<stop offset="0%" stop-color="#2564eb" />
{/* top */}
<stop offset="100%" stop-color="#2564eb" />
</linearGradient>
<linearGradient
id="red"
x1="50"
y1="100"
x2="50"
y2="0"
gradientUnits="userSpaceOnUse"
>
{/* bottom */}
<stop offset="0%" stop-color="#f87171" />
{/* top */}
<stop offset="100%" stop-color="#fca5a5" />
</linearGradient>
</defs>
<rect
x="0"
y={fromY}
width="100"
height="100"
fill="url(#red)"
fillOpacity={0.2}
/>
<path d={steps.join(' ')} fill="url(#blue)" />
</svg>
);
}
function getDropoffColor(value: number) {
if (value > 80) {
return 'text-red-600';
}
if (value > 50) {
return 'text-orange-600';
}
if (value > 30) {
return 'text-yellow-600';
}
return 'text-green-600';
}
export function FunnelSteps({
steps,
totalSessions,
}: RouterOutputs['chart']['funnel']) {
const { editMode } = useChartContext();
return (
<Carousel className="w-full" opts={{ loop: false, dragFree: true }}>
<CarouselContent>
<CarouselItem className={'flex-[0_0_0] pl-3'} />
{steps.map((step, index, list) => {
const finalStep = index === list.length - 1;
return (
<CarouselItem
className={cn(
'max-w-full flex-[0_0_250px] p-0 px-1',
editMode && 'flex-[0_0_320px]'
)}
key={step.event.id}
>
<div className="card divide-y divide-border bg-background">
<div className="p-4">
<p className="text-muted-foreground">Step {index + 1}</p>
<h3 className="font-bold">
{step.event.displayName || step.event.name}
</h3>
</div>
<div className="relative aspect-square">
<FunnelChart from={step.prevPercent} to={step.percent} />
<div className="absolute left-0 right-0 top-0 flex flex-col bg-background/40 p-4">
<div className="font-medium uppercase text-muted-foreground">
Sessions
</div>
<div className="flex items-center text-3xl font-bold uppercase">
<span className="text-muted-foreground">
{step.before}
</span>
<ArrowRightIcon size={16} className="mx-2" />
<span>{step.current}</span>
</div>
{index !== 0 && (
<>
<div className="text-muted-foreground">
{step.current} of {totalSessions} (
{round(step.percent, 1)}%)
</div>
</>
)}
</div>
</div>
{finalStep ? (
<div className={cn('flex flex-col items-center p-4')}>
<div className="text-xs font-medium uppercase">
Conversion
</div>
<div
className={cn(
'text-3xl font-bold uppercase',
getDropoffColor(step.dropoff.percent)
)}
>
{round(step.percent, 1)}%
</div>
<div className="mt-0 text-sm font-medium uppercase text-muted-foreground">
Converted {step.current} of {totalSessions} sessions
</div>
</div>
) : (
<div className={cn('flex flex-col items-center p-4')}>
<div className="text-xs font-medium uppercase">Dropoff</div>
<div
className={cn(
'text-3xl font-bold uppercase',
getDropoffColor(step.dropoff.percent)
)}
>
{round(step.dropoff.percent, 1)}%
</div>
<div className="mt-0 text-sm font-medium uppercase text-muted-foreground">
Lost {step.dropoff.count} sessions
</div>
</div>
)}
</div>
</CarouselItem>
);
})}
<CarouselItem className={'flex-[0_0_0px] pl-3'} />
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
);
}