|
83 | 83 | v-if="favoriteProjects.length" |
84 | 84 | class="menu" |
85 | 85 | > |
| 86 | + <BaseButton |
| 87 | + v-if="enhancedMode" |
| 88 | + class="menu-section-header" |
| 89 | + @click="sectionOpenState.favorites = !sectionOpenState.favorites" |
| 90 | + > |
| 91 | + <Icon |
| 92 | + icon="chevron-down" |
| 93 | + :class="{ 'section-is-collapsed': !sectionOpenState.favorites }" |
| 94 | + /> |
| 95 | + {{ $t('navigation.favorites') }} |
| 96 | + </BaseButton> |
86 | 97 | <ProjectsNavigation |
87 | | - :model-value="favoriteProjects" |
| 98 | + v-if="!enhancedMode || sectionOpenState.favorites" |
| 99 | + :model-value="favoriteProjects" |
88 | 100 | :can-edit-order="false" |
89 | 101 | :can-collapse="false" |
| 102 | + :hide-star-icons="enhancedMode" |
| 103 | + :show-task-counts="showTaskCounts" |
90 | 104 | /> |
91 | 105 | </nav> |
92 | | - |
| 106 | + |
93 | 107 | <nav |
94 | 108 | v-if="savedFilterProjects.length" |
95 | 109 | class="menu" |
96 | 110 | > |
| 111 | + <BaseButton |
| 112 | + v-if="enhancedMode" |
| 113 | + class="menu-section-header" |
| 114 | + @click="sectionOpenState.filters = !sectionOpenState.filters" |
| 115 | + > |
| 116 | + <Icon |
| 117 | + icon="chevron-down" |
| 118 | + :class="{ 'section-is-collapsed': !sectionOpenState.filters }" |
| 119 | + /> |
| 120 | + {{ $t('navigation.filters') }} |
| 121 | + </BaseButton> |
97 | 122 | <ProjectsNavigation |
| 123 | + v-if="!enhancedMode || sectionOpenState.filters" |
98 | 124 | :model-value="savedFilterProjects" |
99 | 125 | :can-edit-order="false" |
100 | 126 | :can-collapse="false" |
| 127 | + :hide-star-icons="enhancedMode" |
| 128 | + :show-task-counts="showTaskCounts" |
101 | 129 | /> |
102 | 130 | </nav> |
103 | 131 |
|
104 | 132 | <nav class="menu"> |
| 133 | + <BaseButton |
| 134 | + v-if="enhancedMode" |
| 135 | + class="menu-section-header" |
| 136 | + @click="sectionOpenState.projects = !sectionOpenState.projects" |
| 137 | + > |
| 138 | + <Icon |
| 139 | + icon="chevron-down" |
| 140 | + :class="{ 'section-is-collapsed': !sectionOpenState.projects }" |
| 141 | + /> |
| 142 | + {{ $t('navigation.myProjects') }} |
| 143 | + </BaseButton> |
105 | 144 | <ProjectsNavigation |
| 145 | + v-if="!enhancedMode || sectionOpenState.projects" |
106 | 146 | :model-value="projects" |
107 | 147 | :can-edit-order="true" |
108 | 148 | :can-collapse="true" |
| 149 | + :hide-star-icons="enhancedMode" |
| 150 | + :show-task-counts="showTaskCounts" |
109 | 151 | /> |
110 | 152 | </nav> |
111 | 153 | </template> |
|
125 | 167 | </template> |
126 | 168 |
|
127 | 169 | <script setup lang="ts"> |
128 | | -import {computed} from 'vue' |
| 170 | +import {computed, watch} from 'vue' |
| 171 | +import {useStorage} from '@vueuse/core' |
129 | 172 |
|
130 | 173 | import PoweredByLink from '@/components/home/PoweredByLink.vue' |
131 | 174 | import Logo from '@/components/home/Logo.vue' |
132 | 175 | import Loading from '@/components/misc/Loading.vue' |
| 176 | +import BaseButton from '@/components/base/BaseButton.vue' |
133 | 177 |
|
134 | 178 | import {useBaseStore} from '@/stores/base' |
135 | 179 | import {useProjectStore} from '@/stores/projects' |
| 180 | +import {useAuthStore} from '@/stores/auth' |
136 | 181 | import ProjectsNavigation from '@/components/home/ProjectsNavigation.vue' |
137 | 182 | import type {IProject} from '@/modelTypes/IProject' |
138 | 183 | import {useSidebarResize} from '@/composables/useSidebarResize' |
| 184 | +import {useProjectTaskCounts} from '@/composables/useProjectTaskCounts' |
139 | 185 |
|
140 | 186 | const baseStore = useBaseStore() |
141 | 187 | const projectStore = useProjectStore() |
| 188 | +const authStore = useAuthStore() |
142 | 189 |
|
143 | 190 | const {sidebarWidth, isResizing, startResize, isMobile} = useSidebarResize() |
| 191 | +const {fetchTaskCountsForProjects} = useProjectTaskCounts() |
| 192 | +
|
| 193 | +// Section collapse state (persisted) |
| 194 | +type SectionState = { favorites: boolean; filters: boolean; projects: boolean } |
| 195 | +const sectionOpenState = useStorage<SectionState>('navigation-sections-open', { |
| 196 | + favorites: true, |
| 197 | + filters: true, |
| 198 | + projects: true, |
| 199 | +}) |
| 200 | +
|
| 201 | +// Enhanced sidebar mode setting |
| 202 | +const enhancedMode = computed(() => authStore.settings?.frontendSettings?.sidebarEnhancedMode ?? false) |
| 203 | +const showTaskCounts = computed(() => authStore.settings?.frontendSettings?.sidebarShowTaskCounts ?? false) |
144 | 204 |
|
145 | 205 | // Cast readonly arrays to mutable type - the arrays are not actually mutated by the component |
146 | 206 | const projects = computed(() => projectStore.notArchivedRootProjects as IProject[]) |
147 | 207 | const favoriteProjects = computed(() => projectStore.favoriteProjects as IProject[]) |
148 | 208 | const savedFilterProjects = computed(() => projectStore.savedFilterProjects as IProject[]) |
| 209 | +
|
| 210 | +// All non-archived projects (including children) for task count fetching |
| 211 | +const allNonArchivedProjects = computed(() => |
| 212 | + (projectStore.projectsArray as IProject[]).filter(p => !p.isArchived), |
| 213 | +) |
| 214 | +
|
| 215 | +// Fetch task counts when the setting is enabled and projects are loaded |
| 216 | +watch( |
| 217 | + [showTaskCounts, () => projectStore.isLoading], |
| 218 | + ([showCounts, isLoading]) => { |
| 219 | + if (showCounts && !isLoading) { |
| 220 | + // Fetch counts for ALL non-archived projects (including nested children) |
| 221 | + fetchTaskCountsForProjects(allNonArchivedProjects.value) |
| 222 | + } |
| 223 | + }, |
| 224 | + {immediate: true}, |
| 225 | +) |
149 | 226 | </script> |
150 | 227 |
|
151 | 228 | <style lang="scss" scoped> |
@@ -235,4 +312,36 @@ const savedFilterProjects = computed(() => projectStore.savedFilterProjects as I |
235 | 312 | .menu + .menu { |
236 | 313 | padding-block-start: math.div($navbar-padding, 2); |
237 | 314 | } |
| 315 | +
|
| 316 | +.menu-section-header { |
| 317 | + display: flex; |
| 318 | + align-items: center; |
| 319 | + gap: 0.5rem; |
| 320 | + inline-size: 100%; |
| 321 | + font-size: 0.75rem; |
| 322 | + font-weight: 600; |
| 323 | + text-transform: uppercase; |
| 324 | + letter-spacing: 0.05em; |
| 325 | + color: var(--grey-500); |
| 326 | + padding-inline-start: 1.5rem; |
| 327 | + padding-block: 0.5rem 0.25rem; |
| 328 | + margin: 0; |
| 329 | + cursor: pointer; |
| 330 | + background: transparent; |
| 331 | + border: none; |
| 332 | + text-align: start; |
| 333 | +
|
| 334 | + &:hover { |
| 335 | + color: var(--grey-400); |
| 336 | + } |
| 337 | +
|
| 338 | + .icon { |
| 339 | + font-size: 0.6rem; |
| 340 | + transition: transform $transition; |
| 341 | + } |
| 342 | +
|
| 343 | + .section-is-collapsed { |
| 344 | + transform: rotate(-90deg); |
| 345 | + } |
| 346 | +} |
238 | 347 | </style> |
0 commit comments