mirror of
https://github.com/docmost/docmost.git
synced 2025-11-13 16:12:38 +10:00
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:
@ -16,6 +16,10 @@ export default function Paginate({
|
|||||||
}: PagePaginationProps) {
|
}: PagePaginationProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
if (!hasPrevPage && !hasNextPage) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group mt="md">
|
<Group mt="md">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@ -24,10 +24,11 @@ export function SearchInput({
|
|||||||
}, [debouncedValue, onSearch]);
|
}, [debouncedValue, onSearch]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group mb="md">
|
<Group mb="sm">
|
||||||
<TextInput
|
<TextInput
|
||||||
|
size="sm"
|
||||||
placeholder={placeholder || t("Search...")}
|
placeholder={placeholder || t("Search...")}
|
||||||
leftSection={<IconSearch size={14} />}
|
leftSection={<IconSearch size={16} />}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(e) => setValue(e.currentTarget.value)}
|
onChange={(e) => setValue(e.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { Group, Table, Text, Menu, ActionIcon } from "@mantine/core";
|
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 { IconDots } from "@tabler/icons-react";
|
||||||
import { modals } from "@mantine/modals";
|
import { modals } from "@mantine/modals";
|
||||||
import { CustomAvatar } from "@/components/ui/custom-avatar.tsx";
|
import { CustomAvatar } from "@/components/ui/custom-avatar.tsx";
|
||||||
@ -18,6 +18,8 @@ import {
|
|||||||
import { formatMemberCount } from "@/lib";
|
import { formatMemberCount } from "@/lib";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import Paginate from "@/components/common/paginate.tsx";
|
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";
|
type MemberType = "user" | "group";
|
||||||
|
|
||||||
@ -31,10 +33,11 @@ export default function SpaceMembersList({
|
|||||||
readOnly,
|
readOnly,
|
||||||
}: SpaceMembersProps) {
|
}: SpaceMembersProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [page, setPage] = useState(1);
|
const { search, page, setPage, handleSearch } = usePaginateAndSearch();
|
||||||
const { data, isLoading } = useSpaceMembersQuery(spaceId, {
|
const { data, isLoading } = useSpaceMembersQuery(spaceId, {
|
||||||
page,
|
page,
|
||||||
limit: 100,
|
limit: 1,
|
||||||
|
query: search,
|
||||||
});
|
});
|
||||||
const removeSpaceMember = useRemoveSpaceMemberMutation();
|
const removeSpaceMember = useRemoveSpaceMemberMutation();
|
||||||
const changeSpaceMemberRoleMutation = useChangeSpaceMemberRoleMutation();
|
const changeSpaceMemberRoleMutation = useChangeSpaceMemberRoleMutation();
|
||||||
@ -102,6 +105,7 @@ export default function SpaceMembersList({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<SearchInput onSearch={handleSearch} />
|
||||||
<Table.ScrollContainer minWidth={500}>
|
<Table.ScrollContainer minWidth={500}>
|
||||||
<Table highlightOnHover verticalSpacing={8}>
|
<Table highlightOnHover verticalSpacing={8}>
|
||||||
<Table.Thead>
|
<Table.Thead>
|
||||||
|
|||||||
@ -41,7 +41,7 @@ export async function getSpaceMembers(
|
|||||||
spaceId: string,
|
spaceId: string,
|
||||||
params?: QueryParams,
|
params?: QueryParams,
|
||||||
): Promise<IPagination<ISpaceMember>> {
|
): 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;
|
return req.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import {
|
|||||||
useWorkspaceMembersQuery,
|
useWorkspaceMembersQuery,
|
||||||
} from "@/features/workspace/queries/workspace-query.ts";
|
} from "@/features/workspace/queries/workspace-query.ts";
|
||||||
import { CustomAvatar } from "@/components/ui/custom-avatar.tsx";
|
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 RoleSelectMenu from "@/components/ui/role-select-menu.tsx";
|
||||||
import {
|
import {
|
||||||
getUserRoleLabel,
|
getUserRoleLabel,
|
||||||
@ -16,11 +16,11 @@ import { useTranslation } from "react-i18next";
|
|||||||
import Paginate from "@/components/common/paginate.tsx";
|
import Paginate from "@/components/common/paginate.tsx";
|
||||||
import { SearchInput } from "@/components/common/search-input.tsx";
|
import { SearchInput } from "@/components/common/search-input.tsx";
|
||||||
import NoTableResults from "@/components/common/no-table-results.tsx";
|
import NoTableResults from "@/components/common/no-table-results.tsx";
|
||||||
|
import { usePaginateAndSearch } from "@/hooks/use-paginate-and-search.tsx";
|
||||||
|
|
||||||
export default function WorkspaceMembersTable() {
|
export default function WorkspaceMembersTable() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [page, setPage] = useState(1);
|
const { search, page, setPage, handleSearch } = usePaginateAndSearch();
|
||||||
const [search, setSearch] = useState(undefined);
|
|
||||||
const { data, isLoading } = useWorkspaceMembersQuery({
|
const { data, isLoading } = useWorkspaceMembersQuery({
|
||||||
page,
|
page,
|
||||||
limit: 100,
|
limit: 100,
|
||||||
@ -52,12 +52,7 @@ export default function WorkspaceMembersTable() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SearchInput
|
<SearchInput onSearch={handleSearch} />
|
||||||
onSearch={(debouncedSearch) => {
|
|
||||||
setSearch(debouncedSearch);
|
|
||||||
setPage(1);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Table.ScrollContainer minWidth={500}>
|
<Table.ScrollContainer minWidth={500}>
|
||||||
<Table highlightOnHover verticalSpacing="sm">
|
<Table highlightOnHover verticalSpacing="sm">
|
||||||
<Table.Thead>
|
<Table.Thead>
|
||||||
|
|||||||
17
apps/client/src/hooks/use-paginate-and-search.tsx
Normal file
17
apps/client/src/hooks/use-paginate-and-search.tsx
Normal 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 };
|
||||||
|
}
|
||||||
@ -97,7 +97,7 @@ export class SpaceMemberRepo {
|
|||||||
spaceId: string,
|
spaceId: string,
|
||||||
pagination: PaginationOptions,
|
pagination: PaginationOptions,
|
||||||
) {
|
) {
|
||||||
const query = this.db
|
let query = this.db
|
||||||
.selectFrom('spaceMembers')
|
.selectFrom('spaceMembers')
|
||||||
.leftJoin('users', 'users.id', 'spaceMembers.userId')
|
.leftJoin('users', 'users.id', 'spaceMembers.userId')
|
||||||
.leftJoin('groups', 'groups.id', 'spaceMembers.groupId')
|
.leftJoin('groups', 'groups.id', 'spaceMembers.groupId')
|
||||||
@ -116,6 +116,16 @@ export class SpaceMemberRepo {
|
|||||||
.where('spaceId', '=', spaceId)
|
.where('spaceId', '=', spaceId)
|
||||||
.orderBy('spaceMembers.createdAt', 'asc');
|
.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, {
|
const result = await executeWithPagination(query, {
|
||||||
page: pagination.page,
|
page: pagination.page,
|
||||||
perPage: pagination.limit,
|
perPage: pagination.limit,
|
||||||
|
|||||||
Reference in New Issue
Block a user