import { compact } from "es-toolkit/compat"; import { observer } from "mobx-react"; import { useMemo, useCallback } from "react"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; import { GroupPermission } from "@shared/types"; import type Group from "~/models/Group"; import type User from "~/models/User"; import { Avatar, AvatarSize } from "~/components/Avatar"; import Badge from "~/components/Badge"; import { HEADER_HEIGHT } from "~/components/Header"; import InputMemberPermissionSelect from "~/components/InputMemberPermissionSelect"; import { type Props as TableProps, SortableTable, } from "~/components/SortableTable"; import { type Column as TableColumn } from "~/components/Table"; import Text from "~/components/Text"; import Time from "~/components/Time"; import usePolicy from "~/hooks/usePolicy"; import useStores from "~/hooks/useStores"; import type { Permission } from "~/types"; import { EmptySelectValue } from "~/types"; import { FILTER_HEIGHT } from "./StickyFilters"; import { HStack } from "~/components/primitives/HStack"; const ROW_HEIGHT = 50; const STICKY_OFFSET = HEADER_HEIGHT + FILTER_HEIGHT; type Props = Omit, "columns" | "rowHeight"> & { group: Group; }; /** * Table component for displaying group members with permission management. */ export const GroupMembersTable = observer(function GroupMembersTable({ group, ...rest }: Props) { const { t } = useTranslation(); const { groupUsers } = useStores(); const can = usePolicy(group); const permissions = useMemo( () => [ { label: t("Group admin"), value: GroupPermission.Admin, }, { label: t("Member"), value: GroupPermission.Member, }, { divider: true, label: t("Remove"), value: EmptySelectValue, }, ] as Permission[], [t] ); const handlePermissionChange = useCallback( async ( user: User, permission: GroupPermission | typeof EmptySelectValue ) => { try { if (permission === EmptySelectValue) { await groupUsers.delete({ userId: user.id, groupId: group.id, }); toast.success( t(`{{userName}} was removed from the group`, { userName: user.name, }), { icon: , } ); } else { await groupUsers.update({ userId: user.id, groupId: group.id, permission, }); } } catch (err) { toast.error((err as Error).message); return false; } return true; }, [t, groupUsers, group.id] ); const columns = useMemo[]>( () => compact>([ { type: "data", id: "name", header: t("Name"), accessor: (user) => user.name, component: (user) => { const gu = groupUsers.orderedData.find( (m) => m.userId === user.id && m.groupId === group.id ); return ( {user.name} {user.isAdmin ? ( {t("Admin")} ) : gu?.permission === GroupPermission.Admin ? ( {t("Group admin")} ) : null} ); }, width: "3fr", }, { type: "data", id: "lastActiveAt", header: t("Last active"), accessor: (user) => user.lastActiveAt, component: (user) => ( {user.lastActiveAt ? ( ), width: "1fr", }, can.update ? { type: "data", id: "permission", header: t("Permission"), sortable: false, accessor: (user) => { const gu = groupUsers.orderedData.find( (m) => m.userId === user.id && m.groupId === group.id ); return gu?.permission ?? ""; }, component: (user: User) => ( handlePermissionChange( user, permission as GroupPermission | typeof EmptySelectValue ) } value={ groupUsers.orderedData.find( (m) => m.userId === user.id && m.groupId === group.id )?.permission } /> ), width: "130px", } : undefined, ]), [ t, can.update, group.id, group.isExternallyManaged, groupUsers.orderedData, permissions, handlePermissionChange, ] ); return ( ); });