feat: add booking id & booking link columns to /insights/routing (#19697)

* feat: add booking id & booking link columns to /insights/routing

* update style

* update i18n

* update i18n keys

* update host

* useCopy instead of raw navigator.clipboard
This commit is contained in:
Eunjae Lee
2025-03-06 10:24:50 +00:00
committed by GitHub
parent 0565e23818
commit d2bc81758c
4 changed files with 93 additions and 18 deletions
@@ -3001,5 +3001,7 @@
"ooo_team_redirect_infinite_not_allowed": "There is already a reverse forwarding between these users.",
"previous_ooo_empty_title": "No previous OOO found",
"previous": "Previous",
"uid": "UID",
"link": "Link",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
+7 -3
View File
@@ -30,18 +30,22 @@ export type DataTableContextType = {
export const DataTableContext = createContext<DataTableContextType | null>(null);
const DEFAULT_ACTIVE_FILTERS: ActiveFilter[] = [];
const DEFAULT_SORTING: SortingState = [];
const DEFAULT_COLUMN_VISIBILITY: VisibilityState = {};
export function DataTableProvider({ children }: { children: React.ReactNode }) {
const [activeFilters, setActiveFilters] = useQueryState(
"activeFilters",
parseAsArrayOf(parseAsJson(ZActiveFilter.parse)).withDefault([])
parseAsArrayOf(parseAsJson(ZActiveFilter.parse)).withDefault(DEFAULT_ACTIVE_FILTERS)
);
const [sorting, setSorting] = useQueryState(
"sorting",
parseAsArrayOf(parseAsJson(ZSorting.parse)).withDefault([])
parseAsArrayOf(parseAsJson(ZSorting.parse)).withDefault(DEFAULT_SORTING)
);
const [columnVisibility, setColumnVisibility] = useQueryState<VisibilityState>(
"cols",
parseAsJson(ZColumnVisibility.parse).withDefault({})
parseAsJson(ZColumnVisibility.parse).withDefault(DEFAULT_COLUMN_VISIBILITY)
);
const clearAll = useCallback(
@@ -6,9 +6,11 @@ import { z } from "zod";
import dayjs from "@calcom/dayjs";
import { dataTableFilter, ColumnFilterType } from "@calcom/features/data-table";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { useCopy } from "@calcom/lib/hooks/useCopy";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { RoutingFormFieldType } from "@calcom/routing-forms/lib/FieldTypes";
import { Badge } from "@calcom/ui";
import { Badge, Icon } from "@calcom/ui";
import { BookedByCell } from "../components/BookedByCell";
import { BookingAtCell } from "../components/BookingAtCell";
@@ -68,6 +70,32 @@ export const useInsightsColumns = ({
return dataTableFilter(cellValue, filterValue);
},
}),
columnHelper.accessor("bookingUid", {
id: "bookingUid",
header: t("uid"),
size: 100,
enableColumnFilter: false,
enableSorting: false,
cell: (info) => {
const bookingUid = info.getValue();
if (!bookingUid) return null;
return <CopyButton label={bookingUid} value={bookingUid} />;
},
}),
columnHelper.accessor("bookingUid", {
id: "bookingLink",
header: t("link"),
size: 100,
enableColumnFilter: false,
enableSorting: false,
cell: (info) => {
const bookingUid = info.getValue();
if (!bookingUid) return null;
const bookingUrl = `${WEBAPP_URL}/booking/${bookingUid}`;
const displayedUrl = bookingUrl.replace(/^https?:\/\//, "");
return <CopyButton label={displayedUrl} value={bookingUrl} />;
},
}),
columnHelper.accessor("bookingAttendees", {
id: "bookingAttendees",
header: t("routing_form_insights_booked_by"),
@@ -225,3 +253,29 @@ export const useInsightsColumns = ({
];
}, [isHeadersSuccess, headers]);
};
function CopyButton({ label, value }: { label: string; value: string }) {
const { copyToClipboard, isCopied } = useCopy();
const { t } = useLocale();
return (
<button
className="flex w-full items-center gap-1 overflow-hidden"
title={value}
onClick={() => {
copyToClipboard(value);
}}>
{!isCopied && (
<>
<span className="truncate">{label}</span>
<Icon name="clipboard" className="shrink-0" size={14} />
</>
)}
{isCopied && (
<>
<span className="grow truncate text-left">{t("copied")}</span>
<Icon name="check" className="shrink-0" size={14} />
</>
)}
</button>
);
}
@@ -27,22 +27,37 @@ export const useInsightsFacetedUniqueValues = ({
teamId: number | undefined;
isAll: boolean;
}) => {
const { data: forms } = trpc.viewer.insights.getRoutingFormsForFilters.useQuery({
userId,
teamId,
isAll,
});
const { data: forms } = trpc.viewer.insights.getRoutingFormsForFilters.useQuery(
{
userId,
teamId,
isAll,
},
{
refetchOnWindowFocus: false,
}
);
const { data: users } = trpc.viewer.insights.userList.useQuery({
teamId,
isAll,
});
const { data: users } = trpc.viewer.insights.userList.useQuery(
{
teamId,
isAll,
},
{
refetchOnWindowFocus: false,
}
);
const { data: eventTypes } = trpc.viewer.insights.eventTypeList.useQuery({
teamId,
userId,
isAll,
});
const { data: eventTypes } = trpc.viewer.insights.eventTypeList.useQuery(
{
teamId,
userId,
isAll,
},
{
refetchOnWindowFocus: false,
}
);
return useCallback(
(_: Table<any>, columnId: string) => (): Map<FacetedValue, number> => {