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:
@@ -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 />}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user