fix: ui issues

This commit is contained in:
Nikolai Giman
2026-04-27 00:55:54 +02:00
parent fd8ea2e328
commit 95f024275c
8 changed files with 39 additions and 4 deletions
@@ -120,7 +120,10 @@ onBeforeUnmount(() => {
watch(() => props.section, () => {
if (!props.section.allowedChartTypes.includes(currentChartType.value)) {
// Reassigning currentChartType triggers its own watcher, which calls render().
// Skip render here to avoid double-init race ("Canvas is already in use").
currentChartType.value = props.section.defaultChartType ?? props.section.allowedChartTypes[0] ?? 'bar'
return
}
render()
}, { deep: true })
@@ -3,19 +3,28 @@
v-model:open="open"
:title="analyticsStore.drillDown.sectionTitle ?? t('analytics.drillDown.defaultTitle')"
:description="analyticsStore.drillDown.bucket ?? ''"
:fullscreen="isMobile"
>
<template #body>
<UAlert
v-if="drillDownErrorMessage"
color="error"
variant="soft"
:title="drillDownErrorMessage"
icon="i-lucide-alert-triangle"
class="mb-4"
/>
<div v-if="analyticsStore.drillDown.loading" class="flex items-center justify-center py-12">
<UIcon name="i-lucide-loader-circle" class="size-6 animate-spin text-zinc-400" />
</div>
<div
v-else-if="analyticsStore.drillDown.tasks.length === 0"
v-else-if="!analyticsStore.drillDown.error && analyticsStore.drillDown.tasks.length === 0"
class="flex flex-col items-center gap-2 py-12 text-center text-sm text-zinc-500"
>
<UIcon name="i-lucide-check-circle-2" class="size-8 text-zinc-400" />
<p>{{ t('analytics.drillDown.empty') }}</p>
</div>
<ul v-else class="flex flex-col gap-2">
<ul v-else-if="analyticsStore.drillDown.tasks.length > 0" class="flex flex-col gap-2">
<li
v-for="task in analyticsStore.drillDown.tasks"
:key="task.id"
@@ -60,12 +69,14 @@ import { ALL_TASKS_LIST_ID, type AnalyticsDrillDownTask } from 'taskview-api'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRoute, useRouter } from 'vue-router'
import { useTaskView } from '@/composables/useTaskView'
import { useAnalyticsStore } from '@/stores/analytics.store'
const analyticsStore = useAnalyticsStore()
const router = useRouter()
const route = useRoute()
const { t } = useI18n()
const { isMobile } = useTaskView()
const open = computed({
get: () => analyticsStore.drillDown.open,
@@ -75,6 +86,12 @@ const open = computed({
},
})
const drillDownErrorMessage = computed(() => {
const e = analyticsStore.drillDown.error
if (!e) return null
return t(`analytics.errors.${e.kind}`)
})
const priorityLabel: Record<number, string> = {
1: t('analytics.priorities.low'),
2: t('analytics.priorities.medium'),
@@ -7,7 +7,7 @@
variant="ghost"
size="xs"
square
aria-label="Help"
:aria-label="t('analytics.help.aria')"
@click="isOpen = true"
/>
</UTooltip>
@@ -31,6 +31,7 @@
<script setup lang="ts">
import type { AnalyticsSectionHelp, LocalizedText } from 'taskview-api'
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { useAnalyticsLocale } from './composables/useAnalyticsLocale'
const props = defineProps<{
@@ -38,6 +39,7 @@ const props = defineProps<{
sectionTitle: LocalizedText
}>()
const { t } = useI18n()
const { pick } = useAnalyticsLocale()
const isOpen = ref(false)
</script>
+3
View File
@@ -456,6 +456,9 @@ export default {
server: 'Server returned an error. Try refreshing the page.',
unknown: 'Failed to load analytics.',
},
help: {
aria: 'Help for this metric',
},
chartTypes: {
line: 'Line',
bar: 'Bar',
+3
View File
@@ -428,6 +428,9 @@ export default {
server: 'Сервер вернул ошибку. Попробуйте обновить страницу.',
unknown: 'Не удалось загрузить аналитику.',
},
help: {
aria: 'Справка по показателю',
},
chartTypes: {
line: 'Линия',
bar: 'Столбцы',
+2
View File
@@ -195,6 +195,8 @@ watch(
if (analyticsStore.scope.kind === 'project') {
analyticsStore.scope = { kind: 'org' }
}
analyticsStore.availableGoals = []
analyticsStore.sections = []
analyticsStore.fetchSections()
},
)
+5 -1
View File
@@ -49,6 +49,7 @@ export const useAnalyticsStore = defineStore('analytics', {
sectionTitle: null,
bucket: null,
tasks: [],
error: null,
},
}),
getters: {
@@ -144,6 +145,7 @@ export const useAnalyticsStore = defineStore('analytics', {
this.drillDown.sectionTitle = args.sectionTitle
this.drillDown.bucket = args.bucket
this.drillDown.tasks = []
this.drillDown.error = null
const orgStore = useOrganizationStore()
const organizationId = orgStore.currentOrg?.id
@@ -171,7 +173,8 @@ export const useAnalyticsStore = defineStore('analytics', {
}
} catch (e) {
if (controller.signal.aborted || isCancelled(e)) return
throw e
const err = classifyError(e)
if (err) this.drillDown.error = err
} finally {
if (drillDownAbortController === controller) {
drillDownAbortController = null
@@ -189,6 +192,7 @@ export const useAnalyticsStore = defineStore('analytics', {
this.drillDown.sectionId = null
this.drillDown.sectionTitle = null
this.drillDown.bucket = null
this.drillDown.error = null
},
},
})
+1
View File
@@ -14,6 +14,7 @@ export type AnalyticsDrillDownState = {
sectionTitle: string | null
bucket: string | null
tasks: AnalyticsDrillDownTask[]
error: AnalyticsError
}
export type AnalyticsErrorKind = 'forbidden' | 'network' | 'server' | 'unknown'