Compare commits

...

11 Commits

Author SHA1 Message Date
Dominik Pschenitschni
cbbc4c0372 fix: i18n missing translation key 2025-01-19 20:10:22 +00:00
Dominik Pschenitschni
c7e708cf7d fix: deprecated import in useTitle 2025-01-19 20:07:55 +00:00
Dominik Pschenitschni
6d3a30c799 fix: postcss-easing-gradient types 2025-01-19 19:58:16 +00:00
Dominik Pschenitschni
4c972e1bc4 feat: load project in project view 2025-01-19 19:56:07 +00:00
Dominik Pschenitschni
144571e448 feat: simplify ProjectView 2025-01-19 19:56:07 +00:00
Dominik Pschenitschni
a24c64da8f fix: vite config linting 2025-01-19 19:39:40 +00:00
renovate
2faa03757c fix(deps): update module github.com/wneessen/go-mail to v0.6.1 2025-01-19 16:30:07 +00:00
kolaente
9cef2c4c97 chore: add Bug type to bug issue template 2025-01-19 16:48:44 +01:00
renovate
ea6b141d42 chore(deps): update dependency caniuse-lite to v1.0.30001695 2025-01-19 03:07:01 +00:00
renovate
c0877dd0ab chore(deps): update dependency vitest to v3.0.2 2025-01-18 10:32:50 +00:00
Frederick [Bot]
5c31ccaddd chore(i18n): update translations via Crowdin 2025-01-18 00:14:03 +00:00
17 changed files with 162 additions and 151 deletions

View File

@@ -1,6 +1,6 @@
name: Bug Report
description: Found something you weren't expecting? Report it here!
labels: kind/bug
type: Bug
body:
- type: markdown
attributes:
@@ -49,4 +49,4 @@ body:
id: screenshots
attributes:
label: Screenshots
description: If this issue involves the Web Interface, please provide one or more screenshots
description: If this issue involves the Web Interface, please provide one or more screenshots

View File

@@ -1,4 +1 @@
declare module 'postcss-easing-gradients' {
import postcssEasingGradients from 'postcss-easing-gradients'
export default postcssEasingGradients
}
declare module 'postcss-easing-gradients';

View File

@@ -130,7 +130,7 @@
"@vue/tsconfig": "0.7.0",
"autoprefixer": "10.4.20",
"browserslist": "4.24.4",
"caniuse-lite": "1.0.30001692",
"caniuse-lite": "1.0.30001695",
"csstype": "3.1.3",
"cypress": "14.0.0",
"esbuild": "0.24.2",
@@ -152,7 +152,7 @@
"vite-plugin-sentry": "1.4.0",
"vite-plugin-vue-devtools": "7.7.0",
"vite-svg-loader": "5.1.0",
"vitest": "3.0.1",
"vitest": "3.0.2",
"vue-tsc": "2.2.0",
"wait-on": "8.0.2",
"workbox-cli": "7.3.0"

106
frontend/pnpm-lock.yaml generated
View File

@@ -255,8 +255,8 @@ importers:
specifier: 4.24.4
version: 4.24.4
caniuse-lite:
specifier: 1.0.30001692
version: 1.0.30001692
specifier: 1.0.30001695
version: 1.0.30001695
csstype:
specifier: 3.1.3
version: 3.1.3
@@ -321,8 +321,8 @@ importers:
specifier: 5.1.0
version: 5.1.0(vue@3.5.13(typescript@5.7.3))
vitest:
specifier: 3.0.1
version: 3.0.1(@types/node@22.10.7)(happy-dom@16.6.0)(jiti@1.21.6)(jsdom@20.0.3)(sass-embedded@1.83.4)(sass@1.80.6)(terser@5.31.6)(yaml@2.5.0)
specifier: 3.0.2
version: 3.0.2(@types/node@22.10.7)(happy-dom@16.6.0)(jiti@1.21.6)(jsdom@20.0.3)(sass-embedded@1.83.4)(sass@1.80.6)(terser@5.31.6)(yaml@2.5.0)
vue-tsc:
specifier: 2.2.0
version: 2.2.0(typescript@5.7.3)
@@ -2514,11 +2514,11 @@ packages:
vite: ^5.0.0 || ^6.0.0
vue: ^3.2.25
'@vitest/expect@3.0.1':
resolution: {integrity: sha512-oPrXe8dwvQdzUxQFWwibY97/smQ6k8iPVeSf09KEvU1yWzu40G6naHExY0lUgjnTPWMRGQOJnhMBb8lBu48feg==}
'@vitest/expect@3.0.2':
resolution: {integrity: sha512-dKSHLBcoZI+3pmP5hiZ7I5grNru2HRtEW8Z5Zp4IXog8QYcxhlox7JUPyIIFWfN53+3HW3KPLIl6nSzUGgKSuQ==}
'@vitest/mocker@3.0.1':
resolution: {integrity: sha512-5letLsVdFhReCPws/SNwyekBCyi4w2IusycV4T7eVdt2mfellS2yKDrEmnE5KPCHr0Ez5xCZVJbJws3ckuNNgQ==}
'@vitest/mocker@3.0.2':
resolution: {integrity: sha512-Hr09FoBf0jlwwSyzIF4Xw31OntpO3XtZjkccpcBf8FeVW3tpiyKlkeUzxS/txzHqpUCNIX157NaTySxedyZLvA==}
peerDependencies:
msw: ^2.4.9
vite: ^5.0.0 || ^6.0.0
@@ -2528,20 +2528,20 @@ packages:
vite:
optional: true
'@vitest/pretty-format@3.0.1':
resolution: {integrity: sha512-FnyGQ9eFJ/Dnqg3jCvq9O6noXtxbZhOlSvNLZsCGJxhsGiZ5LDepmsTCizRfyGJt4Q6pJmZtx7rO/qqr9R9gDA==}
'@vitest/pretty-format@3.0.2':
resolution: {integrity: sha512-yBohcBw/T/p0/JRgYD+IYcjCmuHzjC3WLAKsVE4/LwiubzZkE8N49/xIQ/KGQwDRA8PaviF8IRO8JMWMngdVVQ==}
'@vitest/runner@3.0.1':
resolution: {integrity: sha512-LfVbbYOduTVx8PnYFGH98jpgubHBefIppbPQJBSlgjnRRlaX/KR6J46htECUHpf+ElJZ4xxssAfEz/Cb2iIMYA==}
'@vitest/runner@3.0.2':
resolution: {integrity: sha512-GHEsWoncrGxWuW8s405fVoDfSLk6RF2LCXp6XhevbtDjdDme1WV/eNmUueDfpY1IX3MJaCRelVCEXsT9cArfEg==}
'@vitest/snapshot@3.0.1':
resolution: {integrity: sha512-ZYV+iw2lGyc4QY2xt61b7Y3NJhSAO7UWcYWMcV0UnMrkXa8hXtfZES6WAk4g7Jr3p4qJm1P0cgDcOFyY5me+Ug==}
'@vitest/snapshot@3.0.2':
resolution: {integrity: sha512-h9s67yD4+g+JoYG0zPCo/cLTabpDqzqNdzMawmNPzDStTiwxwkyYM1v5lWE8gmGv3SVJ2DcxA2NpQJZJv9ym3g==}
'@vitest/spy@3.0.1':
resolution: {integrity: sha512-HnGJB3JFflnlka4u7aD0CfqrEtX3FgNaZAar18/KIhfo0r/WADn9PhBfiqAmNw4R/xaRcLzLPFXDwEQV1vHlJA==}
'@vitest/spy@3.0.2':
resolution: {integrity: sha512-8mI2iUn+PJFMT44e3ISA1R+K6ALVs47W6eriDTfXe6lFqlflID05MB4+rIFhmDSLBj8iBsZkzBYlgSkinxLzSQ==}
'@vitest/utils@3.0.1':
resolution: {integrity: sha512-i+Gm61rfIeSitPUsu4ZcWqucfb18ShAanRpOG6KlXfd1j6JVK5XxO2Z6lEmfjMnAQRIvvLtJ3JByzDTv347e8w==}
'@vitest/utils@3.0.2':
resolution: {integrity: sha512-Qu01ZYZlgHvDP02JnMBRpX43nRaZtNpIzw3C1clDXmn8eakgX6iQVGzTQ/NjkIr64WD8ioqOjkaYRVvHQI5qiw==}
'@volar/language-core@2.4.11':
resolution: {integrity: sha512-lN2C1+ByfW9/JRPpqScuZt/4OrUUse57GLI6TbLgTIqBVemdl1wNcZ1qYGEo2+Gw8coYLgCy7SuKqn6IrQcQgg==}
@@ -2947,8 +2947,8 @@ packages:
resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==}
engines: {node: '>=6'}
caniuse-lite@1.0.30001692:
resolution: {integrity: sha512-A95VKan0kdtrsnMubMKxEKUKImOPSuCpYgxSQBo036P5YYgVIcOYJEgt/txJWqObiRQeISNCfef9nvlQ0vbV7A==}
caniuse-lite@1.0.30001695:
resolution: {integrity: sha512-vHyLade6wTgI2u1ec3WQBxv+2BrTERV28UXQu9LO6lZ9pYeMk34vjXFLOxo1A4UBA8XTL4njRQZdno/yYaSmWw==}
capital-case@1.0.4:
resolution: {integrity: sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==}
@@ -6253,8 +6253,8 @@ packages:
engines: {node: '>=v14.18.0'}
hasBin: true
vite-node@3.0.1:
resolution: {integrity: sha512-PoH9mCNsSZQXl3gdymM5IE4WR0k0WbnFd89nAyyDvltF2jVGdFcI8vpB1PBdKTcjAR7kkYiHSlIO68X/UT8Q1A==}
vite-node@3.0.2:
resolution: {integrity: sha512-hsEQerBAHvVAbv40m3TFQe/lTEbOp7yDpyqMJqr2Tnd+W58+DEYOt+fluQgekOePcsNBmR77lpVAnIU2Xu4SvQ==}
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
hasBin: true
@@ -6373,15 +6373,15 @@ packages:
yaml:
optional: true
vitest@3.0.1:
resolution: {integrity: sha512-SWKoSAkxtFHqt8biR3eN53dzmeWkigEpyipqfblcsoAghVvoFMpxQEj0gc7AajMi6Ra49fjcTN6v4AxklmS4aQ==}
vitest@3.0.2:
resolution: {integrity: sha512-5bzaHakQ0hmVVKLhfh/jXf6oETDBtgPo8tQCHYB+wftNgFJ+Hah67IsWc8ivx4vFL025Ow8UiuTf4W57z4izvQ==}
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
hasBin: true
peerDependencies:
'@edge-runtime/vm': '*'
'@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
'@vitest/browser': 3.0.1
'@vitest/ui': 3.0.1
'@vitest/browser': 3.0.2
'@vitest/ui': 3.0.2
happy-dom: '*'
jsdom: '*'
peerDependenciesMeta:
@@ -8978,43 +8978,43 @@ snapshots:
vite: 6.0.7(@types/node@22.10.7)(jiti@1.21.6)(sass-embedded@1.83.4)(sass@1.80.6)(terser@5.31.6)(yaml@2.5.0)
vue: 3.5.13(typescript@5.7.3)
'@vitest/expect@3.0.1':
'@vitest/expect@3.0.2':
dependencies:
'@vitest/spy': 3.0.1
'@vitest/utils': 3.0.1
'@vitest/spy': 3.0.2
'@vitest/utils': 3.0.2
chai: 5.1.2
tinyrainbow: 2.0.0
'@vitest/mocker@3.0.1(vite@6.0.7(@types/node@22.10.7)(jiti@1.21.6)(sass-embedded@1.83.4)(sass@1.80.6)(terser@5.31.6)(yaml@2.5.0))':
'@vitest/mocker@3.0.2(vite@6.0.7(@types/node@22.10.7)(jiti@1.21.6)(sass-embedded@1.83.4)(sass@1.80.6)(terser@5.31.6)(yaml@2.5.0))':
dependencies:
'@vitest/spy': 3.0.1
'@vitest/spy': 3.0.2
estree-walker: 3.0.3
magic-string: 0.30.17
optionalDependencies:
vite: 6.0.7(@types/node@22.10.7)(jiti@1.21.6)(sass-embedded@1.83.4)(sass@1.80.6)(terser@5.31.6)(yaml@2.5.0)
'@vitest/pretty-format@3.0.1':
'@vitest/pretty-format@3.0.2':
dependencies:
tinyrainbow: 2.0.0
'@vitest/runner@3.0.1':
'@vitest/runner@3.0.2':
dependencies:
'@vitest/utils': 3.0.1
'@vitest/utils': 3.0.2
pathe: 2.0.1
'@vitest/snapshot@3.0.1':
'@vitest/snapshot@3.0.2':
dependencies:
'@vitest/pretty-format': 3.0.1
'@vitest/pretty-format': 3.0.2
magic-string: 0.30.17
pathe: 2.0.1
'@vitest/spy@3.0.1':
'@vitest/spy@3.0.2':
dependencies:
tinyspy: 3.0.2
'@vitest/utils@3.0.1':
'@vitest/utils@3.0.2':
dependencies:
'@vitest/pretty-format': 3.0.1
'@vitest/pretty-format': 3.0.2
loupe: 3.1.2
tinyrainbow: 2.0.0
@@ -9081,7 +9081,7 @@ snapshots:
'@vue/compiler-ssr': 3.5.13
'@vue/shared': 3.5.13
estree-walker: 2.0.2
magic-string: 0.30.14
magic-string: 0.30.17
postcss: 8.5.1
source-map-js: 1.2.1
@@ -9349,7 +9349,7 @@ snapshots:
autoprefixer@10.4.20(postcss@8.5.1):
dependencies:
browserslist: 4.24.4
caniuse-lite: 1.0.30001692
caniuse-lite: 1.0.30001695
fraction.js: 4.3.7
normalize-range: 0.1.2
picocolors: 1.0.1
@@ -9450,7 +9450,7 @@ snapshots:
browserslist@4.24.4:
dependencies:
caniuse-lite: 1.0.30001692
caniuse-lite: 1.0.30001695
electron-to-chromium: 1.5.73
node-releases: 2.0.19
update-browserslist-db: 1.1.1(browserslist@4.24.4)
@@ -9513,7 +9513,7 @@ snapshots:
camelcase@5.3.1: {}
caniuse-lite@1.0.30001692: {}
caniuse-lite@1.0.30001695: {}
capital-case@1.0.4:
dependencies:
@@ -13148,7 +13148,7 @@ snapshots:
- supports-color
- terser
vite-node@3.0.1(@types/node@22.10.7)(jiti@1.21.6)(sass-embedded@1.83.4)(sass@1.80.6)(terser@5.31.6)(yaml@2.5.0):
vite-node@3.0.2(@types/node@22.10.7)(jiti@1.21.6)(sass-embedded@1.83.4)(sass@1.80.6)(terser@5.31.6)(yaml@2.5.0):
dependencies:
cac: 6.7.14
debug: 4.4.0(supports-color@8.1.1)
@@ -13266,15 +13266,15 @@ snapshots:
terser: 5.31.6
yaml: 2.5.0
vitest@3.0.1(@types/node@22.10.7)(happy-dom@16.6.0)(jiti@1.21.6)(jsdom@20.0.3)(sass-embedded@1.83.4)(sass@1.80.6)(terser@5.31.6)(yaml@2.5.0):
vitest@3.0.2(@types/node@22.10.7)(happy-dom@16.6.0)(jiti@1.21.6)(jsdom@20.0.3)(sass-embedded@1.83.4)(sass@1.80.6)(terser@5.31.6)(yaml@2.5.0):
dependencies:
'@vitest/expect': 3.0.1
'@vitest/mocker': 3.0.1(vite@6.0.7(@types/node@22.10.7)(jiti@1.21.6)(sass-embedded@1.83.4)(sass@1.80.6)(terser@5.31.6)(yaml@2.5.0))
'@vitest/pretty-format': 3.0.1
'@vitest/runner': 3.0.1
'@vitest/snapshot': 3.0.1
'@vitest/spy': 3.0.1
'@vitest/utils': 3.0.1
'@vitest/expect': 3.0.2
'@vitest/mocker': 3.0.2(vite@6.0.7(@types/node@22.10.7)(jiti@1.21.6)(sass-embedded@1.83.4)(sass@1.80.6)(terser@5.31.6)(yaml@2.5.0))
'@vitest/pretty-format': 3.0.2
'@vitest/runner': 3.0.2
'@vitest/snapshot': 3.0.2
'@vitest/spy': 3.0.2
'@vitest/utils': 3.0.2
chai: 5.1.2
debug: 4.4.0(supports-color@8.1.1)
expect-type: 1.1.0
@@ -13286,7 +13286,7 @@ snapshots:
tinypool: 1.0.2
tinyrainbow: 2.0.0
vite: 6.0.7(@types/node@22.10.7)(jiti@1.21.6)(sass-embedded@1.83.4)(sass@1.80.6)(terser@5.31.6)(yaml@2.5.0)
vite-node: 3.0.1(@types/node@22.10.7)(jiti@1.21.6)(sass-embedded@1.83.4)(sass@1.80.6)(terser@5.31.6)(yaml@2.5.0)
vite-node: 3.0.2(@types/node@22.10.7)(jiti@1.21.6)(sass-embedded@1.83.4)(sass@1.80.6)(terser@5.31.6)(yaml@2.5.0)
why-is-node-running: 2.3.0
optionalDependencies:
'@types/node': 22.10.7

View File

@@ -63,7 +63,7 @@ const motd = computed(() => configStore.motd)
const route = useRoute()
const {t} = useI18n({useScope: 'global'})
const title = computed(() => t(route.meta?.title as string || ''))
const title = computed(() => route.meta?.title ? t(route.meta.title as string) : '')
useTitle(() => title.value)
</script>

View File

@@ -1,7 +1,10 @@
<template>
<div
:class="{ 'is-loading': projectService.loading, 'is-archived': currentProject?.isArchived}"
class="loader-container"
:class="{
'is-loading': isLoadingProject,
'is-archived': currentProject?.isArchived,
}"
>
<h1 class="project-title-print">
{{ getProjectTitle(currentProject) }}
@@ -37,43 +40,37 @@
</Message>
</CustomTransition>
<slot v-if="loadedProjectId" />
<slot v-if="!isLoadingProject" />
</div>
</template>
<script setup lang="ts">
import {computed, ref, watch} from 'vue'
import {useRoute} from 'vue-router'
import {computed} from 'vue'
import {useI18n} from 'vue-i18n'
import BaseButton from '@/components/base/BaseButton.vue'
import Message from '@/components/misc/Message.vue'
import CustomTransition from '@/components/misc/CustomTransition.vue'
import ProjectModel from '@/models/project'
import ProjectService from '@/services/project'
import {getProjectTitle} from '@/helpers/getProjectTitle'
import {saveProjectToHistory} from '@/modules/projectHistory'
import {useTitle} from '@/composables/useTitle'
import {useBaseStore} from '@/stores/base'
import {useProjectStore} from '@/stores/projects'
import type {IProject} from '@/modelTypes/IProject'
import type {IProjectView} from '@/modelTypes/IProjectView'
import {useI18n} from 'vue-i18n'
const props = defineProps<{
isLoadingProject: boolean,
projectId: IProject['id'],
viewId: IProjectView['id'],
}>()
const route = useRoute()
const {t} = useI18n()
const baseStore = useBaseStore()
const projectStore = useProjectStore()
const projectService = ref(new ProjectService())
const loadedProjectId = ref(0)
const currentProject = computed<IProject>(() => {
return typeof baseStore.currentProject === 'undefined' ? {
@@ -87,61 +84,6 @@ useTitle(() => currentProject.value?.id ? getProjectTitle(currentProject.value)
const views = computed(() => projectStore.projects[props.projectId]?.views)
// watchEffect would be called every time the prop would get a value assigned, even if that value was the same as before.
// This resulted in loading and setting the project multiple times, even when navigating away from it.
// This caused wired bugs where the project background would be set on the home page but only right after setting a new
// project background and then navigating to home. It also highlighted the project in the menu and didn't allow changing any
// of it, most likely due to the rights not being properly populated.
watch(
() => props.projectId,
// loadProject
async (projectIdToLoad: number) => {
const projectData = {id: projectIdToLoad}
saveProjectToHistory(projectData)
// Don't load the project if we either already loaded it or aren't dealing with a project at all currently and
// the currently loaded project has the right set.
if (
(
projectIdToLoad === loadedProjectId.value ||
typeof projectIdToLoad === 'undefined' ||
projectIdToLoad === currentProject.value?.id
)
&& typeof currentProject.value !== 'undefined' && currentProject.value.maxRight !== null
) {
loadedProjectId.value = projectIdToLoad
return
}
console.debug('Loading project, $route.params =', route.params, `, loadedProjectId = ${loadedProjectId.value}, currentProject = `, currentProject.value)
// Set the current project to the one we're about to load so that the title is already shown at the top
loadedProjectId.value = 0
const projectFromStore = projectStore.projects[projectData.id]
if (projectFromStore) {
baseStore.handleSetCurrentProject({project: projectFromStore, currentProjectViewId: props.viewId})
}
// We create an extra project object instead of creating it in project.value because that would trigger a ui update which would result in bad ux.
const project = new ProjectModel(projectData)
try {
const loadedProject = await projectService.value.get(project)
baseStore.handleSetCurrentProject({project: loadedProject, currentProjectViewId: props.viewId})
} finally {
loadedProjectId.value = projectIdToLoad
}
},
{immediate: true},
)
watch(
() => props.viewId,
() => {
baseStore.setCurrentProjectViewId(props.viewId)
},
{immediate: true},
)
function getViewTitle(view: IProjectView) {
switch (view.title) {
case 'List':

View File

@@ -1,6 +1,7 @@
<template>
<ProjectWrapper
class="project-gantt"
:is-loading-project="isLoadingProject"
:project-id="filters.projectId"
:view-id
>
@@ -95,6 +96,7 @@ import type {IProjectView} from '@/modelTypes/IProjectView'
type Options = Flatpickr.Options.Options
const props = defineProps<{
isLoadingProject: boolean,
route: RouteLocationNormalized
viewId: IProjectView['id']
}>()

View File

@@ -1,6 +1,7 @@
<template>
<ProjectWrapper
class="project-kanban"
:is-loading-project="isLoadingProject"
:project-id="projectId"
:view-id
>
@@ -315,6 +316,7 @@ import TaskBucketService from '@/services/taskBucket'
import TaskBucketModel from '@/models/taskBucket'
const props = defineProps<{
isLoadingProject: boolean,
projectId: number,
viewId: IProjectView['id'],
}>()

View File

@@ -1,6 +1,7 @@
<template>
<ProjectWrapper
class="project-list"
:is-loading-project="isLoadingProject"
:project-id="projectId"
:view-id
>
@@ -123,6 +124,7 @@ import TaskPositionService from '@/services/taskPosition'
import TaskPositionModel from '@/models/taskPosition'
const props = defineProps<{
isLoadingProject: boolean,
projectId: IProject['id'],
viewId: IProjectView['id'],
}>()

View File

@@ -1,6 +1,7 @@
<template>
<ProjectWrapper
class="project-table"
:is-loading-project="isLoadingProject"
:project-id="projectId"
:view-id
>
@@ -298,6 +299,7 @@ import { camelCase } from 'change-case'
import {isSavedFilter} from '@/services/savedFilter'
const props = defineProps<{
isLoadingProject: boolean,
projectId: IProject['id'],
viewId: IProjectView['id'],
}>()

View File

@@ -1,6 +1,6 @@
import {computed} from 'vue'
import {computed, toValue} from 'vue'
import {useTitle as useTitleVueUse, toValue, type UseTitleOptions, type ReadonlyRefOrGetter, type MaybeRef, type MaybeRefOrGetter} from '@vueuse/core'
import {useTitle as useTitleVueUse, type UseTitleOptions, type ReadonlyRefOrGetter, type MaybeRef, type MaybeRefOrGetter} from '@vueuse/core'
export function useTitle(
newTitle:

View File

@@ -1124,7 +1124,7 @@
}
},
"date": {
"locale": "nb_NO",
"locale": "nn",
"altFormatLong": "d.m.y H:i",
"altFormatShort": "d.m.y"
},

View File

@@ -1,15 +1,21 @@
<script setup lang="ts">
import {computed, watch} from 'vue'
import {useProjectStore} from '@/stores/projects'
import {computed, ref, shallowReactive, watch, watchEffect} from 'vue'
import {useRoute, useRouter} from 'vue-router'
import {useBaseStore} from '@/stores/base'
import {useProjectStore} from '@/stores/projects'
import {useAuthStore} from '@/stores/auth'
import {saveProjectView} from '@/helpers/projectView'
import ProjectService from '@/services/project'
import ProjectList from '@/components/project/views/ProjectList.vue'
import ProjectGantt from '@/components/project/views/ProjectGantt.vue'
import ProjectTable from '@/components/project/views/ProjectTable.vue'
import ProjectKanban from '@/components/project/views/ProjectKanban.vue'
import {useAuthStore} from '@/stores/auth'
import {DEFAULT_PROJECT_VIEW_SETTINGS} from '@/modelTypes/IProjectView'
import {saveProjectToHistory} from '@/modules/projectHistory'
const props = defineProps<{
projectId: number,
@@ -17,8 +23,10 @@ const props = defineProps<{
}>()
const router = useRouter()
const baseStore = useBaseStore()
const projectStore = useProjectStore()
const authStore = useAuthStore()
const route = useRoute()
const currentProject = computed(() => projectStore.projects[props.projectId])
@@ -26,19 +34,68 @@ const currentView = computed(() => {
return currentProject.value?.views.find(v => v.id === props.viewId)
})
const projectService = shallowReactive(new ProjectService())
const isLoadingProject = computed(() => projectService.loading)
const loadedProjectId = ref(0)
watch(
() => props.projectId,
// loadProject
async (projectIdToLoad, oldProjectIdToLoad) => {
console.debug('Loading project, $route.params =', route.params, `, loadedProjectId = ${loadedProjectId.value}, currentProject = `, currentProject.value)
if (projectIdToLoad !== oldProjectIdToLoad) {
loadedProjectId.value = 0
}
try {
const loadedProject = await projectService.get({id: projectIdToLoad})
// Here, we only set the new project in the projectStore.
// Setting that projet as the current one in the baseStore is handled by the watcher below.
projectStore.setProject(loadedProject)
} finally {
loadedProjectId.value = projectIdToLoad
}
},
{immediate: true},
)
watch(
() => [currentProject.value, props.viewId],
([newCurrentProject, newViewId]) => {
if (!newCurrentProject) {
baseStore.handleSetCurrentProject({project: null})
return
}
baseStore.handleSetCurrentProject({
project: newCurrentProject,
currentProjectViewId: newViewId,
})
}, {
deep: true,
immediate: true,
},
)
function redirectToDefaultViewIfNecessary() {
if (props.viewId === 0 || !projectStore.projects[props.projectId]?.views.find(v => v.id === props.viewId)) {
if (props.viewId === 0 || !currentView.value) {
// Ideally, we would do that in the router redirect, but the projects (and therefore, the views)
// are not always loaded then.
const defaultView = authStore.settings.frontendSettings.defaultView
let view
if (authStore.settings.frontendSettings.defaultView !== DEFAULT_PROJECT_VIEW_SETTINGS.FIRST) {
view = projectStore.projects[props.projectId]?.views.find(v => v.viewKind === authStore.settings.frontendSettings.defaultView)
if (defaultView !== DEFAULT_PROJECT_VIEW_SETTINGS.FIRST) {
view = currentProject.value?.views.find(v => v.viewKind === defaultView)
}
// Use the first view as fallback if the default view is not available
if (view === undefined && projectStore.projects[props.projectId]?.views?.length > 0) {
view = projectStore.projects[props.projectId]?.views[0]
if (view === undefined && currentProject.value?.views?.length > 0) {
view = currentProject.value?.views[0]
}
if (view) {
@@ -60,39 +117,39 @@ watch(
)
watch(
() => projectStore.projects[props.projectId],
currentProject,
redirectToDefaultViewIfNecessary,
)
// using a watcher instead of beforeEnter because beforeEnter is not called when only the viewId changes
watch(
() => [props.projectId, props.viewId],
() => saveProjectView(props.projectId, props.viewId),
{immediate: true},
)
watchEffect(() => saveProjectToHistory({id: props.projectId}))
watchEffect(() => saveProjectView(props.projectId, props.viewId))
const route = useRoute()
watchEffect(() => baseStore.setCurrentProjectViewId(props.viewId))
</script>
<template>
<ProjectList
v-if="currentView?.viewKind === 'list'"
:project-id="projectId"
:is-loading-project="isLoadingProject"
:view-id
/>
<ProjectGantt
v-if="currentView?.viewKind === 'gantt'"
:route
:is-loading-project="isLoadingProject"
:view-id
/>
<ProjectTable
v-if="currentView?.viewKind === 'table'"
:project-id="projectId"
:is-loading-project="isLoadingProject"
:view-id
/>
<ProjectKanban
v-if="currentView?.viewKind === 'kanban'"
:project-id="projectId"
:is-loading-project="isLoadingProject"
:view-id
/>
</template>

View File

@@ -3,7 +3,12 @@
"@tsconfig/node22/tsconfig.json",
"@vue/tsconfig/tsconfig.json"
],
"include": ["vite.config.*", "vitest.config.*", "cypress.config.*"],
"include": [
"env.config.d.ts",
"vite.config.*",
"vitest.config.*",
"cypress.config.*"
],
"compilerOptions": {
"composite": true,
"noEmit": true,

View File

@@ -184,7 +184,7 @@ export default defineConfig(({mode}) => {
},
}),
vueDevTools({
launchEditor: env.VUE_DEVTOOLS_LAUNCH_EDITOR || 'code'
launchEditor: env.VUE_DEVTOOLS_LAUNCH_EDITOR || 'code',
}),
viteSentry(getSentryConfig(env)),
],

2
go.mod
View File

@@ -67,7 +67,7 @@ require (
github.com/tkuchiki/go-timezone v0.2.3
github.com/typesense/typesense-go/v2 v2.0.0
github.com/ulule/limiter/v3 v3.11.2
github.com/wneessen/go-mail v0.6.0
github.com/wneessen/go-mail v0.6.1
github.com/yuin/goldmark v1.7.8
golang.org/x/crypto v0.32.0
golang.org/x/image v0.23.0

2
go.sum
View File

@@ -1377,6 +1377,8 @@ github.com/wneessen/go-mail v0.5.2 h1:MZKwgHJoRboLJ+EHMLuHpZc95wo+u1xViL/4XSswDT
github.com/wneessen/go-mail v0.5.2/go.mod h1:kRroJvEq2hOSEPFRiKjN7Csrz0G1w+RpiGR3b6yo+Ck=
github.com/wneessen/go-mail v0.6.0 h1:wO7EeJ8RL6DD+aycFGntil6b11g3FNQpQQQC1gkm97Y=
github.com/wneessen/go-mail v0.6.0/go.mod h1:G702XlFhzHV0Z4w9j2VsH5K9dJDvj0hx+yOOp1oX9vc=
github.com/wneessen/go-mail v0.6.1 h1:cDGqlGuEEhdILRe53VFzmM9WBk8Xh/QMvbO0oxrNJB4=
github.com/wneessen/go-mail v0.6.1/go.mod h1:G702XlFhzHV0Z4w9j2VsH5K9dJDvj0hx+yOOp1oX9vc=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=