* feat: add Recent No-Show Guests chart to insights page - Add RecentNoShowGuestsChart component with ChartCard wrapper - Add tRPC handler for recentNoShowGuests query - Add getRecentNoShowGuests method to InsightsBookingBaseService - Display guest name, booking time, event type, and copy email button - Filter for bookings where ALL attendees are no-shows - Add translation strings for new UI elements - Integrate chart into insights view grid layout Co-Authored-By: eunjae@cal.com <hey@eunjae.dev> * re-order charts * clean up * feat: add optional tooltip functionality to PanelCard - Add titleTooltip prop to PanelCard component with InfoBadge - Pass through titleTooltip prop in ChartCard - Add tooltip to RecentNoShowGuestsChart explaining complete no-show filtering - Add translation string for tooltip explanation Co-Authored-By: eunjae@cal.com <hey@eunjae.dev> * style adjustment * update style * address feedback --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
96 lines
2.4 KiB
TypeScript
96 lines
2.4 KiB
TypeScript
"use client";
|
|
|
|
import { Fragment, type ReactNode } from "react";
|
|
|
|
import classNames from "@calcom/ui/classNames";
|
|
import { PanelCard } from "@calcom/ui/components/card";
|
|
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,
|
|
className,
|
|
titleTooltip,
|
|
}: {
|
|
title: string | ReactNode;
|
|
subtitle?: string;
|
|
cta?: { label: string; onClick: () => void };
|
|
legend?: Array<LegendItem>;
|
|
legendSize?: LegendSize;
|
|
className?: string;
|
|
titleTooltip?: string;
|
|
children: ReactNode;
|
|
}) {
|
|
const legendComponent = legend && legend.length > 0 ? <Legend items={legend} size={legendSize} /> : null;
|
|
|
|
return (
|
|
<PanelCard
|
|
title={title}
|
|
subtitle={subtitle}
|
|
cta={cta}
|
|
headerContent={legendComponent}
|
|
className={className}
|
|
titleTooltip={titleTooltip}>
|
|
{children}
|
|
</PanelCard>
|
|
);
|
|
}
|
|
|
|
export function ChartCardItem({
|
|
count,
|
|
className,
|
|
children,
|
|
}: {
|
|
count?: number | string;
|
|
className?: string;
|
|
children: ReactNode;
|
|
}) {
|
|
return (
|
|
<div
|
|
className={classNames(
|
|
"text-default border-muted flex items-center justify-between border-b px-3 py-3.5 last:border-b-0",
|
|
className
|
|
)}>
|
|
<div className="grow text-sm font-medium">{children}</div>
|
|
{count !== undefined && <div className="text-sm font-medium">{count}</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-0.5"
|
|
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 py-0.5 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>
|
|
);
|
|
}
|