feat: move namespaces list to projects list
This commit is contained in:
parent
c6ef99dde2
commit
e1bdabc8d6
@ -101,7 +101,7 @@ watch(() => route.name as string, (routeName) => {
|
|||||||
'labels.index',
|
'labels.index',
|
||||||
'migrate.start',
|
'migrate.start',
|
||||||
'migrate.wunderlist',
|
'migrate.wunderlist',
|
||||||
'namespaces.index',
|
'projects.index',
|
||||||
].includes(routeName) ||
|
].includes(routeName) ||
|
||||||
routeName.startsWith('user.settings')
|
routeName.startsWith('user.settings')
|
||||||
)
|
)
|
||||||
|
@ -22,11 +22,11 @@
|
|||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<router-link :to="{ name: 'namespaces.index'}" v-shortcut="'g n'">
|
<router-link :to="{ name: 'projects.index'}" v-shortcut="'g n'">
|
||||||
<span class="menu-item-icon icon">
|
<span class="menu-item-icon icon">
|
||||||
<icon icon="layer-group"/>
|
<icon icon="layer-group"/>
|
||||||
</span>
|
</span>
|
||||||
{{ $t('namespace.title') }}
|
{{ $t('project.projects') }}
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
@ -47,101 +47,107 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<nav class="menu namespaces-lists loader-container is-loading-small" :class="{'is-loading': loading}">
|
<nav>
|
||||||
<template v-for="(n, nk) in namespaces" :key="n.id">
|
<template v-for="(p, pk) in projects" :key="p.id">
|
||||||
<div class="namespace-title" :class="{'has-menu': n.id > 0}">
|
{{ p. title }}<br/>
|
||||||
<BaseButton
|
|
||||||
@click="toggleProjects(n.id)"
|
|
||||||
class="menu-label"
|
|
||||||
v-tooltip="namespaceTitles[nk]"
|
|
||||||
>
|
|
||||||
<ColorBubble
|
|
||||||
v-if="n.hexColor !== ''"
|
|
||||||
:color="n.hexColor"
|
|
||||||
class="mr-1"
|
|
||||||
/>
|
|
||||||
<span class="name">{{ namespaceTitles[nk] }}</span>
|
|
||||||
<div
|
|
||||||
class="icon menu-item-icon is-small toggle-lists-icon pl-2"
|
|
||||||
:class="{'active': typeof projectsVisible[n.id] !== 'undefined' ? projectsVisible[n.id] : true}"
|
|
||||||
>
|
|
||||||
<icon icon="chevron-down"/>
|
|
||||||
</div>
|
|
||||||
<span class="count" :class="{'ml-2 mr-0': n.id > 0}">
|
|
||||||
({{ namespaceProjectsCount[nk] }})
|
|
||||||
</span>
|
|
||||||
</BaseButton>
|
|
||||||
<namespace-settings-dropdown class="menu-list-dropdown" :namespace="n" v-if="n.id > 0"/>
|
|
||||||
</div>
|
|
||||||
<!--
|
|
||||||
NOTE: a v-model / computed setter is not possible, since the updateActiveProjects function
|
|
||||||
triggered by the change needs to have access to the current namespace
|
|
||||||
-->
|
|
||||||
<draggable
|
|
||||||
v-if="projectsVisible[n.id] ?? true"
|
|
||||||
v-bind="dragOptions"
|
|
||||||
:modelValue="activeProjects[nk]"
|
|
||||||
@update:modelValue="(projects) => updateActiveProjects(n, projects)"
|
|
||||||
group="namespace-lists"
|
|
||||||
@start="() => drag = true"
|
|
||||||
@end="saveListPosition"
|
|
||||||
handle=".handle"
|
|
||||||
:disabled="n.id < 0 || undefined"
|
|
||||||
tag="ul"
|
|
||||||
item-key="id"
|
|
||||||
:data-namespace-id="n.id"
|
|
||||||
:data-namespace-index="nk"
|
|
||||||
:component-data="{
|
|
||||||
type: 'transition-group',
|
|
||||||
name: !drag ? 'flip-list' : null,
|
|
||||||
class: [
|
|
||||||
'menu-list can-be-hidden',
|
|
||||||
{ 'dragging-disabled': n.id < 0 }
|
|
||||||
]
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<template #item="{element: l}">
|
|
||||||
<li
|
|
||||||
class="list-menu loader-container is-loading-small"
|
|
||||||
:class="{'is-loading': projectUpdating[l.id]}"
|
|
||||||
>
|
|
||||||
<BaseButton
|
|
||||||
:to="{ name: 'project.index', params: { projectId: l.id} }"
|
|
||||||
class="list-menu-link"
|
|
||||||
:class="{'router-link-exact-active': currentProject.id === l.id}"
|
|
||||||
>
|
|
||||||
<span class="icon menu-item-icon handle">
|
|
||||||
<icon icon="grip-lines"/>
|
|
||||||
</span>
|
|
||||||
<ColorBubble
|
|
||||||
v-if="l.hexColor !== ''"
|
|
||||||
:color="l.hexColor"
|
|
||||||
class="mr-1"
|
|
||||||
/>
|
|
||||||
<span class="list-menu-title">{{ getProjectTitle(l) }}</span>
|
|
||||||
</BaseButton>
|
|
||||||
<BaseButton
|
|
||||||
v-if="l.id > 0"
|
|
||||||
class="favorite"
|
|
||||||
:class="{'is-favorite': l.isFavorite}"
|
|
||||||
@click="projectStore.toggleProjectFavorite(l)"
|
|
||||||
>
|
|
||||||
<icon :icon="l.isFavorite ? 'star' : ['far', 'star']"/>
|
|
||||||
</BaseButton>
|
|
||||||
<ProjectSettingsDropdown class="menu-list-dropdown" :project="l" v-if="l.id > 0">
|
|
||||||
<template #trigger="{toggleOpen}">
|
|
||||||
<BaseButton class="menu-list-dropdown-trigger" @click="toggleOpen">
|
|
||||||
<icon icon="ellipsis-h" class="icon"/>
|
|
||||||
</BaseButton>
|
|
||||||
</template>
|
|
||||||
</ProjectSettingsDropdown>
|
|
||||||
<span class="list-setting-spacer" v-else></span>
|
|
||||||
</li>
|
|
||||||
</template>
|
|
||||||
</draggable>
|
|
||||||
</template>
|
</template>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
<!-- <nav class="menu namespaces-lists loader-container is-loading-small" :class="{'is-loading': loading}">-->
|
||||||
|
<!-- <template v-for="(n, nk) in namespaces" :key="n.id">-->
|
||||||
|
<!-- <div class="namespace-title" :class="{'has-menu': n.id > 0}">-->
|
||||||
|
<!-- <BaseButton-->
|
||||||
|
<!-- @click="toggleProjects(n.id)"-->
|
||||||
|
<!-- class="menu-label"-->
|
||||||
|
<!-- v-tooltip="namespaceTitles[nk]"-->
|
||||||
|
<!-- >-->
|
||||||
|
<!-- <ColorBubble-->
|
||||||
|
<!-- v-if="n.hexColor !== ''"-->
|
||||||
|
<!-- :color="n.hexColor"-->
|
||||||
|
<!-- class="mr-1"-->
|
||||||
|
<!-- />-->
|
||||||
|
<!-- <span class="name">{{ namespaceTitles[nk] }}</span>-->
|
||||||
|
<!-- <div-->
|
||||||
|
<!-- class="icon menu-item-icon is-small toggle-lists-icon pl-2"-->
|
||||||
|
<!-- :class="{'active': typeof projectsVisible[n.id] !== 'undefined' ? projectsVisible[n.id] : true}"-->
|
||||||
|
<!-- >-->
|
||||||
|
<!-- <icon icon="chevron-down"/>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- <span class="count" :class="{'ml-2 mr-0': n.id > 0}">-->
|
||||||
|
<!-- ({{ namespaceProjectsCount[nk] }})-->
|
||||||
|
<!-- </span>-->
|
||||||
|
<!-- </BaseButton>-->
|
||||||
|
<!-- <namespace-settings-dropdown class="menu-list-dropdown" :namespace="n" v-if="n.id > 0"/>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- <!–-->
|
||||||
|
<!-- NOTE: a v-model / computed setter is not possible, since the updateActiveProjects function-->
|
||||||
|
<!-- triggered by the change needs to have access to the current namespace-->
|
||||||
|
<!-- –>-->
|
||||||
|
<!-- <draggable-->
|
||||||
|
<!-- v-if="projectsVisible[n.id] ?? true"-->
|
||||||
|
<!-- v-bind="dragOptions"-->
|
||||||
|
<!-- :modelValue="activeProjects[nk]"-->
|
||||||
|
<!-- @update:modelValue="(projects) => updateActiveProjects(n, projects)"-->
|
||||||
|
<!-- group="namespace-lists"-->
|
||||||
|
<!-- @start="() => drag = true"-->
|
||||||
|
<!-- @end="saveListPosition"-->
|
||||||
|
<!-- handle=".handle"-->
|
||||||
|
<!-- :disabled="n.id < 0 || undefined"-->
|
||||||
|
<!-- tag="ul"-->
|
||||||
|
<!-- item-key="id"-->
|
||||||
|
<!-- :data-namespace-id="n.id"-->
|
||||||
|
<!-- :data-namespace-index="nk"-->
|
||||||
|
<!-- :component-data="{-->
|
||||||
|
<!-- type: 'transition-group',-->
|
||||||
|
<!-- name: !drag ? 'flip-list' : null,-->
|
||||||
|
<!-- class: [-->
|
||||||
|
<!-- 'menu-list can-be-hidden',-->
|
||||||
|
<!-- { 'dragging-disabled': n.id < 0 }-->
|
||||||
|
<!-- ]-->
|
||||||
|
<!-- }"-->
|
||||||
|
<!-- >-->
|
||||||
|
<!-- <template #item="{element: l}">-->
|
||||||
|
<!-- <li-->
|
||||||
|
<!-- class="list-menu loader-container is-loading-small"-->
|
||||||
|
<!-- :class="{'is-loading': projectUpdating[l.id]}"-->
|
||||||
|
<!-- >-->
|
||||||
|
<!-- <BaseButton-->
|
||||||
|
<!-- :to="{ name: 'project.index', params: { projectId: l.id} }"-->
|
||||||
|
<!-- class="list-menu-link"-->
|
||||||
|
<!-- :class="{'router-link-exact-active': currentProject.id === l.id}"-->
|
||||||
|
<!-- >-->
|
||||||
|
<!-- <span class="icon menu-item-icon handle">-->
|
||||||
|
<!-- <icon icon="grip-lines"/>-->
|
||||||
|
<!-- </span>-->
|
||||||
|
<!-- <ColorBubble-->
|
||||||
|
<!-- v-if="l.hexColor !== ''"-->
|
||||||
|
<!-- :color="l.hexColor"-->
|
||||||
|
<!-- class="mr-1"-->
|
||||||
|
<!-- />-->
|
||||||
|
<!-- <span class="list-menu-title">{{ getProjectTitle(l) }}</span>-->
|
||||||
|
<!-- </BaseButton>-->
|
||||||
|
<!-- <BaseButton-->
|
||||||
|
<!-- v-if="l.id > 0"-->
|
||||||
|
<!-- class="favorite"-->
|
||||||
|
<!-- :class="{'is-favorite': l.isFavorite}"-->
|
||||||
|
<!-- @click="projectStore.toggleProjectFavorite(l)"-->
|
||||||
|
<!-- >-->
|
||||||
|
<!-- <icon :icon="l.isFavorite ? 'star' : ['far', 'star']"/>-->
|
||||||
|
<!-- </BaseButton>-->
|
||||||
|
<!-- <ProjectSettingsDropdown class="menu-list-dropdown" :project="l" v-if="l.id > 0">-->
|
||||||
|
<!-- <template #trigger="{toggleOpen}">-->
|
||||||
|
<!-- <BaseButton class="menu-list-dropdown-trigger" @click="toggleOpen">-->
|
||||||
|
<!-- <icon icon="ellipsis-h" class="icon"/>-->
|
||||||
|
<!-- </BaseButton>-->
|
||||||
|
<!-- </template>-->
|
||||||
|
<!-- </ProjectSettingsDropdown>-->
|
||||||
|
<!-- <span class="list-setting-spacer" v-else></span>-->
|
||||||
|
<!-- </li>-->
|
||||||
|
<!-- </template>-->
|
||||||
|
<!-- </draggable>-->
|
||||||
|
<!-- </template>-->
|
||||||
|
<!-- </nav>-->
|
||||||
<PoweredByLink/>
|
<PoweredByLink/>
|
||||||
</aside>
|
</aside>
|
||||||
</template>
|
</template>
|
||||||
@ -209,14 +215,11 @@ function toggleProjects(namespaceId: INamespace['id']) {
|
|||||||
const projectsVisible = ref<{ [id: INamespace['id']]: boolean }>({})
|
const projectsVisible = ref<{ [id: INamespace['id']]: boolean }>({})
|
||||||
// FIXME: async action will be unfinished when component mounts
|
// FIXME: async action will be unfinished when component mounts
|
||||||
onBeforeMount(async () => {
|
onBeforeMount(async () => {
|
||||||
const namespaces = await namespaceStore.loadNamespaces()
|
await projectStore.loadProjects()
|
||||||
namespaces.forEach(n => {
|
|
||||||
if (typeof projectsVisible.value[n.id] === 'undefined') {
|
|
||||||
projectsVisible.value[n.id] = true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const projects = computed(() => projectStore.projects)
|
||||||
|
|
||||||
function updateActiveProjects(namespace: INamespace, activeProjects: IProject[]) {
|
function updateActiveProjects(namespace: INamespace, activeProjects: IProject[]) {
|
||||||
// This is a bit hacky: since we do have to filter out the archived items from the list
|
// This is a bit hacky: since we do have to filter out the archived items from the list
|
||||||
// for vue draggable updating it is not as simple as replacing it.
|
// for vue draggable updating it is not as simple as replacing it.
|
||||||
@ -236,7 +239,7 @@ function updateActiveProjects(namespace: INamespace, activeProjects: IProject[])
|
|||||||
|
|
||||||
const projectUpdating = ref<{ [id: INamespace['id']]: boolean }>({})
|
const projectUpdating = ref<{ [id: INamespace['id']]: boolean }>({})
|
||||||
|
|
||||||
async function saveListPosition(e: SortableEvent) {
|
async function saveProjectPosition(e: SortableEvent) {
|
||||||
if (!e.newIndex && e.newIndex !== 0) return
|
if (!e.newIndex && e.newIndex !== 0) return
|
||||||
|
|
||||||
const namespaceId = parseInt(e.to.dataset.namespaceId as string)
|
const namespaceId = parseInt(e.to.dataset.namespaceId as string)
|
||||||
|
@ -166,6 +166,7 @@
|
|||||||
},
|
},
|
||||||
"project": {
|
"project": {
|
||||||
"archived": "This project is archived. It is not possible to create new or edit tasks for it.",
|
"archived": "This project is archived. It is not possible to create new or edit tasks for it.",
|
||||||
|
"showArchived": "Show Archived",
|
||||||
"title": "Project Title",
|
"title": "Project Title",
|
||||||
"color": "Color",
|
"color": "Color",
|
||||||
"projects": "Projects",
|
"projects": "Projects",
|
||||||
@ -324,7 +325,6 @@
|
|||||||
"namespace": {
|
"namespace": {
|
||||||
"title": "Namespaces & Projects",
|
"title": "Namespaces & Projects",
|
||||||
"namespace": "Namespace",
|
"namespace": "Namespace",
|
||||||
"showArchived": "Show Archived",
|
|
||||||
"noneAvailable": "You don't have any namespaces right now.",
|
"noneAvailable": "You don't have any namespaces right now.",
|
||||||
"unarchive": "Un-Archive",
|
"unarchive": "Un-Archive",
|
||||||
"archived": "Archived",
|
"archived": "Archived",
|
||||||
|
@ -22,7 +22,6 @@ const DataExportDownload = () => import('@/views/user/DataExportDownload.vue')
|
|||||||
// Tasks
|
// Tasks
|
||||||
import UpcomingTasksComponent from '@/views/tasks/ShowTasks.vue'
|
import UpcomingTasksComponent from '@/views/tasks/ShowTasks.vue'
|
||||||
import LinkShareAuthComponent from '@/views/sharing/LinkSharingAuth.vue'
|
import LinkShareAuthComponent from '@/views/sharing/LinkSharingAuth.vue'
|
||||||
const ListNamespaces = () => import('@/views/namespaces/ListNamespaces.vue')
|
|
||||||
const TaskDetailView = () => import('@/views/tasks/TaskDetailView.vue')
|
const TaskDetailView = () => import('@/views/tasks/TaskDetailView.vue')
|
||||||
|
|
||||||
// Team Handling
|
// Team Handling
|
||||||
@ -41,6 +40,7 @@ const ProjectKanban = () => import('@/views/project/ProjectKanban.vue')
|
|||||||
const ProjectInfo = () => import('@/views/project/ProjectInfo.vue')
|
const ProjectInfo = () => import('@/views/project/ProjectInfo.vue')
|
||||||
|
|
||||||
// Project Settings
|
// Project Settings
|
||||||
|
const ListProjects = () => import('@/views/project/ListProjects.vue')
|
||||||
const ProjectSettingEdit = () => import('@/views/project/settings/edit.vue')
|
const ProjectSettingEdit = () => import('@/views/project/settings/edit.vue')
|
||||||
const ProjectSettingBackground = () => import('@/views/project/settings/background.vue')
|
const ProjectSettingBackground = () => import('@/views/project/settings/background.vue')
|
||||||
const ProjectSettingDuplicate = () => import('@/views/project/settings/duplicate.vue')
|
const ProjectSettingDuplicate = () => import('@/views/project/settings/duplicate.vue')
|
||||||
@ -203,11 +203,6 @@ const router = createRouter({
|
|||||||
name: 'link-share.auth',
|
name: 'link-share.auth',
|
||||||
component: LinkShareAuthComponent,
|
component: LinkShareAuthComponent,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: '/namespaces',
|
|
||||||
name: 'namespaces.index',
|
|
||||||
component: ListNamespaces,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: '/namespaces/new',
|
path: '/namespaces/new',
|
||||||
name: 'namespace.create',
|
name: 'namespace.create',
|
||||||
@ -281,6 +276,11 @@ const router = createRouter({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/projects',
|
||||||
|
name: 'projects.index',
|
||||||
|
component: ListProjects,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/projects/new/:namespaceId/',
|
path: '/projects/new/:namespaceId/',
|
||||||
name: 'project.create',
|
name: 'project.create',
|
||||||
|
@ -131,7 +131,7 @@ export function useSavedFilter(projectId?: MaybeRef<IProject['id']>) {
|
|||||||
await filterService.delete(filter.value)
|
await filterService.delete(filter.value)
|
||||||
await namespaceStore.loadNamespaces()
|
await namespaceStore.loadNamespaces()
|
||||||
success({message: t('filters.delete.success')})
|
success({message: t('filters.delete.success')})
|
||||||
router.push({name: 'namespaces.index'})
|
router.push({name: 'projects.index'})
|
||||||
}
|
}
|
||||||
|
|
||||||
const titleValid = ref(true)
|
const titleValid = ref(true)
|
||||||
|
@ -158,6 +158,20 @@ export const useProjectStore = defineStore('project', () => {
|
|||||||
cancel()
|
cancel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadProjects() {
|
||||||
|
const cancel = setModuleLoading(setIsLoading)
|
||||||
|
|
||||||
|
const projectService = new ProjectService()
|
||||||
|
try {
|
||||||
|
const projects = await projectService.getAll({}, {is_archived: true}) as IProject[]
|
||||||
|
setProjects(projects)
|
||||||
|
|
||||||
|
return projects
|
||||||
|
} finally {
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isLoading: readonly(isLoading),
|
isLoading: readonly(isLoading),
|
||||||
@ -171,6 +185,7 @@ export const useProjectStore = defineStore('project', () => {
|
|||||||
setProjects,
|
setProjects,
|
||||||
removeProjectById,
|
removeProjectById,
|
||||||
toggleProjectFavorite,
|
toggleProjectFavorite,
|
||||||
|
loadProjects,
|
||||||
createProject,
|
createProject,
|
||||||
updateProject,
|
updateProject,
|
||||||
deleteProject,
|
deleteProject,
|
||||||
|
@ -1,139 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="content loader-container" :class="{'is-loading': loading}" v-cy="'namespaces-list'">
|
|
||||||
<header class="namespace-header">
|
|
||||||
<fancycheckbox v-model="showArchived" v-cy="'show-archived-check'">
|
|
||||||
{{ $t('namespace.showArchived') }}
|
|
||||||
</fancycheckbox>
|
|
||||||
|
|
||||||
<div class="action-buttons">
|
|
||||||
<x-button :to="{name: 'filters.create'}" icon="filter">
|
|
||||||
{{ $t('filters.create.title') }}
|
|
||||||
</x-button>
|
|
||||||
<x-button :to="{name: 'namespace.create'}" icon="plus" v-cy="'new-namespace'">
|
|
||||||
{{ $t('namespace.create.title') }}
|
|
||||||
</x-button>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<p v-if="namespaces.length === 0" class="has-text-centered has-text-grey mt-4 is-italic">
|
|
||||||
{{ $t('namespace.noneAvailable') }}
|
|
||||||
<BaseButton :to="{name: 'namespace.create'}">
|
|
||||||
{{ $t('namespace.create.title') }}.
|
|
||||||
</BaseButton>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<section :key="`n${n.id}`" class="namespace" v-for="n in namespaces">
|
|
||||||
<x-button
|
|
||||||
v-if="n.id > 0 && n.projects.length > 0"
|
|
||||||
:to="{name: 'project.create', params: {namespaceId: n.id}}"
|
|
||||||
class="is-pulled-right"
|
|
||||||
variant="secondary"
|
|
||||||
icon="plus"
|
|
||||||
>
|
|
||||||
{{ $t('project.create.header') }}
|
|
||||||
</x-button>
|
|
||||||
<x-button
|
|
||||||
v-if="n.isArchived"
|
|
||||||
:to="{name: 'namespace.settings.archive', params: {id: n.id}}"
|
|
||||||
class="is-pulled-right mr-4"
|
|
||||||
variant="secondary"
|
|
||||||
icon="archive"
|
|
||||||
>
|
|
||||||
{{ $t('namespace.unarchive') }}
|
|
||||||
</x-button>
|
|
||||||
|
|
||||||
<h2 class="namespace-title">
|
|
||||||
<span v-cy="'namespace-title'">{{ getNamespaceTitle(n) }}</span>
|
|
||||||
<span v-if="n.isArchived" class="is-archived">
|
|
||||||
{{ $t('namespace.archived') }}
|
|
||||||
</span>
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<p v-if="n.projects.length === 0" class="has-text-centered has-text-grey mt-4 is-italic">
|
|
||||||
{{ $t('namespace.noProjects') }}
|
|
||||||
<BaseButton :to="{name: 'project.create', params: {namespaceId: n.id}}">
|
|
||||||
{{ $t('namespace.createProject') }}
|
|
||||||
</BaseButton>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<ProjectCardGrid v-else
|
|
||||||
:projects="n.projects"
|
|
||||||
:show-archived="showArchived"
|
|
||||||
/>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import {computed} from 'vue'
|
|
||||||
import {useI18n} from 'vue-i18n'
|
|
||||||
|
|
||||||
import BaseButton from '@/components/base/BaseButton.vue'
|
|
||||||
import Fancycheckbox from '@/components/input/fancycheckbox.vue'
|
|
||||||
import ProjectCardGrid from '@/components/project/partials/ProjectCardGrid.vue'
|
|
||||||
|
|
||||||
import {getNamespaceTitle} from '@/helpers/getNamespaceTitle'
|
|
||||||
import {useTitle} from '@/composables/useTitle'
|
|
||||||
import {useStorage} from '@vueuse/core'
|
|
||||||
|
|
||||||
import {useNamespaceStore} from '@/stores/namespaces'
|
|
||||||
|
|
||||||
const {t} = useI18n()
|
|
||||||
const namespaceStore = useNamespaceStore()
|
|
||||||
|
|
||||||
useTitle(() => t('namespace.title'))
|
|
||||||
const showArchived = useStorage('showArchived', false)
|
|
||||||
|
|
||||||
const loading = computed(() => namespaceStore.isLoading)
|
|
||||||
const namespaces = computed(() => {
|
|
||||||
return namespaceStore.namespaces.filter(namespace => showArchived.value
|
|
||||||
? true
|
|
||||||
: !namespace.isArchived,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.namespace-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
gap: 1rem;
|
|
||||||
|
|
||||||
@media screen and (max-width: $tablet) {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-buttons {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 1rem;
|
|
||||||
|
|
||||||
@media screen and (max-width: $tablet) {
|
|
||||||
width: 100%;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: stretch;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.namespace:not(:first-child) {
|
|
||||||
margin-top: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.namespace-title {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.is-archived {
|
|
||||||
font-size: 0.75rem;
|
|
||||||
border: 1px solid var(--grey-500);
|
|
||||||
color: $grey !important;
|
|
||||||
padding: 2px 4px;
|
|
||||||
border-radius: 3px;
|
|
||||||
font-family: $vikunja-font;
|
|
||||||
background: var(--white-translucent);
|
|
||||||
margin-left: .5rem;
|
|
||||||
}
|
|
||||||
</style>
|
|
144
src/views/project/ListProjects.vue
Normal file
144
src/views/project/ListProjects.vue
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
<template>
|
||||||
|
<div class="content loader-container" :class="{'is-loading': loading}" v-cy="'projects-list'">
|
||||||
|
<header class="project-header">
|
||||||
|
<fancycheckbox v-model="showArchived" v-cy="'show-archived-check'">
|
||||||
|
{{ $t('project.showArchived') }}
|
||||||
|
</fancycheckbox>
|
||||||
|
|
||||||
|
<div class="action-buttons">
|
||||||
|
<x-button :to="{name: 'filters.create'}" icon="filter">
|
||||||
|
{{ $t('filters.create.title') }}
|
||||||
|
</x-button>
|
||||||
|
<x-button :to="{name: 'project.create'}" icon="plus" v-cy="'new-project'">
|
||||||
|
{{ $t('project.create.title') }}
|
||||||
|
</x-button>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<ProjectCardGrid
|
||||||
|
:projects="projects"
|
||||||
|
:show-archived="showArchived"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- <p v-if="projects.length === 0" class="has-text-centered has-text-grey mt-4 is-italic">-->
|
||||||
|
<!-- {{ $t('project.noneAvailable') }}-->
|
||||||
|
<!-- <BaseButton :to="{name: 'project.create'}">-->
|
||||||
|
<!-- {{ $t('project.create.title') }}.-->
|
||||||
|
<!-- </BaseButton>-->
|
||||||
|
<!-- </p>-->
|
||||||
|
|
||||||
|
<!-- <section :key="`n${n.id}`" class="project" v-for="n in projects">-->
|
||||||
|
<!-- <x-button-->
|
||||||
|
<!-- v-if="n.id > 0 && n.projects.length > 0"-->
|
||||||
|
<!-- :to="{name: 'project.create', params: {projectId: n.id}}"-->
|
||||||
|
<!-- class="is-pulled-right"-->
|
||||||
|
<!-- variant="secondary"-->
|
||||||
|
<!-- icon="plus"-->
|
||||||
|
<!-- >-->
|
||||||
|
<!-- {{ $t('project.create.header') }}-->
|
||||||
|
<!-- </x-button>-->
|
||||||
|
<!-- <x-button-->
|
||||||
|
<!-- v-if="n.isArchived"-->
|
||||||
|
<!-- :to="{name: 'project.settings.archive', params: {id: n.id}}"-->
|
||||||
|
<!-- class="is-pulled-right mr-4"-->
|
||||||
|
<!-- variant="secondary"-->
|
||||||
|
<!-- icon="archive"-->
|
||||||
|
<!-- >-->
|
||||||
|
<!-- {{ $t('project.unarchive') }}-->
|
||||||
|
<!-- </x-button>-->
|
||||||
|
|
||||||
|
<!-- <h2 class="project-title">-->
|
||||||
|
<!-- <span v-cy="'project-title'">{{ getProjectTitle(n) }}</span>-->
|
||||||
|
<!-- <span v-if="n.isArchived" class="is-archived">-->
|
||||||
|
<!-- {{ $t('project.archived') }}-->
|
||||||
|
<!-- </span>-->
|
||||||
|
<!-- </h2>-->
|
||||||
|
|
||||||
|
<!-- <!– <p v-if="n.projects.length === 0" class="has-text-centered has-text-grey mt-4 is-italic">–>-->
|
||||||
|
<!-- <!– {{ $t('project.noProjects') }}–>-->
|
||||||
|
<!-- <!– <BaseButton :to="{name: 'project.create', params: {projectId: n.id}}">–>-->
|
||||||
|
<!-- <!– {{ $t('project.createProject') }}–>-->
|
||||||
|
<!-- <!– </BaseButton>–>-->
|
||||||
|
<!-- <!– </p>–>-->
|
||||||
|
|
||||||
|
<!-- <ProjectCardGrid v-else-->
|
||||||
|
<!-- :projects="n.projects"-->
|
||||||
|
<!-- :show-archived="showArchived"-->
|
||||||
|
<!-- />-->
|
||||||
|
<!-- </section>-->
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {computed} from 'vue'
|
||||||
|
import {useI18n} from 'vue-i18n'
|
||||||
|
|
||||||
|
import BaseButton from '@/components/base/BaseButton.vue'
|
||||||
|
import Fancycheckbox from '@/components/input/fancycheckbox.vue'
|
||||||
|
import ProjectCardGrid from '@/components/project/partials/ProjectCardGrid.vue'
|
||||||
|
|
||||||
|
import {getProjectTitle} from '@/helpers/getProjectTitle'
|
||||||
|
import {useTitle} from '@/composables/useTitle'
|
||||||
|
import {useStorage} from '@vueuse/core'
|
||||||
|
|
||||||
|
import {useProjectStore} from '@/stores/projects'
|
||||||
|
|
||||||
|
const {t} = useI18n()
|
||||||
|
const projectStore = useProjectStore()
|
||||||
|
|
||||||
|
useTitle(() => t('project.title'))
|
||||||
|
const showArchived = useStorage('showArchived', false)
|
||||||
|
|
||||||
|
const loading = computed(() => projectStore.isLoading)
|
||||||
|
const projects = computed(() => {
|
||||||
|
return Object.values(projectStore.projects).filter(project => showArchived.value
|
||||||
|
? true
|
||||||
|
: !project.isArchived,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.project-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
|
||||||
|
@media screen and (max-width: $tablet) {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-buttons {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 1rem;
|
||||||
|
|
||||||
|
@media screen and (max-width: $tablet) {
|
||||||
|
width: 100%;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.project:not(:first-child) {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-archived {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
border: 1px solid var(--grey-500);
|
||||||
|
color: $grey !important;
|
||||||
|
padding: 2px 4px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-family: $vikunja-font;
|
||||||
|
background: var(--white-translucent);
|
||||||
|
margin-left: .5rem;
|
||||||
|
}
|
||||||
|
</style>
|
Loading…
x
Reference in New Issue
Block a user