Feat: Add role label, version count, and update styling of project header (#2820)
This commit is contained in:
@@ -1,9 +1,8 @@
|
||||
<template>
|
||||
<div class="flex flex-col gap-1">
|
||||
<h2 class="text-heading-xl">{{ title }}</h2>
|
||||
<p v-if="description" class="text-body-sm text-foreground-2">{{ description }}</p>
|
||||
<p v-else class="text-body-sm text-foreground-2 italic select-none">
|
||||
No description
|
||||
<div class="flex flex-col">
|
||||
<h2 class="text-heading">{{ title }}</h2>
|
||||
<p class="text-body-sm text-foreground-2">
|
||||
{{ description ? description : 'No description' }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -25,21 +25,23 @@
|
||||
></HeaderNavLink>
|
||||
</Portal>
|
||||
|
||||
<CommonTitleDescription :title="project.name" :description="project.description" />
|
||||
<NuxtLink
|
||||
v-if="project.workspace && isWorkspacesEnabled"
|
||||
:to="workspaceRoute(project.workspace.id)"
|
||||
class="pt-4 flex-1 flex items-center"
|
||||
>
|
||||
<WorkspaceAvatar
|
||||
:logo="project.workspace.logo"
|
||||
:default-logo-index="project.workspace.defaultLogoIndex"
|
||||
size="sm"
|
||||
<div class="flex gap-x-3">
|
||||
<NuxtLink
|
||||
v-if="project.workspace && isWorkspacesEnabled"
|
||||
:to="workspaceRoute(project.workspace.id)"
|
||||
>
|
||||
<WorkspaceAvatar
|
||||
:logo="project.workspace.logo"
|
||||
:default-logo-index="project.workspace.defaultLogoIndex"
|
||||
size="sm"
|
||||
class="mt-0.5"
|
||||
/>
|
||||
</NuxtLink>
|
||||
<CommonTitleDescription
|
||||
:title="project.name"
|
||||
:description="project.description"
|
||||
/>
|
||||
<p class="text-body-2xs text-foreground ml-2">
|
||||
{{ project.workspace.name }}
|
||||
</p>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
<template>
|
||||
<div class="bg-foundation rounded-lg p-4 border border-outline-3">
|
||||
<div class="flex flex-col">
|
||||
<div class="text-foreground-2 mb-2">
|
||||
<slot name="top" />
|
||||
</div>
|
||||
<div class="text-foreground">
|
||||
<slot name="bottom" />
|
||||
</div>
|
||||
</div>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,39 +0,0 @@
|
||||
<template>
|
||||
<ProjectPageStatsBlock>
|
||||
<template #top>
|
||||
<div class="flex items-center">
|
||||
<div class="flex items-center justify-between w-full">
|
||||
<div class="flex items-center gap-1 flex-grow select-none">
|
||||
<span class="text-heading-sm text-foreground">Collaborators</span>
|
||||
</div>
|
||||
<div class="text-body-xs flex items-center capitalize">
|
||||
{{ project.role?.split(':').reverse()[0] }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #bottom>
|
||||
<div class="flex items-center justify-between mt-1">
|
||||
<UserAvatarGroup :users="teamUsers" class="max-w-[104px]" />
|
||||
<div v-if="canEdit">
|
||||
<FormButton class="ml-2" :to="projectCollaboratorsRoute(project.id)">
|
||||
Manage
|
||||
</FormButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</ProjectPageStatsBlock>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { canEditProject } from '~~/lib/projects/helpers/permissions'
|
||||
import type { ProjectPageTeamInternals_ProjectFragment } from '~~/lib/common/generated/gql/graphql'
|
||||
import { projectCollaboratorsRoute } from '~~/lib/common/helpers/route'
|
||||
|
||||
const props = defineProps<{
|
||||
project: ProjectPageTeamInternals_ProjectFragment
|
||||
}>()
|
||||
|
||||
const canEdit = computed(() => canEditProject(props.project))
|
||||
|
||||
const teamUsers = computed(() => props.project.team.map((t) => t.user))
|
||||
</script>
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="flex flex-col sm:gap-4 sm:flex-row justify-between sm:items-center">
|
||||
<div class="flex gap-2 mb-3 mt-2">
|
||||
<div class="flex items-center">
|
||||
<div class="flex flex-col gap-4 sm:flex-row justify-between md:items-center">
|
||||
<div class="flex gap-2 md:mb-3 md:mt-2">
|
||||
<div class="flex items-center mr-2">
|
||||
<WorkspaceAvatar
|
||||
:logo="workspaceInfo.logo"
|
||||
:default-logo-index="workspaceInfo.defaultLogoIndex"
|
||||
@@ -9,45 +9,65 @@
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<h1 class="text-heading-lg">{{ workspaceInfo.name }}</h1>
|
||||
<h1 class="text-heading">{{ workspaceInfo.name }}</h1>
|
||||
<div class="text-body-xs text-foreground-2">
|
||||
{{ workspaceInfo.description || 'No workspace description' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<div
|
||||
class="flex md:items-center gap-x-3 md:flex-row"
|
||||
:class="[isWorkspaceAdmin ? 'flex-col' : 'flex-row items-cenetr']"
|
||||
>
|
||||
<div
|
||||
class="text-body-3xs bg-foundation-2 text-foreground-2 rounded px-3 py-1 font-medium select-none whitespace-nowrap"
|
||||
class="flex items-center gap-x-3 md:mb-0"
|
||||
:class="[!isWorkspaceAdmin ? 'flex-1' : ' mb-3']"
|
||||
>
|
||||
{{ workspaceInfo.totalProjects.totalCount || 0 }} Project{{
|
||||
workspaceInfo.totalProjects.totalCount === 1 ? '' : 's'
|
||||
}}
|
||||
<CommonBadge rounded :color-classes="'text-foreground-2 bg-primary-muted'">
|
||||
{{ workspaceInfo.totalProjects.totalCount || 0 }} Project{{
|
||||
workspaceInfo.totalProjects.totalCount === 1 ? '' : 's'
|
||||
}}
|
||||
</CommonBadge>
|
||||
<CommonBadge rounded :color-classes="'text-foreground-2 bg-primary-muted'">
|
||||
<span class="capitalize">
|
||||
{{ workspaceInfo.role?.split(':').reverse()[0] }}
|
||||
</span>
|
||||
</CommonBadge>
|
||||
</div>
|
||||
<div class="flex items-center gap-x-3">
|
||||
<div v-if="isWorkspaceAdmin" class="flex-1 md:flex-auto">
|
||||
<WorkspacePageVersionCount
|
||||
:versions-count="workspaceInfo.billing.versionsCount"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center gap-x-3">
|
||||
<UserAvatarGroup
|
||||
:users="team.map((teamMember) => teamMember.user)"
|
||||
class="max-w-[104px]"
|
||||
/>
|
||||
<FormButton
|
||||
v-if="isWorkspaceAdmin"
|
||||
color="outline"
|
||||
@click="showInviteDialog = !showInviteDialog"
|
||||
>
|
||||
Invite
|
||||
</FormButton>
|
||||
<LayoutMenu
|
||||
v-model:open="showActionsMenu"
|
||||
:items="actionsItems"
|
||||
:menu-position="HorizontalDirection.Left"
|
||||
@click.stop.prevent
|
||||
@chosen="onActionChosen"
|
||||
>
|
||||
<FormButton
|
||||
color="subtle"
|
||||
hide-text
|
||||
:icon-right="EllipsisHorizontalIcon"
|
||||
@click="showActionsMenu = !showActionsMenu"
|
||||
/>
|
||||
</LayoutMenu>
|
||||
</div>
|
||||
</div>
|
||||
<UserAvatarGroup
|
||||
:users="team.map((teamMember) => teamMember.user)"
|
||||
class="max-w-[104px]"
|
||||
/>
|
||||
<FormButton
|
||||
v-if="isWorkspaceAdmin"
|
||||
color="outline"
|
||||
@click="showInviteDialog = !showInviteDialog"
|
||||
>
|
||||
Invite
|
||||
</FormButton>
|
||||
<LayoutMenu
|
||||
v-model:open="showActionsMenu"
|
||||
:items="actionsItems"
|
||||
:menu-position="HorizontalDirection.Left"
|
||||
@click.stop.prevent
|
||||
@chosen="onActionChosen"
|
||||
>
|
||||
<FormButton
|
||||
color="subtle"
|
||||
hide-text
|
||||
:icon-right="EllipsisHorizontalIcon"
|
||||
@click="showActionsMenu = !showActionsMenu"
|
||||
/>
|
||||
</LayoutMenu>
|
||||
</div>
|
||||
<WorkspaceInviteDialog
|
||||
v-model:open="showInviteDialog"
|
||||
@@ -82,6 +102,11 @@ graphql(`
|
||||
totalProjects: projects {
|
||||
totalCount
|
||||
}
|
||||
billing {
|
||||
versionsCount {
|
||||
...WorkspacePageVersionCount_WorkspaceVersionsCount
|
||||
}
|
||||
}
|
||||
team {
|
||||
items {
|
||||
id
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<template>
|
||||
<div class="w-40 flex flex-col items-center md:mx-4">
|
||||
<CommonProgressBar
|
||||
class="mb-1"
|
||||
:current-value="versionsCount.current"
|
||||
:max-value="versionsCount.max"
|
||||
/>
|
||||
<div class="text-body-3xs text-foreground">
|
||||
<span class="font-medium">
|
||||
{{ versionsCount.current }}/{{ versionsCount.max }}
|
||||
</span>
|
||||
model versions used
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { graphql } from '~/lib/common/generated/gql'
|
||||
import type { WorkspacePageVersionCount_WorkspaceVersionsCountFragment } from '~~/lib/common/generated/gql/graphql'
|
||||
|
||||
graphql(`
|
||||
fragment WorkspacePageVersionCount_WorkspaceVersionsCount on WorkspaceVersionsCount {
|
||||
current
|
||||
max
|
||||
}
|
||||
`)
|
||||
|
||||
defineProps<{
|
||||
versionsCount: WorkspacePageVersionCount_WorkspaceVersionsCountFragment
|
||||
}>()
|
||||
</script>
|
||||
@@ -124,11 +124,12 @@ const documents = {
|
||||
"\n fragment WorkspaceAvatar_Workspace on Workspace {\n id\n logo\n defaultLogoIndex\n }\n": types.WorkspaceAvatar_WorkspaceFragmentDoc,
|
||||
"\n fragment WorkspaceInviteDialog_Workspace on Workspace {\n id\n team {\n items {\n id\n user {\n id\n }\n }\n }\n invitedTeam(filter: $invitesFilter) {\n title\n user {\n id\n }\n }\n }\n": types.WorkspaceInviteDialog_WorkspaceFragmentDoc,
|
||||
"\n fragment WorkspaceProjectList_ProjectCollection on ProjectCollection {\n totalCount\n items {\n ...ProjectDashboardItem\n }\n cursor\n }\n": types.WorkspaceProjectList_ProjectCollectionFragmentDoc,
|
||||
"\n fragment WorkspaceHeader_Workspace on Workspace {\n ...WorkspaceAvatar_Workspace\n id\n role\n name\n logo\n description\n totalProjects: projects {\n totalCount\n }\n team {\n items {\n id\n user {\n id\n name\n ...LimitedUserAvatar\n }\n }\n }\n ...WorkspaceInviteDialog_Workspace\n }\n": types.WorkspaceHeader_WorkspaceFragmentDoc,
|
||||
"\n fragment WorkspaceHeader_Workspace on Workspace {\n ...WorkspaceAvatar_Workspace\n id\n role\n name\n logo\n description\n totalProjects: projects {\n totalCount\n }\n billing {\n versionsCount {\n ...WorkspacePageVersionCount_WorkspaceVersionsCount\n }\n }\n team {\n items {\n id\n user {\n id\n name\n ...LimitedUserAvatar\n }\n }\n }\n ...WorkspaceInviteDialog_Workspace\n }\n": types.WorkspaceHeader_WorkspaceFragmentDoc,
|
||||
"\n fragment WorkspaceInviteBanner_PendingWorkspaceCollaborator on PendingWorkspaceCollaborator {\n id\n invitedBy {\n id\n ...LimitedUserAvatar\n }\n workspaceId\n workspaceName\n token\n user {\n id\n }\n ...UseWorkspaceInviteManager_PendingWorkspaceCollaborator\n }\n": types.WorkspaceInviteBanner_PendingWorkspaceCollaboratorFragmentDoc,
|
||||
"\n fragment WorkspaceInviteBanners_User on User {\n discoverableWorkspaces {\n ...WorkspaceInviteDiscoverableWorkspaceBanner_DiscoverableWorkspace\n }\n workspaceInvites {\n ...WorkspaceInviteBanner_PendingWorkspaceCollaborator\n }\n }\n": types.WorkspaceInviteBanners_UserFragmentDoc,
|
||||
"\n fragment WorkspaceInviteBlock_PendingWorkspaceCollaborator on PendingWorkspaceCollaborator {\n id\n workspaceId\n workspaceName\n token\n user {\n id\n name\n ...LimitedUserAvatar\n }\n title\n email\n ...UseWorkspaceInviteManager_PendingWorkspaceCollaborator\n }\n": types.WorkspaceInviteBlock_PendingWorkspaceCollaboratorFragmentDoc,
|
||||
"\n fragment WorkspaceInviteDiscoverableWorkspaceBanner_DiscoverableWorkspace on DiscoverableWorkspace {\n id\n name\n description\n logo\n defaultLogoIndex\n }\n fragment WorkspaceInviteDiscoverableWorkspaceBanner_Workspace on Workspace {\n id\n name\n description\n createdAt\n updatedAt\n logo\n defaultLogoIndex\n domainBasedMembershipProtectionEnabled\n discoverabilityEnabled\n }\n": types.WorkspaceInviteDiscoverableWorkspaceBanner_DiscoverableWorkspaceFragmentDoc,
|
||||
"\n fragment WorkspacePageVersionCount_WorkspaceVersionsCount on WorkspaceVersionsCount {\n current\n max\n }\n": types.WorkspacePageVersionCount_WorkspaceVersionsCountFragmentDoc,
|
||||
"\n query ActiveUserMainMetadata {\n activeUser {\n id\n email\n company\n bio\n name\n role\n avatar\n isOnboardingFinished\n createdAt\n verified\n notificationPreferences\n versions(limit: 0) {\n totalCount\n }\n }\n }\n": types.ActiveUserMainMetadataDocument,
|
||||
"\n mutation CreateOnboardingProject {\n projectMutations {\n createForOnboarding {\n ...ProjectPageProject\n ...ProjectDashboardItem\n }\n }\n }\n ": types.CreateOnboardingProjectDocument,
|
||||
"\n mutation FinishOnboarding {\n activeUserMutations {\n finishOnboarding\n }\n }\n": types.FinishOnboardingDocument,
|
||||
@@ -310,7 +311,7 @@ const documents = {
|
||||
"\n fragment AutomateFunctionPage_AutomateFunction on AutomateFunction {\n id\n name\n description\n logo\n supportedSourceApps\n tags\n ...AutomateFunctionPageHeader_Function\n ...AutomateFunctionPageInfo_AutomateFunction\n ...AutomateAutomationCreateDialog_AutomateFunction\n creator {\n id\n }\n }\n": types.AutomateFunctionPage_AutomateFunctionFragmentDoc,
|
||||
"\n query AutomateFunctionPage($functionId: ID!) {\n automateFunction(id: $functionId) {\n ...AutomateFunctionPage_AutomateFunction\n }\n }\n": types.AutomateFunctionPageDocument,
|
||||
"\n query AutomateFunctionsPage($search: String, $cursor: String = null) {\n ...AutomateFunctionsPageItems_Query\n ...AutomateFunctionsPageHeader_Query\n }\n": types.AutomateFunctionsPageDocument,
|
||||
"\n fragment ProjectPageProject on Project {\n id\n createdAt\n modelCount: models(limit: 0) {\n totalCount\n }\n commentThreadCount: commentThreads(limit: 0) {\n totalCount\n }\n ...ProjectPageProjectHeader\n ...ProjectPageTeamDialog\n }\n": types.ProjectPageProjectFragmentDoc,
|
||||
"\n fragment ProjectPageProject on Project {\n id\n createdAt\n modelCount: models(limit: 0) {\n totalCount\n }\n commentThreadCount: commentThreads(limit: 0) {\n totalCount\n }\n ...ProjectPageTeamInternals_Project\n ...ProjectPageProjectHeader\n ...ProjectPageTeamDialog\n }\n": types.ProjectPageProjectFragmentDoc,
|
||||
"\n fragment ProjectPageAutomationPage_Automation on Automation {\n id\n ...ProjectPageAutomationHeader_Automation\n ...ProjectPageAutomationFunctions_Automation\n ...ProjectPageAutomationRuns_Automation\n }\n": types.ProjectPageAutomationPage_AutomationFragmentDoc,
|
||||
"\n fragment ProjectPageAutomationPage_Project on Project {\n id\n ...ProjectPageAutomationHeader_Project\n }\n": types.ProjectPageAutomationPage_ProjectFragmentDoc,
|
||||
"\n fragment ProjectPageSettingsTab_Project on Project {\n id\n role\n }\n": types.ProjectPageSettingsTab_ProjectFragmentDoc,
|
||||
@@ -777,7 +778,7 @@ export function graphql(source: "\n fragment WorkspaceProjectList_ProjectCollec
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n fragment WorkspaceHeader_Workspace on Workspace {\n ...WorkspaceAvatar_Workspace\n id\n role\n name\n logo\n description\n totalProjects: projects {\n totalCount\n }\n team {\n items {\n id\n user {\n id\n name\n ...LimitedUserAvatar\n }\n }\n }\n ...WorkspaceInviteDialog_Workspace\n }\n"): (typeof documents)["\n fragment WorkspaceHeader_Workspace on Workspace {\n ...WorkspaceAvatar_Workspace\n id\n role\n name\n logo\n description\n totalProjects: projects {\n totalCount\n }\n team {\n items {\n id\n user {\n id\n name\n ...LimitedUserAvatar\n }\n }\n }\n ...WorkspaceInviteDialog_Workspace\n }\n"];
|
||||
export function graphql(source: "\n fragment WorkspaceHeader_Workspace on Workspace {\n ...WorkspaceAvatar_Workspace\n id\n role\n name\n logo\n description\n totalProjects: projects {\n totalCount\n }\n billing {\n versionsCount {\n ...WorkspacePageVersionCount_WorkspaceVersionsCount\n }\n }\n team {\n items {\n id\n user {\n id\n name\n ...LimitedUserAvatar\n }\n }\n }\n ...WorkspaceInviteDialog_Workspace\n }\n"): (typeof documents)["\n fragment WorkspaceHeader_Workspace on Workspace {\n ...WorkspaceAvatar_Workspace\n id\n role\n name\n logo\n description\n totalProjects: projects {\n totalCount\n }\n billing {\n versionsCount {\n ...WorkspacePageVersionCount_WorkspaceVersionsCount\n }\n }\n team {\n items {\n id\n user {\n id\n name\n ...LimitedUserAvatar\n }\n }\n }\n ...WorkspaceInviteDialog_Workspace\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
@@ -794,6 +795,10 @@ export function graphql(source: "\n fragment WorkspaceInviteBlock_PendingWorksp
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n fragment WorkspaceInviteDiscoverableWorkspaceBanner_DiscoverableWorkspace on DiscoverableWorkspace {\n id\n name\n description\n logo\n defaultLogoIndex\n }\n fragment WorkspaceInviteDiscoverableWorkspaceBanner_Workspace on Workspace {\n id\n name\n description\n createdAt\n updatedAt\n logo\n defaultLogoIndex\n domainBasedMembershipProtectionEnabled\n discoverabilityEnabled\n }\n"): (typeof documents)["\n fragment WorkspaceInviteDiscoverableWorkspaceBanner_DiscoverableWorkspace on DiscoverableWorkspace {\n id\n name\n description\n logo\n defaultLogoIndex\n }\n fragment WorkspaceInviteDiscoverableWorkspaceBanner_Workspace on Workspace {\n id\n name\n description\n createdAt\n updatedAt\n logo\n defaultLogoIndex\n domainBasedMembershipProtectionEnabled\n discoverabilityEnabled\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n fragment WorkspacePageVersionCount_WorkspaceVersionsCount on WorkspaceVersionsCount {\n current\n max\n }\n"): (typeof documents)["\n fragment WorkspacePageVersionCount_WorkspaceVersionsCount on WorkspaceVersionsCount {\n current\n max\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
@@ -1521,7 +1526,7 @@ export function graphql(source: "\n query AutomateFunctionsPage($search: String
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n fragment ProjectPageProject on Project {\n id\n createdAt\n modelCount: models(limit: 0) {\n totalCount\n }\n commentThreadCount: commentThreads(limit: 0) {\n totalCount\n }\n ...ProjectPageProjectHeader\n ...ProjectPageTeamDialog\n }\n"): (typeof documents)["\n fragment ProjectPageProject on Project {\n id\n createdAt\n modelCount: models(limit: 0) {\n totalCount\n }\n commentThreadCount: commentThreads(limit: 0) {\n totalCount\n }\n ...ProjectPageProjectHeader\n ...ProjectPageTeamDialog\n }\n"];
|
||||
export function graphql(source: "\n fragment ProjectPageProject on Project {\n id\n createdAt\n modelCount: models(limit: 0) {\n totalCount\n }\n commentThreadCount: commentThreads(limit: 0) {\n totalCount\n }\n ...ProjectPageTeamInternals_Project\n ...ProjectPageProjectHeader\n ...ProjectPageTeamDialog\n }\n"): (typeof documents)["\n fragment ProjectPageProject on Project {\n id\n createdAt\n modelCount: models(limit: 0) {\n totalCount\n }\n commentThreadCount: commentThreads(limit: 0) {\n totalCount\n }\n ...ProjectPageTeamInternals_Project\n ...ProjectPageProjectHeader\n ...ProjectPageTeamDialog\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -8,10 +8,27 @@
|
||||
@processed="onInviteAccepted"
|
||||
/>
|
||||
<div
|
||||
class="flex flex-col md:flex-row md:justify-between md:items-start gap-8 mb-6 mt-4 md:my-6"
|
||||
class="flex flex-col md:flex-row md:justify-between md:items-center gap-4 my-2"
|
||||
>
|
||||
<ProjectPageHeader :project="project" />
|
||||
<ProjectPageTeamBlock :project="project" class="w-full md:w-72 shrink-0" />
|
||||
<div class="flex gap-x-3 items-center">
|
||||
<CommonBadge rounded :color-classes="'text-foreground-2 bg-primary-muted'">
|
||||
{{ project.modelCount.totalCount || 0 }} Model{{
|
||||
project.modelCount.totalCount === 1 ? '' : 's'
|
||||
}}
|
||||
</CommonBadge>
|
||||
<CommonBadge rounded :color-classes="'text-foreground-2 bg-primary-muted'">
|
||||
<span class="capitalize">{{ project.role?.split(':').reverse()[0] }}</span>
|
||||
</CommonBadge>
|
||||
<UserAvatarGroup :users="teamUsers" class="max-w-[104px]" />
|
||||
<FormButton
|
||||
v-if="canEdit"
|
||||
color="outline"
|
||||
:to="projectCollaboratorsRoute(project.id)"
|
||||
>
|
||||
Manage
|
||||
</FormButton>
|
||||
</div>
|
||||
</div>
|
||||
<LayoutTabsHorizontal v-model:active-item="activePageTab" :items="pageTabItems">
|
||||
<NuxtPage :project="project" />
|
||||
@@ -27,6 +44,8 @@ import { projectPageQuery } from '~~/lib/projects/graphql/queries'
|
||||
import { useGeneralProjectPageUpdateTracking } from '~~/lib/projects/composables/projectPages'
|
||||
import { LayoutTabsHorizontal, type LayoutPageTabItem } from '@speckle/ui-components'
|
||||
import { projectRoute, projectWebhooksRoute } from '~/lib/common/helpers/route'
|
||||
import { canEditProject } from '~~/lib/projects/helpers/permissions'
|
||||
import { projectCollaboratorsRoute } from '~~/lib/common/helpers/route'
|
||||
|
||||
graphql(`
|
||||
fragment ProjectPageProject on Project {
|
||||
@@ -38,6 +57,7 @@ graphql(`
|
||||
commentThreadCount: commentThreads(limit: 0) {
|
||||
totalCount
|
||||
}
|
||||
...ProjectPageTeamInternals_Project
|
||||
...ProjectPageProjectHeader
|
||||
...ProjectPageTeamDialog
|
||||
}
|
||||
@@ -96,6 +116,8 @@ const projectName = computed(() =>
|
||||
const modelCount = computed(() => project.value?.modelCount.totalCount)
|
||||
const commentCount = computed(() => project.value?.commentThreadCount.totalCount)
|
||||
const hasRole = computed(() => project.value?.role)
|
||||
const canEdit = computed(() => (project.value ? canEditProject(project.value) : false))
|
||||
const teamUsers = computed(() => project.value?.team.map((t) => t.user))
|
||||
|
||||
useHead({
|
||||
title: projectName
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
<template>
|
||||
<div class="relative w-full bg-foundation-2 rounded" :class="[heightClass]">
|
||||
<div class="relative w-full bg-outline-3 rounded h-1.5">
|
||||
<div
|
||||
class="aboslute left-0 top-0 bg-success rounded"
|
||||
:class="[heightClass]"
|
||||
class="aboslute left-0 top-0 bg-success rounded h-1.5"
|
||||
:style="{ width: `${percentage <= 100 ? percentage : 100}%` }"
|
||||
/>
|
||||
</div>
|
||||
@@ -11,19 +10,10 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue'
|
||||
|
||||
type ProgressBarSize = 'base'
|
||||
const props = defineProps<{
|
||||
currentValue: number
|
||||
maxValue: number
|
||||
}>()
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
currentValue: number
|
||||
maxValue: number
|
||||
size: ProgressBarSize
|
||||
}>(),
|
||||
{
|
||||
size: 'base'
|
||||
}
|
||||
)
|
||||
|
||||
const heightClass = computed(() => (props.size === 'base' ? 'h-1.5' : 'h-1'))
|
||||
const percentage = computed(() => (props.currentValue / props.maxValue) * 100)
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user