Files
calendar/packages/features/data-table/lib/utils.ts
T
5d88858995 feat: refactor filters on insights response table (#17796)
* feat: improve text filters (WIP)

* move function to bottom

* apply some styles

* fix selection of TextFilterOptions

* rename value to operand

* remove unused file

* merge filters/filters into filters/utils

* fix regression of not putting url params correctly

* move makeWhereClause to filters/utils

* fix negative, empty, and not empty operators

* fix initial filtering from search state (url)

* fix type errors

* do not send an empty array to query

* update yarn.lock

* i18n for text filter operators

* extract logic as useColumnFilters()

* add missing import

* fix type error

* revert yarn.lock

* use i18n

* insensitive text match

* move data-table to @calcom/features

* fix type errors

* fix type errors

* fix type errors

* feat: support DataTable filters for Insights Routing WIP

* remove unused filters

* remove additionalFilters and fix types

* clean up filter components

* support icons for ActiveFilters

* support filters on json

* fix filter ui

* fix type error and clean up

* revert changes

* revert change

* clean up

* revert change

* fix compatibility with insights booking page

* remove unused params

* fix type errors

* update yarn.lock

* fix field filter and adjust ui

* chore: update yarn.lock

* fix text filter

* add Clear Filters button

* add more test data

---------

Co-authored-by: Udit Takkar <udit222001@gmail.com>
2024-12-05 16:32:52 +01:00

114 lines
3.8 KiB
TypeScript

"use client";
import { parseAsArrayOf, parseAsJson, useQueryStates } from "nuqs";
import { useMemo, useCallback } from "react";
import { z } from "zod";
import type { SelectFilterValue, TextFilterValue, FilterValue } from "./types";
import { ZFilterValue } from "./types";
export const dataTableFiltersSchema = z.object({
f: z.string(),
v: ZFilterValue,
});
export function useFiltersState() {
const [state, setState] = useQueryStates({
activeFilters: parseAsArrayOf(parseAsJson(dataTableFiltersSchema.parse)).withDefault([]),
});
const clear = useCallback(() => {
setState({ activeFilters: [] });
}, [setState]);
return { state, setState, clear };
}
export type FiltersSearchState = ReturnType<typeof useFiltersState>["state"];
export type SetFiltersSearchState = ReturnType<typeof useFiltersState>["setState"];
export type ActiveFilter = z.infer<typeof dataTableFiltersSchema>;
export function useColumnFilters() {
const { state } = useFiltersState();
return useMemo(
() =>
(state.activeFilters || [])
.filter((filter) => typeof filter === "object" && filter && "f" in filter && "v" in filter)
.map((filter) => ({
id: filter.f,
value: filter.v,
}))
.filter((filter) => {
// The empty arrays in `filtersSearchState` keep the filter UI component,
// but we do not send them to the actual query.
// Otherwise, `{ my_column_name: { in: []} }` would result in nothing being returned.
if (Array.isArray(filter.value) && filter.value.length === 0) {
return false;
}
return true;
}),
[state]
);
}
export const textFilter = (cellValue: unknown, filterValue: TextFilterValue) => {
if (typeof cellValue !== "string") {
return false;
}
switch (filterValue.data.operator) {
case "equals":
return cellValue.toLowerCase() === (filterValue.data.operand || "").toLowerCase();
case "notEquals":
return cellValue.toLowerCase() !== (filterValue.data.operand || "").toLowerCase();
case "contains":
return cellValue.toLowerCase().includes((filterValue.data.operand || "").toLowerCase());
case "notContains":
return !cellValue.toLowerCase().includes((filterValue.data.operand || "").toLowerCase());
case "startsWith":
return cellValue.toLowerCase().startsWith((filterValue.data.operand || "").toLowerCase());
case "endsWith":
return cellValue.toLowerCase().endsWith((filterValue.data.operand || "").toLowerCase());
case "isEmpty":
return cellValue.trim() === "";
case "isNotEmpty":
return cellValue.trim() !== "";
default:
return false;
}
};
export const isTextFilterValue = (filterValue: unknown): filterValue is TextFilterValue => {
return (
typeof filterValue === "object" &&
filterValue !== null &&
"type" in filterValue &&
filterValue.type === "text"
);
};
export const selectFilter = (cellValue: unknown | undefined, filterValue: SelectFilterValue) => {
const cellValueArray = Array.isArray(cellValue) ? cellValue : [cellValue];
if (!cellValueArray.every((value) => typeof value === "string")) {
return false;
}
return filterValue.length === 0 ? true : cellValueArray.some((v) => filterValue.includes(v));
};
export const isSelectFilterValue = (filterValue: unknown): filterValue is SelectFilterValue => {
return Array.isArray(filterValue) && filterValue.every((item) => typeof item === "string");
};
export const dataTableFilter = (cellValue: unknown, filterValue: FilterValue) => {
if (isSelectFilterValue(filterValue)) {
return selectFilter(cellValue, filterValue);
} else if (isTextFilterValue(filterValue)) {
return textFilter(cellValue, filterValue);
}
return false;
};
export const convertToTitleCase = (str: string) => {
return str.replace(/\b\w/g, (char) => char.toUpperCase());
};