diff --git a/apps/web/app/(use-page-wrapper)/apps/routing-forms/[...pages]/FormEdit.tsx b/apps/web/app/(use-page-wrapper)/apps/routing-forms/[...pages]/FormEdit.tsx index 0a8af241222b3d..f693a2f05df0ab 100644 --- a/apps/web/app/(use-page-wrapper)/apps/routing-forms/[...pages]/FormEdit.tsx +++ b/apps/web/app/(use-page-wrapper)/apps/routing-forms/[...pages]/FormEdit.tsx @@ -164,7 +164,9 @@ function Field({ "h-8 w-full justify-between text-left text-sm", !!router && "bg-subtle cursor-not-allowed" )}> - {defaultValue?.label || t("select_field_type")} + + {defaultValue?.label || t("select_field_type")} + @@ -201,7 +203,7 @@ function Field({ }} /> - {["select", "multiselect"].includes(fieldType) ? ( + {["select", "multiselect", "checkbox", "radio"].includes(fieldType) ? (
({ @@ -25,6 +24,9 @@ const assertCommonWidgetTypes = (config: any) => { expect(config.widgets).toHaveProperty("select"); expect(config.widgets).toHaveProperty("phone"); expect(config.widgets).toHaveProperty("email"); + expect(config.widgets).toHaveProperty("address"); + expect(config.widgets).toHaveProperty("url"); + expect(config.widgets).toHaveProperty("multiemail"); }; const assertSelectOperators = (config: any) => { @@ -95,7 +97,7 @@ describe("Query Builder Config", () => { ); // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore + // @ts-expect-error const jsonLogic = AttributesBaseConfig.operators.multiselect_some_in.jsonLogic( ["A"], "multiselect_some_in", @@ -122,7 +124,7 @@ describe("Query Builder Config", () => { it("should provide jsonlogic for between operator", () => { // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore + // @ts-expect-error const jsonLogic = AttributesBaseConfig.operators.between.jsonLogic( { var: "89ee81ae-953c-409b-aacc-700e1ce5ae20" }, "between", @@ -140,7 +142,7 @@ describe("Query Builder Config", () => { it("should provide jsonlogic for not_between operator", () => { // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore + // @ts-expect-error const jsonLogic = AttributesBaseConfig.operators.not_between.jsonLogic( { var: "89ee81ae-953c-409b-aacc-700e1ce5ae20" }, "not_between", diff --git a/packages/app-store/routing-forms/__tests__/uiConfig.test.ts b/packages/app-store/routing-forms/__tests__/uiConfig.test.ts index 2eac86afc6c1b5..19b0e60abb52d1 100644 --- a/packages/app-store/routing-forms/__tests__/uiConfig.test.ts +++ b/packages/app-store/routing-forms/__tests__/uiConfig.test.ts @@ -1,6 +1,5 @@ import type { Settings } from "react-awesome-query-builder"; -import { describe, it, vi, expect } from "vitest"; - +import { describe, expect, it, vi } from "vitest"; import { ConfigFor, withRaqbSettingsAndWidgets, @@ -14,6 +13,12 @@ vi.mock("../components/react-awesome-query-builder/widgets", () => ({ MultiSelectWidget: vi.fn(), SelectWidget: vi.fn(), NumberWidget: vi.fn(), + AddressWidget: vi.fn(), + URLWidget: vi.fn(), + MultiEmailWidget: vi.fn(), + CheckboxGroupWidget: vi.fn(), + RadioGroupWidget: vi.fn(), + BooleanWidget: vi.fn(), FieldSelect: vi.fn(), Conjs: vi.fn(), Button: vi.fn(), @@ -46,6 +51,24 @@ describe("uiConfig", () => { email: { type: "email", }, + address: { + type: "address", + }, + url: { + type: "url", + }, + multiemail: { + type: "multiemail", + }, + checkbox: { + type: "checkbox", + }, + radio: { + type: "radio", + }, + boolean: { + type: "boolean", + }, }, settings: {} as Settings, }; @@ -65,6 +88,12 @@ describe("uiConfig", () => { expect(result.widgets.select).toHaveProperty("factory"); expect(result.widgets.phone).toHaveProperty("factory"); expect(result.widgets.email).toHaveProperty("factory"); + expect(result.widgets.address).toHaveProperty("factory"); + expect(result.widgets.url).toHaveProperty("factory"); + expect(result.widgets.multiemail).toHaveProperty("factory"); + expect(result.widgets.checkbox).toHaveProperty("factory"); + expect(result.widgets.radio).toHaveProperty("factory"); + expect(result.widgets.boolean).toHaveProperty("factory"); }); it("should add render functions to settings", () => { diff --git a/packages/app-store/routing-forms/components/react-awesome-query-builder/config/BasicConfig.ts b/packages/app-store/routing-forms/components/react-awesome-query-builder/config/BasicConfig.ts index 21689593f9ecd3..fc832a4004ab73 100644 --- a/packages/app-store/routing-forms/components/react-awesome-query-builder/config/BasicConfig.ts +++ b/packages/app-store/routing-forms/components/react-awesome-query-builder/config/BasicConfig.ts @@ -1,10 +1,10 @@ // This is taken from "react-awesome-query-builder/lib/config/basic"; import type { Conjunction as RAQBConjunction, - Widget as RAQBWidget, - Type as RAQBType, - Settings as RAQBSettings, Operator as RAQBOperator, + Settings as RAQBSettings, + Type as RAQBType, + Widget as RAQBWidget, } from "react-awesome-query-builder"; export type Conjunction = RAQBConjunction; @@ -284,6 +284,30 @@ const widgets: WidgetsWithoutFactory = { valuePlaceholder: "Select values", toJS: (val: any) => val, }, + checkbox: { + type: "multiselect", + jsType: "array", + valueSrc: "value" as const, + valueLabel: "Values", + valuePlaceholder: "Select values", + toJS: (val: any) => val, + }, + radio: { + type: "select", + jsType: "string", + valueSrc: "value" as const, + valueLabel: "Value", + valuePlaceholder: "Select value", + toJS: (val: any) => val, + }, + boolean: { + type: "boolean", + jsType: "boolean", + valueSrc: "value" as const, + valueLabel: "Value", + valuePlaceholder: "", + toJS: (val: any) => val, + }, }; const types: Types = { @@ -388,6 +412,15 @@ const types: Types = { }, }, }, + boolean: { + defaultOperator: "equal", + mainWidget: "boolean", + widgets: { + boolean: { + operators: ["equal", "not_equal"], + }, + }, + }, // "!group": { // defaultOperator: "some", // mainWidget: "number", diff --git a/packages/app-store/routing-forms/components/react-awesome-query-builder/config/config.ts b/packages/app-store/routing-forms/components/react-awesome-query-builder/config/config.ts index a39a10ca483e55..b894fdc7cd5056 100644 --- a/packages/app-store/routing-forms/components/react-awesome-query-builder/config/config.ts +++ b/packages/app-store/routing-forms/components/react-awesome-query-builder/config/config.ts @@ -1,10 +1,10 @@ // Figure out why routing-forms/env.d.ts doesn't work // eslint-disable-next-line @typescript-eslint/ban-ts-comment -//@ts-ignore +//@ts-expect-error import type { Operators, Types } from "./BasicConfig"; import BasicConfig from "./BasicConfig"; -import { ConfigFor } from "./types"; import type { WidgetsWithoutFactory } from "./types"; +import { ConfigFor } from "./types"; function getWidgetsWithoutFactory(_configFor: ConfigFor) { const widgetsWithoutFactory: WidgetsWithoutFactory = { @@ -15,6 +15,15 @@ function getWidgetsWithoutFactory(_configFor: ConfigFor) { email: { ...BasicConfig.widgets.text, }, + address: { + ...BasicConfig.widgets.text, + }, + url: { + ...BasicConfig.widgets.text, + }, + multiemail: { + ...BasicConfig.widgets.text, + }, }; return widgetsWithoutFactory; } @@ -42,6 +51,44 @@ function getTypes(configFor: ConfigFor) { ...BasicConfig.types.text.widgets, }, }, + address: { + ...BasicConfig.types.text, + widgets: { + ...BasicConfig.types.text.widgets, + }, + }, + url: { + ...BasicConfig.types.text, + widgets: { + ...BasicConfig.types.text.widgets, + }, + }, + multiemail: { + ...BasicConfig.types.text, + widgets: { + ...BasicConfig.types.text.widgets, + }, + }, + checkbox: { + ...BasicConfig.types.multiselect, + widgets: { + ...BasicConfig.types.multiselect.widgets, + // Checkbox uses the checkbox widget for rendering but multiselect type for query logic + checkbox: { + ...BasicConfig.types.multiselect.widgets.multiselect, + operators: [...multiSelectOperators], + }, + }, + }, + radio: { + ...BasicConfig.types.select, + widgets: { + ...BasicConfig.types.select.widgets, + radio: { + ...BasicConfig.types.select.widgets.select, + }, + }, + }, multiselect: { ...BasicConfig.types.multiselect, widgets: { diff --git a/packages/app-store/routing-forms/components/react-awesome-query-builder/config/uiConfig.tsx b/packages/app-store/routing-forms/components/react-awesome-query-builder/config/uiConfig.tsx index 6f9f77b8f50114..2fa87900b1b661 100644 --- a/packages/app-store/routing-forms/components/react-awesome-query-builder/config/uiConfig.tsx +++ b/packages/app-store/routing-forms/components/react-awesome-query-builder/config/uiConfig.tsx @@ -1,20 +1,17 @@ +import { EmailField as EmailWidget } from "@calcom/ui/components/form"; import type { ChangeEvent } from "react"; import type { - Settings, SelectWidgetProps, SelectWidget as SelectWidgetType, + Settings, WidgetProps, } from "react-awesome-query-builder"; - -import { EmailField as EmailWidget } from "@calcom/ui/components/form"; - import widgetsComponents from "../widgets"; -import type { Widgets, WidgetsWithoutFactory } from "./types"; -import type { ConfigFor } from "./types"; +import type { ConfigFor, Widgets, WidgetsWithoutFactory } from "./types"; export { ConfigFor } from "./types"; -const renderComponent = function (props: T1 | undefined, Component: React.FC) { +const renderComponent = (props: T1 | undefined, Component: React.FC) => { if (!props) { return
; } @@ -27,6 +24,12 @@ const { MultiSelectWidget, SelectWidget, NumberWidget, + AddressWidget, + URLWidget, + MultiEmailWidget, + CheckboxGroupWidget, + RadioGroupWidget, + BooleanWidget, FieldSelect, Conjs, Button, @@ -76,6 +79,30 @@ const EmailFactory = (props: WidgetProps | undefined) => { ); }; +const AddressFactory = (props: WidgetProps | undefined) => renderComponent(props, AddressWidget); + +const URLFactory = (props: WidgetProps | undefined) => renderComponent(props, URLWidget); + +const MultiEmailFactory = (props: WidgetProps | undefined) => renderComponent(props, MultiEmailWidget); + +const CheckboxGroupFactory = ( + props: + | (SelectWidgetProps & { + listValues: { title: string; value: string }[]; + }) + | undefined +) => renderComponent(props, CheckboxGroupWidget); + +const RadioGroupFactory = ( + props: + | (SelectWidgetProps & { + listValues: { title: string; value: string }[]; + }) + | undefined +) => renderComponent(props, RadioGroupWidget); + +const BooleanFactory = (props: WidgetProps | undefined) => renderComponent(props, BooleanWidget); + // react-query-builder types have missing type property on Widget //TODO: Reuse FormBuilder Components - FormBuilder components are built considering Cal.com design system and coding guidelines. But when awesome-query-builder renders these components, it passes its own props which are different from what our Components expect. // So, a mapper should be written here that maps the props provided by awesome-query-builder to the props that our components expect. @@ -111,6 +138,33 @@ function withFactoryWidgets(widgets: WidgetsWithoutFactory) { ...widgets.text, factory: EmailFactory, }, + address: { + ...widgets.text, + factory: AddressFactory, + valuePlaceholder: "Enter Address", + }, + url: { + ...widgets.text, + factory: URLFactory, + valuePlaceholder: "Enter URL", + }, + multiemail: { + ...widgets.text, + factory: MultiEmailFactory, + valuePlaceholder: "Enter email addresses", + }, + checkbox: { + ...widgets.multiselect, + factory: CheckboxGroupFactory, + } as SelectWidgetType, + radio: { + ...widgets.select, + factory: RadioGroupFactory, + } as SelectWidgetType, + boolean: { + ...(widgets.boolean || widgets.text), + factory: BooleanFactory, + }, }; return widgetsWithFactory; } diff --git a/packages/app-store/routing-forms/components/react-awesome-query-builder/widgets.tsx b/packages/app-store/routing-forms/components/react-awesome-query-builder/widgets.tsx index be0de821eabf10..93f33162d74f57 100644 --- a/packages/app-store/routing-forms/components/react-awesome-query-builder/widgets.tsx +++ b/packages/app-store/routing-forms/components/react-awesome-query-builder/widgets.tsx @@ -1,7 +1,12 @@ "use client"; +import { useLocale } from "@calcom/lib/hooks/useLocale"; +import { Button as CalButton } from "@calcom/ui/components/button"; +import { TextArea, TextField } from "@calcom/ui/components/form"; +import { Icon } from "@calcom/ui/components/icon"; import dynamic from "next/dynamic"; import type { ChangeEvent } from "react"; +import { useCallback } from "react"; import type { ButtonGroupProps, ButtonProps, @@ -10,12 +15,6 @@ import type { ProviderProps, } from "react-awesome-query-builder"; -import { useLocale } from "@calcom/lib/hooks/useLocale"; -import { Button as CalButton } from "@calcom/ui/components/button"; -import { TextArea } from "@calcom/ui/components/form"; -import { TextField } from "@calcom/ui/components/form"; -import { Icon } from "@calcom/ui/components/icon"; - const Select = dynamic( async () => (await import("@calcom/ui/components/form")).SelectWithValidation ) as unknown as typeof import("@calcom/ui/components/form").SelectWithValidation; @@ -28,7 +27,7 @@ export type CommonProps< | { value: string; optionValue: string; - } + }, > = { placeholder?: string; readOnly?: boolean; @@ -50,17 +49,17 @@ export type SelectLikeComponentProps< | { value: string; optionValue: string; - } = string + } = string, > = { options: { label: string; value: TVal extends (infer P)[] ? P : TVal extends { - value: string; - } - ? TVal["value"] - : TVal; + value: string; + } + ? TVal["value"] + : TVal; }[]; } & CommonProps; @@ -233,6 +232,158 @@ function SelectWidget({ listValues, setValue, value, ...remainingProps }: Select ); } +function AddressWidget(props: TextLikeComponentPropsRAQB) { + const { value, noLabel, setValue, readOnly, placeholder, customProps, ...remainingProps } = props; + const onChange = (e: ChangeEvent) => { + const val = e.target.value; + setValue(val); + }; + const textValue = value || ""; + return ( + + ); +} + +function URLWidget(props: TextLikeComponentPropsRAQB) { + const { value, noLabel, setValue, readOnly, placeholder, customProps, ...remainingProps } = props; + const onChange = (e: ChangeEvent) => { + const val = e.target.value; + setValue(val); + }; + const textValue = value || ""; + return ( + + ); +} + +function MultiEmailWidget(props: TextLikeComponentPropsRAQB) { + const { value, noLabel, setValue, readOnly, placeholder, customProps, ...remainingProps } = props; + const onChange = (e: ChangeEvent) => { + const val = e.target.value; + setValue(val); + }; + const textValue = value || ""; + return ( + + ); +} + +function CheckboxGroupWidget({ + listValues, + setValue, + value, + ...remainingProps +}: SelectLikeComponentPropsRAQB) { + if (!listValues) { + return null; + } + + const currentValues = value || []; + + const handleChange = useCallback( + (itemValue: string, checked: boolean) => { + const newValues = checked + ? [...currentValues, itemValue] + : currentValues.filter((v) => v !== itemValue); + setValue(newValues); + }, + [currentValues, setValue] + ); + + return ( +
+ {listValues.map((item) => ( + + ))} +
+ ); +} + +function RadioGroupWidget({ listValues, setValue, value, ...remainingProps }: SelectLikeComponentPropsRAQB) { + if (!listValues) { + return null; + } + + return ( +
+ {listValues.map((item) => ( + + ))} +
+ ); +} + +function BooleanWidget({ value, setValue, readOnly }: TextLikeComponentPropsRAQB) { + // Store as "true"/"false" strings for compatibility with routing logic + const isChecked = value === "true"; + + return ( +
+ +
+ ); +} + function Button({ config, type, label, onClick, readonly }: ButtonProps) { const { t } = useLocale(); if (type === "delRule" || type == "delGroup") { @@ -376,6 +527,12 @@ const widgets = { SelectWidget, NumberWidget, MultiSelectWidget, + AddressWidget, + URLWidget, + MultiEmailWidget, + CheckboxGroupWidget, + RadioGroupWidget, + BooleanWidget, FieldSelect, Button, ButtonGroup, diff --git a/packages/app-store/routing-forms/lib/FieldTypes.ts b/packages/app-store/routing-forms/lib/FieldTypes.ts index 456f77617cc98e..0acf1cd5b6f14a 100644 --- a/packages/app-store/routing-forms/lib/FieldTypes.ts +++ b/packages/app-store/routing-forms/lib/FieldTypes.ts @@ -1,4 +1,4 @@ -export const enum RoutingFormFieldType { +export enum RoutingFormFieldType { TEXT = "text", NUMBER = "number", TEXTAREA = "textarea", @@ -6,6 +6,12 @@ export const enum RoutingFormFieldType { MULTI_SELECT = "multiselect", PHONE = "phone", EMAIL = "email", + ADDRESS = "address", + MULTIEMAIL = "multiemail", + CHECKBOX = "checkbox", + RADIO = "radio", + BOOLEAN = "boolean", + URL = "url", } export const isValidRoutingFormFieldType = (type: string): type is RoutingFormFieldType => { @@ -17,6 +23,12 @@ export const isValidRoutingFormFieldType = (type: string): type is RoutingFormFi RoutingFormFieldType.MULTI_SELECT, RoutingFormFieldType.PHONE, RoutingFormFieldType.EMAIL, + RoutingFormFieldType.ADDRESS, + RoutingFormFieldType.MULTIEMAIL, + RoutingFormFieldType.CHECKBOX, + RoutingFormFieldType.RADIO, + RoutingFormFieldType.BOOLEAN, + RoutingFormFieldType.URL, ].includes(type as RoutingFormFieldType); }; @@ -49,4 +61,28 @@ export const FieldTypes = [ label: "Email", value: RoutingFormFieldType.EMAIL, }, + { + label: "Address", + value: RoutingFormFieldType.ADDRESS, + }, + { + label: "Multiple Emails", + value: RoutingFormFieldType.MULTIEMAIL, + }, + { + label: "Checkbox Group", + value: RoutingFormFieldType.CHECKBOX, + }, + { + label: "Radio Group", + value: RoutingFormFieldType.RADIO, + }, + { + label: "Checkbox", + value: RoutingFormFieldType.BOOLEAN, + }, + { + label: "URL", + value: RoutingFormFieldType.URL, + }, ] as const; diff --git a/packages/app-store/routing-forms/lib/__tests__/getQueryBuilderConfig.test.ts b/packages/app-store/routing-forms/lib/__tests__/getQueryBuilderConfig.test.ts index c14c6e81f84b33..ca4278722e77d9 100644 --- a/packages/app-store/routing-forms/lib/__tests__/getQueryBuilderConfig.test.ts +++ b/packages/app-store/routing-forms/lib/__tests__/getQueryBuilderConfig.test.ts @@ -1,9 +1,7 @@ -import { describe, it, expect } from "vitest"; -import { vi } from "vitest"; - +import { describe, expect, it, vi } from "vitest"; import type { RoutingForm } from "../../types/types"; -import { FormFieldsInitialConfig } from "../InitialConfig"; import { getQueryBuilderConfigForFormFields } from "../getQueryBuilderConfig"; +import { FormFieldsInitialConfig } from "../InitialConfig"; type MockedForm = Pick; vi.mock("../InitialConfig", () => ({ @@ -12,6 +10,16 @@ vi.mock("../InitialConfig", () => ({ text: { type: "text" }, select: { type: "select" }, multiselect: { type: "multiselect" }, + phone: { type: "text" }, + email: { type: "text" }, + address: { type: "text" }, + url: { type: "text" }, + multiemail: { type: "text" }, + checkbox: { type: "multiselect" }, + radio: { type: "select" }, + boolean: { type: "boolean" }, + textarea: { type: "text" }, + number: { type: "number" }, }, operators: { is_empty: {}, diff --git a/packages/app-store/routing-forms/lib/formSubmissionUtils.ts b/packages/app-store/routing-forms/lib/formSubmissionUtils.ts index 31b8a77de2ed23..2d5f4e12794490 100644 --- a/packages/app-store/routing-forms/lib/formSubmissionUtils.ts +++ b/packages/app-store/routing-forms/lib/formSubmissionUtils.ts @@ -9,13 +9,11 @@ import { HttpError } from "@calcom/lib/http-error"; import logger from "@calcom/lib/logger"; import { withReporting } from "@calcom/lib/sentryWrapper"; import { prisma } from "@calcom/prisma"; -import type { Prisma } from "@calcom/prisma/client"; -import type { App_RoutingForms_Form, User } from "@calcom/prisma/client"; +import type { App_RoutingForms_Form, Prisma, User } from "@calcom/prisma/client"; import { WebhookTriggerEvents } from "@calcom/prisma/enums"; import { RoutingFormSettings } from "@calcom/prisma/zod-utils"; import type { Ensure } from "@calcom/types/utils"; - -import type { FormResponse, SerializableForm, SerializableField, OrderedResponses } from "../types/types"; +import type { FormResponse, OrderedResponses, SerializableField, SerializableForm } from "../types/types"; import getFieldIdentifier from "./getFieldIdentifier"; const moduleLogger = logger.getSubLogger({ prefix: ["routing-forms/lib/formSubmissionUtils"] }); @@ -45,7 +43,13 @@ export type FORM_SUBMITTED_WEBHOOK_RESPONSES = Record< >; function isOptionsField(field: Pick) { - return (field.type === "select" || field.type === "multiselect") && field.options; + return ( + (field.type === "select" || + field.type === "multiselect" || + field.type === "checkbox" || + field.type === "radio") && + field.options + ); } export function getFieldResponse({ @@ -192,11 +196,14 @@ export async function _onFormSubmission( }, rootData: { // Send responses unwrapped at root level for backwards compatibility - ...Object.entries(fieldResponsesByIdentifier).reduce((acc, [key, value]) => { - const normalizedKey = normalizeIdentifierForHandlebars(key); - acc[normalizedKey] = value.value; - return acc; - }, {} as Record), + ...Object.entries(fieldResponsesByIdentifier).reduce( + (acc, [key, value]) => { + const normalizedKey = normalizeIdentifierForHandlebars(key); + acc[normalizedKey] = value.value; + return acc; + }, + {} as Record + ), }, }).catch((e) => { console.error(`Error executing routing form webhook`, webhook, e); diff --git a/packages/app-store/routing-forms/lib/getQueryBuilderConfig.ts b/packages/app-store/routing-forms/lib/getQueryBuilderConfig.ts index 000ece889a1d5a..3f8e629f2e723c 100644 --- a/packages/app-store/routing-forms/lib/getQueryBuilderConfig.ts +++ b/packages/app-store/routing-forms/lib/getQueryBuilderConfig.ts @@ -1,6 +1,5 @@ import { AttributeType } from "@calcom/prisma/enums"; - -import type { RoutingForm, Attribute } from "../types/types"; +import type { Attribute, RoutingForm } from "../types/types"; import { FieldTypes, RoutingFormFieldType } from "./FieldTypes"; import { AttributesInitialConfig, FormFieldsInitialConfig } from "./InitialConfig"; import { getUIOptionsForSelect } from "./selectOptions"; @@ -58,13 +57,19 @@ export function getQueryBuilderConfigForFormFields(form: Pick f.fields).flat()); + const fields = routingFormFieldsSchema.parse(routingForms.flatMap((f) => f.fields)); return fields; } @@ -249,7 +248,7 @@ class RoutingEventsInsights { organizationId, routingFormId, }: RoutingFormInsightsTeamFilter) { - const formsWhereCondition = await this.getWhereForTeamOrAllTeams({ + const formsWhereCondition = await RoutingEventsInsights.getWhereForTeamOrAllTeams({ userId, teamId, isAll, @@ -266,7 +265,7 @@ class RoutingEventsInsights { }, }); - const fields = routingFormFieldsSchema.parse(routingForms.map((f) => f.fields).flat()); + const fields = routingFormFieldsSchema.parse(routingForms.flatMap((f) => f.fields)); const ids = new Set(); const headers = (fields || []) .filter((f) => !f.deleted) @@ -284,7 +283,9 @@ class RoutingEventsInsights { } if ( field.type === RoutingFormFieldType.SINGLE_SELECT || - field.type === RoutingFormFieldType.MULTI_SELECT + field.type === RoutingFormFieldType.MULTI_SELECT || + field.type === RoutingFormFieldType.CHECKBOX || + field.type === RoutingFormFieldType.RADIO ) { return field.options && field.options.length > 0; }