diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json index acd15b9251..ecda2c17dd 100644 --- a/apps/web/public/static/locales/en/common.json +++ b/apps/web/public/static/locales/en/common.json @@ -1566,6 +1566,7 @@ "workflow_turned_on_successfully": "{{workflowName}} workflow turned {{offOn}} successfully", "download_responses": "Download responses", "download_responses_description": "Download all responses to your form in CSV format.", + "view_responses": "View Responses", "download": "Download", "downloading": "Downloading", "download_recording": "Download Recording", diff --git a/packages/app-store/routing-forms/components/FormActions.tsx b/packages/app-store/routing-forms/components/FormActions.tsx index 94a959aaf8..77f6f3ecf0 100644 --- a/packages/app-store/routing-forms/components/FormActions.tsx +++ b/packages/app-store/routing-forms/components/FormActions.tsx @@ -4,6 +4,8 @@ import { useForm } from "react-hook-form"; import { v4 as uuidv4 } from "uuid"; import { Dialog } from "@calcom/features/components/controlled-dialog"; +import { dataTableQueryParamsSerializer } from "@calcom/features/data-table/lib/serializers"; +import { ColumnFilterType } from "@calcom/features/data-table/lib/types"; import { useOrgBranding } from "@calcom/features/ee/organizations/context/provider"; import { RoutingFormEmbedButton, RoutingFormEmbedDialog } from "@calcom/features/embed/RoutingFormEmbed"; import { EmbedDialogProvider } from "@calcom/features/embed/lib/hooks/useEmbedDialogCtx"; @@ -400,6 +402,7 @@ type FormActionType = | "duplicate" | "download" | "copyRedirectUrl" + | "viewResponses" | "create"; type FormActionProps = { @@ -484,6 +487,17 @@ export const FormAction = forwardRef(function FormAction setNewFormDialogState?.({ action: "new", target: "" }), }, + viewResponses: { + href: `/insights/routing${ + routingForm?.id + ? dataTableQueryParamsSerializer({ + activeFilters: [ + { f: "formId", v: { type: ColumnFilterType.SINGLE_SELECT, data: routingForm.id } }, + ], + }) + : "" + }`, + }, copyRedirectUrl: { onClick: () => { navigator.clipboard.writeText(redirectUrl); diff --git a/packages/app-store/routing-forms/components/_components/Header.tsx b/packages/app-store/routing-forms/components/_components/Header.tsx index 32de4c49f6..f4ce1c7150 100644 --- a/packages/app-store/routing-forms/components/_components/Header.tsx +++ b/packages/app-store/routing-forms/components/_components/Header.tsx @@ -141,6 +141,18 @@ const Actions = ({ StartIcon="download"> {t("download_responses")} + {form?.id && ( + + {t("view_responses")} + + )} (null); -const DEFAULT_ACTIVE_FILTERS: ActiveFilters = []; -const DEFAULT_SORTING: SortingState = []; -const DEFAULT_COLUMN_VISIBILITY: VisibilityState = {}; -const DEFAULT_COLUMN_SIZING: ColumnSizingState = {}; -const DEFAULT_PAGE_SIZE = 10; - interface DataTableProviderProps { useSegments?: UseSegments; tableIdentifier?: string; @@ -83,26 +84,17 @@ export function DataTableProvider({ ctaContainerClassName = CTA_CONTAINER_CLASS_NAME, }: DataTableProviderProps) { const filterToOpen = useRef(undefined); - const [activeFilters, setActiveFilters] = useQueryState( - "activeFilters", - parseAsArrayOf(parseAsJson(ZActiveFilter.parse)).withDefault(DEFAULT_ACTIVE_FILTERS) - ); - const [sorting, setSorting] = useQueryState( - "sorting", - parseAsArrayOf(parseAsJson(ZSorting.parse)).withDefault(DEFAULT_SORTING) - ); + const [activeFilters, setActiveFilters] = useQueryState("activeFilters", activeFiltersParser); + const [sorting, setSorting] = useQueryState("sorting", sortingParser); const [columnVisibility, setColumnVisibility] = useQueryState( "cols", - parseAsJson(ZColumnVisibility.parse).withDefault(DEFAULT_COLUMN_VISIBILITY) + columnVisibilityParser ); - const [columnSizing, setColumnSizing] = useQueryState( - "widths", - parseAsJson(ZColumnSizing.parse).withDefault(DEFAULT_COLUMN_SIZING) - ); - const [segmentId, setSegmentId] = useQueryState("segment", parseAsInteger.withDefault(-1)); - const [pageIndex, setPageIndex] = useQueryState("page", parseAsInteger.withDefault(0)); - const [pageSize, setPageSize] = useQueryState("size", parseAsInteger.withDefault(defaultPageSize)); - const [searchTerm, setSearchTerm] = useQueryState("q", parseAsString.withDefault("")); + const [columnSizing, setColumnSizing] = useQueryState("widths", columnSizingParser); + const [segmentId, setSegmentId] = useQueryState("segment", segmentIdParser); + const [pageIndex, setPageIndex] = useQueryState("page", pageIndexParser); + const [pageSize, setPageSize] = useQueryState("size", pageSizeParser); + const [searchTerm, setSearchTerm] = useQueryState("q", searchTermParser); const setDebouncedSearchTerm = useMemo( () => debounce((value: string | null) => setSearchTerm(value ? value.trim() : null), 500), diff --git a/packages/features/data-table/index.ts b/packages/features/data-table/index.ts index d2b8471a0f..a2dc6af694 100644 --- a/packages/features/data-table/index.ts +++ b/packages/features/data-table/index.ts @@ -1,6 +1,7 @@ export * from "./components"; export * from "./lib/types"; export * from "./lib/utils"; +export * from "./lib/serializers"; export * from "./DataTableProvider"; export * from "./hooks/useColumnResizing"; export * from "./hooks"; diff --git a/packages/features/data-table/lib/parsers.ts b/packages/features/data-table/lib/parsers.ts new file mode 100644 index 0000000000..de90cbf4dd --- /dev/null +++ b/packages/features/data-table/lib/parsers.ts @@ -0,0 +1,24 @@ +import type { SortingState, VisibilityState, ColumnSizingState } from "@tanstack/react-table"; +import { parseAsArrayOf, parseAsJson, parseAsInteger, parseAsString } from "nuqs"; + +import { ZActiveFilter, ZSorting, ZColumnVisibility, ZColumnSizing } from "./types"; +import type { ActiveFilters } from "./types"; + +const DEFAULT_ACTIVE_FILTERS: ActiveFilters = [] as ActiveFilters; +const DEFAULT_SORTING: SortingState = [] as SortingState; +const DEFAULT_COLUMN_VISIBILITY: VisibilityState = {}; +const DEFAULT_COLUMN_SIZING: ColumnSizingState = {}; +export const DEFAULT_PAGE_SIZE = 10; + +export const activeFiltersParser = parseAsArrayOf(parseAsJson(ZActiveFilter.parse)).withDefault( + DEFAULT_ACTIVE_FILTERS +); +export const sortingParser = parseAsArrayOf(parseAsJson(ZSorting.parse)).withDefault(DEFAULT_SORTING); +export const columnVisibilityParser = parseAsJson(ZColumnVisibility.parse).withDefault( + DEFAULT_COLUMN_VISIBILITY +); +export const columnSizingParser = parseAsJson(ZColumnSizing.parse).withDefault(DEFAULT_COLUMN_SIZING); +export const segmentIdParser = parseAsInteger.withDefault(-1); +export const pageIndexParser = parseAsInteger.withDefault(0); +export const pageSizeParser = parseAsInteger.withDefault(DEFAULT_PAGE_SIZE); +export const searchTermParser = parseAsString.withDefault(""); diff --git a/packages/features/data-table/lib/serializers.ts b/packages/features/data-table/lib/serializers.ts new file mode 100644 index 0000000000..bb17b2ac5d --- /dev/null +++ b/packages/features/data-table/lib/serializers.ts @@ -0,0 +1,28 @@ +import { createSerializer } from "nuqs"; + +import { + activeFiltersParser, + sortingParser, + columnVisibilityParser, + columnSizingParser, + segmentIdParser, + pageIndexParser, + pageSizeParser, + searchTermParser, +} from "./parsers"; + +/** + * Serializer for data table query parameters + * Used to generate URL query parameters for data tables + * Based on the parameters used in DataTableProvider + */ +export const dataTableQueryParamsSerializer = createSerializer({ + activeFilters: activeFiltersParser, + sorting: sortingParser, + cols: columnVisibilityParser, + widths: columnSizingParser, + segment: segmentIdParser, + page: pageIndexParser, + size: pageSizeParser, + q: searchTermParser, +});