Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: refactor filters on insights response table #17796

Merged
Merged
Show file tree
Hide file tree
Changes from 55 commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
cb47e5e
feat: improve text filters (WIP)
eunjae-lee Nov 13, 2024
7056c6c
move function to bottom
eunjae-lee Nov 14, 2024
19ecc1d
apply some styles
eunjae-lee Nov 14, 2024
5049f01
Merge branch 'main' into eunjae/cal-4676-improved-filters
eunjae-lee Nov 14, 2024
f3dc923
fix selection of TextFilterOptions
eunjae-lee Nov 14, 2024
0895aaf
rename value to operand
eunjae-lee Nov 14, 2024
6e2657e
remove unused file
eunjae-lee Nov 14, 2024
7e0cc79
merge filters/filters into filters/utils
eunjae-lee Nov 14, 2024
ad405ed
fix regression of not putting url params correctly
eunjae-lee Nov 14, 2024
4e9c97b
move makeWhereClause to filters/utils
eunjae-lee Nov 14, 2024
71f6a69
fix negative, empty, and not empty operators
eunjae-lee Nov 14, 2024
9ff9ede
fix initial filtering from search state (url)
eunjae-lee Nov 14, 2024
f4b8471
fix type errors
eunjae-lee Nov 14, 2024
149d46b
do not send an empty array to query
eunjae-lee Nov 14, 2024
aaf96ce
Merge branch 'main' into eunjae/cal-4676-improved-filters
eunjae-lee Nov 14, 2024
d8f4f84
update yarn.lock
eunjae-lee Nov 14, 2024
1f5770c
i18n for text filter operators
eunjae-lee Nov 15, 2024
7115998
extract logic as useColumnFilters()
eunjae-lee Nov 15, 2024
b29aec1
Merge branch 'main' into eunjae/cal-4676-improved-filters
eunjae-lee Nov 15, 2024
4d6cf66
add missing import
eunjae-lee Nov 15, 2024
5fb1848
fix type error
eunjae-lee Nov 15, 2024
647b3f0
Merge branch 'main' into eunjae/cal-4676-improved-filters
eunjae-lee Nov 15, 2024
efe0037
revert yarn.lock
eunjae-lee Nov 18, 2024
bec59e1
Merge branch 'main' into eunjae/cal-4676-improved-filters
eunjae-lee Nov 18, 2024
f6bcebd
use i18n
eunjae-lee Nov 19, 2024
9944161
insensitive text match
eunjae-lee Nov 19, 2024
693a1aa
Merge branch 'main' into eunjae/cal-4676-improved-filters
eunjae-lee Nov 19, 2024
722adba
move data-table to @calcom/features
eunjae-lee Nov 19, 2024
084f9c9
fix type errors
eunjae-lee Nov 19, 2024
6fd6a5b
fix type errors
eunjae-lee Nov 19, 2024
6f62f31
fix type errors
eunjae-lee Nov 19, 2024
f29506b
Merge branch 'main' into eunjae/cal-4676-improved-filters
eunjae-lee Nov 20, 2024
8194b4e
feat: support DataTable filters for Insights Routing WIP
eunjae-lee Nov 21, 2024
1fbf0ff
remove unused filters
eunjae-lee Nov 22, 2024
0befbde
Merge branch 'main' into eunjae/cal-4760-insights-text-filter-on-rout…
eunjae-lee Nov 22, 2024
31e6b35
remove additionalFilters and fix types
eunjae-lee Nov 22, 2024
9db916a
clean up filter components
eunjae-lee Nov 22, 2024
d39551c
support icons for ActiveFilters
eunjae-lee Nov 22, 2024
34bd452
support filters on json
eunjae-lee Nov 25, 2024
e6b4860
Merge branch 'main' into eunjae/cal-4760-insights-text-filter-on-rout…
eunjae-lee Nov 26, 2024
a20667f
fix filter ui
eunjae-lee Nov 26, 2024
bfb9d91
Merge branch 'main' into eunjae/cal-4760-insights-text-filter-on-rout…
eunjae-lee Nov 27, 2024
1dddd5a
fix type error and clean up
eunjae-lee Nov 27, 2024
b75b0aa
revert changes
eunjae-lee Nov 27, 2024
c3f635a
revert change
eunjae-lee Nov 27, 2024
5df9b69
clean up
eunjae-lee Nov 28, 2024
0f2ee0b
revert change
eunjae-lee Nov 28, 2024
df49d0f
fix compatibility with insights booking page
eunjae-lee Nov 28, 2024
37e9bc9
Merge branch 'main' into eunjae/cal-4760-insights-text-filter-on-rout…
eunjae-lee Nov 28, 2024
8d4560e
remove unused params
eunjae-lee Nov 28, 2024
336b810
fix type errors
eunjae-lee Nov 28, 2024
f0e00f2
update yarn.lock
eunjae-lee Nov 28, 2024
74f9818
Merge branch 'main' into eunjae/cal-4760-insights-text-filter-on-rout…
eunjae-lee Nov 29, 2024
50338dd
fix field filter and adjust ui
eunjae-lee Dec 2, 2024
057a1b0
chore: update yarn.lock
Udit-takkar Dec 2, 2024
d5547eb
Merge branch 'main' into eunjae/cal-4760-insights-text-filter-on-rout…
eunjae-lee Dec 4, 2024
52f0d45
fix text filter
eunjae-lee Dec 4, 2024
ecec66b
add Clear Filters button
eunjae-lee Dec 4, 2024
3019ab7
add more test data
eunjae-lee Dec 4, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 3 additions & 18 deletions apps/web/modules/insights/insights-routing-view.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,19 @@
"use client";

import {
FailedBookingsByField,
RoutingFormResponsesTable,
RoutingKPICards,
} from "@calcom/features/insights/components";
import { FailedBookingsByField, RoutingFormResponsesTable } from "@calcom/features/insights/components";
import { FiltersProvider } from "@calcom/features/insights/context/FiltersProvider";
import { RoutingInsightsFilters } from "@calcom/features/insights/filters/routing/FilterBar";
import { useLocale } from "@calcom/lib/hooks/useLocale";

import InsightsLayout from "./layout";

export default function InsightsPage() {
export default function InsightsRoutingFormResponsesPage() {
const { t } = useLocale();

return (
<InsightsLayout>
<FiltersProvider>
<div className="mb-4 space-y-4">
<RoutingFormResponsesTable>
{/* We now render the "filters and KPI as a children of the table but we still need to pass the table instance to it so we can access column status in the toolbar.*/}
{(table) => (
<div className="header mb-4">
<div className="flex items-center justify-between">
<RoutingInsightsFilters table={table} />
</div>
<RoutingKPICards />
</div>
)}
</RoutingFormResponsesTable>
<RoutingFormResponsesTable />

Comment on lines +16 to 17
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've moved this into <RoutingFormResponsesTable>

<FailedBookingsByField />

Expand Down
2 changes: 2 additions & 0 deletions apps/web/public/static/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -2821,6 +2821,8 @@
"rr_distribution_method_balanced_description": "We will monitor how many bookings have been made with each host and compare this with others, disabling some hosts that are too far ahead so bookings are evenly distributed.",
"exclude_emails_that_contain": "Exclude emails that contain ...",
"exclude_emails_match_found_error_message": "Please enter a valid work email address",
"search_columns": "Search columns",
"search_options": "Search options",
"disable_org_url_label": "Disable public organization profile and redirect",
"disable_org_url_description": "Redirects {{orgSlug}}.cal.com to {{destination}}",
"single_select": "Single Select",
Expand Down
2 changes: 1 addition & 1 deletion packages/features/apps/AdminAppsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ const AdminAppsList = ({
<form
{...rest}
className={
classNames?.form ?? "max-w-80 bg-default mb-4 rounded-md px-0 pt-0 md:max-w-full md:px-8 md:pt-10"
classNames?.form ?? "bg-default mb-4 max-w-80 rounded-md px-0 pt-0 md:max-w-full md:px-8 md:pt-10"
}
onSubmit={(e) => {
e.preventDefault();
Expand Down
2 changes: 1 addition & 1 deletion packages/features/bookings/Booker/components/EventMeta.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ export const EventMeta = ({
<>{timezone}</>
) : (
<span
className={`current-timezone before:bg-subtle min-w-32 -mt-[2px] flex h-6 max-w-full items-center justify-start before:absolute before:inset-0 before:bottom-[-3px] before:left-[-30px] before:top-[-3px] before:w-[calc(100%_+_35px)] before:rounded-md before:py-3 before:opacity-0 before:transition-opacity ${
className={`current-timezone before:bg-subtle -mt-[2px] flex h-6 min-w-32 max-w-full items-center justify-start before:absolute before:inset-0 before:bottom-[-3px] before:left-[-30px] before:top-[-3px] before:w-[calc(100%_+_35px)] before:rounded-md before:py-3 before:opacity-0 before:transition-opacity ${
event.lockTimeZoneToggleOnBookingPage ? "cursor-not-allowed" : ""
}`}>
<TimezoneSelect
Expand Down
5 changes: 4 additions & 1 deletion packages/features/data-table/components/DataTableToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { ButtonProps } from "@calcom/ui";
import { Button, Input } from "@calcom/ui";

import { useColumnFilters } from "../lib/utils";

interface DataTableToolbarProps extends ComponentPropsWithoutRef<"div"> {
children: React.ReactNode;
}
Expand Down Expand Up @@ -89,7 +91,8 @@ function ClearFiltersButtonComponent<TData>(
ref: React.Ref<HTMLButtonElement>
) {
const { t } = useLocale();
const isFiltered = table.getState().columnFilters.length > 0;
const columnFilters = useColumnFilters();
const isFiltered = columnFilters.length > 0;
if (!isFiltered) return null;
return (
<Button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export function FilterOptions<TData>({ column, filter, state, setState, table }:
table.getColumn(columnId)?.setFilterValue(undefined);
};

if (column.filterType === "text") {
if (column.type === "text") {
return (
<TextFilterOptions
column={column}
Expand All @@ -44,7 +44,7 @@ export function FilterOptions<TData>({ column, filter, state, setState, table }:
removeFilter={removeFilter}
/>
);
} else if (column.filterType === "select") {
} else if (column.type === "select") {
return (
<MultiSelectFilterOptions
column={column}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
import type { FilterableColumn, SelectFilterValue } from "../../lib/types";

export type MultiSelectFilterOptionsProps = {
column: FilterableColumn;
column: Extract<FilterableColumn, { type: "select" }>;
filterValue?: SelectFilterValue;
setFilterValue: (value: SelectFilterValue) => void;
removeFilter: (columnId: string) => void;
Expand All @@ -36,27 +36,30 @@ export function MultiSelectFilterOptions({
<CommandEmpty>{t("no_options_found")}</CommandEmpty>
{Array.from(column.options.keys()).map((option) => {
if (!option) return null;
const { label: optionLabel, value: optionValue } =
typeof option === "string" ? { label: option, value: option } : option;

return (
<CommandItem
key={option}
key={optionValue}
onSelect={() => {
const newFilterValue = filterValue?.includes(option)
? filterValue?.filter((value) => value !== option)
: [...(filterValue || []), option];
const newFilterValue = filterValue?.includes(optionValue)
? filterValue?.filter((value) => value !== optionValue)
: [...(filterValue || []), optionValue];
setFilterValue(newFilterValue);
}}>
<div
className={classNames(
"border-subtle mr-2 flex h-4 w-4 items-center justify-center rounded-sm border",
Array.isArray(filterValue) && (filterValue as string[])?.includes(option)
Array.isArray(filterValue) && (filterValue as string[])?.includes(optionValue)
? "bg-primary"
: "opacity-50"
)}>
{Array.isArray(filterValue) && (filterValue as string[])?.includes(option) && (
{Array.isArray(filterValue) && (filterValue as string[])?.includes(optionValue) && (
<Icon name="check" className="text-primary-foreground h-4 w-4" />
)}
</div>
{option}
{optionLabel}
</CommandItem>
);
})}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const useTextFilterOperatorOptions = (): TextFilterOperatorOption[] => {
};

export type TextFilterOptionsProps = {
column: FilterableColumn;
column: Extract<FilterableColumn, { type: "text" }>;
filterValue?: TextFilterValue;
setFilterValue: (value: TextFilterValue) => void;
removeFilter: (columnId: string) => void;
Expand Down
108 changes: 65 additions & 43 deletions packages/features/data-table/components/filters/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {
} from "@calcom/ui";

import type { FilterableColumn } from "../../lib/types";
import { useFiltersSearchState } from "../../lib/utils";
import { convertToTitleCase, useFiltersState } from "../../lib/utils";
import { FilterOptions } from "./FilterOptions";

interface ColumnVisiblityProps<TData> {
Expand Down Expand Up @@ -118,35 +118,24 @@ const ColumnVisibilityButton = forwardRef(ColumnVisibilityButtonComponent) as <T
) => ReturnType<typeof ColumnVisibilityButtonComponent>;

// Filters
interface FilterButtonProps<TData> {
interface AddFilterButtonProps<TData> {
table: Table<TData>;
omit?: string[];
}

function FilterButtonComponent<TData>(
{ table, omit }: FilterButtonProps<TData>,
function AddFilterButtonComponent<TData>(
{ table, omit }: AddFilterButtonProps<TData>,
ref: React.Ref<HTMLButtonElement>
) {
const { t } = useLocale();
const [_state, _setState] = useFiltersSearchState();

const activeFilters = _state.activeFilters;
const columns = table
.getAllColumns()
.filter((column) => column.getCanFilter())
.filter((column) => !omit?.includes(column.id));

const filterableColumns = useMemo(() => {
return columns.map((column) => ({
id: column.id,
title: typeof column.columnDef.header === "string" ? column.columnDef.header : column.id,
options: column.getFacetedUniqueValues(),
}));
}, [columns]);
const [state, setState] = useFiltersState();

const activeFilters = state.activeFilters;
const filterableColumns = useFilterableColumns(table, omit);

const handleAddFilter = (columnId: string) => {
if (!activeFilters?.some((filter) => filter.f === columnId)) {
_setState({ activeFilters: [...activeFilters, { f: columnId, v: [] }] });
setState({ activeFilters: [...activeFilters, { f: columnId, v: [] }] });
}
};

Expand All @@ -167,8 +156,11 @@ function FilterButtonComponent<TData>(
{filterableColumns.map((column) => {
if (activeFilters?.some((filter) => filter.f === column.id)) return null;
return (
<CommandItem key={column.id} onSelect={() => handleAddFilter(column.id)}>
{column.title}
<CommandItem
key={column.id}
onSelect={() => handleAddFilter(column.id)}
className="px-4 py-2">
{convertToTitleCase(column.title)}
</CommandItem>
);
})}
Expand All @@ -180,50 +172,80 @@ function FilterButtonComponent<TData>(
);
}

const FilterButton = forwardRef(FilterButtonComponent) as <TData>(
props: FilterButtonProps<TData> & { ref?: React.Ref<HTMLButtonElement>; omit?: string[] }
) => ReturnType<typeof FilterButtonComponent>;
function useFilterableColumns<TData>(table: Table<TData>, omit?: string[]) {
const columns = useMemo(
() =>
table
.getAllColumns()
.filter((column) => column.getCanFilter())
.filter((column) => !omit?.includes(column.id)),
[table.getAllColumns(), omit]
);

const filterableColumns = useMemo<FilterableColumn[]>(
() =>
columns
.map((column) => {
const type = column.columnDef.meta?.filter?.type || "select";
const base = {
id: column.id,
title: typeof column.columnDef.header === "string" ? column.columnDef.header : column.id,
...(column.columnDef.meta?.filter || {}),
type,
};
if (type === "select") {
return {
...base,
options: column.getFacetedUniqueValues(),
};
} else if (type === "text") {
return {
...base,
};
}
})
.filter((column): column is FilterableColumn => Boolean(column)),
[columns]
);

return filterableColumns;
}

const AddFilterButton = forwardRef(AddFilterButtonComponent) as <TData>(
props: AddFilterButtonProps<TData> & { ref?: React.Ref<HTMLButtonElement>; omit?: string[] }
) => ReturnType<typeof AddFilterButtonComponent>;

// Add the new ActiveFilters component
interface ActiveFiltersProps<TData> {
table: Table<TData>;
}

function ActiveFilters<TData>({ table }: ActiveFiltersProps<TData>) {
const [_state, _setState] = useFiltersSearchState();

const columns = table.getAllColumns().filter((column) => column.getCanFilter());
const [state, setState] = useFiltersState();

const filterableColumns = useMemo<FilterableColumn[]>(() => {
return columns.map((column) => {
return {
id: column.id,
title: typeof column.columnDef.header === "string" ? column.columnDef.header : column.id,
filterType: column.columnDef.meta?.filterType || "select",
options: column.getFacetedUniqueValues(),
};
});
}, [columns]);
const filterableColumns = useFilterableColumns(table);

return (
<>
{(_state.activeFilters || []).map((filter) => {
{state.activeFilters.map((filter) => {
const column = filterableColumns.find((col) => col.id === filter.f);
if (!column) return null;
const icon = column.icon || (column.type === "text" ? "file-text" : "layers");
return (
<Popover key={column.id}>
<PopoverTrigger asChild>
<Button color="secondary">
{column.title}
<Icon name={icon} className="mr-2 h-4 w-4" />
{convertToTitleCase(column.title)}
<Icon name="chevron-down" className="ml-2 h-4 w-4" />
</Button>
</PopoverTrigger>
<PopoverContent className="p-0" align="start">
<FilterOptions
column={column}
filter={filter}
state={_state}
setState={_setState}
state={state}
setState={setState}
table={table}
/>
</PopoverContent>
Expand All @@ -235,4 +257,4 @@ function ActiveFilters<TData>({ table }: ActiveFiltersProps<TData>) {
}

// Update the export to include ActiveFilters
export const DataTableFilters = { ColumnVisibilityButton, FilterButton, ActiveFilters };
export const DataTableFilters = { ColumnVisibilityButton, AddFilterButton, ActiveFilters };
Loading
Loading