diff --git a/src/app/pages/dashboard/overview/Overview.vue b/src/app/pages/dashboard/overview/Overview.vue index f2db4186..f48639c6 100644 --- a/src/app/pages/dashboard/overview/Overview.vue +++ b/src/app/pages/dashboard/overview/Overview.vue @@ -1,7 +1,8 @@ <template> <div :class="$style.overview"> - <SummaryPanels /> + <SummaryPanels @hovered-panel="highlight = $event" /> <AsyncComponent + :properties="{ highlight }" :show="media !== 'mobile'" :class="$style.chart" :import="() => import('./widgets/charts/DistributionChart.vue')" @@ -11,10 +12,12 @@ <script lang="ts" setup> import AsyncComponent from '@components/misc/async-component/AsyncComponent.vue'; -import { useMediaQuery } from '../../../../composables/useMediaQuery'; +import { useMediaQuery } from '@composables'; import SummaryPanels from './widgets/header-panels/SummaryPanels.vue'; +import { ref } from 'vue'; const media = useMediaQuery(); +const highlight = ref<string>(); </script> <style lang="scss" module> diff --git a/src/app/pages/dashboard/overview/widgets/charts/DistributionChart.vue b/src/app/pages/dashboard/overview/widgets/charts/DistributionChart.vue index 5d5e030a..8731754e 100644 --- a/src/app/pages/dashboard/overview/widgets/charts/DistributionChart.vue +++ b/src/app/pages/dashboard/overview/widgets/charts/DistributionChart.vue @@ -15,6 +15,7 @@ import SankeyChart from './sankey-chart/SankeyChart.vue'; const props = defineProps<{ class?: ClassNames; + highlight?: 'income' | 'expenses'; }>(); const classes = computed(() => props.class); @@ -54,13 +55,15 @@ const data = computed((): SankeyChartConfig => { labels.push({ id: group.id, name: `${group.name} (${format(total)})`, - color: color(60 + 60 * (total / totalIncome)) + color: color(60 + 60 * (total / totalIncome)), + muted: props.highlight === 'expenses' }); links.push({ target: income.id, source: group.id, - value: total + value: total, + muted: props.highlight === 'expenses' }); for (let i = 0; i < group.budgets.length; i++) { @@ -71,13 +74,15 @@ const data = computed((): SankeyChartConfig => { labels.push({ id: budget.id, name: `${budget.name} (${format(total)})`, - color: color(60 + 60 * (total / totalIncome)) + color: color(60 + 60 * (total / totalIncome)), + muted: props.highlight === 'expenses' }); links.push({ target: group.id, source: budget.id, - value: total + value: total, + muted: props.highlight === 'expenses' }); } } @@ -93,13 +98,15 @@ const data = computed((): SankeyChartConfig => { labels.push({ id: group.id, name: `${group.name} (${format(total)})`, - color: color(60 * (1 - total / totalExpenses)) + color: color(60 * (1 - total / totalExpenses)), + muted: props.highlight === 'income' }); links.push({ target: group.id, source: income.id, - value: total + value: total, + muted: props.highlight === 'income' }); for (let i = 0; i < group.budgets.length; i++) { @@ -111,13 +118,15 @@ const data = computed((): SankeyChartConfig => { id: budget.id, name: `${budget.name} (${format(total)})`, color: color(60 * (1 - total / totalExpenses)), - align: 'left' + align: 'left', + muted: props.highlight === 'income' }); links.push({ target: budget.id, source: group.id, - value: total + value: total, + muted: props.highlight === 'income' }); } } diff --git a/src/app/pages/dashboard/overview/widgets/charts/sankey-chart/SankeyChart.types.ts b/src/app/pages/dashboard/overview/widgets/charts/sankey-chart/SankeyChart.types.ts index 3ec5182b..23e23686 100644 --- a/src/app/pages/dashboard/overview/widgets/charts/sankey-chart/SankeyChart.types.ts +++ b/src/app/pages/dashboard/overview/widgets/charts/sankey-chart/SankeyChart.types.ts @@ -2,12 +2,14 @@ export interface SankeyChartLink { source: string; target: string; value: number; + muted?: boolean; } export interface SankeyChartLabel { id: string; name: string; color: string; + muted?: boolean; align?: 'left' | 'right'; } diff --git a/src/app/pages/dashboard/overview/widgets/charts/sankey-chart/SankeyChart.vue b/src/app/pages/dashboard/overview/widgets/charts/sankey-chart/SankeyChart.vue index bec4fed8..23f87849 100644 --- a/src/app/pages/dashboard/overview/widgets/charts/sankey-chart/SankeyChart.vue +++ b/src/app/pages/dashboard/overview/widgets/charts/sankey-chart/SankeyChart.vue @@ -48,6 +48,7 @@ const classes = computed(() => props.class); const options = computed( (): EChartsOption => ({ animation: false, + silent: true, series: { type: 'sankey', label: { @@ -65,20 +66,27 @@ const options = computed( nodeWidth: 7, left: 0, right: 0, - links: props.data.links, + links: props.data.links.map((v) => ({ + ...v, + animation: true, + lineStyle: { opacity: v.muted ? 0.05 : 0.25 } + })), data: props.data.labels.map((v) => ({ name: v.name, id: v.id, - itemStyle: { color: v.color }, + itemStyle: { + color: v.color, + opacity: v.muted ? 0.25 : 1 + }, label: v.align === 'left' ? { align: 'right', + opacity: v.muted ? 0.65 : 1, padding: [0, 20, 0, 0] } - : undefined - })), - silent: true + : { opacity: v.muted ? 0.65 : 1 } + })) } }) ); diff --git a/src/app/pages/dashboard/overview/widgets/header-panels/SummaryPanel.vue b/src/app/pages/dashboard/overview/widgets/header-panels/SummaryPanel.vue index 5dac4f05..deae1a70 100644 --- a/src/app/pages/dashboard/overview/widgets/header-panels/SummaryPanel.vue +++ b/src/app/pages/dashboard/overview/widgets/header-panels/SummaryPanel.vue @@ -72,7 +72,7 @@ const element = computed(() => (props.to ? Link : 'div')); width: 100%; height: 100%; background: v-bind('theme.light.base'); - transition: background var(--transition-m); + transition: background var(--transition-s); &.clickable:hover { background: v-bind('theme.light.dimmed'); diff --git a/src/app/pages/dashboard/overview/widgets/header-panels/SummaryPanelChart.vue b/src/app/pages/dashboard/overview/widgets/header-panels/SummaryPanelChart.vue index df95a619..ca945f3a 100644 --- a/src/app/pages/dashboard/overview/widgets/header-panels/SummaryPanelChart.vue +++ b/src/app/pages/dashboard/overview/widgets/header-panels/SummaryPanelChart.vue @@ -5,9 +5,8 @@ <script lang="ts" setup> import EChart from '@components/charts/echart/EChart.vue'; import { ClassNames } from '@utils'; -import { GridComponentOption, LineSeriesOption } from 'echarts'; -import { LineChart } from 'echarts/charts'; -import { GridComponent } from 'echarts/components'; +import { GridComponentOption, GridComponent } from 'echarts/components'; +import { LineChart, LineSeriesOption } from 'echarts/charts'; import * as echarts from 'echarts/core'; import { SVGRenderer } from 'echarts/renderers'; import { computed } from 'vue'; diff --git a/src/app/pages/dashboard/overview/widgets/header-panels/SummaryPanels.types.ts b/src/app/pages/dashboard/overview/widgets/header-panels/SummaryPanels.types.ts new file mode 100644 index 00000000..886e3915 --- /dev/null +++ b/src/app/pages/dashboard/overview/widgets/header-panels/SummaryPanels.types.ts @@ -0,0 +1 @@ +export type HoveredPanel = 'income' | 'expenses'; diff --git a/src/app/pages/dashboard/overview/widgets/header-panels/SummaryPanels.vue b/src/app/pages/dashboard/overview/widgets/header-panels/SummaryPanels.vue index 869069e4..07027079 100644 --- a/src/app/pages/dashboard/overview/widgets/header-panels/SummaryPanels.vue +++ b/src/app/pages/dashboard/overview/widgets/header-panels/SummaryPanels.vue @@ -6,6 +6,9 @@ to="/income" :tooltip="t('page.dashboard.jumpToIncome', { year: state.activeYear })" :title="t('page.dashboard.income')" + @pointerenter="emit('hoveredPanel', 'income')" + @pointerleave="emit('hoveredPanel')" + @pointercancel="emit('hoveredPanel')" /> <SummaryPanel @@ -15,6 +18,9 @@ :values="expenses" color="warning" :title="t('page.dashboard.expenses')" + @pointerenter="emit('hoveredPanel', 'expenses')" + @pointerleave="emit('hoveredPanel')" + @pointercancel="emit('hoveredPanel')" /> <SummaryPanel @@ -50,6 +56,11 @@ import { aggregate, ClassNames, subtract, sum } from '@utils'; import { computed, ref, useCssModule } from 'vue'; import { useI18n } from 'vue-i18n'; import SummaryPanel from './SummaryPanel.vue'; +import { HoveredPanel } from '@app/pages/dashboard/overview/widgets/header-panels/SummaryPanels.types.ts'; + +const emit = defineEmits<{ + hoveredPanel: (panel?: HoveredPanel) => void; +}>(); const props = defineProps<{ class?: ClassNames; diff --git a/src/app/pages/dashboard/summary/Summary.vue b/src/app/pages/dashboard/summary/Summary.vue index d15fc1a2..53fc8ff7 100644 --- a/src/app/pages/dashboard/summary/Summary.vue +++ b/src/app/pages/dashboard/summary/Summary.vue @@ -18,7 +18,7 @@ import { useDataStore } from '@store/state'; import { totals } from '@store/state/utils/budgets'; import { computed } from 'vue'; import { useI18n } from 'vue-i18n'; -import { useMediaQuery } from '../../../../composables/useMediaQuery'; +import { useMediaQuery } from '@composables'; import GroupsSummaryTable from './widgets/tables/GroupsSummaryTable.vue'; import TotalsSummaryTable from './widgets/tables/TotalsSummaryTable.vue';