web/satellite: match projects table with the designs
This change updates the projects table on all projects dashboard to more closely match the designs. Change-Id: I547a83352fba8c3ad7958802db7b38b342b383e8
This commit is contained in:
parent
8d8f6734de
commit
f131047f1a
@ -14,8 +14,13 @@
|
||||
v-for="(val, keyVal, index) in item" :key="index" class="align-left data"
|
||||
:class="{'overflow-visible': showBucketGuide(index)}"
|
||||
>
|
||||
<div v-if="Array.isArray(val)" class="few-items">
|
||||
<p v-for="str in val" :key="str" class="array-val">{{ str }}</p>
|
||||
<div v-if="Array.isArray(val)" class="few-items-container">
|
||||
<div v-if="icon && index === 0 && itemType?.includes('project')" class="item-icon file-background" :class="customIconClasses">
|
||||
<component :is="icon" />
|
||||
</div>
|
||||
<div class="few-items">
|
||||
<p v-for="str in val" :key="str" class="array-val">{{ str }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="table-item">
|
||||
<div v-if="icon && index === 0" class="item-icon file-background" :class="customIconClasses">
|
||||
@ -83,17 +88,15 @@ const icon = computed((): string => ObjectType.findIcon(props.itemType));
|
||||
const customIconClasses = computed(() => {
|
||||
const classes = {};
|
||||
if (props.itemType === 'project') {
|
||||
if (props.item['role'] === ProjectRole.Owner) {
|
||||
classes['project-owner'] = true;
|
||||
} else if (props.item['role'] === ProjectRole.Member) {
|
||||
classes['project-member'] = true;
|
||||
}
|
||||
classes['project-owner'] = true;
|
||||
} else if (props.itemType === 'shared-project') {
|
||||
classes['project-member'] = true;
|
||||
}
|
||||
return classes;
|
||||
});
|
||||
|
||||
function isProjectRoleIconShown(role: ProjectRole) {
|
||||
return props.itemType === 'project' || role === ProjectRole.Invited || role === ProjectRole.InviteExpired;
|
||||
return props.itemType.includes('project') || role === ProjectRole.Invited || role === ProjectRole.InviteExpired;
|
||||
}
|
||||
|
||||
function selectClicked(event: Event): void {
|
||||
@ -204,11 +207,27 @@ function cellContentClicked(cellIndex: number, event: Event) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&__title {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&__subtitle {
|
||||
font-family: 'font_regular', sans-serif;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
color: var(--c-grey-6);
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.few-items-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@media screen and (width <= 370px) {
|
||||
max-width: 9rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -33,6 +33,7 @@ export class ObjectType {
|
||||
['text', TxtIcon],
|
||||
['archive', ZipIcon],
|
||||
['project', ProjectIcon],
|
||||
['shared-project', ProjectIcon],
|
||||
]);
|
||||
|
||||
static findIcon(type: string): string {
|
||||
|
@ -14,7 +14,7 @@
|
||||
|
||||
<heading class="all-dashboard__heading" />
|
||||
|
||||
<div class="all-dashboard__content">
|
||||
<div class="all-dashboard__content" :class="{ 'no-x-padding': isMyProjectsPage }">
|
||||
<div class="all-dashboard__content__divider" />
|
||||
|
||||
<router-view />
|
||||
@ -134,6 +134,10 @@ const sessionDuration = computed((): number => {
|
||||
return duration;
|
||||
});
|
||||
|
||||
const isMyProjectsPage = computed((): boolean => {
|
||||
return route.path === RouteConfig.AllProjectsDashboard.path;
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns the session refresh interval from the store.
|
||||
*/
|
||||
@ -565,6 +569,11 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
}
|
||||
|
||||
.no-x-padding {
|
||||
padding-left: 0 !important;
|
||||
padding-right: 0 !important;
|
||||
}
|
||||
|
||||
.all-dashboard {
|
||||
box-sizing: border-box;
|
||||
overflow-y: auto;
|
||||
|
@ -64,7 +64,10 @@
|
||||
|
||||
<all-projects-dashboard-banners v-if="content" :parent-ref="content" />
|
||||
|
||||
<div v-if="projects.length || invites.length" class="my-projects__list">
|
||||
<div
|
||||
v-if="projects.length || invites.length" class="my-projects__list"
|
||||
:style="{'padding': isTableViewSelected && isMobile ? '0' : '0 20px'}"
|
||||
>
|
||||
<projects-table v-if="isTableViewSelected" :invites="invites" class="my-projects__list__table" />
|
||||
<div v-else-if="!isTableViewSelected" class="my-projects__list__cards">
|
||||
<project-invitation-item v-for="invite in invites" :key="invite.projectID" :invitation="invite" />
|
||||
@ -98,6 +101,7 @@ import { useProjectsStore } from '@/store/modules/projectsStore';
|
||||
import { useConfigStore } from '@/store/modules/configStore';
|
||||
import ProjectsTable from '@/views/all-dashboard/components/ProjectsTable.vue';
|
||||
import AllProjectsDashboardBanners from '@/views/all-dashboard/components/AllProjectsDashboardBanners.vue';
|
||||
import { useResize } from '@/composables/resize';
|
||||
|
||||
import VButton from '@/components/common/VButton.vue';
|
||||
import VChip from '@/components/common/VChip.vue';
|
||||
@ -113,6 +117,8 @@ const projectsStore = useProjectsStore();
|
||||
|
||||
const analytics = new AnalyticsHttpApi();
|
||||
|
||||
const { isMobile } = useResize();
|
||||
|
||||
const content = ref<HTMLElement | null>(null);
|
||||
|
||||
const hasProjectTableViewConfigured = ref(appStore.hasProjectTableViewConfigured());
|
||||
@ -173,6 +179,7 @@ function onCreateProjectClicked(): void {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 20px;
|
||||
|
||||
@media screen and (width <= 500px) {
|
||||
margin-top: 20px;
|
||||
@ -268,6 +275,10 @@ function onCreateProjectClicked(): void {
|
||||
}
|
||||
}
|
||||
|
||||
& :deep(.all-dashboard-banners) {
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
&__list {
|
||||
margin-top: 20px;
|
||||
|
||||
@ -276,7 +287,7 @@ function onCreateProjectClicked(): void {
|
||||
gap: 10px;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
|
||||
& :deep(.project-item) {
|
||||
& :deep(.project-item), &:deep(.invite-item) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
@ -3,41 +3,43 @@
|
||||
|
||||
<template>
|
||||
<table-item
|
||||
item-type="project"
|
||||
item-type="shared-project"
|
||||
:item="itemToRender"
|
||||
class="invitation-item"
|
||||
>
|
||||
<template #options>
|
||||
<th class="options overflow-visible">
|
||||
<v-button
|
||||
:loading="isLoading"
|
||||
:disabled="isLoading"
|
||||
:on-press="onJoinClicked"
|
||||
border-radius="8px"
|
||||
font-size="12px"
|
||||
label="Join Project"
|
||||
class="invitation-item__menu__button"
|
||||
/>
|
||||
<v-button
|
||||
:loading="isLoading"
|
||||
:disabled="isLoading"
|
||||
:on-press="onJoinClicked"
|
||||
border-radius="8px"
|
||||
font-size="12px"
|
||||
label="Join"
|
||||
class="invitation-item__menu__mobile-button"
|
||||
/>
|
||||
<div class="invitation-item__menu">
|
||||
<div class="invitation-item__menu__icon" @click.stop="toggleDropDown">
|
||||
<div class="invitation-item__menu__icon__content" :class="{open: isDropdownOpen}">
|
||||
<menu-icon />
|
||||
<th class="overflow-visible">
|
||||
<div class="options">
|
||||
<v-button
|
||||
:loading="isLoading"
|
||||
:disabled="isLoading"
|
||||
:on-press="onJoinClicked"
|
||||
border-radius="8px"
|
||||
font-size="12px"
|
||||
label="Join Project"
|
||||
class="invitation-item__button"
|
||||
/>
|
||||
<v-button
|
||||
:loading="isLoading"
|
||||
:disabled="isLoading"
|
||||
:on-press="onJoinClicked"
|
||||
border-radius="8px"
|
||||
font-size="12px"
|
||||
label="Join"
|
||||
class="invitation-item__mobile-button"
|
||||
/>
|
||||
<div class="invitation-item__menu">
|
||||
<div class="invitation-item__menu__icon" @click.stop="toggleDropDown">
|
||||
<div class="invitation-item__menu__icon__content" :class="{open: isDropdownOpen}">
|
||||
<menu-icon />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="isDropdownOpen" v-click-outside="closeDropDown" class="invitation-item__menu__dropdown">
|
||||
<div class="invitation-item__menu__dropdown__item" @click.stop="onDeclineClicked">
|
||||
<logout-icon />
|
||||
<p class="invitation-item__menu__dropdown__item__label">Decline invite</p>
|
||||
<div v-if="isDropdownOpen" v-click-outside="closeDropDown" class="invitation-item__menu__dropdown">
|
||||
<div class="invitation-item__menu__dropdown__item" @click.stop="onDeclineClicked">
|
||||
<logout-icon />
|
||||
<p class="invitation-item__menu__dropdown__item__label">Decline invite</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -59,6 +61,7 @@ import { useAppStore } from '@/store/modules/appStore';
|
||||
import { useProjectsStore } from '@/store/modules/projectsStore';
|
||||
import { useResize } from '@/composables/resize';
|
||||
import { AnalyticsHttpApi } from '@/api/analytics';
|
||||
import { useLoading } from '@/composables/useLoading';
|
||||
|
||||
import VButton from '@/components/common/VButton.vue';
|
||||
import TableItem from '@/components/common/TableItem.vue';
|
||||
@ -72,25 +75,36 @@ const notify = useNotify();
|
||||
|
||||
const analytics: AnalyticsHttpApi = new AnalyticsHttpApi();
|
||||
|
||||
const isLoading = ref<boolean>(false);
|
||||
const { isLoading, withLoading } = useLoading();
|
||||
|
||||
const props = defineProps<{
|
||||
invitation: ProjectInvitation,
|
||||
}>();
|
||||
|
||||
const { isMobile } = useResize();
|
||||
const { isMobile, screenWidth } = useResize();
|
||||
|
||||
const itemToRender = computed((): { [key: string]: unknown | string[] } => {
|
||||
if (!isMobile.value) {
|
||||
if (screenWidth.value <= 600 && !isMobile.value) {
|
||||
return {
|
||||
multi: { title: props.invitation.projectName, subtitle: props.invitation.projectDescription },
|
||||
};
|
||||
}
|
||||
if (screenWidth.value <= 850 && !isMobile.value) {
|
||||
return {
|
||||
multi: { title: props.invitation.projectName, subtitle: props.invitation.projectDescription },
|
||||
date: props.invitation.invitedDate,
|
||||
memberCount: '',
|
||||
role: ProjectRole.Invited,
|
||||
};
|
||||
}
|
||||
if (isMobile.value) {
|
||||
return { info: [ props.invitation.projectName, props.invitation.projectDescription ] };
|
||||
}
|
||||
|
||||
return { info: [ props.invitation.projectName, props.invitation.projectDescription ] };
|
||||
return {
|
||||
multi: { title: props.invitation.projectName, subtitle: props.invitation.projectDescription },
|
||||
date: props.invitation.invitedDate,
|
||||
memberCount: '',
|
||||
role: ProjectRole.Invited,
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
@ -111,25 +125,22 @@ function onJoinClicked(): void {
|
||||
/**
|
||||
* Declines the project member invitation.
|
||||
*/
|
||||
async function onDeclineClicked(): Promise<void> {
|
||||
if (isLoading.value) return;
|
||||
isLoading.value = true;
|
||||
function onDeclineClicked(): void {
|
||||
withLoading(async () => {
|
||||
try {
|
||||
await projectsStore.respondToInvitation(props.invitation.projectID, ProjectInvitationResponse.Decline);
|
||||
analytics.eventTriggered(AnalyticsEvent.PROJECT_INVITATION_DECLINED);
|
||||
} catch (error) {
|
||||
notify.error(`Failed to decline project invitation. ${error.message}`, AnalyticsErrorEventSource.PROJECT_INVITATION);
|
||||
}
|
||||
|
||||
try {
|
||||
await projectsStore.respondToInvitation(props.invitation.projectID, ProjectInvitationResponse.Decline);
|
||||
analytics.eventTriggered(AnalyticsEvent.PROJECT_INVITATION_DECLINED);
|
||||
} catch (error) {
|
||||
notify.error(`Failed to decline project invitation. ${error.message}`, AnalyticsErrorEventSource.PROJECT_INVITATION);
|
||||
}
|
||||
|
||||
try {
|
||||
await projectsStore.getUserInvitations();
|
||||
await projectsStore.getProjects();
|
||||
} catch (error) {
|
||||
notify.error(`Failed to reload projects and invitations list. ${error.message}`, AnalyticsErrorEventSource.PROJECT_INVITATION);
|
||||
}
|
||||
|
||||
isLoading.value = false;
|
||||
try {
|
||||
await projectsStore.getUserInvitations();
|
||||
await projectsStore.getProjects();
|
||||
} catch (error) {
|
||||
notify.error(`Failed to reload projects and invitations list. ${error.message}`, AnalyticsErrorEventSource.PROJECT_INVITATION);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function toggleDropDown() {
|
||||
@ -149,31 +160,35 @@ function closeDropDown() {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
column-gap: 10px;
|
||||
column-gap: 20px;
|
||||
padding-right: 10px;
|
||||
|
||||
@media screen and (width <= 900px) {
|
||||
column-gap: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
&__button {
|
||||
padding: 10px 16px;
|
||||
|
||||
@media screen and (width <= 900px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__mobile-button {
|
||||
display: none;
|
||||
padding: 10px 16px;
|
||||
|
||||
@media screen and (width <= 900px) {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
&__menu {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
|
||||
&__button {
|
||||
padding: 10px 16px;
|
||||
|
||||
@media screen and (width <= 500px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__mobile-button {
|
||||
display: none;
|
||||
padding: 10px 16px;
|
||||
|
||||
@media screen and (width <= 500px) {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
&__icon {
|
||||
|
||||
&__content {
|
||||
|
@ -3,28 +3,48 @@
|
||||
|
||||
<template>
|
||||
<table-item
|
||||
item-type="project"
|
||||
:item-type="isOwner ? 'project' : 'shared-project'"
|
||||
:item="itemToRender"
|
||||
:on-click="onOpenClicked"
|
||||
class="project-item"
|
||||
>
|
||||
<template #options>
|
||||
<th class="project-item__menu options overflow-visible" @click.stop="toggleDropDown">
|
||||
<div class="project-item__menu__icon">
|
||||
<div class="project-item__menu__icon__content" :class="{open: isDropdownOpen}">
|
||||
<menu-icon />
|
||||
</div>
|
||||
</div>
|
||||
<th class="overflow-visible">
|
||||
<div class="options">
|
||||
<v-button
|
||||
:on-press="onOpenClicked"
|
||||
is-white
|
||||
border-radius="8px"
|
||||
font-size="12px"
|
||||
label="Open Project"
|
||||
class="project-item__button"
|
||||
/>
|
||||
<v-button
|
||||
:on-press="onOpenClicked"
|
||||
is-white
|
||||
border-radius="8px"
|
||||
font-size="12px"
|
||||
label="Open"
|
||||
class="project-item__mobile-button"
|
||||
/>
|
||||
<div class="project-item__menu">
|
||||
<div class="project-item__menu__icon" @click.stop="toggleDropDown">
|
||||
<div class="project-item__menu__icon__content" :class="{open: isDropdownOpen}">
|
||||
<menu-icon />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="isDropdownOpen" v-click-outside="closeDropDown" class="project-item__menu__dropdown">
|
||||
<div v-if="isOwner" class="project-item__menu__dropdown__item" @click.stop="goToProjectEdit">
|
||||
<gear-icon />
|
||||
<p class="project-item__menu__dropdown__item__label">Project settings</p>
|
||||
</div>
|
||||
<div v-if="isDropdownOpen" v-click-outside="closeDropDown" class="project-item__menu__dropdown">
|
||||
<div v-if="isOwner" class="project-item__menu__dropdown__item" @click.stop="goToProjectEdit">
|
||||
<gear-icon />
|
||||
<p class="project-item__menu__dropdown__item__label">Project settings</p>
|
||||
</div>
|
||||
|
||||
<div class="project-item__menu__dropdown__item" @click.stop="goToProjectMembers">
|
||||
<users-icon />
|
||||
<p class="project-item__menu__dropdown__item__label">Invite members</p>
|
||||
<div class="project-item__menu__dropdown__item" @click.stop="goToProjectMembers">
|
||||
<users-icon />
|
||||
<p class="project-item__menu__dropdown__item__label">Invite members</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</th>
|
||||
@ -53,6 +73,7 @@ import { useProjectsStore } from '@/store/modules/projectsStore';
|
||||
import { useResize } from '@/composables/resize';
|
||||
|
||||
import TableItem from '@/components/common/TableItem.vue';
|
||||
import VButton from '@/components/common/VButton.vue';
|
||||
|
||||
import UsersIcon from '@/../static/images/navigation/users.svg';
|
||||
import GearIcon from '@/../static/images/common/gearIcon.svg';
|
||||
@ -71,19 +92,30 @@ const props = defineProps<{
|
||||
project: Project,
|
||||
}>();
|
||||
|
||||
const { isMobile } = useResize();
|
||||
const { isMobile, screenWidth } = useResize();
|
||||
|
||||
const itemToRender = computed((): { [key: string]: unknown | string[] } => {
|
||||
if (!isMobile.value) {
|
||||
if (screenWidth.value <= 600 && !isMobile.value) {
|
||||
return {
|
||||
multi: { title: props.project.name, subtitle: props.project.description },
|
||||
};
|
||||
}
|
||||
if (screenWidth.value <= 850 && !isMobile.value) {
|
||||
return {
|
||||
multi: { title: props.project.name, subtitle: props.project.description },
|
||||
date: props.project.createdDate(),
|
||||
memberCount: props.project.memberCount.toString(),
|
||||
role: isOwner.value ? ProjectRole.Owner : ProjectRole.Member,
|
||||
};
|
||||
}
|
||||
if (isMobile.value) {
|
||||
return { info: [ props.project.name, props.project.description ] };
|
||||
}
|
||||
|
||||
return { info: [ props.project.name, props.project.description ] };
|
||||
return {
|
||||
multi: { title: props.project.name, subtitle: props.project.description },
|
||||
date: props.project.createdDate(),
|
||||
memberCount: props.project.memberCount.toString(),
|
||||
role: isOwner.value ? ProjectRole.Owner : ProjectRole.Member,
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
@ -166,8 +198,38 @@ async function goToProjectEdit(): Promise<void> {
|
||||
<style scoped lang="scss">
|
||||
.project-item {
|
||||
|
||||
.options {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
column-gap: 20px;
|
||||
padding-right: 10px;
|
||||
|
||||
@media screen and (width <= 900px) {
|
||||
column-gap: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
&__button {
|
||||
padding: 10px 16px;
|
||||
box-shadow: 0 0 20px 0 rgb(0 0 0 / 4%);
|
||||
|
||||
@media screen and (width <= 900px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__mobile-button {
|
||||
display: none;
|
||||
padding: 10px 16px;
|
||||
box-shadow: 0 0 20px 0 rgb(0 0 0 / 4%);
|
||||
|
||||
@media screen and (width <= 900px) {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
&__menu {
|
||||
padding: 0 10px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
|
||||
@ -176,8 +238,6 @@ async function goToProjectEdit(): Promise<void> {
|
||||
&__content {
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
margin-left: auto;
|
||||
margin-right: 0;
|
||||
padding: 12px 5px;
|
||||
border-radius: 5px;
|
||||
box-sizing: border-box;
|
||||
@ -193,9 +253,9 @@ async function goToProjectEdit(): Promise<void> {
|
||||
|
||||
&__dropdown {
|
||||
position: absolute;
|
||||
top: 55px;
|
||||
right: 10px;
|
||||
background: var(--c-white);
|
||||
top: 40px;
|
||||
right: 0;
|
||||
background: #fff;
|
||||
box-shadow: 0 7px 20px rgb(0 0 0 / 15%);
|
||||
border: 1px solid var(--c-grey-2);
|
||||
border-radius: 8px;
|
||||
|
@ -8,10 +8,10 @@
|
||||
items-label="projects"
|
||||
>
|
||||
<template #head>
|
||||
<th class="sort-header-container__name-item align-left">Project</th>
|
||||
<th class="sort-header-container__date-item align-left">Date Added</th>
|
||||
<th class="sort-header-container__date-item align-left">Members</th>
|
||||
<th class="sort-header-container__date-item align-left">Role</th>
|
||||
<th class="align-left">Project</th>
|
||||
<th class="date-added align-left">Date Added</th>
|
||||
<th class="members align-left">Members</th>
|
||||
<th class="role align-left">Role</th>
|
||||
</template>
|
||||
<template #body>
|
||||
<project-table-invitation-item
|
||||
@ -54,3 +54,28 @@ const projects = computed((): Project[] => {
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.projects-table {
|
||||
@media screen and (width <= 550px) {
|
||||
|
||||
:deep(.table-footer), :deep(.base-table) {
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (width <= 850px) {
|
||||
|
||||
.date-added, .members {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (width <= 600px) {
|
||||
|
||||
.role {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
Loading…
Reference in New Issue
Block a user