Implement space member search (#731)

* Hide pagination buttons if there is nothing to paginate
* Create reusable hook for search and pagination
This commit is contained in:
Philip Okugbe 2025-02-15 14:14:30 +00:00 committed by GitHub
parent 4d51986250
commit f92d63261d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 47 additions and 16 deletions

View File

@ -16,6 +16,10 @@ export default function Paginate({
}: PagePaginationProps) {
const { t } = useTranslation();
if (!hasPrevPage && !hasNextPage) {
return null;
}
return (
<Group mt="md">
<Button

View File

@ -24,10 +24,11 @@ export function SearchInput({
}, [debouncedValue, onSearch]);
return (
<Group mb="md">
<Group mb="sm">
<TextInput
size="sm"
placeholder={placeholder || t("Search...")}
leftSection={<IconSearch size={14} />}
leftSection={<IconSearch size={16} />}
value={value}
onChange={(e) => setValue(e.currentTarget.value)}
/>

View File

@ -1,5 +1,5 @@
import { Group, Table, Text, Menu, ActionIcon } from "@mantine/core";
import React, { useState } from "react";
import React from "react";
import { IconDots } from "@tabler/icons-react";
import { modals } from "@mantine/modals";
import { CustomAvatar } from "@/components/ui/custom-avatar.tsx";
@ -18,6 +18,8 @@ import {
import { formatMemberCount } from "@/lib";
import { useTranslation } from "react-i18next";
import Paginate from "@/components/common/paginate.tsx";
import { SearchInput } from "@/components/common/search-input.tsx";
import { usePaginateAndSearch } from "@/hooks/use-paginate-and-search.tsx";
type MemberType = "user" | "group";
@ -31,10 +33,11 @@ export default function SpaceMembersList({
readOnly,
}: SpaceMembersProps) {
const { t } = useTranslation();
const [page, setPage] = useState(1);
const { search, page, setPage, handleSearch } = usePaginateAndSearch();
const { data, isLoading } = useSpaceMembersQuery(spaceId, {
page,
limit: 100,
limit: 1,
query: search,
});
const removeSpaceMember = useRemoveSpaceMemberMutation();
const changeSpaceMemberRoleMutation = useChangeSpaceMemberRoleMutation();
@ -102,6 +105,7 @@ export default function SpaceMembersList({
return (
<>
<SearchInput onSearch={handleSearch} />
<Table.ScrollContainer minWidth={500}>
<Table highlightOnHover verticalSpacing={8}>
<Table.Thead>

View File

@ -41,7 +41,7 @@ export async function getSpaceMembers(
spaceId: string,
params?: QueryParams,
): Promise<IPagination<ISpaceMember>> {
const req = await api.post<any>("/spaces/members", { spaceId, params });
const req = await api.post<any>("/spaces/members", { spaceId, ...params });
return req.data;
}

View File

@ -4,7 +4,7 @@ import {
useWorkspaceMembersQuery,
} from "@/features/workspace/queries/workspace-query.ts";
import { CustomAvatar } from "@/components/ui/custom-avatar.tsx";
import React, { useState } from "react";
import React, { useCallback, useRef, useState } from "react";
import RoleSelectMenu from "@/components/ui/role-select-menu.tsx";
import {
getUserRoleLabel,
@ -16,11 +16,11 @@ import { useTranslation } from "react-i18next";
import Paginate from "@/components/common/paginate.tsx";
import { SearchInput } from "@/components/common/search-input.tsx";
import NoTableResults from "@/components/common/no-table-results.tsx";
import { usePaginateAndSearch } from "@/hooks/use-paginate-and-search.tsx";
export default function WorkspaceMembersTable() {
const { t } = useTranslation();
const [page, setPage] = useState(1);
const [search, setSearch] = useState(undefined);
const { search, page, setPage, handleSearch } = usePaginateAndSearch();
const { data, isLoading } = useWorkspaceMembersQuery({
page,
limit: 100,
@ -52,12 +52,7 @@ export default function WorkspaceMembersTable() {
return (
<>
<SearchInput
onSearch={(debouncedSearch) => {
setSearch(debouncedSearch);
setPage(1);
}}
/>
<SearchInput onSearch={handleSearch} />
<Table.ScrollContainer minWidth={500}>
<Table highlightOnHover verticalSpacing="sm">
<Table.Thead>

View File

@ -0,0 +1,17 @@
import { useState, useRef, useCallback } from "react";
export function usePaginateAndSearch(initialQuery: string = "") {
const [search, setSearch] = useState(initialQuery);
const [page, setPage] = useState(1);
const prevSearchRef = useRef(search);
const handleSearch = useCallback((newQuery: string) => {
if (prevSearchRef.current !== newQuery) {
prevSearchRef.current = newQuery;
setSearch(newQuery);
setPage(1);
}
}, []);
return { search, page, setPage, handleSearch };
}

View File

@ -97,7 +97,7 @@ export class SpaceMemberRepo {
spaceId: string,
pagination: PaginationOptions,
) {
const query = this.db
let query = this.db
.selectFrom('spaceMembers')
.leftJoin('users', 'users.id', 'spaceMembers.userId')
.leftJoin('groups', 'groups.id', 'spaceMembers.groupId')
@ -116,6 +116,16 @@ export class SpaceMemberRepo {
.where('spaceId', '=', spaceId)
.orderBy('spaceMembers.createdAt', 'asc');
if (pagination.query) {
query = query.where((eb) =>
eb('users.name', 'ilike', `%${pagination.query}%`).or(
'groups.name',
'ilike',
`%${pagination.query}%`,
),
);
}
const result = await executeWithPagination(query, {
page: pagination.page,
perPage: pagination.limit,