* 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>
114 lines
3.8 KiB
TypeScript
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());
|
|
};
|