feat: add 'View Responses' menu item to Routing Form dropdown (#21114)
* feat: add 'View Responses' menu item to Routing Form dropdown Co-Authored-By: eunjae@cal.com <eunjae@cal.com> * fix: use nuqs createSerializer for URL generation Co-Authored-By: eunjae@cal.com <eunjae@cal.com> * refactor: move serializer to data-table lib and use proper type constants Co-Authored-By: eunjae@cal.com <eunjae@cal.com> * fix: type errors in serializers and FormActions Co-Authored-By: eunjae@cal.com <eunjae@cal.com> * fix: use parseAsArrayOf with parseAsJson for proper nuqs serialization Co-Authored-By: eunjae@cal.com <eunjae@cal.com> * fix: update to use dataTableQueryParamsSerializer with proper nuqs pattern Co-Authored-By: eunjae@cal.com <eunjae@cal.com> * refactor: extract parsers from DataTableProvider to be reused in serializers Co-Authored-By: eunjae@cal.com <eunjae@cal.com> * fix: remove deprecated activeFiltersSerializer and use empty array instead of null Co-Authored-By: eunjae@cal.com <eunjae@cal.com> * fix: only show View Responses menu when form id and teamId exist Co-Authored-By: eunjae@cal.com <eunjae@cal.com> * remove teamId condition * refactor: improve viewResponses URL generation with cleaner conditional Co-Authored-By: eunjae@cal.com <eunjae@cal.com> * Update packages/features/data-table/lib/parsers.ts * refactor: make constants in parsers.ts non-exported Co-Authored-By: eunjae@cal.com <eunjae@cal.com> * fix: add explicit type annotations to constants in parsers.ts Co-Authored-By: eunjae@cal.com <eunjae@cal.com> * fix: add explicit type annotations and export DEFAULT_PAGE_SIZE in parsers.ts Co-Authored-By: eunjae@cal.com <eunjae@cal.com> * fix: add explicit type assertions to DEFAULT_ACTIVE_FILTERS and DEFAULT_SORTING Co-Authored-By: eunjae@cal.com <eunjae@cal.com> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: eunjae@cal.com <eunjae@cal.com> Co-authored-by: Eunjae Lee <hey@eunjae.dev> Co-authored-by: Benny Joo <sldisek783@gmail.com>
This commit is contained in:
7 files changed
+100
-28
No files matched your search
@@ -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",
|
||||
|
||||
@@ -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<T> = {
|
||||
@@ -484,6 +487,17 @@ export const FormAction = forwardRef(function FormAction<T extends typeof Button
|
||||
create: {
|
||||
onClick: () => 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);
|
||||
|
||||
@@ -141,6 +141,18 @@ const Actions = ({
|
||||
StartIcon="download">
|
||||
{t("download_responses")}
|
||||
</FormAction>
|
||||
{form?.id && (
|
||||
<FormAction
|
||||
action="viewResponses"
|
||||
routingForm={form}
|
||||
className="w-full"
|
||||
color="minimal"
|
||||
type="button"
|
||||
data-testid="view-responses"
|
||||
StartIcon="eye">
|
||||
{t("view_responses")}
|
||||
</FormAction>
|
||||
)}
|
||||
<FormAction
|
||||
action="embed"
|
||||
routingForm={form}
|
||||
|
||||
@@ -4,16 +4,23 @@ import type { SortingState, OnChangeFn, VisibilityState, ColumnSizingState } fro
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import debounce from "lodash/debounce";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { useQueryState, parseAsArrayOf, parseAsJson, parseAsInteger, parseAsString } from "nuqs";
|
||||
import { useQueryState } from "nuqs";
|
||||
import { createContext, useCallback, useEffect, useRef, useMemo } from "react";
|
||||
|
||||
import { useSegmentsNoop } from "./hooks/useSegmentsNoop";
|
||||
import {
|
||||
activeFiltersParser,
|
||||
sortingParser,
|
||||
columnVisibilityParser,
|
||||
columnSizingParser,
|
||||
segmentIdParser,
|
||||
pageIndexParser,
|
||||
pageSizeParser,
|
||||
searchTermParser,
|
||||
DEFAULT_PAGE_SIZE,
|
||||
} from "./lib/parsers";
|
||||
import {
|
||||
type FilterValue,
|
||||
ZSorting,
|
||||
ZColumnVisibility,
|
||||
ZActiveFilter,
|
||||
ZColumnSizing,
|
||||
type FilterSegmentOutput,
|
||||
type ActiveFilters,
|
||||
type UseSegments,
|
||||
@@ -61,12 +68,6 @@ export type DataTableContextType = {
|
||||
|
||||
export const DataTableContext = createContext<DataTableContextType | null>(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<string | undefined>(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<VisibilityState>(
|
||||
"cols",
|
||||
parseAsJson(ZColumnVisibility.parse).withDefault(DEFAULT_COLUMN_VISIBILITY)
|
||||
columnVisibilityParser
|
||||
);
|
||||
const [columnSizing, setColumnSizing] = useQueryState<ColumnSizingState>(
|
||||
"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<ColumnSizingState>("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),
|
||||
|
||||
@@ -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";
|
||||
@@ -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("");
|
||||
@@ -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,
|
||||
});
|
||||
Reference in new issue
Block a user