From 28e16f7ee0dc6d75ae12e0fe1314de99fc97ce74 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 12 Nov 2025 18:34:00 +0530 Subject: [PATCH] feat: allow selecting month range in overview reports (#12701) Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Co-authored-by: iamsivin --- .../dropdown-menu/DropdownMenu.vue | 215 ++++++++++++++---- .../dashboard/i18n/locale/en/report.json | 2 + .../components/heatmaps/BaseHeatmap.vue | 2 +- .../heatmaps/BaseHeatmapContainer.vue | 162 ++++++++----- .../heatmaps/HeatmapDateRangeSelector.vue | 211 +++++++++++++++++ 5 files changed, 484 insertions(+), 108 deletions(-) create mode 100644 app/javascript/dashboard/routes/dashboard/settings/reports/components/heatmaps/HeatmapDateRangeSelector.vue diff --git a/app/javascript/dashboard/components-next/dropdown-menu/DropdownMenu.vue b/app/javascript/dashboard/components-next/dropdown-menu/DropdownMenu.vue index 111b9868e..6276dfec6 100644 --- a/app/javascript/dashboard/components-next/dropdown-menu/DropdownMenu.vue +++ b/app/javascript/dashboard/components-next/dropdown-menu/DropdownMenu.vue @@ -8,11 +8,15 @@ import Avatar from 'dashboard/components-next/avatar/Avatar.vue'; const props = defineProps({ menuItems: { type: Array, - required: true, + default: () => [], validator: value => { return value.every(item => item.action && item.value && item.label); }, }, + menuSections: { + type: Array, + default: () => [], + }, thumbnailSize: { type: Number, default: 20, @@ -42,19 +46,62 @@ const { t } = useI18n(); const searchInput = ref(null); const searchQuery = ref(''); -const filteredMenuItems = computed(() => { - if (!searchQuery.value) return props.menuItems; +const hasSections = computed(() => props.menuSections.length > 0); - return props.menuItems.filter(item => +const flattenedMenuItems = computed(() => { + if (!hasSections.value) { + return props.menuItems; + } + + return props.menuSections.flatMap(section => section.items || []); +}); + +const filteredMenuItems = computed(() => { + if (!searchQuery.value) return flattenedMenuItems.value; + + return flattenedMenuItems.value.filter(item => item.label.toLowerCase().includes(searchQuery.value.toLowerCase()) ); }); +const filteredMenuSections = computed(() => { + if (!hasSections.value) { + return []; + } + + if (!searchQuery.value) { + return props.menuSections; + } + + const query = searchQuery.value.toLowerCase(); + + return props.menuSections + .map(section => { + const filteredItems = (section.items || []).filter(item => + item.label.toLowerCase().includes(query) + ); + + return { + ...section, + items: filteredItems, + }; + }) + .filter(section => section.items.length > 0); +}); + const handleAction = item => { const { action, value, ...rest } = item; emit('action', { action, value, ...rest }); }; +const shouldShowEmptyState = computed(() => { + if (hasSections.value) { + return filteredMenuSections.value.length === 0; + } + + return filteredMenuItems.value.length === 0; +}); + onMounted(() => { if (searchInput.value && props.showSearch) { searchInput.value.focus(); @@ -64,54 +111,122 @@ onMounted(() => {