fix: keep search term on the url (DataTableToolbar.SearchBar) (#20521)
* fix: keep search term on the url (DataTableToolbar.SearchBar) * update comment * Update packages/features/data-table/components/DataTableToolbar.tsx Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com> --------- Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
This commit is contained in:
co-authored by
sean-brydon
parent
f353fdf252
commit
1e7453edcd
@@ -1,9 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import type { SortingState, OnChangeFn, VisibilityState, ColumnSizingState } from "@tanstack/react-table";
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import debounce from "lodash/debounce";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { useQueryState, parseAsArrayOf, parseAsJson, parseAsInteger } from "nuqs";
|
||||
import { createContext, useCallback, useEffect, useRef } from "react";
|
||||
import { useQueryState, parseAsArrayOf, parseAsJson, parseAsInteger, parseAsString } from "nuqs";
|
||||
import { createContext, useCallback, useEffect, useRef, useMemo } from "react";
|
||||
|
||||
import { useSegments } from "./lib/segments";
|
||||
import {
|
||||
@@ -49,6 +51,9 @@ export type DataTableContextType = {
|
||||
segmentId: number | undefined;
|
||||
setSegmentId: (id: number | null) => void;
|
||||
canSaveSegment: boolean;
|
||||
|
||||
searchTerm: string;
|
||||
setSearchTerm: (searchTerm: string | null) => void;
|
||||
};
|
||||
|
||||
export const DataTableContext = createContext<DataTableContextType | null>(null);
|
||||
@@ -91,6 +96,12 @@ export function DataTableProvider({
|
||||
const [segmentId, setSegmentId] = useQueryState("segment", parseAsInteger.withDefault(-1));
|
||||
const [pageIndex, setPageIndex] = useQueryState("page", parseAsInteger.withDefault(0));
|
||||
const [pageSize, setPageSize] = useQueryState("size", parseAsInteger.withDefault(defaultPageSize));
|
||||
const [searchTerm, setSearchTerm] = useQueryState("q", parseAsString.withDefault(""));
|
||||
|
||||
const setDebouncedSearchTerm = useMemo(
|
||||
() => debounce((value: string | null) => setSearchTerm(value ? value.trim() : null), 500),
|
||||
[setSearchTerm]
|
||||
);
|
||||
|
||||
const pathname = usePathname() as string | null;
|
||||
const tableIdentifier = _tableIdentifier ?? pathname ?? undefined;
|
||||
@@ -109,18 +120,6 @@ export function DataTableProvider({
|
||||
[activeFilters, setActiveFilters]
|
||||
);
|
||||
|
||||
const clearAll = useCallback(
|
||||
(exclude?: string[]) => {
|
||||
setSegmentIdAndSaveToLocalStorage(null);
|
||||
setPageIndex(null);
|
||||
setActiveFilters((prev) => {
|
||||
const remainingFilters = prev.filter((filter) => exclude?.includes(filter.f));
|
||||
return remainingFilters.length === 0 ? null : remainingFilters;
|
||||
});
|
||||
},
|
||||
[setActiveFilters, setPageIndex]
|
||||
);
|
||||
|
||||
const setPageIndexWrapper = useCallback(
|
||||
(newPageIndex: number | null) => setPageIndex(newPageIndex || null),
|
||||
[setPageIndex]
|
||||
@@ -184,6 +183,18 @@ export function DataTableProvider({
|
||||
setPageIndex,
|
||||
});
|
||||
|
||||
const clearAll = useCallback(
|
||||
(exclude?: string[]) => {
|
||||
setSegmentIdAndSaveToLocalStorage(null);
|
||||
setPageIndex(null);
|
||||
setActiveFilters((prev) => {
|
||||
const remainingFilters = prev.filter((filter) => exclude?.includes(filter.f));
|
||||
return remainingFilters.length === 0 ? null : remainingFilters;
|
||||
});
|
||||
},
|
||||
[setActiveFilters, setPageIndex, setSegmentIdAndSaveToLocalStorage]
|
||||
);
|
||||
|
||||
const ctaContainerRef = useRef<HTMLDivElement | null>(null);
|
||||
useEffect(() => {
|
||||
if (ctaContainerClassName) {
|
||||
@@ -219,6 +230,8 @@ export function DataTableProvider({
|
||||
segmentId: segmentId || undefined,
|
||||
setSegmentId: setSegmentIdAndSaveToLocalStorage,
|
||||
canSaveSegment,
|
||||
searchTerm,
|
||||
setSearchTerm: setDebouncedSearchTerm,
|
||||
}}>
|
||||
{children}
|
||||
</DataTableContext.Provider>
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
"use client";
|
||||
|
||||
import type { Table } from "@tanstack/react-table";
|
||||
import { useEffect, useState, forwardRef } from "react";
|
||||
import { forwardRef } from "react";
|
||||
import type { ComponentPropsWithoutRef } from "react";
|
||||
import { useState, type Ref, type ChangeEvent } from "react";
|
||||
|
||||
import { useDebounce } from "@calcom/lib/hooks/useDebounce";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import classNames from "@calcom/ui/classNames";
|
||||
import { Button, type ButtonProps } from "@calcom/ui/components/button";
|
||||
import { Input } from "@calcom/ui/components/form";
|
||||
|
||||
import { useColumnFilters } from "../hooks";
|
||||
import { useColumnFilters, useDataTable } from "../hooks";
|
||||
|
||||
interface DataTableToolbarProps extends ComponentPropsWithoutRef<"div"> {
|
||||
children: React.ReactNode;
|
||||
@@ -30,56 +30,34 @@ const Root = forwardRef<HTMLDivElement, DataTableToolbarProps>(function DataTabl
|
||||
);
|
||||
});
|
||||
|
||||
interface SearchBarProps<TData> {
|
||||
table: Table<TData>;
|
||||
searchKey?: string;
|
||||
onSearch?: (value: string) => void;
|
||||
interface SearchBarProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function SearchBarComponent<TData>(
|
||||
{ table, searchKey, onSearch, className }: SearchBarProps<TData>,
|
||||
ref: React.Ref<HTMLInputElement>
|
||||
) {
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const debouncedSearchTerm = useDebounce(searchTerm, 500);
|
||||
function SearchBarComponent({ className }: SearchBarProps, ref: Ref<HTMLInputElement>) {
|
||||
const { searchTerm, setSearchTerm } = useDataTable();
|
||||
const { t } = useLocale();
|
||||
const [localValue, setLocalValue] = useState(searchTerm);
|
||||
|
||||
useEffect(() => {
|
||||
onSearch?.(debouncedSearchTerm);
|
||||
}, [debouncedSearchTerm, onSearch]);
|
||||
|
||||
if (onSearch) {
|
||||
return (
|
||||
<Input
|
||||
ref={ref}
|
||||
className={`max-w-48 ${className ?? ""}`}
|
||||
placeholder="Search"
|
||||
value={searchTerm}
|
||||
onChange={(event) => setSearchTerm(event.target.value.trim())}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (!searchKey) {
|
||||
console.error("searchKey is required if onSearch is not provided");
|
||||
return null;
|
||||
}
|
||||
const handleSearchChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
const value = event.target.value;
|
||||
setLocalValue(value);
|
||||
setSearchTerm(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<Input
|
||||
ref={ref}
|
||||
className="max-w-48"
|
||||
placeholder="Search"
|
||||
value={(table.getColumn(searchKey)?.getFilterValue() as string) ?? ""}
|
||||
onChange={(event) => {
|
||||
return table.getColumn(searchKey)?.setFilterValue(event.target.value.trim());
|
||||
}}
|
||||
className={classNames("max-w-48" ,className)}
|
||||
placeholder={t("search")}
|
||||
value={localValue}
|
||||
onChange={handleSearchChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const SearchBar = forwardRef(SearchBarComponent) as <TData>(
|
||||
props: SearchBarProps<TData> & { ref?: React.Ref<HTMLInputElement> }
|
||||
const SearchBar = forwardRef(SearchBarComponent) as (
|
||||
props: SearchBarProps & { ref?: React.Ref<HTMLInputElement> }
|
||||
) => ReturnType<typeof SearchBarComponent>;
|
||||
|
||||
interface ClearFiltersButtonProps<TData> {
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
DataTableToolbar,
|
||||
DataTableFilters,
|
||||
DataTableSelectionBar,
|
||||
useDataTable,
|
||||
useFetchMoreOnBottomReached,
|
||||
useColumnFilters,
|
||||
} from "@calcom/features/data-table";
|
||||
@@ -172,13 +173,14 @@ function MemberListContent(props: Props) {
|
||||
|
||||
const tableContainerRef = useRef<HTMLDivElement>(null);
|
||||
const [state, dispatch] = useReducer(reducer, initialState);
|
||||
const [debouncedSearchTerm, setDebouncedSearchTerm] = useState("");
|
||||
|
||||
const { searchTerm } = useDataTable();
|
||||
|
||||
const { data, isPending, hasNextPage, fetchNextPage, isFetching } =
|
||||
trpc.viewer.teams.listMembers.useInfiniteQuery(
|
||||
{
|
||||
limit: 10,
|
||||
searchTerm: debouncedSearchTerm,
|
||||
searchTerm,
|
||||
teamId: props.team.id,
|
||||
// TODO: send `columnFilters` to server for server side filtering
|
||||
// filters: columnFilters,
|
||||
@@ -238,7 +240,7 @@ function MemberListContent(props: Props) {
|
||||
const previousValue = utils.viewer.teams.listMembers.getInfiniteData({
|
||||
limit: 10,
|
||||
teamId: teamIds[0],
|
||||
searchTerm: debouncedSearchTerm,
|
||||
searchTerm,
|
||||
});
|
||||
|
||||
if (previousValue) {
|
||||
@@ -246,7 +248,7 @@ function MemberListContent(props: Props) {
|
||||
utils,
|
||||
memberId: state.deleteMember.user?.id as number,
|
||||
teamId: teamIds[0],
|
||||
searchTerm: debouncedSearchTerm,
|
||||
searchTerm,
|
||||
});
|
||||
}
|
||||
return { previousValue };
|
||||
@@ -667,7 +669,7 @@ function MemberListContent(props: Props) {
|
||||
onScroll={(e) => fetchMoreOnBottomReached(e.target as HTMLDivElement)}>
|
||||
<DataTableToolbar.Root>
|
||||
<div className="flex w-full gap-2">
|
||||
<DataTableToolbar.SearchBar table={table} onSearch={(value) => setDebouncedSearchTerm(value)} />
|
||||
<DataTableToolbar.SearchBar />
|
||||
<DataTableFilters.AddFilterButton table={table} />
|
||||
<DataTableFilters.ColumnVisibilityButton table={table} />
|
||||
{isAdminOrOwner && (
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
DataTableToolbar,
|
||||
DataTableProvider,
|
||||
ColumnFilterType,
|
||||
useDataTable,
|
||||
useFilterValue,
|
||||
ZDateRangeFilterValue,
|
||||
DataTableFilters,
|
||||
@@ -71,7 +72,6 @@ export default function OutOfOfficeEntriesList() {
|
||||
function OutOfOfficeEntriesListContent() {
|
||||
const { t } = useLocale();
|
||||
const tableContainerRef = useRef<HTMLDivElement>(null);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [deletedEntry, setDeletedEntry] = useState(0);
|
||||
const [currentlyEditingOutOfOfficeEntry, setCurrentlyEditingOutOfOfficeEntry] =
|
||||
useState<BookingRedirectForm | null>(null);
|
||||
@@ -81,6 +81,7 @@ function OutOfOfficeEntriesListContent() {
|
||||
setOpenModal(true);
|
||||
};
|
||||
|
||||
const { searchTerm } = useDataTable();
|
||||
const searchParams = useCompatSearchParams();
|
||||
const selectedTab = searchParams?.get("type") ?? OutOfOfficeTab.MINE;
|
||||
|
||||
@@ -348,7 +349,7 @@ function OutOfOfficeEntriesListContent() {
|
||||
paginationMode="infinite"
|
||||
ToolbarLeft={
|
||||
<>
|
||||
<DataTableToolbar.SearchBar table={table} onSearch={(value) => setSearchTerm(value)} />
|
||||
<DataTableToolbar.SearchBar />
|
||||
<DataTableFilters.FilterBar table={table} />
|
||||
</>
|
||||
}
|
||||
|
||||
@@ -8,9 +8,9 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { DataTableProvider } from "@calcom/features/data-table/DataTableProvider";
|
||||
import { DataTable, DataTableToolbar } from "@calcom/features/data-table/components";
|
||||
import { useDataTable } from "@calcom/features/data-table/hooks";
|
||||
import { APP_NAME, WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import type { DateRange } from "@calcom/lib/date-ranges";
|
||||
import { useDebounce } from "@calcom/lib/hooks/useDebounce";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { CURRENT_TIMEZONE } from "@calcom/lib/timezoneConstants";
|
||||
import type { MembershipRole } from "@calcom/prisma/enums";
|
||||
@@ -80,8 +80,7 @@ function AvailabilitySliderTableContent(props: { userTimeFormat: number | null;
|
||||
const [browsingDate, setBrowsingDate] = useState(dayjs());
|
||||
const [editSheetOpen, setEditSheetOpen] = useState(false);
|
||||
const [selectedUser, setSelectedUser] = useState<SliderUser | null>(null);
|
||||
const [searchString, setSearchString] = useState("");
|
||||
const debouncedSearchString = useDebounce(searchString, 500);
|
||||
const { searchTerm } = useDataTable();
|
||||
|
||||
const tbStore = createTimezoneBuddyStore({
|
||||
browsingDate: browsingDate.toDate(),
|
||||
@@ -93,7 +92,7 @@ function AvailabilitySliderTableContent(props: { userTimeFormat: number | null;
|
||||
loggedInUsersTz: CURRENT_TIMEZONE,
|
||||
startDate: browsingDate.startOf("day").toISOString(),
|
||||
endDate: browsingDate.endOf("day").toISOString(),
|
||||
searchString: debouncedSearchString,
|
||||
searchString: searchTerm,
|
||||
},
|
||||
{
|
||||
getNextPageParam: (lastPage) => lastPage.nextCursor,
|
||||
@@ -253,7 +252,7 @@ function AvailabilitySliderTableContent(props: { userTimeFormat: number | null;
|
||||
isPending={isPending}
|
||||
onScroll={(e) => fetchMoreOnBottomReached(e.target as HTMLDivElement)}>
|
||||
<DataTableToolbar.Root>
|
||||
<DataTableToolbar.SearchBar table={table} onSearch={(value) => setSearchString(value)} />
|
||||
<DataTableToolbar.SearchBar />
|
||||
</DataTableToolbar.Root>
|
||||
</DataTable>
|
||||
</CellHighlightContainer>
|
||||
|
||||
@@ -68,12 +68,11 @@ function UserListTableContent({ oAuthClientId }: PlatformManagedUsersTableProps)
|
||||
const { t } = useLocale();
|
||||
|
||||
const [state, dispatch] = useReducer(reducer, initialState);
|
||||
const [debouncedSearchTerm, setDebouncedSearchTerm] = useState("");
|
||||
const [rowSelection, setRowSelection] = useState({});
|
||||
|
||||
const columnFilters = useColumnFilters();
|
||||
|
||||
const { pageIndex, pageSize } = useDataTable();
|
||||
const { pageIndex, pageSize, searchTerm } = useDataTable();
|
||||
const limit = pageSize;
|
||||
const offset = pageIndex * pageSize;
|
||||
|
||||
@@ -81,7 +80,7 @@ function UserListTableContent({ oAuthClientId }: PlatformManagedUsersTableProps)
|
||||
{
|
||||
limit,
|
||||
offset,
|
||||
searchTerm: debouncedSearchTerm,
|
||||
searchTerm,
|
||||
filters: columnFilters,
|
||||
oAuthClientId,
|
||||
},
|
||||
@@ -289,11 +288,7 @@ function UserListTableContent({ oAuthClientId }: PlatformManagedUsersTableProps)
|
||||
paginationMode="standard"
|
||||
ToolbarLeft={
|
||||
<>
|
||||
<DataTableToolbar.SearchBar
|
||||
table={table}
|
||||
onSearch={(value) => setDebouncedSearchTerm(value)}
|
||||
className="sm:max-w-64 max-w-full"
|
||||
/>
|
||||
<DataTableToolbar.SearchBar className="sm:max-w-64 max-w-full" />
|
||||
<DataTableFilters.ColumnVisibilityButton table={table} />
|
||||
<DataTableFilters.FilterBar table={table} />
|
||||
</>
|
||||
|
||||
@@ -134,19 +134,18 @@ function UserListTableContent() {
|
||||
});
|
||||
|
||||
const [state, dispatch] = useReducer(reducer, initialState);
|
||||
const [debouncedSearchTerm, setDebouncedSearchTerm] = useState("");
|
||||
const [isDownloading, setIsDownloading] = useState(false);
|
||||
const [rowSelection, setRowSelection] = useState({});
|
||||
|
||||
const columnFilters = useColumnFilters();
|
||||
|
||||
const { limit, offset, ctaContainerRef } = useDataTable();
|
||||
const { limit, offset, searchTerm, ctaContainerRef } = useDataTable();
|
||||
|
||||
const { data, isPending } = trpc.viewer.organizations.listMembers.useQuery(
|
||||
{
|
||||
limit,
|
||||
offset,
|
||||
searchTerm: debouncedSearchTerm,
|
||||
searchTerm,
|
||||
expand: ["attributes"],
|
||||
filters: columnFilters,
|
||||
},
|
||||
@@ -483,7 +482,7 @@ function UserListTableContent() {
|
||||
const result = await utils.viewer.organizations.listMembers.fetch({
|
||||
limit,
|
||||
offset,
|
||||
searchTerm: debouncedSearchTerm,
|
||||
searchTerm,
|
||||
expand: ["attributes"],
|
||||
filters: columnFilters,
|
||||
});
|
||||
@@ -531,7 +530,7 @@ function UserListTableContent() {
|
||||
paginationMode="standard"
|
||||
ToolbarLeft={
|
||||
<>
|
||||
<DataTableToolbar.SearchBar table={table} onSearch={(value) => setDebouncedSearchTerm(value)} />
|
||||
<DataTableToolbar.SearchBar />
|
||||
<DataTableFilters.ColumnVisibilityButton table={table} />
|
||||
<DataTableFilters.FilterBar table={table} />
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user