Merge pull request #2447 from specklesystems/mike/move-server-management-table-to-layout-table
Fix: Change ServerManagementTable to LayoutTable
This commit is contained in:
@@ -1,109 +0,0 @@
|
||||
<!-- eslint-disable vuejs-accessibility/no-static-element-interactions -->
|
||||
<template>
|
||||
<div class="text-foreground">
|
||||
<div class="w-full text-sm overflow-x-auto overflow-y-visible simple-scrollbar">
|
||||
<div
|
||||
class="grid z-10 grid-cols-12 items-center gap-6 font-semibold bg-foundation rounded-t-lg w-full border-b border-outline-3 pb-2 pt-4 px-4 min-w-[900px]"
|
||||
:style="{ paddingRight: paddingRightStyle }"
|
||||
>
|
||||
<div
|
||||
v-for="header in headers"
|
||||
:key="header.id"
|
||||
:class="columnClasses[header.id]"
|
||||
class="capitalize"
|
||||
>
|
||||
{{ header.title }}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="divide-y divide-outline-3 h-full overflow-visible"
|
||||
:class="{ 'pb-32': overflowCells }"
|
||||
>
|
||||
<div
|
||||
v-for="item in items"
|
||||
:key="item.id"
|
||||
class="relative grid grid-cols-12 items-center gap-6 px-4 py-1 min-w-[900px] bg-foundation"
|
||||
:style="{ paddingRight: paddingRightStyle }"
|
||||
:class="{ 'cursor-pointer hover:bg-primary-muted': !!props.onRowClick }"
|
||||
tabindex="0"
|
||||
@click="handleRowClick(item)"
|
||||
@keypress="keyboardClick(() => handleRowClick(item))"
|
||||
>
|
||||
<template v-for="(column, colIndex) in headers" :key="column.id">
|
||||
<div :class="getClasses(column.id, colIndex)" tabindex="0">
|
||||
<slot :name="column.id" :item="item">
|
||||
<div class="text-gray-900 font-medium order-1">
|
||||
{{ (item as any)[column.id] }}
|
||||
</div>
|
||||
</slot>
|
||||
</div>
|
||||
</template>
|
||||
<div class="absolute right-0 flex items-center p-0 pr-0.5">
|
||||
<div v-for="button in buttons" :key="button.label" class="p-1">
|
||||
<FormButton
|
||||
:icon-left="button.icon"
|
||||
size="sm"
|
||||
color="secondary"
|
||||
hide-text
|
||||
class="text-red-500"
|
||||
@click.stop="button.action(item)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import type { ConcreteComponent } from 'vue'
|
||||
import type {
|
||||
ItemType,
|
||||
UserItem,
|
||||
ProjectItem,
|
||||
InviteItem
|
||||
} from '~~/lib/server-management/helpers/types'
|
||||
import { keyboardClick } from '@speckle/ui-components'
|
||||
|
||||
type OnRowClickType = (item: ItemType) => void
|
||||
|
||||
interface RowButton {
|
||||
icon: ConcreteComponent
|
||||
label: string
|
||||
action: (item: ItemType) => void
|
||||
}
|
||||
|
||||
interface Header {
|
||||
id: string
|
||||
title: string
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
headers: Header[]
|
||||
items: Array<UserItem | ProjectItem | InviteItem>
|
||||
buttons?: RowButton[]
|
||||
columnClasses: Record<string, string>
|
||||
overflowCells?: boolean
|
||||
onRowClick?: OnRowClickType
|
||||
}>()
|
||||
|
||||
const paddingRightStyle = computed(() => {
|
||||
const padding = 52 + ((props.buttons?.length || 0) - 1) * 25
|
||||
return `${padding}px`
|
||||
})
|
||||
|
||||
const getClasses = (column: string, colIndex: number): string => {
|
||||
const columnClass = props.columnClasses[column]
|
||||
|
||||
if (colIndex === 0) {
|
||||
return `bg-transparent py-3 pr-5 px-1 ${columnClass}`
|
||||
}
|
||||
return `lg:p-0 px-1 ${columnClass}`
|
||||
}
|
||||
|
||||
const handleRowClick = (item: ItemType) => {
|
||||
props.onRowClick?.(item)
|
||||
}
|
||||
</script>
|
||||
@@ -29,25 +29,17 @@
|
||||
@change="($event) => searchUpdateHandler($event.value)"
|
||||
/>
|
||||
|
||||
<ServerManagementTable
|
||||
<LayoutTable
|
||||
class="mt-8"
|
||||
:headers="[
|
||||
{ id: 'name', title: 'Name' },
|
||||
{ id: 'email', title: 'Email' },
|
||||
{ id: 'emailState', title: 'Email State' },
|
||||
{ id: 'company', title: 'Company' },
|
||||
{ id: 'role', title: 'Role' }
|
||||
:columns="[
|
||||
{ id: 'name', header: 'Name', classes: 'col-span-3 truncate' },
|
||||
{ id: 'email', header: 'Email', classes: 'col-span-3 truncate' },
|
||||
{ id: 'emailState', header: 'Email State', classes: 'col-span-2' },
|
||||
{ id: 'company', header: 'Company', classes: 'col-span-2 truncate' },
|
||||
{ id: 'role', header: 'Role', classes: 'col-span-2' }
|
||||
]"
|
||||
:items="users"
|
||||
:buttons="[{ icon: TrashIcon, label: 'Delete', action: openUserDeleteDialog }]"
|
||||
:column-classes="{
|
||||
name: 'col-span-3 truncate',
|
||||
email: 'col-span-3 truncate',
|
||||
emailState: 'col-span-2',
|
||||
company: 'col-span-2 truncate',
|
||||
role: 'col-span-2'
|
||||
}"
|
||||
:overflow-cells="true"
|
||||
>
|
||||
<template #name="{ item }">
|
||||
<div class="flex items-center gap-2">
|
||||
@@ -65,12 +57,12 @@
|
||||
<template #emailState="{ item }">
|
||||
<div class="flex items-center gap-2 select-none">
|
||||
<template v-if="isUser(item) && item.verified">
|
||||
<ShieldCheckIcon class="h-4 w-4 text-primary" />
|
||||
<span>verified</span>
|
||||
<CheckCircleIcon class="h-4 w-4 text-primary" />
|
||||
<span>Verified</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<ShieldExclamationIcon class="h-4 w-4 text-danger" />
|
||||
<span>not verified</span>
|
||||
<ExclamationCircleIcon class="h-4 w-4 text-danger" />
|
||||
<span>Not verified</span>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
@@ -90,7 +82,7 @@
|
||||
@update:model-value="(newRoleValue) => isUser(item) && !isArray(newRoleValue) && newRoleValue && openChangeUserRoleDialog(item, newRoleValue as ServerRoles)"
|
||||
/>
|
||||
</template>
|
||||
</ServerManagementTable>
|
||||
</LayoutTable>
|
||||
|
||||
<CommonLoadingBar v-if="loading && !users?.length" loading />
|
||||
|
||||
@@ -131,11 +123,11 @@ import { isUser } from '~~/lib/server-management/helpers/utils'
|
||||
import { useActiveUser } from '~~/lib/auth/composables/activeUser'
|
||||
import {
|
||||
MagnifyingGlassIcon,
|
||||
ShieldExclamationIcon,
|
||||
ShieldCheckIcon,
|
||||
ExclamationCircleIcon,
|
||||
CheckCircleIcon,
|
||||
TrashIcon,
|
||||
UserPlusIcon
|
||||
} from '@heroicons/vue/20/solid'
|
||||
} from '@heroicons/vue/24/outline'
|
||||
import { useServerInfo } from '~~/lib/core/composables/server'
|
||||
|
||||
useHead({
|
||||
|
||||
@@ -29,22 +29,17 @@
|
||||
@change="($event) => searchUpdateHandler($event.value)"
|
||||
/>
|
||||
|
||||
<ServerManagementTable
|
||||
<LayoutTable
|
||||
class="mt-8"
|
||||
:headers="[
|
||||
{ id: 'email', title: 'Email' },
|
||||
{ id: 'invitedBy', title: 'Invited By' },
|
||||
{ id: 'resend', title: '' }
|
||||
:columns="[
|
||||
{ id: 'email', header: 'Email', classes: 'col-span-5 truncate' },
|
||||
{ id: 'invitedBy', header: 'Invited By', classes: 'col-span-4' },
|
||||
{ id: 'resend', header: '', classes: 'col-span-3' }
|
||||
]"
|
||||
:items="invites"
|
||||
:buttons="[
|
||||
{ icon: TrashIcon, label: 'Delete', action: openDeleteInvitationDialog }
|
||||
]"
|
||||
:column-classes="{
|
||||
email: 'col-span-5 truncate',
|
||||
invitedBy: 'col-span-4',
|
||||
resend: 'col-span-3'
|
||||
}"
|
||||
>
|
||||
<template #email="{ item }">
|
||||
{{ isInvite(item) ? item.email : '' }}
|
||||
@@ -77,7 +72,7 @@
|
||||
}}
|
||||
</FormButton>
|
||||
</template>
|
||||
</ServerManagementTable>
|
||||
</LayoutTable>
|
||||
|
||||
<ServerManagementDeleteInvitationDialog
|
||||
v-model:open="showDeleteInvitationDialog"
|
||||
@@ -101,7 +96,7 @@
|
||||
import { ref } from 'vue'
|
||||
import { debounce } from 'lodash-es'
|
||||
import { useQuery, useMutation } from '@vue/apollo-composable'
|
||||
import { MagnifyingGlassIcon, TrashIcon, UserPlusIcon } from '@heroicons/vue/20/solid'
|
||||
import { MagnifyingGlassIcon, TrashIcon, UserPlusIcon } from '@heroicons/vue/24/outline'
|
||||
import type { ItemType, InviteItem } from '~~/lib/server-management/helpers/types'
|
||||
import type { InfiniteLoaderState } from '~~/lib/global/helpers/components'
|
||||
import { getInvitesQuery } from '~~/lib/server-management/graphql/queries'
|
||||
|
||||
@@ -24,28 +24,19 @@
|
||||
@change="($event) => searchUpdateHandler($event.value)"
|
||||
/>
|
||||
|
||||
<ServerManagementTable
|
||||
<LayoutTable
|
||||
class="mt-8"
|
||||
:headers="[
|
||||
{ id: 'name', title: 'Name' },
|
||||
{ id: 'type', title: 'Type' },
|
||||
{ id: 'created', title: 'Created' },
|
||||
{ id: 'modified', title: 'Modified' },
|
||||
{ id: 'models', title: 'Models' },
|
||||
{ id: 'versions', title: 'Versions' },
|
||||
{ id: 'contributors', title: 'Contributors' }
|
||||
:columns="[
|
||||
{ id: 'name', header: 'Name', classes: 'col-span-3 truncate' },
|
||||
{ id: 'type', header: 'Type', classes: 'col-span-1' },
|
||||
{ id: 'created', header: 'Created', classes: 'col-span-2' },
|
||||
{ id: 'modified', header: 'Modified', classes: 'col-span-2' },
|
||||
{ id: 'models', header: 'Models', classes: 'col-span-1 text-right' },
|
||||
{ id: 'versions', header: 'Versions', classes: 'col-span-1 text-right' },
|
||||
{ id: 'contributors', header: 'Contributors', classes: 'col-span-2' }
|
||||
]"
|
||||
:items="projects"
|
||||
:buttons="[{ icon: TrashIcon, label: 'Delete', action: openProjectDeleteDialog }]"
|
||||
:column-classes="{
|
||||
name: 'col-span-3 truncate',
|
||||
type: 'col-span-1',
|
||||
created: 'col-span-2',
|
||||
modified: 'col-span-2',
|
||||
models: 'col-span-1 text-right',
|
||||
versions: 'col-span-1 text-right',
|
||||
contributors: 'col-span-2'
|
||||
}"
|
||||
:on-row-click="handleProjectClick"
|
||||
>
|
||||
<template #name="{ item }">
|
||||
@@ -53,33 +44,25 @@
|
||||
</template>
|
||||
|
||||
<template #type="{ item }">
|
||||
<div class="capitalize">
|
||||
<span class="capitalize">
|
||||
{{ isProject(item) ? item.visibility.toLowerCase() : '' }}
|
||||
</div>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<template #created="{ item }">
|
||||
<div class="font-mono text-xs">
|
||||
{{ isProject(item) ? new Date(item.createdAt).toLocaleString('en-GB') : '' }}
|
||||
</div>
|
||||
{{ isProject(item) ? new Date(item.createdAt).toLocaleString('en-GB') : '' }}
|
||||
</template>
|
||||
|
||||
<template #modified="{ item }">
|
||||
<div class="font-mono text-xs">
|
||||
{{ isProject(item) ? new Date(item.updatedAt).toLocaleString('en-GB') : '' }}
|
||||
</div>
|
||||
{{ isProject(item) ? new Date(item.updatedAt).toLocaleString('en-GB') : '' }}
|
||||
</template>
|
||||
|
||||
<template #models="{ item }">
|
||||
<div class="font-mono text-xs">
|
||||
{{ isProject(item) ? item.models.totalCount : '' }}
|
||||
</div>
|
||||
{{ isProject(item) ? item.models.totalCount : '' }}
|
||||
</template>
|
||||
|
||||
<template #versions="{ item }">
|
||||
<div class="font-mono text-xs">
|
||||
{{ isProject(item) ? item.versions.totalCount : '' }}
|
||||
</div>
|
||||
{{ isProject(item) ? item.versions.totalCount : '' }}
|
||||
</template>
|
||||
|
||||
<template #contributors="{ item }">
|
||||
@@ -87,7 +70,7 @@
|
||||
<UserAvatarGroup :users="item.team.map((t) => t.user)" :max-count="3" />
|
||||
</div>
|
||||
</template>
|
||||
</ServerManagementTable>
|
||||
</LayoutTable>
|
||||
|
||||
<CommonLoadingBar v-if="loading && !projects?.length" loading />
|
||||
|
||||
@@ -113,7 +96,7 @@
|
||||
import { ref } from 'vue'
|
||||
import { debounce } from 'lodash-es'
|
||||
import { useQuery } from '@vue/apollo-composable'
|
||||
import { MagnifyingGlassIcon, TrashIcon, PlusIcon } from '@heroicons/vue/20/solid'
|
||||
import { MagnifyingGlassIcon, TrashIcon, PlusIcon } from '@heroicons/vue/24/outline'
|
||||
import { getProjectsQuery } from '~~/lib/server-management/graphql/queries'
|
||||
import type { ItemType, ProjectItem } from '~~/lib/server-management/helpers/types'
|
||||
import type { InfiniteLoaderState } from '~~/lib/global/helpers/components'
|
||||
|
||||
Reference in New Issue
Block a user