diff --git a/api/src/tv-modules/ui-preferences/types.ts b/api/src/tv-modules/ui-preferences/types.ts index 61789d9..26a2a0f 100644 --- a/api/src/tv-modules/ui-preferences/types.ts +++ b/api/src/tv-modules/ui-preferences/types.ts @@ -44,6 +44,8 @@ const firstDayOfWeekArkType = type('number.integer').narrow((v, ctx) => export const UiSettingsArkType = type({ 'firstDayOfWeek?': firstDayOfWeekArkType, + 'defaultProjectId?': 'number.integer >= 1', + 'defaultView?': "'tasks' | 'kanban' | 'graph' | 'sprints'", }) export type UiSettings = typeof UiSettingsArkType.infer diff --git a/taskview-packages/taskview-api/src/api/ui-preferences.types.ts b/taskview-packages/taskview-api/src/api/ui-preferences.types.ts index b982f1e..4bb6adf 100644 --- a/taskview-packages/taskview-api/src/api/ui-preferences.types.ts +++ b/taskview-packages/taskview-api/src/api/ui-preferences.types.ts @@ -7,8 +7,12 @@ export type UiPreferencesItem = { export type FirstDayOfWeek = 0 | 1 | 2 | 3 | 4 | 5 | 6 +export type DefaultView = 'tasks' | 'kanban' | 'graph' | 'sprints' + export type UiSettings = { firstDayOfWeek?: FirstDayOfWeek + defaultProjectId?: number + defaultView?: DefaultView } export const UI_SETTINGS_KEY = '__settings__' diff --git a/web/src/components/features/auth/auth.helper.ts b/web/src/components/features/auth/auth.helper.ts index c6fe9c4..038b216 100644 --- a/web/src/components/features/auth/auth.helper.ts +++ b/web/src/components/features/auth/auth.helper.ts @@ -1,15 +1,51 @@ +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 { Router } from 'vue-router' +import { useUiPreferencesStore } from '@/stores/uiPreferences.store' + +const VIEW_ROUTES: Record = { + tasks: 'user', + kanban: 'kanban', + graph: 'graph', + sprints: 'sprints', +} export const redirectToUser = async (router: Router) => { const userStore = useUserStore() - if (userStore.accessToken) { - const orgStore = useOrganizationStore() - if (!orgStore.organizations.length) { - await orgStore.fetchOrganizations() - orgStore.restoreCurrentOrg() - } - await router.push({ name: 'user', params: { orgSlug: orgStore.currentOrgSlug } }) + if (!userStore.accessToken) return + + const orgStore = useOrganizationStore() + if (!orgStore.organizations.length) { + await orgStore.fetchOrganizations() + orgStore.restoreCurrentOrg() } -} \ No newline at end of file + + const defaultRoute = await resolveDefaultRoute() + await router.push(defaultRoute ?? { name: 'user', params: { orgSlug: orgStore.currentOrgSlug } }) +} + +export const resolveDefaultRoute = async (): Promise => { + const uiPrefs = useUiPreferencesStore() + if (!uiPrefs.loaded) await uiPrefs.fetch() + + const projectId = uiPrefs.settings.defaultProjectId + if (!projectId) return null + + // 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)) + + 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 } + } + + return null +} diff --git a/web/src/components/features/ui-customization/UiCustomizationOthers.vue b/web/src/components/features/ui-customization/UiCustomizationOthers.vue index 8aac546..c4cc179 100644 --- a/web/src/components/features/ui-customization/UiCustomizationOthers.vue +++ b/web/src/components/features/ui-customization/UiCustomizationOthers.vue @@ -14,19 +14,84 @@ :ui="{ base: 'rounded-xl' }" /> + + + + + + + + diff --git a/web/src/locales/de.ts b/web/src/locales/de.ts index 8abde8c..badbc42 100644 --- a/web/src/locales/de.ts +++ b/web/src/locales/de.ts @@ -792,6 +792,15 @@ export default { friday: 'Freitag', saturday: 'Samstag', sunday: 'Sonntag', + defaultProject: 'Standardprojekt', + defaultProjectHint: 'Dieses Projekt direkt nach der Anmeldung öffnen.', + defaultProjectNone: 'Startbildschirm (Standard)', + defaultView: 'Standardansicht', + defaultViewHint: 'Welche Ansicht des Projekts geöffnet wird.', + viewTasks: 'Aufgaben', + viewKanban: 'Kanban', + viewGraph: 'Graph', + viewSprints: 'Sprints', }, taskFields: { subtasks: 'Teilaufgaben', diff --git a/web/src/locales/en.ts b/web/src/locales/en.ts index 1541acb..01e5ef8 100644 --- a/web/src/locales/en.ts +++ b/web/src/locales/en.ts @@ -806,6 +806,15 @@ export default { friday: 'Friday', saturday: 'Saturday', sunday: 'Sunday', + defaultProject: 'Default project', + defaultProjectHint: 'Open this project right after signing in.', + defaultProjectNone: 'Home screen (default)', + defaultView: 'Default view', + defaultViewHint: 'Which view of the default project to open.', + viewTasks: 'Tasks', + viewKanban: 'Kanban', + viewGraph: 'Graph', + viewSprints: 'Sprints', }, taskFields: { subtasks: 'Subtasks', diff --git a/web/src/locales/es.ts b/web/src/locales/es.ts index 671ca6b..3839029 100644 --- a/web/src/locales/es.ts +++ b/web/src/locales/es.ts @@ -792,6 +792,15 @@ export default { friday: 'Viernes', saturday: 'Sábado', sunday: 'Domingo', + defaultProject: 'Proyecto predeterminado', + 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.', + viewTasks: 'Tareas', + viewKanban: 'Kanban', + viewGraph: 'Grafo', + viewSprints: 'Sprints', }, taskFields: { subtasks: 'Subtareas', diff --git a/web/src/locales/ru.ts b/web/src/locales/ru.ts index cfbb1f4..4bbfbd9 100644 --- a/web/src/locales/ru.ts +++ b/web/src/locales/ru.ts @@ -779,6 +779,15 @@ export default { friday: 'Пятница', saturday: 'Суббота', sunday: 'Воскресенье', + defaultProject: 'Проект по умолчанию', + defaultProjectHint: 'Открывать этот проект сразу после входа.', + defaultProjectNone: 'Главный экран (по умолчанию)', + defaultView: 'Вид по умолчанию', + defaultViewHint: 'Какой вид проекта открывать.', + viewTasks: 'Задачи', + viewKanban: 'Канбан', + viewGraph: 'Граф', + viewSprints: 'Спринты', }, taskFields: { subtasks: 'Подзадачи', diff --git a/web/src/uiCustomization/sections/others.ts b/web/src/uiCustomization/sections/others.ts index 976e063..268cda8 100644 --- a/web/src/uiCustomization/sections/others.ts +++ b/web/src/uiCustomization/sections/others.ts @@ -1,3 +1,4 @@ +import { markRaw } from 'vue' import UiCustomizationOthers from '@/components/features/ui-customization/UiCustomizationOthers.vue' import type { UiCustomizationSectionDef } from '../types' @@ -5,5 +6,7 @@ export const othersSection: UiCustomizationSectionDef = { kind: 'custom', id: 'others', labelKey: 'uiCustomization.sections.others', - component: UiCustomizationOthers, + // markRaw: the def ends up inside reactive tab items — a component proxied by + // reactivity triggers a Vue warning and needless overhead + component: markRaw(UiCustomizationOthers), }