* feat: add Drop-off funnel chart for /insights/routing * convert insights services to use kysely * WIP * implement drop-off data WIP * updates * revert services back to Prisma * do not expose findMany * WIP * revert usage * fix type errors * updates * clean up * update playground * update styles * support columnFilters * update tooltip * add skeleton * remove dynamic loading * avoid using Prisma.raw * update condition for user scope * Update packages/features/insights/components/RoutingFunnelContent.tsx Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> * playground fix * fix type error * update playground * update styles * implement subtitle to ChartCard * revert insights booking service * update border * update ChartCard style * style updates * convert the playground to a client component * clean up * apply feedback * fix tests * fix unit test * hide scrollbar --------- Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
79 lines
2.4 KiB
TypeScript
79 lines
2.4 KiB
TypeScript
import { Fragment } from "react";
|
|
|
|
import classNames from "@calcom/ui/classNames";
|
|
import { Button } from "@calcom/ui/components/button";
|
|
import { Tooltip } from "@calcom/ui/components/tooltip";
|
|
|
|
type LegendItem = {
|
|
label: string;
|
|
color: string; // hex format
|
|
};
|
|
|
|
export type LegendSize = "sm" | "default";
|
|
|
|
export function ChartCard({
|
|
title,
|
|
subtitle,
|
|
cta,
|
|
legend,
|
|
legendSize,
|
|
children,
|
|
}: {
|
|
title: string;
|
|
subtitle?: string;
|
|
cta?: { label: string; onClick: () => void };
|
|
legend?: Array<LegendItem>;
|
|
legendSize?: LegendSize;
|
|
children: React.ReactNode;
|
|
}) {
|
|
return (
|
|
<div className="bg-muted group relative flex w-full flex-col items-center rounded-2xl px-1 pb-1">
|
|
<div className="flex h-11 w-full shrink-0 items-center justify-between gap-2 px-4">
|
|
<h2 className="text-emphasis mr-4 shrink-0 text-sm font-semibold">{title}</h2>
|
|
<div className="no-scrollbar flex items-center gap-2 overflow-x-auto">
|
|
{legend && legend.length > 0 && <Legend items={legend} size={legendSize} />}
|
|
{cta && (
|
|
<Button className="shrink-0" color="secondary" onClick={cta.onClick}>
|
|
{cta.label}
|
|
</Button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
<div className="bg-default border-muted w-full grow gap-3 rounded-xl border">
|
|
{subtitle && (
|
|
<h3 className="text-subtle border-muted border-b p-3 text-sm font-medium leading-none">
|
|
{subtitle}
|
|
</h3>
|
|
)}
|
|
{children}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function Legend({ items, size = "default" }: { items: LegendItem[]; size?: LegendSize }) {
|
|
return (
|
|
<div className="bg-default flex items-center gap-2 rounded-lg px-1.5 py-1">
|
|
{items.map((item, index) => (
|
|
<Fragment key={item.label}>
|
|
<div
|
|
className="relative flex items-center gap-2 rounded-md px-1.5 py-1"
|
|
style={{ backgroundColor: `${item.color}33` }}>
|
|
<div className="h-2 w-2 rounded-full" style={{ backgroundColor: item.color }} />
|
|
<Tooltip content={item.label}>
|
|
<p
|
|
className={classNames(
|
|
"text-default truncate text-sm font-medium leading-none",
|
|
size === "sm" ? "w-16" : ""
|
|
)}>
|
|
{item.label}
|
|
</p>
|
|
</Tooltip>
|
|
</div>
|
|
{index < items.length - 1 && <div className="bg-muted h-5 w-[1px]" />}
|
|
</Fragment>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|