import { Center, Group, Loader, Table, Text, Menu, ActionIcon, ScrollArea, } from "@mantine/core"; 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, 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"; import RoleSelectMenu from "@/components/ui/role-select-menu.tsx"; import { getSpaceRoleLabel, spaceRoleData, } from "@/features/space/types/space-role-data.ts"; import { formatMemberCount } from "@/lib"; import { useTranslation } from "react-i18next"; import { SearchInput } from "@/components/common/search-input.tsx"; import { AutoTooltipText } from "@/components/ui/auto-tooltip-text.tsx"; type MemberType = "user" | "group"; interface SpaceMembersProps { spaceId: string; readOnly?: boolean; } export default function SpaceMembersList({ spaceId, readOnly, }: SpaceMembersProps) { const { t } = useTranslation(); const [search, setSearch] = useState(""); const handleSearch = useCallback((query: string) => setSearch(query), []); const { data, isLoading, hasNextPage, fetchNextPage, isFetchingNextPage } = useSpaceMembersInfiniteQuery(spaceId, search); const sentinelRef = useRef(null); const viewportRef = useRef(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(); const handleRoleChange = async ( memberId: string, type: MemberType, newRole: string, currentRole: string, ) => { if (newRole === currentRole) { return; } const memberRoleUpdate: { spaceId: string; role: string; userId?: string; groupId?: string; } = { spaceId: spaceId, role: newRole, }; if (type === "user") { memberRoleUpdate.userId = memberId; } if (type === "group") { memberRoleUpdate.groupId = memberId; } await changeSpaceMemberRoleMutation.mutateAsync(memberRoleUpdate); }; const onRemove = async (memberId: string, type: MemberType) => { const memberToRemove: IRemoveSpaceMember = { spaceId: spaceId, }; if (type === "user") { memberToRemove.userId = memberId; } if (type === "group") { memberToRemove.groupId = memberId; } await removeSpaceMember.mutateAsync(memberToRemove); }; const openRemoveModal = (memberId: string, type: MemberType) => modals.openConfirmModal({ title: t("Remove space member"), children: ( {t( "Are you sure you want to remove this user from the space? The user will lose all access to this space.", )} ), centered: true, labels: { confirm: t("Remove"), cancel: t("Cancel") }, confirmProps: { color: "red" }, onConfirm: () => onRemove(memberId, type), }); const members = data?.pages.flatMap((page) => page.items) ?? []; return ( <> {t("Member")} {t("Role")} {members.map((member, index) => ( {member.type === "user" && ( )} {member.type === "group" && }
{member?.name} {member.type == "user" && member?.email} {member.type == "group" && `${t("Group")} - ${formatMemberCount(member?.memberCount, t)}`}
{readOnly ? ( {t(getSpaceRoleLabel(member.role))} ) : ( handleRoleChange( member.id, member.type, newRole, member.role, ) } /> )} {!readOnly && ( openRemoveModal(member.id, member.type) } > {t("Remove space member")} )}
))}
{isFetchingNextPage && (
)} ); }