diff --git a/apps/web/modules/insights/insights-routing-view.tsx b/apps/web/modules/insights/insights-routing-view.tsx index cddc6f97d9..87f1c4be5f 100644 --- a/apps/web/modules/insights/insights-routing-view.tsx +++ b/apps/web/modules/insights/insights-routing-view.tsx @@ -4,6 +4,7 @@ import { FailedBookingsByField, RoutingFormResponsesTable, RoutingKPICards, + RoutedToPerPeriod, } from "@calcom/features/insights/components"; import { FiltersProvider } from "@calcom/features/insights/context/FiltersProvider"; import { RoutingInsightsFilters } from "@calcom/features/insights/filters/routing/FilterBar"; @@ -30,6 +31,8 @@ export default function InsightsPage() { )} + + diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json index 5db1d376cb..70cd3e6bd9 100644 --- a/apps/web/public/static/locales/en/common.json +++ b/apps/web/public/static/locales/en/common.json @@ -2824,6 +2824,11 @@ "routing_form_insights_booking_status": "Booking Status", "routing_form_insights_booking_at": "Booking At", "routing_form_insights_submitted_at": "Submitted At", + "total_bookings_per_period":"Total Bookings per period", + "routed_to_per_period":"Routed to per period", + "per_day": "Per day", + "per_week": "Per week", + "per_month": "Per month", "routing_form_insights_assignment_reason":"Assignment Reason", "access_denied": "Access Denied", "salesforce_route_to_owner": "Contact owner will be the Round Robin host if available", diff --git a/packages/features/data-table/components/DataTableSkeleton.tsx b/packages/features/data-table/components/DataTableSkeleton.tsx new file mode 100644 index 0000000000..7a148d392b --- /dev/null +++ b/packages/features/data-table/components/DataTableSkeleton.tsx @@ -0,0 +1,48 @@ +import { TableNew, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@calcom/ui"; + +interface DataTableSkeletonProps { + columns: number; + rows?: number; + columnWidths?: number[]; +} + +export function DataTableSkeleton({ columns, rows = 10, columnWidths = [] }: DataTableSkeletonProps) { + return ( +
+
+ + + + {[...Array(columns)].map((_, index) => ( + +
+ + ))} + + + + {[...Array(rows)].map((_, rowIndex) => ( + + {[...Array(columns)].map((_, colIndex) => ( + +
+ + ))} + + ))} + + +
+
+ ); +} diff --git a/packages/features/data-table/components/index.ts b/packages/features/data-table/components/index.ts index 4b60a21365..059d7cc6a4 100644 --- a/packages/features/data-table/components/index.ts +++ b/packages/features/data-table/components/index.ts @@ -4,4 +4,5 @@ export { DataTablePagination } from "./DataTablePagination"; export { DataTableFilters } from "./filters"; export { useFetchMoreOnBottomReached } from "./useFetchMoreOnBottomReached"; export { DataTable } from "./DataTable"; +export { DataTableSkeleton } from "./DataTableSkeleton"; export type { DataTableProps } from "./DataTable"; diff --git a/packages/features/insights/components/RoutedToPerPeriod.tsx b/packages/features/insights/components/RoutedToPerPeriod.tsx new file mode 100644 index 0000000000..c73c39a0b3 --- /dev/null +++ b/packages/features/insights/components/RoutedToPerPeriod.tsx @@ -0,0 +1,454 @@ +import type { TFunction } from "next-i18next"; +import { useQueryState } from "nuqs"; +import { type ReactNode, useMemo, useRef, useState } from "react"; + +import type { Dayjs } from "@calcom/dayjs"; +import { DataTableSkeleton } from "@calcom/features/data-table"; +import classNames from "@calcom/lib/classNames"; +import { downloadAsCsv } from "@calcom/lib/csvUtils"; +import { useInViewObserver } from "@calcom/lib/hooks/useInViewObserver"; +import { useLocale } from "@calcom/lib/hooks/useLocale"; +import { trpc } from "@calcom/trpc"; +import { + Avatar, + ToggleGroup, + Badge, + Tooltip, + HoverCard, + HoverCardContent, + HoverCardTrigger, + Input, + Button, +} from "@calcom/ui"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@calcom/ui/components/table/TableNew"; + +import { useFilterContext } from "../context/provider"; + +interface DownloadButtonProps { + teamId?: number; + isAll?: boolean; + routingFormId?: string; + dateRange: [Dayjs, Dayjs, string | null]; + selectedPeriod: string; + searchQuery?: string; +} + +function DownloadButton({ + teamId, + isAll, + routingFormId, + dateRange, + selectedPeriod, + searchQuery, +}: DownloadButtonProps) { + const [isDownloading, setIsDownloading] = useState(false); + const utils = trpc.useContext(); + const { t } = useLocale(); + + const handleDownload = async (e: React.MouseEvent) => { + e.preventDefault(); // Prevent default form submission + + try { + const result = await utils.viewer.insights.routedToPerPeriodCsv.fetch({ + teamId: teamId ?? undefined, + startDate: dateRange[0]?.toISOString() ?? "", + endDate: dateRange[1]?.toISOString() ?? "", + period: selectedPeriod as "perDay" | "perWeek" | "perMonth", + isAll: !!isAll, + routingFormId: routingFormId ?? undefined, + searchQuery: searchQuery || undefined, + }); + + if (!result?.data) { + throw new Error("No data received"); + } + + downloadAsCsv(result.data, result.filename || "routing-data.csv"); + } catch (error) { + console.error("Download failed:", error); + } finally { + setIsDownloading(false); + } + }; + + return ( +