c89fe339ec
* refactor: fix pagination with stable resolveKey, use reactive defaultRoles, and remove email permission check * Changes from call * More changes from call * WIP fixing team composite cursor * paginated items fix * minor rename * composite cursor tools improved * fe undoing debugging stuff * extra fixes * invitable collabs fix --------- Co-authored-by: Kristaps Fabians Geikins <fabis94@live.com>
193 lines
5.9 KiB
Vue
193 lines
5.9 KiB
Vue
<template>
|
|
<div>
|
|
<CommonAlert color="neutral" hide-icon class="mb-6 mt-2">
|
|
<template #description>
|
|
Workspace members can view all projects by default. If on a Viewer seat, they
|
|
can view projects on web and comment. If on an Editor seat, they can create new
|
|
projects in the workspace and fully contribute to other projects if given the
|
|
permission.
|
|
</template>
|
|
</CommonAlert>
|
|
<SettingsWorkspacesMembersTableHeader
|
|
v-model:search="search"
|
|
v-model:role="roleFilter"
|
|
v-model:seat-type="seatTypeFilter"
|
|
search-placeholder="Search members..."
|
|
:workspace="workspace"
|
|
show-role-filter
|
|
show-seat-filter
|
|
/>
|
|
<LayoutTable
|
|
class="mt-6 mb-12"
|
|
:columns="[
|
|
{ id: 'name', header: 'Name', classes: 'col-span-3' },
|
|
{ id: 'email', header: 'Email', classes: 'col-span-4' },
|
|
{ id: 'seat', header: 'Seat', classes: 'col-span-2' },
|
|
{ id: 'joined', header: 'Joined', classes: 'col-span-2' },
|
|
{
|
|
id: 'actions',
|
|
header: '',
|
|
classes: 'col-span-1 flex items-center justify-end'
|
|
}
|
|
]"
|
|
:items="members"
|
|
:loading="isVeryFirstLoading"
|
|
:empty-message="
|
|
search.length || seatTypeFilter || roleFilter
|
|
? 'No results'
|
|
: 'This workspace has no members'
|
|
"
|
|
>
|
|
<template #name="{ item }">
|
|
<div class="flex items-center gap-2">
|
|
<UserAvatar
|
|
hide-tooltip
|
|
:user="item.user"
|
|
light-style
|
|
class="bg-foundation"
|
|
no-bg
|
|
/>
|
|
<span class="truncate text-body-xs text-foreground">
|
|
{{ item.user.name }}
|
|
<span
|
|
v-if="item.id === activeUser?.id"
|
|
class="text-foreground-3 text-body-3xs"
|
|
>
|
|
(You)
|
|
</span>
|
|
</span>
|
|
<CommonBadge
|
|
v-if="item.role === Roles.Workspace.Admin"
|
|
rounded
|
|
color-classes="bg-highlight-3 text-foreground-2"
|
|
>
|
|
Admin
|
|
</CommonBadge>
|
|
<div
|
|
v-if="
|
|
item.user.workspaceDomainPolicyCompliant === false &&
|
|
item.role !== Roles.Workspace.Guest
|
|
"
|
|
v-tippy="
|
|
'This user does not comply with the domain policy set on this workspace'
|
|
"
|
|
>
|
|
<ExclamationCircleIcon class="text-danger w-5" />
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<template #email="{ item }">
|
|
<div class="flex">
|
|
<span class="text-foreground-2 truncate">
|
|
{{ item.email }}
|
|
</span>
|
|
</div>
|
|
</template>
|
|
<template #seat="{ item }">
|
|
<SettingsWorkspacesMembersTableSeatType
|
|
:seat-type="item.seatType"
|
|
:role="Roles.Workspace.Member"
|
|
/>
|
|
</template>
|
|
<template #joined="{ item }">
|
|
<span class="text-foreground-2">{{ formattedFullDate(item.joinDate) }}</span>
|
|
</template>
|
|
<template #actions="{ item }">
|
|
<SettingsWorkspacesMembersActionsMenu
|
|
:target-user="item"
|
|
:workspace="workspace"
|
|
:initial-action="selectedAction[item.id]"
|
|
/>
|
|
</template>
|
|
</LayoutTable>
|
|
<InfiniteLoading
|
|
v-if="members?.length"
|
|
:settings="{ identifier }"
|
|
class="py-4"
|
|
@infinite="onInfiniteLoad"
|
|
/>
|
|
<SettingsWorkspacesMembersActionsProjectPermissionsDialog
|
|
v-model:open="showProjectPermissionsDialog"
|
|
:user="targetUser"
|
|
:workspace-id="workspace?.id || ''"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { Roles, type Nullable, type WorkspaceRoles } from '@speckle/shared'
|
|
import {
|
|
settingsWorkspacesMembersSearchQuery,
|
|
settingsWorkspacesMembersTableQuery
|
|
} from '~~/lib/settings/graphql/queries'
|
|
import type {
|
|
WorkspaceSeatType,
|
|
SettingsWorkspacesMembersActionsMenu_UserFragment
|
|
} from '~~/lib/common/generated/gql/graphql'
|
|
import { graphql } from '~/lib/common/generated/gql'
|
|
import { ExclamationCircleIcon } from '@heroicons/vue/24/outline'
|
|
import type { WorkspaceUserActionTypes } from '~/lib/settings/helpers/types'
|
|
import { usePaginatedQuery } from '~/lib/common/composables/graphql'
|
|
import { useQuery } from '@vue/apollo-composable'
|
|
|
|
graphql(`
|
|
fragment SettingsWorkspacesMembersTable_WorkspaceCollaborator on WorkspaceCollaborator {
|
|
id
|
|
email
|
|
...SettingsWorkspacesMembersActionsMenu_User
|
|
}
|
|
`)
|
|
|
|
const props = defineProps<{
|
|
workspaceSlug: string
|
|
}>()
|
|
|
|
const { activeUser } = useActiveUser()
|
|
const { result } = useQuery(settingsWorkspacesMembersTableQuery, () => ({
|
|
slug: props.workspaceSlug
|
|
}))
|
|
|
|
const selectedAction = ref<Record<string, WorkspaceUserActionTypes>>({})
|
|
const search = ref('')
|
|
const roleFilter = ref<WorkspaceRoles>()
|
|
const seatTypeFilter = ref<WorkspaceSeatType>()
|
|
const showProjectPermissionsDialog = ref(false)
|
|
const targetUser = ref<SettingsWorkspacesMembersActionsMenu_UserFragment | undefined>(
|
|
undefined
|
|
)
|
|
|
|
const defaultRoles = shallowRef([Roles.Workspace.Admin, Roles.Workspace.Member])
|
|
|
|
const {
|
|
identifier,
|
|
onInfiniteLoad,
|
|
query: { result: membersResult },
|
|
isVeryFirstLoading
|
|
} = usePaginatedQuery({
|
|
query: settingsWorkspacesMembersSearchQuery,
|
|
baseVariables: computed(() => ({
|
|
limit: 10,
|
|
slug: props.workspaceSlug,
|
|
filter: {
|
|
search: search.value,
|
|
roles: roleFilter.value ? [roleFilter.value] : defaultRoles.value,
|
|
seatType: seatTypeFilter.value
|
|
},
|
|
cursor: null as Nullable<string>
|
|
})),
|
|
resolveKey: (vars) => ({
|
|
slug: vars.slug,
|
|
filter: vars.filter
|
|
}),
|
|
resolveCurrentResult: (res) => res?.workspaceBySlug.team,
|
|
resolveNextPageVariables: (baseVars, cursor) => ({
|
|
...baseVars,
|
|
cursor
|
|
}),
|
|
resolveCursorFromVariables: (vars) => vars.cursor
|
|
})
|
|
|
|
const workspace = computed(() => result.value?.workspaceBySlug)
|
|
const members = computed(() => membersResult.value?.workspaceBySlug.team.items)
|
|
</script>
|