feat: implement serverside search over client side (#14234)

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
This commit is contained in:
sean-brydon
2024-03-28 13:18:16 +00:00
committed by GitHub
co-authored by Udit Takkar
parent f5876ae6d6
commit eec76ecfe2
5 changed files with 45 additions and 3 deletions
@@ -2,7 +2,7 @@ import { keepPreviousData } from "@tanstack/react-query";
import type { ColumnDef } from "@tanstack/react-table";
import { Plus } from "lucide-react";
import { useSession } from "next-auth/react";
import { useCallback, useEffect, useMemo, useReducer, useRef } from "react";
import { useCallback, useEffect, useMemo, useReducer, useRef, useState } from "react";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
@@ -115,11 +115,12 @@ export function UserListTable() {
const [state, dispatch] = useReducer(reducer, initialState);
const { t } = useLocale();
const orgBranding = useOrgBranding();
const [debouncedSearchTerm, setDebouncedSearchTerm] = useState("");
const { data, isPending, fetchNextPage, isFetching } =
trpc.viewer.organizations.listMembers.useInfiniteQuery(
{
limit: 10,
searchTerm: debouncedSearchTerm,
},
{
getNextPageParam: (lastPage) => lastPage.nextCursor,
@@ -310,7 +311,7 @@ export function UserListTable() {
return (
<>
<DataTable
searchKey="member"
onSearch={(value) => setDebouncedSearchTerm(value)}
selectionOptions={[
{
type: "render",
@@ -15,6 +15,7 @@ type GetOptions = {
export const listMembersHandler = async ({ ctx, input }: GetOptions) => {
const organizationId = ctx.user.organizationId;
const searchTerm = input.searchTerm;
if (!organizationId) {
throw new TRPCError({ code: "NOT_FOUND", message: "User is not part of any organization." });
@@ -42,6 +43,22 @@ export const listMembersHandler = async ({ ctx, input }: GetOptions) => {
const teamMembers = await prisma.membership.findMany({
where: {
teamId: organizationId,
...(searchTerm && {
user: {
OR: [
{
email: {
contains: searchTerm,
},
},
{
username: {
contains: searchTerm,
},
},
],
},
}),
},
select: {
id: true,
@@ -3,6 +3,7 @@ import { z } from "zod";
export const ZListMembersSchema = z.object({
limit: z.number().min(1).max(100),
cursor: z.number().nullish(),
searchTerm: z.string().optional(),
});
export type TListMembersSchema = z.infer<typeof ZListMembersSchema>;
@@ -3,7 +3,9 @@
import type { Table } from "@tanstack/react-table";
import type { LucideIcon } from "lucide-react";
import { X } from "lucide-react";
import { useEffect, useState } from "react";
import { useDebounce } from "@calcom/lib/hooks/useDebounce";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button } from "../button";
@@ -25,6 +27,7 @@ interface DataTableToolbarProps<TData> {
filterableItems?: FilterableItems;
searchKey?: string;
tableCTA?: React.ReactNode;
onSearch?: (value: string) => void;
}
export function DataTableToolbar<TData>({
@@ -32,10 +35,17 @@ export function DataTableToolbar<TData>({
filterableItems,
tableCTA,
searchKey,
onSearch,
}: DataTableToolbarProps<TData>) {
// TODO: Is there a better way to check if the table is filtered?
// If you select ALL filters for a column, the table is not filtered and we dont get a reset button
const isFiltered = table.getState().columnFilters.length > 0;
const [searchTerm, setSearchTerm] = useState("");
const debouncedSearchTerm = useDebounce(searchTerm, 500);
useEffect(() => {
onSearch?.(debouncedSearchTerm);
}, [debouncedSearchTerm, onSearch]);
const { t } = useLocale();
@@ -49,6 +59,16 @@ export function DataTableToolbar<TData>({
onChange={(event) => table.getColumn(searchKey)?.setFilterValue(event.target.value.trim())}
/>
)}
{onSearch && (
<Input
className="max-w-64 mb-0 mr-auto rounded-md"
placeholder="Search"
value={searchTerm}
onChange={(e) => {
setSearchTerm(e.target.value);
}}
/>
)}
{isFiltered && (
<Button
color="minimal"
@@ -30,6 +30,7 @@ export interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[];
data: TData[];
searchKey?: string;
onSearch?: (value: string) => void;
filterableItems?: FilterableItems;
selectionOptions?: ActionItem<TData>[];
tableCTA?: React.ReactNode;
@@ -53,6 +54,7 @@ export function DataTable<TData, TValue>({
tableOverlay,
variant,
/** This should only really be used if you dont have actions in a row. */
onSearch,
onRowMouseclick,
onScroll,
}: DataTableProps<TData, TValue>) {
@@ -102,6 +104,7 @@ export function DataTable<TData, TValue>({
table={table}
filterableItems={filterableItems}
searchKey={searchKey}
onSearch={onSearch}
tableCTA={tableCTA}
/>
<div ref={tableContainerRef} onScroll={onScroll}>