mirror of https://github.com/nocodb/nocodb
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
285 lines
9.5 KiB
285 lines
9.5 KiB
<script lang="ts" setup> |
|
import { |
|
OrderedProjectRoles, |
|
OrgUserRoles, |
|
ProjectRoles, |
|
WorkspaceRolesToProjectRoles, |
|
extractRolesObj, |
|
parseStringDateTime, |
|
timeAgo, |
|
} from 'nocodb-sdk' |
|
import type { Roles, WorkspaceUserRoles } from 'nocodb-sdk' |
|
import type { User } from '#imports' |
|
import { isEeUI, storeToRefs, useUserSorts } from '#imports' |
|
|
|
const basesStore = useBases() |
|
const { getBaseUsers, createProjectUser, updateProjectUser, removeProjectUser } = basesStore |
|
const { activeProjectId } = storeToRefs(basesStore) |
|
|
|
const { orgRoles, baseRoles } = useRoles() |
|
|
|
const { sorts, sortDirection, loadSorts, saveOrUpdate, handleGetSortedData } = useUserSorts('Project') |
|
|
|
const isSuper = computed(() => orgRoles.value?.[OrgUserRoles.SUPER_ADMIN]) |
|
|
|
const isInviteModalVisible = ref(false) |
|
|
|
interface Collaborators { |
|
id: string |
|
email: string |
|
main_roles: OrgUserRoles |
|
roles: ProjectRoles |
|
base_roles: Roles |
|
workspace_roles: WorkspaceUserRoles |
|
created_at: string |
|
display_name: string | null |
|
} |
|
const collaborators = ref<Collaborators[]>([]) |
|
const totalCollaborators = ref(0) |
|
const userSearchText = ref('') |
|
|
|
const isLoading = ref(false) |
|
const isSearching = ref(false) |
|
const accessibleRoles = ref<(typeof ProjectRoles)[keyof typeof ProjectRoles][]>([]) |
|
|
|
const filteredCollaborators = computed(() => |
|
collaborators.value.filter( |
|
(collab) => |
|
collab.display_name?.toLowerCase()?.includes(userSearchText.value.toLowerCase()) || |
|
collab.email.toLowerCase().includes(userSearchText.value.toLowerCase()), |
|
), |
|
) |
|
|
|
const sortedCollaborators = computed(() => { |
|
return handleGetSortedData(filteredCollaborators.value, sorts.value) |
|
}) |
|
|
|
const loadCollaborators = async () => { |
|
try { |
|
const { users, totalRows } = await getBaseUsers({ |
|
baseId: activeProjectId.value!, |
|
...(!userSearchText.value ? {} : ({ searchText: userSearchText.value } as any)), |
|
force: true, |
|
}) |
|
|
|
totalCollaborators.value = totalRows |
|
collaborators.value = [ |
|
...users |
|
.filter((u: any) => !u?.deleted) |
|
.map((user: any) => ({ |
|
...user, |
|
base_roles: user.roles, |
|
roles: extractRolesObj(user.main_roles)?.[OrgUserRoles.SUPER_ADMIN] |
|
? OrgUserRoles.SUPER_ADMIN |
|
: user.roles ?? |
|
(user.workspace_roles |
|
? WorkspaceRolesToProjectRoles[user.workspace_roles as WorkspaceUserRoles] ?? ProjectRoles.NO_ACCESS |
|
: ProjectRoles.NO_ACCESS), |
|
})), |
|
] |
|
} catch (e: any) { |
|
message.error(await extractSdkResponseErrorMsg(e)) |
|
} |
|
} |
|
|
|
const updateCollaborator = async (collab: any, roles: ProjectRoles) => { |
|
const currentCollaborator = collaborators.value.find((coll) => coll.id === collab.id)! |
|
|
|
try { |
|
if ( |
|
!roles || |
|
(roles === ProjectRoles.NO_ACCESS && !isEeUI) || |
|
(currentCollaborator.workspace_roles && |
|
WorkspaceRolesToProjectRoles[currentCollaborator.workspace_roles as WorkspaceUserRoles] === roles && |
|
isEeUI) |
|
) { |
|
await removeProjectUser(activeProjectId.value!, currentCollaborator as unknown as User) |
|
if ( |
|
currentCollaborator.workspace_roles && |
|
WorkspaceRolesToProjectRoles[currentCollaborator.workspace_roles as WorkspaceUserRoles] === roles && |
|
isEeUI |
|
) { |
|
currentCollaborator.roles = WorkspaceRolesToProjectRoles[currentCollaborator.workspace_roles as WorkspaceUserRoles] |
|
} else { |
|
currentCollaborator.roles = ProjectRoles.NO_ACCESS |
|
} |
|
} else if (currentCollaborator.base_roles) { |
|
currentCollaborator.roles = roles |
|
await updateProjectUser(activeProjectId.value!, currentCollaborator as unknown as User) |
|
} else { |
|
currentCollaborator.roles = roles |
|
currentCollaborator.base_roles = roles |
|
await createProjectUser(activeProjectId.value!, currentCollaborator as unknown as User) |
|
} |
|
} catch (e: any) { |
|
message.error(await extractSdkResponseErrorMsg(e)) |
|
loadCollaborators() |
|
} |
|
} |
|
|
|
onMounted(async () => { |
|
isLoading.value = true |
|
try { |
|
await loadCollaborators() |
|
const currentRoleIndex = OrderedProjectRoles.findIndex( |
|
(role) => baseRoles.value && Object.keys(baseRoles.value).includes(role), |
|
) |
|
if (isSuper.value) { |
|
accessibleRoles.value = OrderedProjectRoles.slice(1) |
|
} else if (currentRoleIndex !== -1) { |
|
accessibleRoles.value = OrderedProjectRoles.slice(currentRoleIndex + 1) |
|
} |
|
loadSorts() |
|
} catch (e: any) { |
|
message.error(await extractSdkResponseErrorMsg(e)) |
|
} finally { |
|
isLoading.value = false |
|
} |
|
}) |
|
|
|
watch(isInviteModalVisible, () => { |
|
if (!isInviteModalVisible.value) { |
|
loadCollaborators() |
|
} |
|
}) |
|
</script> |
|
|
|
<template> |
|
<div class="nc-collaborator-table-container mt-4 nc-access-settings-view h-[calc(100vh-8rem)]"> |
|
<LazyProjectShareBaseDlg v-model:model-value="isInviteModalVisible" /> |
|
<div v-if="isLoading" class="nc-collaborators-list items-center justify-center"> |
|
<GeneralLoader size="xlarge" /> |
|
</div> |
|
<template v-else> |
|
<div class="w-full flex flex-row justify-between items-baseline max-w-350 mt-6.5 mb-2 pr-0.25"> |
|
<a-input v-model:value="userSearchText" class="!max-w-90 !rounded-md" :placeholder="$t('title.searchMembers')"> |
|
<template #prefix> |
|
<PhMagnifyingGlassBold class="!h-3.5 text-gray-500" /> |
|
</template> |
|
</a-input> |
|
|
|
<NcButton size="small" @click="isInviteModalVisible = true"> |
|
<div class="flex gap-1"> |
|
<component :is="iconMap.plus" class="w-4 h-4" /> |
|
{{ $t('activity.addMembers') }} |
|
</div> |
|
</NcButton> |
|
</div> |
|
|
|
<div v-if="isSearching" class="nc-collaborators-list items-center justify-center"> |
|
<GeneralLoader size="xlarge" /> |
|
</div> |
|
|
|
<div |
|
v-else-if="!filteredCollaborators?.length" |
|
class="nc-collaborators-list w-full h-full flex flex-col items-center justify-center mt-36" |
|
> |
|
<a-empty description="$t('title.noMembersFound')" /> |
|
</div> |
|
<div v-else class="nc-collaborators-list mt-6 h-full"> |
|
<div class="flex flex-col rounded-lg overflow-hidden border-1 max-w-350 max-h-[calc(100%-8rem)]"> |
|
<div class="flex flex-row bg-gray-50 min-h-12 items-center border-b-1"> |
|
<div class="text-gray-700 users-email-grid flex items-center space-x-2"> |
|
<span> |
|
{{ $t('objects.users') }} |
|
</span> |
|
<LazyAccountUserMenu :direction="sortDirection.email" field="email" :handle-user-sort="saveOrUpdate" /> |
|
</div> |
|
|
|
<div class="text-gray-700 user-access-grid flex items-center space-x-2"> |
|
<span> |
|
{{ $t('general.access') }} |
|
</span> |
|
<LazyAccountUserMenu :direction="sortDirection.roles" field="roles" :handle-user-sort="saveOrUpdate" /> |
|
</div> |
|
<div class="text-gray-700 date-joined-grid">{{ $t('title.dateJoined') }}</div> |
|
</div> |
|
|
|
<div class="flex flex-col nc-scrollbar-md"> |
|
<div |
|
v-for="(collab, i) of sortedCollaborators" |
|
:key="i" |
|
class="user-row flex flex-row border-b-1 py-1 min-h-14 items-center" |
|
> |
|
<div class="flex gap-3 items-center users-email-grid"> |
|
<GeneralUserIcon size="base" :email="collab.email" /> |
|
<NcTooltip v-if="collab.display_name"> |
|
<template #title> |
|
{{ collab.email }} |
|
</template> |
|
<span class="truncate"> |
|
{{ collab.display_name }} |
|
</span> |
|
</NcTooltip> |
|
<span v-else class="truncate"> |
|
{{ collab.email }} |
|
</span> |
|
</div> |
|
<div class="user-access-grid"> |
|
<template v-if="accessibleRoles.includes(collab.roles)"> |
|
<RolesSelector |
|
:role="collab.roles" |
|
:roles="accessibleRoles" |
|
:inherit=" |
|
isEeUI && collab.workspace_roles && WorkspaceRolesToProjectRoles[collab.workspace_roles] |
|
? WorkspaceRolesToProjectRoles[collab.workspace_roles] |
|
: null |
|
" |
|
:description="false" |
|
:on-role-change="(role) => updateCollaborator(collab, role as ProjectRoles)" |
|
/> |
|
</template> |
|
<template v-else> |
|
<RolesBadge :role="collab.roles" /> |
|
</template> |
|
</div> |
|
<div class="date-joined-grid"> |
|
<NcTooltip class="max-w-full"> |
|
<template #title> |
|
{{ parseStringDateTime(collab.created_at) }} |
|
</template> |
|
<span> |
|
{{ timeAgo(collab.created_at) }} |
|
</span> |
|
</NcTooltip> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</template> |
|
</div> |
|
</template> |
|
|
|
<style scoped lang="scss"> |
|
.color-band { |
|
@apply w-6 h-6 left-0 top-2.5 rounded-full flex justify-center uppercase text-white font-weight-bold text-xs items-center; |
|
} |
|
|
|
:deep(.nc-collaborator-role-select .ant-select-selector) { |
|
@apply !rounded; |
|
} |
|
|
|
:deep(.ant-select-selection-item) { |
|
@apply mt-0.75; |
|
} |
|
|
|
.users-email-grid { |
|
@apply flex-grow ml-4 w-1/2; |
|
} |
|
|
|
.date-joined-grid { |
|
@apply w-1/4 flex items-start; |
|
} |
|
|
|
.user-access-grid { |
|
@apply w-1/4 flex justify-start; |
|
} |
|
|
|
.user-row { |
|
@apply w-full; |
|
} |
|
.user-row:last-child { |
|
@apply border-b-0; |
|
} |
|
</style>
|
|
|