* 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>
83 lines
2.8 KiB
TypeScript
83 lines
2.8 KiB
TypeScript
"use client";
|
|
|
|
import { useCopy } from "@calcom/lib/hooks/useCopy";
|
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
|
import { trpc } from "@calcom/trpc";
|
|
import { Button } from "@calcom/ui/components/button";
|
|
import { showToast } from "@calcom/ui/components/toast";
|
|
|
|
import { useInsightsBookingParameters } from "../../hooks/useInsightsBookingParameters";
|
|
import { ChartCard, ChartCardItem } from "../ChartCard";
|
|
import { LoadingInsight } from "../LoadingInsights";
|
|
|
|
export const RecentNoShowGuestsChart = () => {
|
|
const { t } = useLocale();
|
|
const { copyToClipboard, isCopied } = useCopy();
|
|
const insightsBookingParams = useInsightsBookingParameters();
|
|
const timeZone = insightsBookingParams.timeZone;
|
|
|
|
const { data, isSuccess, isPending } = trpc.viewer.insights.recentNoShowGuests.useQuery(
|
|
insightsBookingParams,
|
|
{
|
|
staleTime: 180000,
|
|
refetchOnWindowFocus: false,
|
|
trpc: {
|
|
context: { skipBatch: true },
|
|
},
|
|
}
|
|
);
|
|
|
|
if (isPending) return <LoadingInsight />;
|
|
|
|
if (!isSuccess || !data) return null;
|
|
|
|
const handleCopyEmail = (email: string) => {
|
|
copyToClipboard(email);
|
|
showToast(t("email_copied"), "success");
|
|
};
|
|
|
|
return (
|
|
<ChartCard
|
|
title={t("recent_no_show_guests")}
|
|
titleTooltip={t("recent_no_show_guests_tooltip")}
|
|
className="h-full">
|
|
<div className="sm:max-h-[30.6rem] sm:overflow-y-auto">
|
|
{data.map((item) => (
|
|
<ChartCardItem key={item.bookingId}>
|
|
<div className="flex w-full items-center justify-between">
|
|
<div className="flex gap-2">
|
|
<div className="bg-subtle h-16 w-[2px] shrink-0 rounded-sm" />
|
|
<div className="flex flex-col space-y-1">
|
|
<p className="text-sm font-medium">{item.guestName}</p>
|
|
<div className="text-subtle text-sm leading-tight">
|
|
<p>{item.eventTypeName}</p>
|
|
<p>
|
|
{Intl.DateTimeFormat(undefined, {
|
|
timeZone,
|
|
dateStyle: "medium",
|
|
timeStyle: "short",
|
|
}).format(new Date(item.startTime))}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<Button
|
|
color="minimal"
|
|
size="sm"
|
|
StartIcon={isCopied ? "clipboard-check" : "clipboard"}
|
|
onClick={() => handleCopyEmail(item.guestEmail)}>
|
|
{!isCopied ? t("email") : t("copied")}
|
|
</Button>
|
|
</div>
|
|
</ChartCardItem>
|
|
))}
|
|
</div>
|
|
{data.length === 0 && (
|
|
<div className="flex h-60 text-center">
|
|
<p className="m-auto text-sm font-light">{t("insights_no_data_found_for_filter")}</p>
|
|
</div>
|
|
)}
|
|
</ChartCard>
|
|
);
|
|
};
|