Work to new structures
This commit is contained in:
@@ -23,6 +23,14 @@
|
||||
hide-description
|
||||
:hide-items="[Roles.Workspace.Guest]"
|
||||
/>
|
||||
<FormSelectSeatType
|
||||
v-if="showSeatFilter"
|
||||
v-model="seatType"
|
||||
fully-control-value
|
||||
clearable
|
||||
class="!min-w-40"
|
||||
hide-description
|
||||
/>
|
||||
</div>
|
||||
<template v-if="showInviteButton">
|
||||
<div v-if="!isWorkspaceAdmin" v-tippy="'You must be a workspace admin'">
|
||||
@@ -60,10 +68,12 @@ const props = defineProps<{
|
||||
workspace: MaybeNullOrUndefined<SettingsWorkspacesMembersTableHeader_WorkspaceFragment>
|
||||
showRoleFilter?: boolean
|
||||
showInviteButton?: boolean
|
||||
showSeatFilter?: boolean
|
||||
}>()
|
||||
|
||||
const search = defineModel<string>('search')
|
||||
const role = defineModel<WorkspaceRoles>('role')
|
||||
const seatType = defineModel<string>('seatType')
|
||||
const { on, bind } = useDebouncedTextInput({ model: search })
|
||||
const isInviteDialogOpen = ref(false)
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div>
|
||||
<CommonAlert color="neutral" hide-icon class="mb-6 mt-2">
|
||||
<template #description>
|
||||
Guests are external collaborators. They can’t create or add others to workspace
|
||||
Guests are external collaborators. They can't create or add others to workspace
|
||||
projects. Read more about
|
||||
<!-- TODO: Add link to roles and seats page -->
|
||||
<NuxtLink to="#" class="underline">Speckle roles and seats.</NuxtLink>
|
||||
@@ -10,16 +10,18 @@
|
||||
</CommonAlert>
|
||||
<SettingsWorkspacesMembersTableHeader
|
||||
v-model:search="search"
|
||||
v-model:seat-type="seatTypeFilter"
|
||||
search-placeholder="Search guests..."
|
||||
:workspace="workspace"
|
||||
show-invite-button
|
||||
show-seat-filter
|
||||
/>
|
||||
<LayoutTable
|
||||
class="mt-6 md:mt-8"
|
||||
:columns="[
|
||||
{ id: 'name', header: 'Name', classes: 'col-span-3' },
|
||||
{ id: 'email', header: 'Email', classes: 'col-span-3' },
|
||||
{ id: 'role', header: 'Seat', classes: 'col-span-2' },
|
||||
{ id: 'seat', header: 'Seat', classes: 'col-span-2' },
|
||||
{ id: 'joined', header: 'Joined', classes: 'col-span-3' },
|
||||
{
|
||||
id: 'actions',
|
||||
@@ -43,17 +45,18 @@
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<!-- TODO: Add email -->
|
||||
<template #email="">
|
||||
<!-- TODO: Add email -->
|
||||
<span class="text-body-xs text-foreground">EMAIL</span>
|
||||
</template>
|
||||
<template #projects="{ item }">
|
||||
<span class="text-body-xs text-foreground-2">
|
||||
<CommonBadge color-classes="bg-foundation-2 text-foreground-2" rounded>
|
||||
{{ item.projectRoles.length }} project{{
|
||||
item.projectRoles.length !== 1 ? 's' : ''
|
||||
}}
|
||||
</CommonBadge>
|
||||
<template #seat="{ item }">
|
||||
<span class="text-foreground">
|
||||
<div
|
||||
v-tippy="`Explainer`"
|
||||
class="border-b border-dashed border-outline-5 max-w-max select-none capitalize"
|
||||
>
|
||||
{{ item.seatType }}
|
||||
</div>
|
||||
</span>
|
||||
</template>
|
||||
<!-- TODO: Add joined at date -->
|
||||
@@ -110,7 +113,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import type {
|
||||
SettingsWorkspacesMembersGuestsTable_WorkspaceFragment,
|
||||
SettingsWorkspacesMembersNewGuestsTable_WorkspaceFragment,
|
||||
WorkspaceCollaborator
|
||||
} from '~/lib/common/generated/gql/graphql'
|
||||
import { graphql } from '~/lib/common/generated/gql'
|
||||
@@ -123,9 +126,10 @@ import type { LayoutMenuItem } from '~~/lib/layout/helpers/components'
|
||||
import { EllipsisHorizontalIcon, XMarkIcon } from '@heroicons/vue/24/outline'
|
||||
|
||||
graphql(`
|
||||
fragment SettingsWorkspacesMembersGuestsTable_WorkspaceCollaborator on WorkspaceCollaborator {
|
||||
fragment SettingsWorkspacesMembersNewGuestsTable_WorkspaceCollaborator on WorkspaceCollaborator {
|
||||
id
|
||||
role
|
||||
seatType
|
||||
user {
|
||||
id
|
||||
avatar
|
||||
@@ -143,7 +147,7 @@ graphql(`
|
||||
`)
|
||||
|
||||
graphql(`
|
||||
fragment SettingsWorkspacesMembersGuestsTable_Workspace on Workspace {
|
||||
fragment SettingsWorkspacesMembersNewGuestsTable_Workspace on Workspace {
|
||||
id
|
||||
...SettingsWorkspacesMembersTableHeader_Workspace
|
||||
...SettingsSharedDeleteUserDialog_Workspace
|
||||
@@ -151,7 +155,7 @@ graphql(`
|
||||
team {
|
||||
items {
|
||||
id
|
||||
...SettingsWorkspacesMembersGuestsTable_WorkspaceCollaborator
|
||||
...SettingsWorkspacesMembersNewGuestsTable_WorkspaceCollaborator
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -164,13 +168,14 @@ enum ActionTypes {
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
workspace: MaybeNullOrUndefined<SettingsWorkspacesMembersGuestsTable_WorkspaceFragment>
|
||||
workspace: MaybeNullOrUndefined<SettingsWorkspacesMembersNewGuestsTable_WorkspaceFragment>
|
||||
workspaceSlug: string
|
||||
}>()
|
||||
|
||||
const updateUserRole = useWorkspaceUpdateRole()
|
||||
|
||||
const search = ref('')
|
||||
const seatTypeFilter = ref<string>()
|
||||
const showActionsMenu = ref<Record<string, boolean>>({})
|
||||
const showDeleteUserRoleDialog = ref(false)
|
||||
const showGuestsPermissionsDialog = ref(false)
|
||||
@@ -191,18 +196,21 @@ const { result: searchResult, loading: searchResultLoading } = useQuery(
|
||||
slug: props.workspaceSlug
|
||||
}),
|
||||
() => ({
|
||||
enabled: !!search.value.length
|
||||
enabled: !!search.value.length || !!seatTypeFilter.value
|
||||
})
|
||||
)
|
||||
|
||||
const guests = computed(() => {
|
||||
const guestArray = search.value.length
|
||||
? searchResult.value?.workspaceBySlug?.team.items
|
||||
: props.workspace?.team.items
|
||||
const guestArray =
|
||||
search.value.length || seatTypeFilter.value
|
||||
? searchResult.value?.workspaceBySlug?.team.items
|
||||
: props.workspace?.team.items
|
||||
|
||||
return (guestArray || []).filter(
|
||||
(item): item is WorkspaceCollaborator => item.role === Roles.Workspace.Guest
|
||||
)
|
||||
return (guestArray || [])
|
||||
.filter(
|
||||
(item): item is WorkspaceCollaborator => item.role === Roles.Workspace.Guest
|
||||
)
|
||||
.filter((item) => !seatTypeFilter.value || item.seatType === seatTypeFilter.value)
|
||||
})
|
||||
|
||||
const isWorkspaceAdmin = computed(() => props.workspace?.role === Roles.Workspace.Admin)
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
<template>
|
||||
<div>
|
||||
<SettingsWorkspacesMembersTableHeader
|
||||
v-model:search="search"
|
||||
search-placeholder="Search pending invites..."
|
||||
:workspace="workspace"
|
||||
show-invite-button
|
||||
/>
|
||||
<LayoutTable
|
||||
class="mt-6 md:mt-8 mb-12"
|
||||
:columns="[
|
||||
{ id: 'name', header: 'Name', classes: 'col-span-4' },
|
||||
{ id: 'invitedBy', header: 'Invited by', classes: 'col-span-3' },
|
||||
{ id: 'seat', header: 'Seat', classes: 'col-span-2' },
|
||||
{ id: 'lastRemindedOn', header: 'Last reminded on', classes: 'col-span-2' },
|
||||
{
|
||||
id: 'actions',
|
||||
header: '',
|
||||
classes: 'col-span-1 flex items-center justify-end'
|
||||
}
|
||||
]"
|
||||
:items="invites"
|
||||
:loading="searchResultLoading"
|
||||
:empty-message="
|
||||
search.length
|
||||
? 'No invites with the specified filter found'
|
||||
: 'No pending invites'
|
||||
"
|
||||
>
|
||||
<template #name="{ item }">
|
||||
<div class="flex items-center gap-2">
|
||||
<UserAvatar v-if="item.user" hide-tooltip :user="item.user" />
|
||||
<span class="truncate text-body-xs text-foreground">{{ item.title }}</span>
|
||||
<CommonBadge
|
||||
rounded
|
||||
color-classes="bg-highlight-3 text-foreground-2 capitalize"
|
||||
>
|
||||
{{ item.role }}
|
||||
</CommonBadge>
|
||||
</div>
|
||||
</template>
|
||||
<template #invitedBy="{ item }">
|
||||
<div class="flex items-center gap-2">
|
||||
<UserAvatar hide-tooltip :user="item.invitedBy" />
|
||||
<span class="truncate text-body-xs text-foreground">
|
||||
{{ item.invitedBy.name }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<!-- TODO: Add seat type -->
|
||||
<template #seat="">
|
||||
<span class="text-foreground">
|
||||
<div
|
||||
v-tippy="`Explainer`"
|
||||
class="border-b border-dashed border-outline-5 max-w-max select-none capitalize"
|
||||
>
|
||||
SEAT TYPE
|
||||
</div>
|
||||
</span>
|
||||
</template>
|
||||
<template #lastRemindedOn="{ item }">
|
||||
<span class="text-body-xs text-foreground-2">
|
||||
{{ formattedFullDate(item.updatedAt) }}
|
||||
</span>
|
||||
</template>
|
||||
<template #actions="{ item }">
|
||||
<LayoutMenu
|
||||
v-model:open="showActionsMenu[item.id]"
|
||||
:items="actionsItems"
|
||||
mount-menu-on-body
|
||||
:menu-position="HorizontalDirection.Left"
|
||||
@chosen="({ item: actionItem }) => onActionChosen(actionItem, item)"
|
||||
>
|
||||
<FormButton
|
||||
:color="showActionsMenu[item.id] ? 'outline' : 'subtle'"
|
||||
hide-text
|
||||
:icon-right="showActionsMenu[item.id] ? XMarkIcon : EllipsisHorizontalIcon"
|
||||
@click="toggleMenu(item.id)"
|
||||
/>
|
||||
</LayoutMenu>
|
||||
</template>
|
||||
</LayoutTable>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { XMarkIcon, EllipsisHorizontalIcon } from '@heroicons/vue/24/outline'
|
||||
import { useQuery } from '@vue/apollo-composable'
|
||||
import { graphql } from '~/lib/common/generated/gql'
|
||||
import type { SettingsWorkspacesMembersInvitesTable_WorkspaceFragment } from '~/lib/common/generated/gql/graphql'
|
||||
import {
|
||||
useCancelWorkspaceInvite,
|
||||
useResendWorkspaceInvite
|
||||
} from '~/lib/settings/composables/workspaces'
|
||||
import { settingsWorkspacesInvitesSearchQuery } from '~/lib/settings/graphql/queries'
|
||||
import type { LayoutMenuItem } from '~~/lib/layout/helpers/components'
|
||||
import { HorizontalDirection } from '~~/lib/common/composables/window'
|
||||
import type { MaybeNullOrUndefined } from '@speckle/shared'
|
||||
|
||||
graphql(`
|
||||
fragment SettingsWorkspacesMembersNewInvitesTable_PendingWorkspaceCollaborator on PendingWorkspaceCollaborator {
|
||||
id
|
||||
inviteId
|
||||
role
|
||||
title
|
||||
updatedAt
|
||||
user {
|
||||
id
|
||||
...LimitedUserAvatar
|
||||
}
|
||||
invitedBy {
|
||||
id
|
||||
...LimitedUserAvatar
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
graphql(`
|
||||
fragment SettingsWorkspacesMembersInvitesTable_Workspace on Workspace {
|
||||
id
|
||||
...SettingsWorkspacesMembersTableHeader_Workspace
|
||||
invitedTeam {
|
||||
...SettingsWorkspacesMembersInvitesTable_PendingWorkspaceCollaborator
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
const props = defineProps<{
|
||||
workspaceSlug: string
|
||||
workspace: MaybeNullOrUndefined<SettingsWorkspacesMembersInvitesTable_WorkspaceFragment>
|
||||
}>()
|
||||
|
||||
const search = ref('')
|
||||
const showActionsMenu = ref<Record<string, boolean>>({})
|
||||
|
||||
const cancelInvite = useCancelWorkspaceInvite()
|
||||
const resendInvite = useResendWorkspaceInvite()
|
||||
const { result: searchResult, loading: searchResultLoading } = useQuery(
|
||||
settingsWorkspacesInvitesSearchQuery,
|
||||
() => ({
|
||||
invitesFilter: {
|
||||
search: search.value
|
||||
},
|
||||
slug: props.workspaceSlug
|
||||
}),
|
||||
() => ({
|
||||
enabled: !!search.value.length
|
||||
})
|
||||
)
|
||||
|
||||
const invites = computed(() =>
|
||||
search.value.length
|
||||
? searchResult.value?.workspaceBySlug.invitedTeam
|
||||
: props.workspace?.invitedTeam
|
||||
)
|
||||
|
||||
const actionsItems: LayoutMenuItem[][] = [
|
||||
[{ title: 'Resend invite', id: 'resend-invite' }],
|
||||
[{ title: 'Delete invite', id: 'delete-invite' }]
|
||||
]
|
||||
|
||||
const onActionChosen = async (
|
||||
actionItem: LayoutMenuItem,
|
||||
item: NonNullable<typeof invites.value>[0]
|
||||
) => {
|
||||
if (!props.workspace?.id) return
|
||||
|
||||
switch (actionItem.id) {
|
||||
case 'resend-invite':
|
||||
await resendInvite({
|
||||
input: {
|
||||
workspaceId: props.workspace.id,
|
||||
inviteId: item.inviteId
|
||||
}
|
||||
})
|
||||
break
|
||||
case 'delete-invite':
|
||||
await cancelInvite({
|
||||
workspaceId: props.workspace.id,
|
||||
inviteId: item.inviteId
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const toggleMenu = (itemId: string) => {
|
||||
showActionsMenu.value[itemId] = !showActionsMenu.value[itemId]
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,143 @@
|
||||
<template>
|
||||
<div>
|
||||
<LayoutTable
|
||||
class="mt-2 mb-12"
|
||||
:columns="[
|
||||
{ id: 'name', header: 'Name', classes: 'col-span-2' },
|
||||
{ id: 'email', header: 'Email', classes: 'col-span-3' },
|
||||
{ id: 'createdAt', header: 'Requested at', classes: 'col-span-3' },
|
||||
{ id: 'status', header: 'Status', classes: 'col-span-2' },
|
||||
{
|
||||
id: 'actions',
|
||||
header: '',
|
||||
classes: 'col-span-2 flex items-center justify-end'
|
||||
}
|
||||
]"
|
||||
:items="joinRequests"
|
||||
empty-message="There are no pending join requests"
|
||||
>
|
||||
<template #name="{ item }">
|
||||
<div class="flex items-center gap-2">
|
||||
<UserAvatar hide-tooltip :user="item.user" />
|
||||
<span class="truncate text-body-xs text-foreground">
|
||||
{{ item.user.name }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<!-- TODO: Add email -->
|
||||
<template #email="">
|
||||
<span class="truncate text-body-xs text-foreground">EMAIL</span>
|
||||
</template>
|
||||
<template #createdAt="{ item }">
|
||||
<p class="text-body-xs text-foreground">
|
||||
{{ formattedFullDate(item.createdAt) }}
|
||||
</p>
|
||||
</template>
|
||||
<template #status="{ item }">
|
||||
<span class="truncate text-body-xs text-foreground capitalize">
|
||||
{{ item.status }}
|
||||
</span>
|
||||
</template>
|
||||
<template #actions="{ item }">
|
||||
<div
|
||||
v-if="item.status === WorkspaceJoinRequestStatus.Pending"
|
||||
class="flex items-center gap-x-2"
|
||||
>
|
||||
<FormButton color="outline" size="sm" @click="onApprove(item)">
|
||||
Approve
|
||||
</FormButton>
|
||||
<FormButton color="outline" size="sm" @click="onDeny(item)">Deny</FormButton>
|
||||
</div>
|
||||
<span v-else />
|
||||
</template>
|
||||
</LayoutTable>
|
||||
|
||||
<WorkspaceJoinRequestApproveDialog
|
||||
v-model:open="showApproveJoinRequestDialog"
|
||||
:join-request="requestToApprove"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { orderBy } from 'lodash-es'
|
||||
import dayjs from 'dayjs'
|
||||
import type { MaybeNullOrUndefined } from '@speckle/shared'
|
||||
import {
|
||||
type SettingsWorkspacesMembersRequestsTable_WorkspaceFragment,
|
||||
type WorkspaceJoinRequestApproveDialog_WorkspaceJoinRequestFragment,
|
||||
WorkspaceJoinRequestStatus
|
||||
} from '~~/lib/common/generated/gql/graphql'
|
||||
import { graphql } from '~/lib/common/generated/gql'
|
||||
import { useWorkspaceJoinRequest } from '~/lib/workspaces/composables/joinRequests'
|
||||
|
||||
graphql(`
|
||||
fragment SettingsWorkspacesMembersRequestsTable_Workspace on Workspace {
|
||||
...SettingsWorkspacesMembersTableHeader_Workspace
|
||||
id
|
||||
adminWorkspacesJoinRequests {
|
||||
totalCount
|
||||
items {
|
||||
...WorkspaceJoinRequestApproveDialog_WorkspaceJoinRequest
|
||||
id
|
||||
createdAt
|
||||
status
|
||||
user {
|
||||
id
|
||||
avatar
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
const props = defineProps<{
|
||||
workspace: MaybeNullOrUndefined<SettingsWorkspacesMembersRequestsTable_WorkspaceFragment>
|
||||
}>()
|
||||
|
||||
const { deny } = useWorkspaceJoinRequest()
|
||||
|
||||
const showApproveJoinRequestDialog = ref(false)
|
||||
const requestToApprove =
|
||||
ref<WorkspaceJoinRequestApproveDialog_WorkspaceJoinRequestFragment>()
|
||||
|
||||
const joinRequests = computed(() => {
|
||||
const thirtyDaysAgo = dayjs().subtract(30, 'days')
|
||||
|
||||
const filtered =
|
||||
props.workspace?.adminWorkspacesJoinRequests?.items.filter((request) => {
|
||||
if (request.status === WorkspaceJoinRequestStatus.Pending) return true
|
||||
return dayjs(request.createdAt).isAfter(thirtyDaysAgo)
|
||||
}) ?? []
|
||||
|
||||
return orderBy(
|
||||
filtered,
|
||||
[
|
||||
(request) => (request.status === WorkspaceJoinRequestStatus.Pending ? 1 : 0),
|
||||
'createdAt'
|
||||
],
|
||||
['desc', 'desc']
|
||||
)
|
||||
})
|
||||
|
||||
const onApprove = (
|
||||
request: WorkspaceJoinRequestApproveDialog_WorkspaceJoinRequestFragment
|
||||
) => {
|
||||
requestToApprove.value = request
|
||||
showApproveJoinRequestDialog.value = true
|
||||
}
|
||||
|
||||
const onDeny = async (
|
||||
request: WorkspaceJoinRequestApproveDialog_WorkspaceJoinRequestFragment
|
||||
) => {
|
||||
if (!props.workspace?.id) return
|
||||
await deny(
|
||||
{
|
||||
workspaceId: props.workspace?.id,
|
||||
userId: request.user.id
|
||||
},
|
||||
request.id
|
||||
)
|
||||
}
|
||||
</script>
|
||||
@@ -11,9 +11,11 @@
|
||||
<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
|
||||
show-invite-button
|
||||
/>
|
||||
<LayoutTable
|
||||
@@ -21,7 +23,7 @@
|
||||
:columns="[
|
||||
{ id: 'name', header: 'Name', classes: 'col-span-3' },
|
||||
{ id: 'email', header: 'Email', classes: 'col-span-3' },
|
||||
{ id: 'role', header: 'Seat', classes: 'col-span-2' },
|
||||
{ id: 'seat', header: 'Seat', classes: 'col-span-2' },
|
||||
{ id: 'joined', header: 'Joined', classes: 'col-span-3' },
|
||||
{
|
||||
id: 'actions',
|
||||
@@ -39,6 +41,13 @@
|
||||
<div class="flex items-center gap-2">
|
||||
<UserAvatar hide-tooltip :user="item" />
|
||||
<span class="truncate text-body-xs text-foreground">{{ item.name }}</span>
|
||||
<CommonBadge
|
||||
v-if="item.role === Roles.Workspace.Admin"
|
||||
rounded
|
||||
color-classes="bg-highlight-3 text-foreground-2"
|
||||
>
|
||||
Admin
|
||||
</CommonBadge>
|
||||
<div
|
||||
v-if="
|
||||
item.workspaceDomainPolicyCompliant === false &&
|
||||
@@ -57,11 +66,14 @@
|
||||
<!-- TODO: Add email -->
|
||||
<span class="text-body-xs text-foreground">EMAIL</span>
|
||||
</template>
|
||||
<template #role="{ item }">
|
||||
<span class="text-foreground-2">
|
||||
<span>
|
||||
{{ isWorkspaceRole(item.role) ? getRoleLabel(item.role).title : '' }}
|
||||
</span>
|
||||
<template #seat="{ item }">
|
||||
<span class="text-foreground">
|
||||
<div
|
||||
v-tippy="`Explainer`"
|
||||
class="border-b border-dashed border-outline-5 max-w-max select-none capitalize"
|
||||
>
|
||||
{{ item.seatType }}
|
||||
</div>
|
||||
</span>
|
||||
</template>
|
||||
<!-- TODO: Add joined at date -->
|
||||
@@ -112,7 +124,7 @@
|
||||
import { Roles, type WorkspaceRoles, type MaybeNullOrUndefined } from '@speckle/shared'
|
||||
import { settingsWorkspacesMembersSearchQuery } from '~~/lib/settings/graphql/queries'
|
||||
import { useQuery } from '@vue/apollo-composable'
|
||||
import type { SettingsWorkspacesMembersTable_WorkspaceFragment } from '~~/lib/common/generated/gql/graphql'
|
||||
import type { SettingsWorkspacesNewMembersTable_WorkspaceFragment } from '~~/lib/common/generated/gql/graphql'
|
||||
import { graphql } from '~/lib/common/generated/gql'
|
||||
import {
|
||||
EllipsisHorizontalIcon,
|
||||
@@ -122,7 +134,6 @@ import {
|
||||
import { useWorkspaceUpdateRole } from '~/lib/workspaces/composables/management'
|
||||
import type { LayoutMenuItem } from '~~/lib/layout/helpers/components'
|
||||
import { HorizontalDirection } from '~~/lib/common/composables/window'
|
||||
import { getRoleLabel } from '~~/lib/settings/helpers/utils'
|
||||
|
||||
type UserItem = (typeof members)['value'][0]
|
||||
|
||||
@@ -130,6 +141,7 @@ graphql(`
|
||||
fragment SettingsWorkspacesNewMembersTable_WorkspaceCollaborator on WorkspaceCollaborator {
|
||||
id
|
||||
role
|
||||
seatType
|
||||
user {
|
||||
id
|
||||
avatar
|
||||
@@ -149,7 +161,7 @@ graphql(`
|
||||
team {
|
||||
items {
|
||||
id
|
||||
...SettingsWorkspacesMembersTable_WorkspaceCollaborator
|
||||
...SettingsWorkspacesNewMembersTable_WorkspaceCollaborator
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -162,12 +174,13 @@ enum ActionTypes {
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
workspace: MaybeNullOrUndefined<SettingsWorkspacesMembersTable_WorkspaceFragment>
|
||||
workspace: MaybeNullOrUndefined<SettingsWorkspacesNewMembersTable_WorkspaceFragment>
|
||||
workspaceSlug: string
|
||||
}>()
|
||||
|
||||
const search = ref('')
|
||||
const roleFilter = ref<WorkspaceRoles>()
|
||||
const seatTypeFilter = ref<string>()
|
||||
|
||||
const { result: searchResult, loading: searchResultLoading } = useQuery(
|
||||
settingsWorkspacesMembersSearchQuery,
|
||||
@@ -176,12 +189,13 @@ const { result: searchResult, loading: searchResultLoading } = useQuery(
|
||||
search: search.value,
|
||||
roles: roleFilter.value
|
||||
? [roleFilter.value]
|
||||
: [Roles.Workspace.Admin, Roles.Workspace.Member]
|
||||
: [Roles.Workspace.Admin, Roles.Workspace.Member],
|
||||
seatType: seatTypeFilter.value
|
||||
},
|
||||
slug: props.workspaceSlug
|
||||
}),
|
||||
() => ({
|
||||
enabled: !!search.value.length || !!roleFilter.value
|
||||
enabled: !!search.value.length || !!roleFilter.value || !!seatTypeFilter.value
|
||||
})
|
||||
)
|
||||
|
||||
@@ -198,12 +212,13 @@ const showActionsMenu = ref<Record<string, boolean>>({})
|
||||
|
||||
const members = computed(() => {
|
||||
const memberArray =
|
||||
search.value.length || roleFilter.value
|
||||
search.value.length || roleFilter.value || seatTypeFilter.value
|
||||
? searchResult.value?.workspaceBySlug?.team.items
|
||||
: props.workspace?.team.items
|
||||
return (memberArray || [])
|
||||
.map(({ user, ...rest }) => ({
|
||||
.map(({ user, seatType, ...rest }) => ({
|
||||
...user,
|
||||
seatType,
|
||||
...rest
|
||||
}))
|
||||
.filter((user) => user.role !== Roles.Workspace.Guest)
|
||||
@@ -218,7 +233,7 @@ const canRemoveMember = computed(
|
||||
)
|
||||
const hasNoResults = computed(
|
||||
() =>
|
||||
(search.value.length || roleFilter.value) &&
|
||||
(search.value.length || roleFilter.value || seatTypeFilter.value) &&
|
||||
searchResult.value?.workspaceBySlug?.team.items.length === 0
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user