From 2dbcb73b4371cb92c8d6d4f6bab03e8465b5e2d5 Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Tue, 14 Jan 2025 15:49:08 +0100 Subject: [PATCH] fix: re-implement team filter & people filter components --- apps/web/public/icons/sprite.svg | 11 ++ apps/web/public/static/locales/en/common.json | 2 +- .../components/DataTableWrapper.tsx | 2 +- .../components/filters/FilterOptions.tsx | 2 + .../filters/MultiSelectFilterOptions.tsx | 2 +- .../filters/SingleSelectFilterOptions.tsx | 2 +- .../data-table/components/filters/index.tsx | 34 +--- packages/features/data-table/lib/context.tsx | 21 +- packages/features/data-table/lib/types.ts | 2 +- .../components/RoutingFormResponsesTable.tsx | 179 +++++++++++------- .../insights/components/RoutingKPICards.tsx | 21 +- .../insights/filters/OrgTeamsFilter.tsx | 148 +++++++++++++++ .../filters/RoutingFormFilterList.tsx | 64 +++---- .../insights/filters/UserListInTeam.tsx | 10 +- .../features/insights/server/trpc-router.ts | 62 ++---- packages/ui/components/icon/icon-list.mjs | 1 + packages/ui/components/icon/icon-names.ts | 1 + .../ui/components/popover/AnimatedPopover.tsx | 3 + 18 files changed, 362 insertions(+), 205 deletions(-) create mode 100644 packages/features/insights/filters/OrgTeamsFilter.tsx diff --git a/apps/web/public/icons/sprite.svg b/apps/web/public/icons/sprite.svg index 23e31196942e56..642fbf2d2f7fa5 100644 --- a/apps/web/public/icons/sprite.svg +++ b/apps/web/public/icons/sprite.svg @@ -548,6 +548,17 @@ + + + + + + + + + + + diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json index 8133ae2c6e8178..187ac885daf150 100644 --- a/apps/web/public/static/locales/en/common.json +++ b/apps/web/public/static/locales/en/common.json @@ -2075,7 +2075,7 @@ "looking_for_more_analytics": "Looking for more analytics?", "looking_for_more_insights": "Looking for more Insights?", "filters": "Filters", - "add_filter": "Add filter", + "add_filter": "Filter", "add_rule": "Add rule", "add_rule_group": "Add rule group", "remove_filters": "Clear all filters", diff --git a/packages/features/data-table/components/DataTableWrapper.tsx b/packages/features/data-table/components/DataTableWrapper.tsx index 52df35f0e6247a..95c70e3478b688 100644 --- a/packages/features/data-table/components/DataTableWrapper.tsx +++ b/packages/features/data-table/components/DataTableWrapper.tsx @@ -54,7 +54,7 @@ export function DataTableWrapper({
-
{ToolbarLeft}
+
{ToolbarLeft}
{ToolbarRight}
diff --git a/packages/features/data-table/components/filters/FilterOptions.tsx b/packages/features/data-table/components/filters/FilterOptions.tsx index 694d647bc20d7a..9977d69d6ae602 100644 --- a/packages/features/data-table/components/filters/FilterOptions.tsx +++ b/packages/features/data-table/components/filters/FilterOptions.tsx @@ -1,5 +1,7 @@ "use client"; +import React from "react"; + import type { FilterableColumn } from "../../lib/types"; import { MultiSelectFilterOptions } from "./MultiSelectFilterOptions"; import { NumberFilterOptions } from "./NumberFilterOptions"; diff --git a/packages/features/data-table/components/filters/MultiSelectFilterOptions.tsx b/packages/features/data-table/components/filters/MultiSelectFilterOptions.tsx index eeeb17a058c5ff..e6b9c6ebfc6183 100644 --- a/packages/features/data-table/components/filters/MultiSelectFilterOptions.tsx +++ b/packages/features/data-table/components/filters/MultiSelectFilterOptions.tsx @@ -29,7 +29,7 @@ export function MultiSelectFilterOptions({ column }: MultiSelectFilterOptionsPro return ( - + {t("no_options_found")} {Array.from(column.options.keys()).map((option) => { diff --git a/packages/features/data-table/components/filters/SingleSelectFilterOptions.tsx b/packages/features/data-table/components/filters/SingleSelectFilterOptions.tsx index 6d1742daea86c7..8651b4adeb6632 100644 --- a/packages/features/data-table/components/filters/SingleSelectFilterOptions.tsx +++ b/packages/features/data-table/components/filters/SingleSelectFilterOptions.tsx @@ -29,7 +29,7 @@ export function SingleSelectFilterOptions({ column }: SingleSelectFilterOptionsP return ( - + {t("no_options_found")} {Array.from(column.options.keys()).map((option) => { diff --git a/packages/features/data-table/components/filters/index.tsx b/packages/features/data-table/components/filters/index.tsx index 852e076565c1e7..a888bb1b8599db 100644 --- a/packages/features/data-table/components/filters/index.tsx +++ b/packages/features/data-table/components/filters/index.tsx @@ -25,7 +25,7 @@ import { } from "@calcom/ui"; import { useDataTable } from "../../hooks"; -import type { FilterableColumn, ExternalFilter } from "../../lib/types"; +import type { FilterableColumn } from "../../lib/types"; import { FilterOptions } from "./FilterOptions"; interface ColumnVisiblityProps { @@ -122,16 +122,14 @@ const ColumnVisibilityButton = forwardRef(ColumnVisibilityButtonComponent) as { table: Table; - externalFilters?: ExternalFilter[]; } function AddFilterButtonComponent( - { table, externalFilters }: AddFilterButtonProps, + { table }: AddFilterButtonProps, ref: React.Ref ) { const { t } = useLocale(); - const { activeFilters, setActiveFilters, displayedExternalFilters, setDisplayedExternalFilters } = - useDataTable(); + const { activeFilters, setActiveFilters } = useDataTable(); const filterableColumns = useFilterableColumns(table); @@ -148,14 +146,14 @@ function AddFilterButtonComponent(
- - + {t("no_columns_found")} {filterableColumns.map((column) => { @@ -169,16 +167,6 @@ function AddFilterButtonComponent( ); })} - {(externalFilters || []) - .filter((filter) => !displayedExternalFilters.includes(filter.key)) - .map((filter, index) => ( - setDisplayedExternalFilters((prev) => [...prev, filter.key])} - className="px-4 py-2"> - {t(filter.titleKey)} - - ))} @@ -229,7 +217,6 @@ const AddFilterButton = forwardRef(AddFilterButtonComponent) as ( // Add the new ActiveFilters component interface ActiveFiltersProps { table: Table; - externalFilters?: ExternalFilter[]; } const filterIcons = { @@ -239,8 +226,8 @@ const filterIcons = { single_select: "disc", } as const; -function ActiveFilters({ table, externalFilters }: ActiveFiltersProps) { - const { activeFilters, displayedExternalFilters } = useDataTable(); +function ActiveFilters({ table }: ActiveFiltersProps) { + const { activeFilters } = useDataTable(); const filterableColumns = useFilterableColumns(table); return ( @@ -264,11 +251,6 @@ function ActiveFilters({ table, externalFilters }: ActiveFiltersProps ); })} - {(displayedExternalFilters || []).map((key) => { - const filter = externalFilters?.find((filter) => filter.key === key); - if (!filter) return null; - return {filter.component()}; - })} ); } diff --git a/packages/features/data-table/lib/context.tsx b/packages/features/data-table/lib/context.tsx index 933ae0cb829d51..f7aa0a6a7c84e2 100644 --- a/packages/features/data-table/lib/context.tsx +++ b/packages/features/data-table/lib/context.tsx @@ -2,7 +2,7 @@ import type { SortingState, OnChangeFn } from "@tanstack/react-table"; import { useQueryState, parseAsArrayOf, parseAsJson } from "nuqs"; -import { createContext, useCallback, useState, type Dispatch, type SetStateAction } from "react"; +import { createContext, useCallback } from "react"; import { z } from "zod"; import { type FilterValue, ZFilterValue, ZSorting } from "./types"; @@ -23,10 +23,6 @@ export type DataTableContextType = { sorting: SortingState; setSorting: OnChangeFn; - - displayedExternalFilters: string[]; - setDisplayedExternalFilters: Dispatch>; - removeDisplayedExternalFilter: (key: string) => void; }; export const DataTableContext = createContext(null); @@ -41,19 +37,9 @@ export function DataTableProvider({ children }: { children: React.ReactNode }) { parseAsArrayOf(parseAsJson(ZSorting.parse)).withDefault([]) ); - const [displayedExternalFilters, setDisplayedExternalFilters] = useState([]); - - const removeDisplayedExternalFilter = useCallback( - (key: string) => { - setDisplayedExternalFilters((prev) => prev.filter((f) => f !== key)); - }, - [setDisplayedExternalFilters] - ); - const clearAll = useCallback(() => { setActiveFilters([]); - setDisplayedExternalFilters([]); - }, [setActiveFilters, setDisplayedExternalFilters]); + }, [setActiveFilters]); const updateFilter = useCallback( (columnId: string, value: FilterValue) => { @@ -81,9 +67,6 @@ export function DataTableProvider({ children }: { children: React.ReactNode }) { removeFilter, sorting, setSorting, - displayedExternalFilters, - setDisplayedExternalFilters, - removeDisplayedExternalFilter, }}> {children} diff --git a/packages/features/data-table/lib/types.ts b/packages/features/data-table/lib/types.ts index 11c8b844bb639e..e51a72275ee34b 100644 --- a/packages/features/data-table/lib/types.ts +++ b/packages/features/data-table/lib/types.ts @@ -17,7 +17,7 @@ export type TextFilterOperator = z.infer; export const ZSingleSelectFilterValue = z.object({ type: z.literal("single_select"), - data: z.string(), + data: z.union([z.string(), z.number()]), }); export type SingleSelectFilterValue = z.infer; diff --git a/packages/features/insights/components/RoutingFormResponsesTable.tsx b/packages/features/insights/components/RoutingFormResponsesTable.tsx index d26e4a214e7d32..ba7ab3bc1ab60a 100644 --- a/packages/features/insights/components/RoutingFormResponsesTable.tsx +++ b/packages/features/insights/components/RoutingFormResponsesTable.tsx @@ -10,12 +10,12 @@ import { } from "@tanstack/react-table"; // eslint-disable-next-line no-restricted-imports import startCase from "lodash/startCase"; +import { useSession } from "next-auth/react"; import Link from "next/link"; -import { useMemo, useId } from "react"; +import { useMemo, useId, useState } from "react"; import { z } from "zod"; import dayjs from "@calcom/dayjs"; -import type { ExternalFilter } from "@calcom/features/data-table"; import { DataTableWrapper, DataTableFilters, @@ -48,9 +48,8 @@ import { useFilterContext } from "../context/provider"; import { ClearFilters } from "../filters/ClearFilters"; import { DateSelect } from "../filters/DateSelect"; import { RoutingFormResponsesDownload } from "../filters/Download"; -import { RoutingFormFilterList } from "../filters/RoutingFormFilterList"; -import { TeamAndSelfList } from "../filters/TeamAndSelfList"; -import { UserListInTeam } from "../filters/UserListInTeam"; +import type { OrgTeamsType } from "../filters/OrgTeamsFilter"; +import { OrgTeamsFilter } from "../filters/OrgTeamsFilter"; import { ZResponseMultipleValues, ZResponseSingleValue, @@ -263,43 +262,56 @@ export function RoutingFormResponsesTableContent() { const { t } = useLocale(); const { filter } = useFilterContext(); const { copyToClipboard } = useCopy(); + const session = useSession(); + const currentOrgId = session.data?.user.org?.id; + const [orgTeamsType, setOrgTeamsType] = useState(currentOrgId ? "org" : "yours"); + const [selectedTeamId, setSelectedTeamId] = useState(); - const { - dateRange, - selectedTeamId, - isAll, - initialConfig, - selectedRoutingFormId, - selectedMemberUserId, - selectedUserId, - } = filter; - const initialConfigIsReady = !!(initialConfig?.teamId || initialConfig?.userId || initialConfig?.isAll); - const [startDate, endDate] = dateRange; + const [startDate, endDate] = filter.dateRange; + + const wholeColumnFilters = useColumnFilters(); - const columnFilters = useColumnFilters(); + const isAll = orgTeamsType === "org"; + const teamId = orgTeamsType === "team" ? selectedTeamId : undefined; + const userId = orgTeamsType === "yours" ? session.data?.user.id : undefined; - const teamId = selectedTeamId ?? undefined; - const userId = selectedUserId ?? undefined; - const memberUserId = selectedMemberUserId ?? undefined; - const routingFormId = selectedRoutingFormId ?? undefined; + const memberUserId = useMemo( + () => + wholeColumnFilters.find((filter) => filter.id === "bookingUserId")?.value.data as number | undefined, + [wholeColumnFilters] + ); + const routingFormId = useMemo( + () => wholeColumnFilters.find((filter) => filter.id === "formId")?.value.data as string | undefined, + [wholeColumnFilters] + ); + const columnFilters = useMemo( + () => wholeColumnFilters.filter((filter) => filter.id !== "formId" && filter.id !== "bookingUserId"), + [wholeColumnFilters] + ); const { data: headers, isLoading: isHeadersLoading, isSuccess: isHeadersSuccess, - } = trpc.viewer.insights.routingFormResponsesHeaders.useQuery( - { - userId, - teamId, - isAll: isAll ?? false, - routingFormId, - }, - { - enabled: initialConfigIsReady, - } - ); + } = trpc.viewer.insights.routingFormResponsesHeaders.useQuery({ + userId, + teamId, + isAll, + routingFormId, + }); - const { removeDisplayedExternalFilter, sorting, setSorting } = useDataTable(); + const { data: forms } = trpc.viewer.insights.getRoutingFormsForFilters.useQuery({ + userId, + teamId, + isAll, + }); + + const { data: users } = trpc.viewer.insights.userList.useQuery({ + teamId: teamId ?? -1, + isAll, + }); + + const { sorting, setSorting } = useDataTable(); const { data, fetchNextPage, isFetching, hasNextPage, isLoading } = trpc.viewer.insights.routingFormResponses.useInfiniteQuery( @@ -309,7 +321,7 @@ export function RoutingFormResponsesTableContent() { endDate: endDate.toISOString(), userId, memberUserId, - isAll: isAll ?? false, + isAll, routingFormId, columnFilters, sorting, @@ -321,7 +333,6 @@ export function RoutingFormResponsesTableContent() { trpc: { context: { skipBatch: true }, }, - enabled: initialConfigIsReady, } ); @@ -337,6 +348,26 @@ export function RoutingFormResponsesTableContent() { return []; } return [ + columnHelper.accessor("formId", { + id: "formId", + header: t("routing_forms"), + enableColumnFilter: true, + enableSorting: false, + meta: { + filter: { type: "single_select" }, + }, + cell: () => null, + }), + columnHelper.accessor("bookingUserId", { + id: "bookingUserId", + header: t("people"), + enableColumnFilter: true, + enableSorting: false, + meta: { + filter: { type: "single_select" }, + }, + cell: () => null, + }), columnHelper.accessor("bookingAttendees", { id: "bookingAttendees", header: t("routing_form_insights_booked_by"), @@ -500,6 +531,12 @@ export function RoutingFormResponsesTableContent() { defaultColumn: { size: 150, }, + initialState: { + columnVisibility: { + formId: false, + bookingUserId: false, + }, + }, state: { sorting, columnFilters, @@ -510,40 +547,46 @@ export function RoutingFormResponsesTableContent() { return new Map(); } + const fromArrayToMap = (array: { label: string; value: string | number }[]) => { + return new Map(array.map((option) => [{ label: option.label, value: option.value }, 1])); + }; + const fieldHeader = headers.find((h) => h.id === columnId); if (fieldHeader?.options) { - return new Map(fieldHeader.options.map((option) => [{ label: option.label, value: option.id }, 1])); + return fromArrayToMap( + fieldHeader.options + .filter((option): option is { id: string; label: string } => option.id !== null) + .map((option) => ({ + label: option.label, + value: option.id, + })) + ); } else if (columnId === "bookingStatusOrder") { - return new Map( - Object.keys(statusOrder).map((status) => [ - { - value: statusOrder[status as BookingStatus], - label: bookingStatusToText(status as BookingStatus), - }, - 1, - ]) + return fromArrayToMap( + Object.keys(statusOrder).map((status) => ({ + value: statusOrder[status as BookingStatus], + label: bookingStatusToText(status as BookingStatus), + })) + ); + } else if (columnId === "formId") { + return fromArrayToMap( + forms?.map((form) => ({ + label: form.name, + value: form.id, + })) ?? [] + ); + } else if (columnId === "bookingUserId") { + return fromArrayToMap( + users?.map((user) => ({ + label: user.name ?? user.email, + value: user.id, + })) ?? [] ); } return new Map(); }, }); - const externalFilters = useMemo( - () => [ - { - key: "memberUserId", - titleKey: "people", - component: () => ( - removeDisplayedExternalFilter("memberUserId")} - /> - ), - }, - ], - [removeDisplayedExternalFilter] - ); - if (isHeadersLoading || ((isFetching || isLoading) && !data)) { return ; } @@ -558,10 +601,16 @@ export function RoutingFormResponsesTableContent() { isFetching={isFetching} ToolbarLeft={ <> - - - - + { + setOrgTeamsType(params.type); + setSelectedTeamId(params.teamId); + }} + /> + + } @@ -582,7 +631,7 @@ export function RoutingFormResponsesTableContent() { }> - +
); diff --git a/packages/features/insights/components/RoutingKPICards.tsx b/packages/features/insights/components/RoutingKPICards.tsx index a3d67bafb75bac..82b6a3cd88fe6b 100644 --- a/packages/features/insights/components/RoutingKPICards.tsx +++ b/packages/features/insights/components/RoutingKPICards.tsx @@ -8,26 +8,31 @@ import { useFilterContext } from "../context/provider"; import { valueFormatter } from "../lib"; import { CardInsights } from "./Card"; -export const RoutingKPICards = () => { +export const RoutingKPICards = ({ + given, +}: { + given?: { isAll: boolean; teamId: number | undefined; userId: number | undefined }; +}) => { const { t } = useLocale(); const { filter } = useFilterContext(); + const userId = given?.userId ?? filter.selectedUserId; + const isAll = given?.isAll ?? filter.isAll; + const teamId = given?.teamId ?? filter.selectedTeamId; + const { dateRange, selectedEventTypeId, - selectedUserId, selectedMemberUserId, - isAll, initialConfig, selectedRoutingFormId, selectedBookingStatus, selectedRoutingFormFilter, } = filter; - const initialConfigIsReady = !!(initialConfig?.teamId || initialConfig?.userId || initialConfig?.isAll); + const initialConfigIsReady = + Boolean(given) || !!(initialConfig?.teamId || initialConfig?.userId || initialConfig?.isAll); const [startDate, endDate] = dateRange; - const { selectedTeamId: teamId } = filter; - const { data, isSuccess, isPending } = trpc.viewer.insights.routingFormsByStatus.useQuery( { startDate: startDate.toISOString(), @@ -36,7 +41,7 @@ export const RoutingKPICards = () => { eventTypeId: selectedEventTypeId ?? undefined, isAll, routingFormId: selectedRoutingFormId ?? undefined, - userId: selectedUserId ?? undefined, + userId, memberUserId: selectedMemberUserId ?? undefined, bookingStatus: selectedBookingStatus ?? undefined, fieldFilter: selectedRoutingFormFilter ?? undefined, @@ -72,7 +77,7 @@ export const RoutingKPICards = () => { return ; } - if (!isSuccess || !startDate || !endDate || (!teamId && !selectedUserId)) return null; + if (!isSuccess || !startDate || !endDate || (!teamId && !userId)) return null; return ( <> diff --git a/packages/features/insights/filters/OrgTeamsFilter.tsx b/packages/features/insights/filters/OrgTeamsFilter.tsx new file mode 100644 index 00000000000000..0c89e4df04ee8f --- /dev/null +++ b/packages/features/insights/filters/OrgTeamsFilter.tsx @@ -0,0 +1,148 @@ +import { useSession } from "next-auth/react"; +import { useState } from "react"; + +import { + FilterCheckboxField, + FilterCheckboxFieldsContainer, +} from "@calcom/features/filters/components/TeamsFilter"; +import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage"; +import { useLocale } from "@calcom/lib/hooks/useLocale"; +import { trpc } from "@calcom/trpc"; +import { AnimatedPopover, Avatar, Divider, Icon, FilterSearchField } from "@calcom/ui"; + +export type OrgTeamsType = "org" | "team" | "yours"; + +// This is a cleaned-up version of TeamAndSelfList. +export const OrgTeamsFilter = ({ + selectedType, + selectedTeamId, + onSelected, +}: { + selectedType: OrgTeamsType; + selectedTeamId?: number; + onSelected: (params: { type: OrgTeamsType; teamId?: number }) => void; +}) => { + const { t } = useLocale(); + const session = useSession(); + const currentOrgId = session.data?.user.org?.id; + const currentUserId = session.data?.user.id; + const currentUserName = session.data?.user.name; + + const [query, setQuery] = useState(""); + + const { data, isSuccess } = trpc.viewer.insights.teamListForUser.useQuery(undefined, { + // Teams don't change that frequently + refetchOnWindowFocus: false, + trpc: { + context: { + skipBatch: true, + }, + }, + }); + + const getPopoverProps = () => { + if (selectedType === "org") { + return { text: t("all"), placeholder: undefined, imageUrl: data?.[0].logoUrl }; + } else if (selectedType === "yours") { + return { text: t("yours"), placeholder: currentUserName, imageUrl: session.data?.user.avatarUrl }; + } else if (selectedType === "team") { + const selectedTeam = data?.find((item) => { + return item.id === selectedTeamId; + }); + return { + text: `${t("team")}: ${selectedTeam?.name}`, + placeholder: selectedTeam?.name, + imageUrl: selectedTeam?.logoUrl, + }; + } + + return { text: t("select"), imageUrl: undefined }; + }; + + const { text, placeholder, imageUrl } = getPopoverProps(); + const isOrgDataAvailable = !!data && data.length > 0 && !!data[0].isOrg; + + const PrefixComponent = + selectedType !== undefined && (imageUrl || placeholder) ? ( + + ) : null; + + return ( + + + setQuery(e.target.value)} + /> + + + {isOrgDataAvailable && ( + } + checked={selectedType === "org"} + onChange={(e) => { + onSelected({ type: "org", teamId: undefined }); + }} + label={t("all")} + /> + )} + + + } + checked={selectedType === "yours"} + onChange={(e) => { + if (e.target.checked) { + onSelected({ type: "yours", teamId: undefined }); + } else if (!e.target.checked) { + onSelected({ type: currentOrgId ? "org" : "yours", teamId: undefined }); + } + }} + label={t("yours")} + /> + + + {data + ?.filter((team) => !team.isOrg) + .filter((team) => team.name?.toLowerCase().includes(query.toLowerCase())) + .map((team) => { + return ( + { + if (e.target.checked) { + onSelected({ type: "team", teamId: team.id }); + } else if (!e.target.checked) { + onSelected({ type: currentOrgId ? "org" : "yours", teamId: undefined }); + } + }} + icon={ + + } + /> + ); + })} + + + ); +}; diff --git a/packages/features/insights/filters/RoutingFormFilterList.tsx b/packages/features/insights/filters/RoutingFormFilterList.tsx index 9ebd23f2c0b83e..9be2bd2e8ee404 100644 --- a/packages/features/insights/filters/RoutingFormFilterList.tsx +++ b/packages/features/insights/filters/RoutingFormFilterList.tsx @@ -16,38 +16,36 @@ function buildFilterOptions(forms: Form[]) { })); } -export const RoutingFormFilterList = memo( - ({ showOnlyWhenSelectedInContext = true }: { showOnlyWhenSelectedInContext?: boolean }) => { - const { t } = useLocale(); - const { filter, setConfigFilters } = useFilterContext(); - const { selectedTeamId, selectedUserId, selectedRoutingFormId, isAll } = filter; - const { selectedFilter } = filter; - - const { data: allForms } = trpc.viewer.insights.getRoutingFormsForFilters.useQuery( - { - userId: selectedUserId ?? undefined, - teamId: selectedTeamId ?? undefined, - isAll: isAll ?? false, - }, - { - enabled: selectedFilter?.includes("routing_forms"), - } - ); - - if (showOnlyWhenSelectedInContext && !selectedFilter?.includes("routing_forms")) return null; - - const filterOptions = buildFilterOptions(allForms || []); - - return ( - setConfigFilters({ selectedRoutingFormId: value as string })} - buttonIcon={} - /> - ); - } -); +export const RoutingFormFilterList = memo(() => { + const { t } = useLocale(); + const { filter, setConfigFilters } = useFilterContext(); + const { selectedTeamId, selectedUserId, selectedRoutingFormId, isAll } = filter; + const { selectedFilter } = filter; + + const { data: allForms } = trpc.viewer.insights.getRoutingFormsForFilters.useQuery( + { + userId: selectedUserId ?? undefined, + teamId: selectedTeamId ?? undefined, + isAll: isAll ?? false, + }, + { + enabled: selectedFilter?.includes("routing_forms"), + } + ); + + if (!selectedFilter?.includes("routing_forms")) return null; + + const filterOptions = buildFilterOptions(allForms || []); + + return ( + setConfigFilters({ selectedRoutingFormId: value as string })} + buttonIcon={} + /> + ); +}); RoutingFormFilterList.displayName = "RoutingFormFilterList"; diff --git a/packages/features/insights/filters/UserListInTeam.tsx b/packages/features/insights/filters/UserListInTeam.tsx index 5ce42026a87ddb..ce96f621878bb8 100644 --- a/packages/features/insights/filters/UserListInTeam.tsx +++ b/packages/features/insights/filters/UserListInTeam.tsx @@ -13,13 +13,7 @@ const mapUserToOption = (user: User) => ({ icon: , }); -export const UserListInTeam = ({ - showOnlyWhenSelectedInContext = true, - onClear, -}: { - showOnlyWhenSelectedInContext?: boolean; - onClear?: () => void; -}) => { +export const UserListInTeam = ({ onClear }: { onClear?: () => void }) => { const { t } = useLocale(); const { filter, setConfigFilters } = useFilterContext(); const { selectedFilter, selectedTeamId, selectedMemberUserId, isAll } = filter; @@ -29,7 +23,7 @@ export const UserListInTeam = ({ isAll: !!isAll, }); - if (showOnlyWhenSelectedInContext && !selectedFilter?.includes("user")) { + if (!selectedFilter?.includes("user")) { return null; } diff --git a/packages/features/insights/server/trpc-router.ts b/packages/features/insights/server/trpc-router.ts index 0533f73ea7eb3c..5be1a133e5542a 100644 --- a/packages/features/insights/server/trpc-router.ts +++ b/packages/features/insights/server/trpc-router.ts @@ -912,38 +912,11 @@ export const insightsRouter = router({ return []; } - const membershipConditional: Prisma.MembershipWhereInput = { - team: { - slug: { not: null }, - }, - accepted: true, - userId: user.id, - OR: [ - { - role: "ADMIN", - }, - { - role: "OWNER", - }, - ], - }; - // Validate if user belongs to org as admin/owner if (user.organizationId && user.organization.isOrgAdmin) { - const teamsFromOrg = await ctx.insightsDb.team.findMany({ - where: { - parentId: user.organizationId, - }, - select: { - id: true, - slug: true, - name: true, - logoUrl: true, - }, - }); - const orgTeam = await ctx.insightsDb.team.findUnique({ + const teamsAndOrg = await ctx.insightsDb.team.findMany({ where: { - id: user.organizationId, + OR: [{ parentId: user.organizationId }, { id: user.organizationId }], }, select: { id: true, @@ -952,25 +925,18 @@ export const insightsRouter = router({ logoUrl: true, }, }); + const teamsFromOrg = teamsAndOrg.filter((team) => team.id !== user.organizationId); + const orgTeam = teamsAndOrg.find((team) => team.id === user.organizationId); if (!orgTeam) { return []; } const result: IResultTeamList[] = [ { - id: orgTeam.id, - slug: orgTeam.slug, - name: orgTeam.name, - logoUrl: orgTeam.logoUrl, + ...orgTeam, isOrg: true, }, - ...teamsFromOrg.map( - (team: Prisma.TeamGetPayload<{ select: { id: true; slug: true; name: true; logoUrl: true } }>) => { - return { - ...team, - }; - } - ), + ...teamsFromOrg, ]; return result; @@ -978,7 +944,21 @@ export const insightsRouter = router({ // Look if user it's admin/owner in multiple teams const belongsToTeams = await ctx.insightsDb.membership.findMany({ - where: membershipConditional, + where: { + team: { + slug: { not: null }, + }, + accepted: true, + userId: user.id, + OR: [ + { + role: "ADMIN", + }, + { + role: "OWNER", + }, + ], + }, include: { team: { select: { diff --git a/packages/ui/components/icon/icon-list.mjs b/packages/ui/components/icon/icon-list.mjs index 340bce40f33208..078791b960cf26 100644 --- a/packages/ui/components/icon/icon-list.mjs +++ b/packages/ui/components/icon/icon-list.mjs @@ -142,4 +142,5 @@ export const lucideIconList = new Set([ "zap", "waypoints", "chevrons-down-up", + "sliders-horizontal" ]); diff --git a/packages/ui/components/icon/icon-names.ts b/packages/ui/components/icon/icon-names.ts index 993400acb6a853..9ae2d811ae0bdc 100644 --- a/packages/ui/components/icon/icon-names.ts +++ b/packages/ui/components/icon/icon-names.ts @@ -114,6 +114,7 @@ export type IconName = | "shield-check" | "shield" | "shuffle" + | "sliders-horizontal" | "sliders-vertical" | "smartphone" | "sparkles" diff --git a/packages/ui/components/popover/AnimatedPopover.tsx b/packages/ui/components/popover/AnimatedPopover.tsx index 0fb686c31518a6..8854aac3ba0b83 100644 --- a/packages/ui/components/popover/AnimatedPopover.tsx +++ b/packages/ui/components/popover/AnimatedPopover.tsx @@ -8,6 +8,7 @@ import { Tooltip } from "../tooltip"; export const AnimatedPopover = ({ text, + PrefixComponent, count, popoverTriggerClassNames, children, @@ -16,6 +17,7 @@ export const AnimatedPopover = ({ prefix, }: { text: string; + PrefixComponent?: React.ReactNode; count?: number; children: React.ReactNode; popoverTriggerClassNames?: string; @@ -62,6 +64,7 @@ export const AnimatedPopover = ({
+ {PrefixComponent ? PrefixComponent : null} {prefix && {prefix} } {text} {count && count > 0 && (