From cfda4c49d1c954aa19cd8e17b04f596cd90fbb18 Mon Sep 17 00:00:00 2001 From: pa Date: Mon, 16 Mar 2026 20:57:24 +0900 Subject: [PATCH] add activity period filter to user dialog heatmap --- .../UserDialog/UserDialogActivityTab.vue | 56 ++++++++++++++++++- src/localization/en.json | 7 +++ 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/src/components/dialogs/UserDialog/UserDialogActivityTab.vue b/src/components/dialogs/UserDialog/UserDialogActivityTab.vue index c03e1147..cc6875d1 100644 --- a/src/components/dialogs/UserDialog/UserDialogActivityTab.vue +++ b/src/components/dialogs/UserDialog/UserDialogActivityTab.vue @@ -15,6 +15,21 @@ {{ t('dialog.user.activity.total_events', { count: totalOnlineEvents }) }} +
+ {{ t('dialog.user.activity.period') }} + +
@@ -29,8 +44,11 @@
+
+ {{ t('dialog.user.activity.no_data_in_period') }} +
@@ -42,6 +60,7 @@ import { computed, h, nextTick, onBeforeUnmount, ref, watch } from 'vue'; import { Button } from '@/components/ui/button'; import { DataTableEmpty } from '@/components/ui/data-table'; + import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { RefreshCw, Tractor } from 'lucide-vue-next'; import { Spinner } from '@/components/ui/spinner'; import { storeToRefs } from 'pinia'; @@ -63,6 +82,8 @@ const totalOnlineEvents = ref(0); const peakDayText = ref(''); const peakTimeText = ref(''); + const selectedPeriod = ref('all'); + const filteredEventCount = ref(0); let echartsInstance = null; let resizeObserver = null; @@ -110,6 +131,11 @@ watch(() => isDarkMode.value, rebuildChart); watch(locale, rebuildChart); + watch(selectedPeriod, () => { + if (cachedTimestamps.length > 0 && echartsInstance) { + initChart(); + } + }); onBeforeUnmount(() => { disposeChart(); @@ -126,6 +152,14 @@ } } + function getFilteredTimestamps() { + if (selectedPeriod.value === 'all') return cachedTimestamps; + const days = parseInt(selectedPeriod.value, 10); + const cutoff = dayjs().subtract(days, 'day'); + return cachedTimestamps.filter((ts) => dayjs(ts).isAfter(cutoff)); + } + + /** * @param {string[]} timestamps * @returns {{ data: number[][], maxVal: number, peakText: string }} @@ -210,7 +244,15 @@ function initChart() { if (!chartRef.value || !echartsInstance) return; - const { data, maxVal, peakDayResult, peakTimeResult } = aggregateHeatmapData(cachedTimestamps); + const filtered = getFilteredTimestamps(); + filteredEventCount.value = filtered.length; + + if (filtered.length === 0) { + peakDayText.value = ''; + peakTimeText.value = ''; + return; + } + const { data, maxVal, peakDayResult, peakTimeResult } = aggregateHeatmapData(filtered); peakDayText.value = peakDayResult; peakTimeText.value = peakTimeResult; @@ -301,6 +343,10 @@ const userId = userDialog.value.id; if (!userId) return; + if (userId !== lastLoadedUserId) { + selectedPeriod.value = 'all'; + } + const requestId = ++activeRequestId; isLoading.value = true; try { @@ -314,6 +360,11 @@ await nextTick(); if (timestamps.length > 0) { + const filtered = getFilteredTimestamps(); + filteredEventCount.value = filtered.length; + + await nextTick(); + if (!echartsInstance && chartRef.value) { echartsInstance = echarts.init( chartRef.value, @@ -335,6 +386,7 @@ } else { peakDayText.value = ''; peakTimeText.value = ''; + filteredEventCount.value = 0; } } catch (error) { console.error('Error loading online frequency data:', error); diff --git a/src/localization/en.json b/src/localization/en.json index c0073c9c..3189dea3 100644 --- a/src/localization/en.json +++ b/src/localization/en.json @@ -1286,6 +1286,13 @@ "times_online": "times online", "most_active_day": "Most active day:", "most_active_time": "Peak hours:", + "period": "Period:", + "period_all": "All Time", + "period_365": "Last Year", + "period_180": "Last 6 Months", + "period_90": "Last 90 Days", + "period_30": "Last 30 Days", + "no_data_in_period": "No activity data in selected period", "days": { "mon": "Mon", "tue": "Tue",