feat: support Filter Segment in other pages (#20461)

* feat: support Filter Segment in other pages

* minor changes to the behaviors

* remove comment

* fix type error

* fix e2e test
This commit is contained in:
Eunjae Lee
2025-04-02 10:36:42 +02:00
committed by GitHub
parent 1fb778ae78
commit 4cd8dbbab4
13 changed files with 107 additions and 48 deletions
@@ -1,5 +1,6 @@
import { getTranslate } from "app/_utils";
import { CTA_CONTAINER_CLASS_NAME } from "@calcom/features/data-table/lib/utils";
import Shell from "@calcom/features/shell/Shell";
import UpgradeTipWrapper from "./UpgradeTipWrapper";
@@ -8,7 +9,11 @@ export default async function InsightsLayout({ children }: { children: React.Rea
const t = await getTranslate();
return (
<div>
<Shell withoutMain={false} heading={t("insights")} subtitle={t("insights_subtitle")}>
<Shell
withoutMain={false}
heading={t("insights")}
subtitle={t("insights_subtitle")}
actions={<div className={`mb-2 flex items-center gap-2 ${CTA_CONTAINER_CLASS_NAME}`} />}>
<UpgradeTipWrapper>{children}</UpgradeTipWrapper>
</Shell>
</div>
@@ -10,6 +10,7 @@ import {
DataTableProvider,
DataTableWrapper,
DataTableFilters,
DataTableSegment,
ColumnFilterType,
useFilterValue,
ZMultiSelectFilterValue,
@@ -374,10 +375,14 @@ function BookingsContent({ status }: BookingsProps) {
paginationMode="standard"
ToolbarLeft={
<>
<DataTableFilters.AddFilterButton table={table} hideWhenFilterApplied />
<DataTableFilters.ActiveFilters table={table} />
<DataTableFilters.AddFilterButton table={table} variant="sm" showWhenFilterApplied />
<DataTableFilters.FilterBar table={table} />
</>
}
ToolbarRight={
<>
<DataTableFilters.ClearFiltersButton />
<DataTableSegment.SaveButton />
<DataTableSegment.Select />
</>
}
LoaderView={<SkeletonLoader />}
-2
View File
@@ -676,7 +676,6 @@ test.describe("Out of office", () => {
);
await page.locator('[data-testid="add-filter-button"]').click();
await page.locator('[data-testid="add-filter-item-dateRange"]').click();
await page.locator('[data-testid="add-filter-button"]').click();
await page.locator('[data-testid="filter-popover-trigger-dateRange"]').click();
await expect(page.locator('[data-testid="date-range-options-tdy"]')).toBeVisible(); //Today
@@ -777,7 +776,6 @@ test.describe("Out of office", () => {
);
await page.locator('[data-testid="add-filter-item-dateRange"]').click();
await entriesListRespPromise;
await page.locator('[data-testid="add-filter-button"]').click(); //close popover
//1 OOO record should be visible for member3, end=currentDate-4days
expect(await page.locator('[data-testid^="table-redirect-"]').count()).toBe(1);
@@ -15,10 +15,11 @@ import {
type FilterSegmentOutput,
type ActiveFilters,
} from "./lib/types";
import { CTA_CONTAINER_CLASS_NAME } from "./lib/utils";
export type DataTableContextType = {
tableIdentifier: string;
ctaContainerRef?: React.RefObject<HTMLDivElement>;
ctaContainerRef: React.RefObject<HTMLDivElement>;
activeFilters: ActiveFilters;
clearAll: (exclude?: string[]) => void;
@@ -69,7 +70,7 @@ export function DataTableProvider({
tableIdentifier: _tableIdentifier,
children,
defaultPageSize = DEFAULT_PAGE_SIZE,
ctaContainerClassName,
ctaContainerClassName = CTA_CONTAINER_CLASS_NAME,
}: DataTableProviderProps) {
const [activeFilters, setActiveFilters] = useQueryState(
"activeFilters",
@@ -110,6 +111,7 @@ export function DataTableProvider({
const clearAll = useCallback(
(exclude?: string[]) => {
setSegmentIdAndSaveToLocalStorage(null);
setPageIndex(null);
setActiveFilters((prev) => {
const remainingFilters = prev.filter((filter) => exclude?.includes(filter.f));
@@ -3,7 +3,7 @@
import { type Table } from "@tanstack/react-table";
// eslint-disable-next-line no-restricted-imports
import startCase from "lodash/startCase";
import { forwardRef } from "react";
import { forwardRef, useState } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button } from "@calcom/ui/components/button";
@@ -32,8 +32,12 @@ function AddFilterButtonComponent<TData>(
) {
const { t } = useLocale();
const { activeFilters, addFilter } = useDataTable();
const [open, setOpen] = useState(false);
const filterableColumns = useFilterableColumns(table);
const availableColumns = filterableColumns.filter(
(column) => !activeFilters?.some((filter) => filter.f === column.id)
);
if (hideWhenFilterApplied && activeFilters?.length > 0) {
return null;
@@ -43,9 +47,13 @@ function AddFilterButtonComponent<TData>(
return null;
}
if (variant === "sm" && availableColumns.length === 0) {
return null;
}
return (
<div className="flex items-center space-x-2">
<Popover>
<Popover open={open} onOpenChange={setOpen}>
{variant === "base" && (
<PopoverTrigger asChild>
<Button
@@ -73,16 +81,18 @@ function AddFilterButtonComponent<TData>(
<CommandInput placeholder={t("search")} />
<CommandList>
<CommandEmpty>{t("no_columns_found")}</CommandEmpty>
{filterableColumns.map((column) => {
{availableColumns.map((column) => {
const showHiddenIndicator =
!table.getColumn(column.id)?.getIsVisible() &&
table.initialState.columnVisibility?.[column.id] !== false;
if (activeFilters?.some((filter) => filter.f === column.id)) return null;
return (
<CommandItem
key={column.id}
onSelect={() => addFilter(column.id)}
onSelect={() => {
addFilter(column.id);
setOpen(false);
}}
className="flex items-center justify-between px-4 py-2"
data-testid={`add-filter-item-${column.id}`}>
<span>{startCase(column.title)}</span>
@@ -0,0 +1,18 @@
import { type Table } from "@tanstack/react-table";
import { ActiveFilters } from "./ActiveFilters";
import { AddFilterButton } from "./AddFilterButton";
interface FilterBarProps<TData> {
table: Table<TData>;
}
export function FilterBar<TData>({ table }: FilterBarProps<TData>) {
return (
<>
<AddFilterButton table={table} hideWhenFilterApplied />
<ActiveFilters table={table} />
<AddFilterButton table={table} variant="sm" showWhenFilterApplied />
</>
);
}
@@ -5,6 +5,7 @@ import { AddFilterButton } from "./AddFilterButton";
import { ClearFiltersButton } from "./ClearFiltersButton";
import { ColumnVisibilityButton } from "./ColumnVisibilityButton";
import { DateRangeFilter } from "./DateRangeFilter";
import { FilterBar } from "./FilterBar";
// Update the export to include ActiveFilters
export const DataTableFilters = {
@@ -12,6 +13,7 @@ export const DataTableFilters = {
AddFilterButton,
ActiveFilters,
ClearFiltersButton,
FilterBar,
};
export { DateRangeFilter };
@@ -6,5 +6,4 @@ export { DataTable } from "./DataTable";
export { DataTableSkeleton } from "./DataTableSkeleton";
export { DataTableWrapper } from "./DataTableWrapper";
export type { DataTableProps } from "./DataTable";
export { FilterSegmentSelect } from "./segment/FilterSegmentSelect";
export { SaveFilterSegmentButton } from "./segment/SaveFilterSegmentButton";
export { DataTableSegment } from "./segment";
@@ -0,0 +1,7 @@
import { FilterSegmentSelect } from "./FilterSegmentSelect";
import { SaveFilterSegmentButton } from "./SaveFilterSegmentButton";
export const DataTableSegment = {
SaveButton: SaveFilterSegmentButton,
Select: FilterSegmentSelect,
};
@@ -3,6 +3,7 @@
import { useReactTable, getCoreRowModel, getSortedRowModel } from "@tanstack/react-table";
// eslint-disable-next-line no-restricted-imports
import { useMemo, useEffect } from "react";
import { createPortal } from "react-dom";
import {
DataTableWrapper,
@@ -10,6 +11,7 @@ import {
DataTableSkeleton,
useDataTable,
DateRangeFilter,
DataTableSegment,
ColumnFilterType,
convertMapToFacetedValues,
type FilterableColumn,
@@ -49,9 +51,9 @@ export function RoutingFormResponsesTable() {
const getInsightsFacetedUniqueValues = useInsightsFacetedUniqueValues({ headers, userId, teamId, isAll });
const { sorting, limit, offset, updateFilter } = useDataTable();
const { sorting, limit, offset, ctaContainerRef, updateFilter } = useDataTable();
const { data, isFetching, isPending, isLoading } = trpc.viewer.insights.routingFormResponses.useQuery({
const { data, isPending } = trpc.viewer.insights.routingFormResponses.useQuery({
teamId,
startDate,
endDate,
@@ -123,23 +125,30 @@ export function RoutingFormResponsesTable() {
LoaderView={<DataTableSkeleton columns={4} columnWidths={[200, 200, 250, 250]} />}
ToolbarLeft={
<>
<DataTableFilters.ColumnVisibilityButton table={table} />
<OrgTeamsFilter />
<DataTableFilters.AddFilterButton table={table} hideWhenFilterApplied />
<DataTableFilters.ActiveFilters table={table} />
<DataTableFilters.AddFilterButton table={table} variant="sm" showWhenFilterApplied />
<DataTableFilters.ClearFiltersButton exclude={["createdAt"]} />
<DataTableFilters.FilterBar table={table} />
</>
}
ToolbarRight={
<>
<DateRangeFilter column={createdAtColumn} />
<RoutingFormResponsesDownload sorting={sorting} />
<DataTableFilters.ColumnVisibilityButton table={table} />
<DataTableFilters.ClearFiltersButton exclude={["createdAt"]} />
<DataTableSegment.SaveButton />
<DataTableSegment.Select />
</>
}>
<RoutingKPICards />
</DataTableWrapper>
</div>
{ctaContainerRef.current &&
createPortal(
<>
<DateRangeFilter column={createdAtColumn} />
<RoutingFormResponsesDownload sorting={sorting} />
</>,
ctaContainerRef.current
)}
</>
);
}
@@ -19,6 +19,7 @@ import {
useFilterValue,
ZDateRangeFilterValue,
DataTableFilters,
DataTableSegment,
} from "@calcom/features/data-table";
import ServerTrans from "@calcom/lib/components/ServerTrans";
import { getUserAvatarUrl } from "@calcom/lib/getAvatarUrl";
@@ -345,12 +346,17 @@ function OutOfOfficeEntriesListContent() {
totalRowCount={totalRowCount}
tableContainerRef={tableContainerRef}
paginationMode="infinite"
ToolbarLeft={<DataTableToolbar.SearchBar table={table} onSearch={(value) => setSearchTerm(value)} />}
ToolbarLeft={
<>
<DataTableToolbar.SearchBar table={table} onSearch={(value) => setSearchTerm(value)} />
<DataTableFilters.FilterBar table={table} />
</>
}
ToolbarRight={
<>
<DataTableFilters.AddFilterButton table={table} />
<DataTableFilters.ActiveFilters table={table} />
<DataTableFilters.ClearFiltersButton />
<DataTableSegment.SaveButton />
<DataTableSegment.Select />
</>
}
EmptyView={
@@ -10,6 +10,7 @@ import {
DataTableToolbar,
DataTableSelectionBar,
DataTableFilters,
DataTableSegment,
useColumnFilters,
useDataTable,
} from "@calcom/features/data-table";
@@ -287,22 +288,23 @@ function UserListTableContent({ oAuthClientId }: PlatformManagedUsersTableProps)
totalRowCount={data?.meta?.totalRowCount}
paginationMode="standard"
ToolbarLeft={
<DataTableToolbar.SearchBar
table={table}
onSearch={(value) => setDebouncedSearchTerm(value)}
className="sm:max-w-64 max-w-full"
/>
<>
<DataTableToolbar.SearchBar
table={table}
onSearch={(value) => setDebouncedSearchTerm(value)}
className="sm:max-w-64 max-w-full"
/>
<DataTableFilters.ColumnVisibilityButton table={table} />
<DataTableFilters.FilterBar table={table} />
</>
}
ToolbarRight={
<>
<DataTableFilters.AddFilterButton table={table} />
<DataTableFilters.ColumnVisibilityButton table={table} />
<DataTableFilters.ClearFiltersButton />
<DataTableSegment.SaveButton />
<DataTableSegment.Select />
</>
}>
<div className="flex gap-2 justify-self-start">
<DataTableFilters.ActiveFilters table={table} />
</div>
{numberOfSelectedRows > 0 && (
<DataTableSelectionBar.Root className="!bottom-16 justify-center md:w-max">
<p className="text-brand-subtle px-2 text-center text-xs leading-none sm:text-sm sm:font-medium">
@@ -13,13 +13,11 @@ import {
DataTableToolbar,
DataTableSelectionBar,
DataTableFilters,
DataTableSegment,
useColumnFilters,
ColumnFilterType,
convertFacetedValuesToMap,
SaveFilterSegmentButton,
FilterSegmentSelect,
useDataTable,
CTA_CONTAINER_CLASS_NAME,
} from "@calcom/features/data-table";
import { useOrgBranding } from "@calcom/features/ee/organizations/context/provider";
import { WEBAPP_URL } from "@calcom/lib/constants";
@@ -105,7 +103,7 @@ function reducer(state: UserTableState, action: UserTableAction): UserTableState
export function UserListTable() {
return (
<DataTableProvider defaultPageSize={25} ctaContainerClassName={CTA_CONTAINER_CLASS_NAME}>
<DataTableProvider defaultPageSize={25}>
<UserListTableContent />
</DataTableProvider>
);
@@ -535,16 +533,14 @@ function UserListTableContent() {
<>
<DataTableToolbar.SearchBar table={table} onSearch={(value) => setDebouncedSearchTerm(value)} />
<DataTableFilters.ColumnVisibilityButton table={table} />
<DataTableFilters.AddFilterButton table={table} hideWhenFilterApplied />
<DataTableFilters.ActiveFilters table={table} />
<DataTableFilters.AddFilterButton table={table} variant="sm" showWhenFilterApplied />
<DataTableFilters.FilterBar table={table} />
</>
}
ToolbarRight={
<>
<DataTableFilters.ClearFiltersButton />
<SaveFilterSegmentButton />
<FilterSegmentSelect />
<DataTableSegment.SaveButton />
<DataTableSegment.Select />
</>
}>
{numberOfSelectedRows >= 2 && dynamicLinkVisible && (
@@ -586,7 +582,7 @@ function UserListTableContent() {
{state.changeMemberRole.showModal && <ChangeUserRoleModal dispatch={dispatch} state={state} />}
{state.editSheet.showModal && <EditUserSheet dispatch={dispatch} state={state} />}
{ctaContainerRef?.current &&
{ctaContainerRef.current &&
createPortal(
<div className="flex items-center gap-2">
<DataTableToolbar.CTA