From 8011da12596e885da96d498cca93f30cc0094922 Mon Sep 17 00:00:00 2001 From: Nikolai Giman Date: Sat, 22 Aug 2026 09:01:33 +0200 Subject: [PATCH] fix: #106 --- .../components/features/auth/auth.helper.ts | 18 ++------- .../features/projects/ProjectsSidebar.vue | 5 ++- .../projects/parts/ProjectListBase.vue | 4 +- .../UiCustomizationOthers.vue | 3 +- .../sidebars/DashboardSidebarSecond.vue | 5 ++- .../dashboard-second/SidebarProjectSelect.vue | 5 ++- web/src/composables/useProjectRoute.ts | 37 +++++++++++++++++++ web/src/locales/de.ts | 2 +- web/src/locales/en.ts | 2 +- web/src/locales/es.ts | 2 +- web/src/locales/ru.ts | 2 +- 11 files changed, 58 insertions(+), 27 deletions(-) create mode 100644 web/src/composables/useProjectRoute.ts diff --git a/web/src/components/features/auth/auth.helper.ts b/web/src/components/features/auth/auth.helper.ts index 038b216..b8892c3 100644 --- a/web/src/components/features/auth/auth.helper.ts +++ b/web/src/components/features/auth/auth.helper.ts @@ -1,16 +1,9 @@ -import { ALL_TASKS_LIST_ID, type DefaultView } from 'taskview-api' import type { RouteLocationRaw, Router } from 'vue-router' import { $tvApi } from '@/plugins/axios' import { useUserStore } from '@/stores/user.store' import { useOrganizationStore } from '@/stores/organization.store' import { useUiPreferencesStore } from '@/stores/uiPreferences.store' - -const VIEW_ROUTES: Record = { - tasks: 'user', - kanban: 'kanban', - graph: 'graph', - sprints: 'sprints', -} +import { useProjectRoute } from '@/composables/useProjectRoute' export const redirectToUser = async (router: Router) => { const userStore = useUserStore() @@ -36,15 +29,12 @@ export const resolveDefaultRoute = async (): Promise => // The default project may live in any of the user's organizations; try the current one first const orgStore = useOrganizationStore() const orgs = [...orgStore.organizations].sort((a) => (a.slug === orgStore.currentOrgSlug ? -1 : 1)) + const { projectRoute } = useProjectRoute() for (const org of orgs) { const goals = await $tvApi.goals.fetchGoals(org.id) - if (!goals?.some((goal) => goal.id === projectId)) continue - - const view = uiPrefs.settings.defaultView ?? 'tasks' - const params: Record = { orgSlug: org.slug, projectId } - if (view === 'tasks') params.listId = ALL_TASKS_LIST_ID - return { name: VIEW_ROUTES[view], params } + const goal = goals?.find((g) => g.id === projectId) + if (goal) return projectRoute(goal, org.slug) } return null diff --git a/web/src/components/features/projects/ProjectsSidebar.vue b/web/src/components/features/projects/ProjectsSidebar.vue index 51df128..7a3e03f 100644 --- a/web/src/components/features/projects/ProjectsSidebar.vue +++ b/web/src/components/features/projects/ProjectsSidebar.vue @@ -25,9 +25,10 @@ import { useOrganizationStore } from '@/stores/organization.store' import ProjectsList from '@/components/features/projects/ProjectsList.vue' import ArchiveList from '@/components/features/projects/ArchiveList.vue' import type { Project, ProjectSaveData } from '@/components/features/projects/types' -import { ALL_TASKS_LIST_ID } from 'taskview-api' +import { useProjectRoute } from '@/composables/useProjectRoute' const router = useRouter() +const { projectRoute } = useProjectRoute() const goalsStore = useGoalsStore() const orgStore = useOrganizationStore() @@ -75,7 +76,7 @@ async function handleAdd(name: string) { ...(orgStore.currentOrg && { organizationId: orgStore.currentOrg.id }), }) if (newProject) { - router.push({ name: 'user', params: { projectId: newProject.id, listId: ALL_TASKS_LIST_ID } }) + router.push(projectRoute(newProject)) } } diff --git a/web/src/components/features/projects/parts/ProjectListBase.vue b/web/src/components/features/projects/parts/ProjectListBase.vue index 6d7c832..f64a3ae 100644 --- a/web/src/components/features/projects/parts/ProjectListBase.vue +++ b/web/src/components/features/projects/parts/ProjectListBase.vue @@ -26,7 +26,7 @@ v-for="project in projects" :key="project.id" variant="taskview" - :to="{ name: 'user', params: { projectId: project.id, listId: '-1401' } }" + :to="projectRoute(project)" :active="currentProjectId === project.id" >
() const { t } = useI18n() +const { projectRoute } = useProjectRoute() const isOpen = defineModel('open', { required: false, default: true }) diff --git a/web/src/components/features/ui-customization/UiCustomizationOthers.vue b/web/src/components/features/ui-customization/UiCustomizationOthers.vue index c4cc179..2557dcc 100644 --- a/web/src/components/features/ui-customization/UiCustomizationOthers.vue +++ b/web/src/components/features/ui-customization/UiCustomizationOthers.vue @@ -38,7 +38,7 @@ v-model="defaultView" :items="viewItems" value-key="value" - :disabled="defaultProject === NONE" + data-testid="default-view-select" variant="soft" class="w-full lg:w-72" size="xl" @@ -70,7 +70,6 @@ const defaultProject = computed({ get: () => store.settings.defaultProjectId ?? NONE, set: (value) => { store.setSetting('defaultProjectId', value === NONE ? undefined : value) - if (value === NONE) store.setSetting('defaultView', undefined) }, }) diff --git a/web/src/components/sidebars/DashboardSidebarSecond.vue b/web/src/components/sidebars/DashboardSidebarSecond.vue index 565d1aa..1265d35 100644 --- a/web/src/components/sidebars/DashboardSidebarSecond.vue +++ b/web/src/components/sidebars/DashboardSidebarSecond.vue @@ -50,8 +50,8 @@ diff --git a/web/src/components/sidebars/dashboard-second/SidebarProjectSelect.vue b/web/src/components/sidebars/dashboard-second/SidebarProjectSelect.vue index 6503c5a..4f65b7e 100644 --- a/web/src/components/sidebars/dashboard-second/SidebarProjectSelect.vue +++ b/web/src/components/sidebars/dashboard-second/SidebarProjectSelect.vue @@ -150,9 +150,9 @@ import { computed, ref } from 'vue' import { useI18n } from 'vue-i18n' import { useRoute, useRouter } from 'vue-router' import { storeToRefs } from 'pinia' -import { ALL_TASKS_LIST_ID } from 'taskview-api' import { useGoalsStore } from '@/stores/goals.store' import { useGoalPermissionsFor } from '@/composables/useGoalPermissions' +import { useProjectRoute } from '@/composables/useProjectRoute' import type { Project, ProjectSaveData } from '@/components/features/projects/types' import ProjectEditModal from '@/components/features/projects/parts/ProjectEditModal.vue' import ProjectDeleteDialog from '@/components/features/projects/parts/ProjectDeleteDialog.vue' @@ -160,6 +160,7 @@ import ProjectDeleteDialog from '@/components/features/projects/parts/ProjectDel const { t } = useI18n() const route = useRoute() const router = useRouter() +const { projectRoute } = useProjectRoute() const goalsStore = useGoalsStore() const { goals } = storeToRefs(goalsStore) @@ -186,7 +187,7 @@ const { canEditGoal, canDeleteGoal } = useGoalPermissionsFor(currentProject) function selectProject(project: Project) { open.value = false - router.push({ name: 'user', params: { projectId: project.id, listId: ALL_TASKS_LIST_ID } }) + router.push(projectRoute(project)) } async function archive() { diff --git a/web/src/composables/useProjectRoute.ts b/web/src/composables/useProjectRoute.ts new file mode 100644 index 0000000..16ef649 --- /dev/null +++ b/web/src/composables/useProjectRoute.ts @@ -0,0 +1,37 @@ +import type { RouteLocationRaw } from 'vue-router' +import { ALL_TASKS_LIST_ID, type DefaultView, type GoalItem, type GoalPermissions } from 'taskview-api' +import { useUiPreferencesStore } from '@/stores/uiPreferences.store' +import { AllGoalPermissions } from '@/types/goals.types' + +export const VIEW_ROUTES: Record = { + tasks: 'user', + kanban: 'kanban', + graph: 'graph', + sprints: 'sprints', +} + +const VIEW_PERMISSIONS: Record, (keyof GoalPermissions)[]> = { + kanban: [AllGoalPermissions.KANBAN_CAN_VIEW, AllGoalPermissions.KANBAN_CAN_MANAGE], + graph: [AllGoalPermissions.GRAPH_CAN_VIEW, AllGoalPermissions.GRAPH_CAN_MANAGE], + sprints: [AllGoalPermissions.SPRINT_CAN_VIEW], +} + +function canOpenView(goal: GoalItem, view: DefaultView): boolean { + if (view === 'tasks') return true + return VIEW_PERMISSIONS[view].some((perm) => !!goal.permissions[perm]) +} + +export function useProjectRoute() { + const uiPrefs = useUiPreferencesStore() + + function projectRoute(goal: GoalItem, orgSlug?: string): RouteLocationRaw { + const preferred = uiPrefs.settings.defaultView ?? 'tasks' + const view: DefaultView = canOpenView(goal, preferred) ? preferred : 'tasks' + const params: Record = { projectId: goal.id } + if (orgSlug) params.orgSlug = orgSlug + if (view === 'tasks') params.listId = ALL_TASKS_LIST_ID + return { name: VIEW_ROUTES[view], params } + } + + return { projectRoute } +} diff --git a/web/src/locales/de.ts b/web/src/locales/de.ts index d28da1e..7deb0dc 100644 --- a/web/src/locales/de.ts +++ b/web/src/locales/de.ts @@ -810,7 +810,7 @@ export default { defaultProjectHint: 'Dieses Projekt direkt nach der Anmeldung öffnen.', defaultProjectNone: 'Startbildschirm (Standard)', defaultView: 'Standardansicht', - defaultViewHint: 'Welche Ansicht des Projekts geöffnet wird.', + defaultViewHint: 'Welche Ansicht beim Öffnen eines Projekts angezeigt wird.', viewTasks: 'Aufgaben', viewKanban: 'Kanban', viewGraph: 'Graph', diff --git a/web/src/locales/en.ts b/web/src/locales/en.ts index db3513a..6ab32bd 100644 --- a/web/src/locales/en.ts +++ b/web/src/locales/en.ts @@ -824,7 +824,7 @@ export default { defaultProjectHint: 'Open this project right after signing in.', defaultProjectNone: 'Home screen (default)', defaultView: 'Default view', - defaultViewHint: 'Which view of the default project to open.', + defaultViewHint: 'Which view to open when you enter a project.', viewTasks: 'Tasks', viewKanban: 'Kanban', viewGraph: 'Graph', diff --git a/web/src/locales/es.ts b/web/src/locales/es.ts index 8a90880..ddda0b6 100644 --- a/web/src/locales/es.ts +++ b/web/src/locales/es.ts @@ -810,7 +810,7 @@ export default { defaultProjectHint: 'Abrir este proyecto justo después de iniciar sesión.', defaultProjectNone: 'Pantalla de inicio (predeterminado)', defaultView: 'Vista predeterminada', - defaultViewHint: 'Qué vista del proyecto abrir.', + defaultViewHint: 'Qué vista abrir al entrar en un proyecto.', viewTasks: 'Tareas', viewKanban: 'Kanban', viewGraph: 'Grafo', diff --git a/web/src/locales/ru.ts b/web/src/locales/ru.ts index e45acda..d4c8049 100644 --- a/web/src/locales/ru.ts +++ b/web/src/locales/ru.ts @@ -797,7 +797,7 @@ export default { defaultProjectHint: 'Открывать этот проект сразу после входа.', defaultProjectNone: 'Главный экран (по умолчанию)', defaultView: 'Вид по умолчанию', - defaultViewHint: 'Какой вид проекта открывать.', + defaultViewHint: 'Какой вид открывать при переходе в проект.', viewTasks: 'Задачи', viewKanban: 'Канбан', viewGraph: 'Граф',