mirror of
https://github.com/docmost/docmost.git
synced 2026-05-07 06:23:06 +08:00
fix: space members view enhancement
This commit is contained in:
@@ -1,19 +1,21 @@
|
||||
import {
|
||||
Center,
|
||||
Group,
|
||||
Loader,
|
||||
Table,
|
||||
Text,
|
||||
Menu,
|
||||
ActionIcon,
|
||||
ScrollArea,
|
||||
} from "@mantine/core";
|
||||
import React from "react";
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { IconDots } from "@tabler/icons-react";
|
||||
import { modals } from "@mantine/modals";
|
||||
import { CustomAvatar } from "@/components/ui/custom-avatar.tsx";
|
||||
import {
|
||||
useChangeSpaceMemberRoleMutation,
|
||||
useRemoveSpaceMemberMutation,
|
||||
useSpaceMembersQuery,
|
||||
useSpaceMembersInfiniteQuery,
|
||||
} from "@/features/space/queries/space-query.ts";
|
||||
import { IconGroupCircle } from "@/components/icons/icon-people-circle.tsx";
|
||||
import { IRemoveSpaceMember } from "@/features/space/types/space.types.ts";
|
||||
@@ -24,9 +26,7 @@ import {
|
||||
} from "@/features/space/types/space-role-data.ts";
|
||||
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";
|
||||
import { AutoTooltipText } from "@/components/ui/auto-tooltip-text.tsx";
|
||||
|
||||
type MemberType = "user" | "group";
|
||||
@@ -41,12 +41,32 @@ export default function SpaceMembersList({
|
||||
readOnly,
|
||||
}: SpaceMembersProps) {
|
||||
const { t } = useTranslation();
|
||||
const { search, cursor, goNext, goPrev, handleSearch } = usePaginateAndSearch();
|
||||
const { data, isLoading } = useSpaceMembersQuery(spaceId, {
|
||||
cursor,
|
||||
limit: 100,
|
||||
query: search,
|
||||
});
|
||||
const [search, setSearch] = useState("");
|
||||
const handleSearch = useCallback((query: string) => setSearch(query), []);
|
||||
|
||||
const { data, isLoading, hasNextPage, fetchNextPage, isFetchingNextPage } =
|
||||
useSpaceMembersInfiniteQuery(spaceId, search);
|
||||
|
||||
const sentinelRef = useRef<HTMLDivElement>(null);
|
||||
const viewportRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const sentinel = sentinelRef.current;
|
||||
if (!sentinel) return;
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
if (entries[0].isIntersecting && hasNextPage && !isFetchingNextPage) {
|
||||
fetchNextPage();
|
||||
}
|
||||
},
|
||||
{ root: viewportRef.current, threshold: 0.1 },
|
||||
);
|
||||
|
||||
observer.observe(sentinel);
|
||||
return () => observer.disconnect();
|
||||
}, [hasNextPage, isFetchingNextPage, fetchNextPage]);
|
||||
|
||||
const removeSpaceMember = useRemoveSpaceMemberMutation();
|
||||
const changeSpaceMemberRoleMutation = useChangeSpaceMemberRoleMutation();
|
||||
|
||||
@@ -111,10 +131,12 @@ export default function SpaceMembersList({
|
||||
onConfirm: () => onRemove(memberId, type),
|
||||
});
|
||||
|
||||
const members = data?.pages.flatMap((page) => page.items) ?? [];
|
||||
|
||||
return (
|
||||
<>
|
||||
<SearchInput onSearch={handleSearch} />
|
||||
<ScrollArea h={450}>
|
||||
<ScrollArea h={450} viewportRef={viewportRef}>
|
||||
<Table.ScrollContainer minWidth={500}>
|
||||
<Table highlightOnHover verticalSpacing={8}>
|
||||
<Table.Thead>
|
||||
@@ -126,7 +148,7 @@ export default function SpaceMembersList({
|
||||
</Table.Thead>
|
||||
|
||||
<Table.Tbody>
|
||||
{data?.items.map((member, index) => (
|
||||
{members.map((member, index) => (
|
||||
<Table.Tr key={index}>
|
||||
<Table.Td>
|
||||
<Group gap="sm" wrap="nowrap">
|
||||
@@ -154,19 +176,24 @@ export default function SpaceMembersList({
|
||||
</Table.Td>
|
||||
|
||||
<Table.Td>
|
||||
<RoleSelectMenu
|
||||
roles={spaceRoleData}
|
||||
roleName={getSpaceRoleLabel(member.role)}
|
||||
onChange={(newRole) =>
|
||||
handleRoleChange(
|
||||
member.id,
|
||||
member.type,
|
||||
newRole,
|
||||
member.role,
|
||||
)
|
||||
}
|
||||
disabled={readOnly}
|
||||
/>
|
||||
{readOnly ? (
|
||||
<Text fz="sm">
|
||||
{t(getSpaceRoleLabel(member.role))}
|
||||
</Text>
|
||||
) : (
|
||||
<RoleSelectMenu
|
||||
roles={spaceRoleData}
|
||||
roleName={getSpaceRoleLabel(member.role)}
|
||||
onChange={(newRole) =>
|
||||
handleRoleChange(
|
||||
member.id,
|
||||
member.type,
|
||||
newRole,
|
||||
member.role,
|
||||
)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</Table.Td>
|
||||
|
||||
<Table.Td>
|
||||
@@ -202,16 +229,15 @@ export default function SpaceMembersList({
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
</Table.ScrollContainer>
|
||||
</ScrollArea>
|
||||
|
||||
{data?.items.length > 0 && (
|
||||
<Paginate
|
||||
hasPrevPage={data?.meta?.hasPrevPage}
|
||||
hasNextPage={data?.meta?.hasNextPage}
|
||||
onNext={() => goNext(data?.meta?.nextCursor)}
|
||||
onPrev={goPrev}
|
||||
/>
|
||||
)}
|
||||
<div ref={sentinelRef} style={{ height: 1 }} />
|
||||
|
||||
{isFetchingNextPage && (
|
||||
<Center py="xs">
|
||||
<Loader size="xs" />
|
||||
</Center>
|
||||
)}
|
||||
</ScrollArea>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
keepPreviousData,
|
||||
useInfiniteQuery,
|
||||
useMutation,
|
||||
useQuery,
|
||||
useQueryClient,
|
||||
@@ -10,7 +11,6 @@ import {
|
||||
IChangeSpaceMemberRole,
|
||||
IRemoveSpaceMember,
|
||||
ISpace,
|
||||
ISpaceMember,
|
||||
} from "@/features/space/types/space.types";
|
||||
import {
|
||||
addSpaceMember,
|
||||
@@ -190,15 +190,19 @@ export function useDeleteSpaceMutation() {
|
||||
});
|
||||
}
|
||||
|
||||
export function useSpaceMembersQuery(
|
||||
export function useSpaceMembersInfiniteQuery(
|
||||
spaceId: string,
|
||||
params?: QueryParams,
|
||||
): UseQueryResult<IPagination<ISpaceMember>, Error> {
|
||||
return useQuery({
|
||||
queryKey: ["spaceMembers", spaceId, params],
|
||||
queryFn: () => getSpaceMembers(spaceId, params),
|
||||
query?: string,
|
||||
) {
|
||||
return useInfiniteQuery({
|
||||
queryKey: ["spaceMembers", spaceId, query],
|
||||
queryFn: ({ pageParam }) =>
|
||||
getSpaceMembers(spaceId, { cursor: pageParam, limit: 50, query }),
|
||||
enabled: !!spaceId,
|
||||
placeholderData: keepPreviousData,
|
||||
initialPageParam: undefined as string | undefined,
|
||||
getNextPageParam: (lastPage) =>
|
||||
lastPage.meta.hasNextPage ? lastPage.meta.nextCursor : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -104,6 +104,7 @@ export class SpaceMemberRepo {
|
||||
.leftJoin('users', 'users.id', 'spaceMembers.userId')
|
||||
.leftJoin('groups', 'groups.id', 'spaceMembers.groupId')
|
||||
.select([
|
||||
'spaceMembers.id as id',
|
||||
'users.id as userId',
|
||||
'users.name as userName',
|
||||
'users.avatarUrl as userAvatarUrl',
|
||||
@@ -120,6 +121,12 @@ export class SpaceMemberRepo {
|
||||
'isGroup',
|
||||
),
|
||||
)
|
||||
.select(
|
||||
sql<number>`case "space_members"."role" when 'admin' then 1 when 'writer' then 2 when 'reader' then 3 else 4 end`.as(
|
||||
'roleOrder',
|
||||
),
|
||||
)
|
||||
.select(sql<string>`coalesce(users.name, groups.name)`.as('memberName'))
|
||||
.where('spaceId', '=', spaceId);
|
||||
|
||||
if (pagination.query) {
|
||||
@@ -149,12 +156,16 @@ export class SpaceMemberRepo {
|
||||
cursor: pagination.cursor,
|
||||
beforeCursor: pagination.beforeCursor,
|
||||
fields: [
|
||||
{ expression: 'sub.roleOrder', direction: 'asc', key: 'roleOrder' },
|
||||
{ expression: 'sub.isGroup', direction: 'desc', key: 'isGroup' },
|
||||
{ expression: 'sub.createdAt', direction: 'asc', key: 'createdAt' },
|
||||
{ expression: 'sub.memberName', direction: 'asc', key: 'memberName' },
|
||||
{ expression: 'sub.id', direction: 'asc', key: 'id' },
|
||||
],
|
||||
parseCursor: (cursor) => ({
|
||||
roleOrder: parseInt(cursor.roleOrder, 10),
|
||||
isGroup: parseInt(cursor.isGroup, 10),
|
||||
createdAt: new Date(cursor.createdAt),
|
||||
memberName: cursor.memberName,
|
||||
id: cursor.id,
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user