feat: Add URL persistence for report filters
This commit is contained in:
@@ -13,14 +13,12 @@ import {
|
||||
subDays,
|
||||
startOfDay,
|
||||
endOfDay,
|
||||
isBefore,
|
||||
subMonths,
|
||||
addMonths,
|
||||
isSameMonth,
|
||||
differenceInCalendarMonths,
|
||||
setMonth,
|
||||
setYear,
|
||||
isAfter,
|
||||
} from 'date-fns';
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import DatePickerButton from './components/DatePickerButton.vue';
|
||||
@@ -32,91 +30,94 @@ import CalendarWeek from './components/CalendarWeek.vue';
|
||||
import CalendarFooter from './components/CalendarFooter.vue';
|
||||
|
||||
const emit = defineEmits(['dateRangeChanged']);
|
||||
const { LAST_7_DAYS, LAST_30_DAYS, CUSTOM_RANGE } = DATE_RANGE_TYPES;
|
||||
|
||||
const dateRange = defineModel('dateRange', {
|
||||
type: Array,
|
||||
default: undefined,
|
||||
});
|
||||
|
||||
const rangeType = defineModel('rangeType', {
|
||||
type: String,
|
||||
default: undefined,
|
||||
});
|
||||
const { LAST_7_DAYS, CUSTOM_RANGE } = DATE_RANGE_TYPES;
|
||||
const { START_CALENDAR, END_CALENDAR } = CALENDAR_TYPES;
|
||||
const { WEEK, MONTH, YEAR } = CALENDAR_PERIODS;
|
||||
|
||||
const showDatePicker = ref(false);
|
||||
const calendarViews = ref({ start: WEEK, end: WEEK });
|
||||
const currentDate = ref(new Date());
|
||||
const selectedStartDate = ref(startOfDay(subDays(currentDate.value, 6))); // LAST_7_DAYS
|
||||
const selectedEndDate = ref(endOfDay(currentDate.value));
|
||||
// Setting the start and end calendar
|
||||
const startCurrentDate = ref(startOfDay(selectedStartDate.value));
|
||||
|
||||
// Use dates from v-model if provided, otherwise default to last 7 days
|
||||
const selectedStartDate = ref(
|
||||
dateRange.value?.[0]
|
||||
? startOfDay(dateRange.value[0])
|
||||
: startOfDay(subDays(currentDate.value, 6)) // LAST_7_DAYS
|
||||
);
|
||||
const selectedEndDate = ref(
|
||||
dateRange.value?.[1]
|
||||
? endOfDay(dateRange.value[1])
|
||||
: endOfDay(currentDate.value)
|
||||
);
|
||||
// Calendar month positioning (left and right calendars)
|
||||
// These control which months are displayed in the dual calendar view
|
||||
const startCurrentDate = ref(startOfMonth(selectedStartDate.value));
|
||||
const endCurrentDate = ref(
|
||||
isSameMonth(selectedStartDate.value, selectedEndDate.value)
|
||||
? startOfMonth(addMonths(selectedEndDate.value, 1)) // Moves to the start of the next month if dates are in the same month (Mounted case LAST_7_DAYS)
|
||||
: startOfMonth(selectedEndDate.value) // Always shows the month of the end date starting from the first (Mounted case LAST_7_DAYS)
|
||||
? startOfMonth(addMonths(selectedEndDate.value, 1)) // Same month: show next month on right (e.g., Jan 25-31 shows Jan + Feb)
|
||||
: startOfMonth(selectedEndDate.value) // Different months: show end month on right (e.g., Dec 5 - Jan 3 shows Dec + Jan)
|
||||
);
|
||||
const selectingEndDate = ref(false);
|
||||
const selectedRange = ref(LAST_7_DAYS);
|
||||
const selectedRange = ref(rangeType.value || LAST_7_DAYS);
|
||||
const hoveredEndDate = ref(null);
|
||||
|
||||
const manualStartDate = ref(selectedStartDate.value);
|
||||
const manualEndDate = ref(selectedEndDate.value);
|
||||
|
||||
// Watcher will set the start and end dates based on the selected range
|
||||
watch(selectedRange, newRange => {
|
||||
if (newRange !== CUSTOM_RANGE) {
|
||||
// If selecting a range other than last 7 days or last 30 days, set the start and end dates to the selected start and end dates
|
||||
// If selecting last 7 days or last 30 days is, set the start date to the selected start date
|
||||
// and the end date to one month ahead of the start date if the start date and end date are in the same month
|
||||
// Otherwise set the end date to the selected end date
|
||||
const isLastSevenOrThirtyDays =
|
||||
newRange === LAST_7_DAYS || newRange === LAST_30_DAYS;
|
||||
startCurrentDate.value = selectedStartDate.value;
|
||||
endCurrentDate.value =
|
||||
isLastSevenOrThirtyDays &&
|
||||
isSameMonth(selectedStartDate.value, selectedEndDate.value)
|
||||
? startOfMonth(addMonths(selectedStartDate.value, 1))
|
||||
: selectedEndDate.value;
|
||||
selectingEndDate.value = false;
|
||||
} else if (!selectingEndDate.value) {
|
||||
// If selecting a custom range and not selecting an end date, set the start date to the selected start date
|
||||
startCurrentDate.value = startOfDay(currentDate.value);
|
||||
}
|
||||
});
|
||||
|
||||
// Watcher will set the input values based on the selected start and end dates
|
||||
// Watcher 1: Sync v-model props from parent component
|
||||
// Handles: URL params, parent component updates, rangeType changes
|
||||
watch(
|
||||
[selectedStartDate, selectedEndDate],
|
||||
([newStart, newEnd]) => {
|
||||
if (isValid(newStart)) {
|
||||
manualStartDate.value = newStart;
|
||||
} else {
|
||||
manualStartDate.value = selectedStartDate.value;
|
||||
[rangeType, dateRange],
|
||||
([newRangeType, newDateRange]) => {
|
||||
if (newRangeType && newRangeType !== selectedRange.value) {
|
||||
selectedRange.value = newRangeType;
|
||||
|
||||
// If rangeType changes without dateRange, recompute dates from the range
|
||||
if (!newDateRange && newRangeType !== CUSTOM_RANGE) {
|
||||
const activeDates = getActiveDateRange(newRangeType);
|
||||
if (activeDates) {
|
||||
selectedStartDate.value = startOfDay(activeDates.startDate);
|
||||
selectedEndDate.value = endOfDay(activeDates.endDate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isValid(newEnd)) {
|
||||
manualEndDate.value = newEnd;
|
||||
} else {
|
||||
manualEndDate.value = selectedEndDate.value;
|
||||
// When parent provides new dateRange (e.g., from URL params)
|
||||
if (newDateRange?.[0] && newDateRange?.[1]) {
|
||||
selectedStartDate.value = startOfDay(newDateRange[0]);
|
||||
selectedEndDate.value = endOfDay(newDateRange[1]);
|
||||
|
||||
// Update calendar to show the months of the new date range
|
||||
startCurrentDate.value = startOfMonth(newDateRange[0]);
|
||||
endCurrentDate.value = isSameMonth(newDateRange[0], newDateRange[1])
|
||||
? startOfMonth(addMonths(newDateRange[1], 1))
|
||||
: startOfMonth(newDateRange[1]);
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
// Watcher to ensure dates are always in logical order
|
||||
// This watch is will ensure that the start date is always before the end date
|
||||
// Watcher 2: Keep manual input fields in sync with selected dates
|
||||
// Updates the input field values when dates change programmatically
|
||||
watch(
|
||||
[startCurrentDate, endCurrentDate],
|
||||
([newStart, newEnd], [oldStart, oldEnd]) => {
|
||||
const monthDifference = differenceInCalendarMonths(newEnd, newStart);
|
||||
|
||||
if (newStart !== oldStart) {
|
||||
if (isAfter(newStart, newEnd) || monthDifference === 0) {
|
||||
// Adjust the end date forward if the start date is adjusted and is after the end date or in the same month
|
||||
endCurrentDate.value = addMonths(newStart, 1);
|
||||
}
|
||||
}
|
||||
if (newEnd !== oldEnd) {
|
||||
if (isBefore(newEnd, newStart) || monthDifference === 0) {
|
||||
// Adjust the start date backward if the end date is adjusted and is before the start date or in the same month
|
||||
startCurrentDate.value = subMonths(newEnd, 1);
|
||||
}
|
||||
}
|
||||
[selectedStartDate, selectedEndDate],
|
||||
([newStart, newEnd]) => {
|
||||
manualStartDate.value = isValid(newStart)
|
||||
? newStart
|
||||
: selectedStartDate.value;
|
||||
manualEndDate.value = isValid(newEnd) ? newEnd : selectedEndDate.value;
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
const setDateRange = range => {
|
||||
@@ -124,6 +125,12 @@ const setDateRange = range => {
|
||||
const { start, end } = getActiveDateRange(range.value, currentDate.value);
|
||||
selectedStartDate.value = start;
|
||||
selectedEndDate.value = end;
|
||||
|
||||
// Position calendar to show the months of the selected range
|
||||
startCurrentDate.value = startOfMonth(start);
|
||||
endCurrentDate.value = isSameMonth(start, end)
|
||||
? startOfMonth(addMonths(start, 1))
|
||||
: startOfMonth(end);
|
||||
};
|
||||
|
||||
const moveCalendar = (calendar, direction, period = MONTH) => {
|
||||
@@ -134,8 +141,22 @@ const moveCalendar = (calendar, direction, period = MONTH) => {
|
||||
direction,
|
||||
period
|
||||
);
|
||||
startCurrentDate.value = start;
|
||||
endCurrentDate.value = end;
|
||||
|
||||
// Prevent calendar months from overlapping
|
||||
const monthDiff = differenceInCalendarMonths(end, start);
|
||||
if (monthDiff === 0) {
|
||||
// If they would be the same month, adjust the other calendar
|
||||
if (calendar === START_CALENDAR) {
|
||||
endCurrentDate.value = addMonths(start, 1);
|
||||
startCurrentDate.value = start;
|
||||
} else {
|
||||
startCurrentDate.value = subMonths(end, 1);
|
||||
endCurrentDate.value = end;
|
||||
}
|
||||
} else {
|
||||
startCurrentDate.value = start;
|
||||
endCurrentDate.value = end;
|
||||
}
|
||||
};
|
||||
|
||||
const selectDate = day => {
|
||||
@@ -175,10 +196,10 @@ const openCalendar = (index, calendarType, period = MONTH) => {
|
||||
const updateManualInput = (newDate, calendarType) => {
|
||||
if (calendarType === START_CALENDAR) {
|
||||
selectedStartDate.value = newDate;
|
||||
startCurrentDate.value = newDate;
|
||||
startCurrentDate.value = startOfMonth(newDate);
|
||||
} else {
|
||||
selectedEndDate.value = newDate;
|
||||
endCurrentDate.value = newDate;
|
||||
endCurrentDate.value = startOfMonth(newDate);
|
||||
}
|
||||
selectingEndDate.value = false;
|
||||
};
|
||||
@@ -188,13 +209,21 @@ const handleManualInputError = message => {
|
||||
};
|
||||
|
||||
const resetDatePicker = () => {
|
||||
startCurrentDate.value = startOfDay(currentDate.value); // Resets to today at start of the day
|
||||
endCurrentDate.value = addMonths(startOfDay(currentDate.value), 1); // Resets to one month ahead
|
||||
selectedStartDate.value = startOfDay(subDays(currentDate.value, 6));
|
||||
selectedEndDate.value = endOfDay(currentDate.value);
|
||||
// Calculate Last 7 days from today
|
||||
const startDate = startOfDay(subDays(currentDate.value, 6));
|
||||
const endDate = endOfDay(currentDate.value);
|
||||
|
||||
selectedStartDate.value = startDate;
|
||||
selectedEndDate.value = endDate;
|
||||
|
||||
// Position calendar to show the months of Last 7 days
|
||||
// Example: If today is Feb 5, Last 7 days = Jan 30 - Feb 5, so show Jan + Feb
|
||||
startCurrentDate.value = startOfMonth(startDate);
|
||||
endCurrentDate.value = isSameMonth(startDate, endDate)
|
||||
? startOfMonth(addMonths(startDate, 1))
|
||||
: startOfMonth(endDate);
|
||||
selectingEndDate.value = false;
|
||||
selectedRange.value = LAST_7_DAYS;
|
||||
// Reset view modes if they are being used to toggle between different calendar views
|
||||
calendarViews.value = { start: WEEK, end: WEEK };
|
||||
};
|
||||
|
||||
@@ -203,10 +232,33 @@ const emitDateRange = () => {
|
||||
useAlert('Please select a valid time range');
|
||||
} else {
|
||||
showDatePicker.value = false;
|
||||
emit('dateRangeChanged', [selectedStartDate.value, selectedEndDate.value]);
|
||||
emit('dateRangeChanged', [
|
||||
selectedStartDate.value,
|
||||
selectedEndDate.value,
|
||||
selectedRange.value,
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
// Called when picker opens - positions calendar to show selected date range
|
||||
// Fixes issue where calendar showed wrong months when loaded from URL params
|
||||
const initializeCalendarMonths = () => {
|
||||
if (selectedStartDate.value && selectedEndDate.value) {
|
||||
startCurrentDate.value = startOfMonth(selectedStartDate.value);
|
||||
endCurrentDate.value = isSameMonth(
|
||||
selectedStartDate.value,
|
||||
selectedEndDate.value
|
||||
)
|
||||
? startOfMonth(addMonths(selectedEndDate.value, 1))
|
||||
: startOfMonth(selectedEndDate.value);
|
||||
}
|
||||
};
|
||||
|
||||
const toggleDatePicker = () => {
|
||||
showDatePicker.value = !showDatePicker.value;
|
||||
if (showDatePicker.value) initializeCalendarMonths();
|
||||
};
|
||||
|
||||
const closeDatePicker = () => {
|
||||
showDatePicker.value = false;
|
||||
};
|
||||
@@ -218,7 +270,7 @@ const closeDatePicker = () => {
|
||||
:selected-start-date="selectedStartDate"
|
||||
:selected-end-date="selectedEndDate"
|
||||
:selected-range="selectedRange"
|
||||
@open="showDatePicker = !showDatePicker"
|
||||
@open="toggleDatePicker"
|
||||
/>
|
||||
<div
|
||||
v-if="showDatePicker"
|
||||
|
||||
+52
-2
@@ -1,16 +1,38 @@
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { getUnixStartOfDay, getUnixEndOfDay } from 'helpers/DateHelper';
|
||||
import subDays from 'date-fns/subDays';
|
||||
import WootDatePicker from 'dashboard/components/ui/DatePicker/DatePicker.vue';
|
||||
import ToggleSwitch from 'dashboard/components-next/switch/Switch.vue';
|
||||
import {
|
||||
generateReportURLParams,
|
||||
parseReportURLParams,
|
||||
} from '../helpers/reportFilterHelper';
|
||||
import { DATE_RANGE_TYPES } from 'dashboard/components/ui/DatePicker/helpers/DatePickerHelper';
|
||||
|
||||
const emit = defineEmits(['filterChange']);
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const customDateRange = ref([subDays(new Date(), 6), new Date()]);
|
||||
const selectedDateRange = ref(DATE_RANGE_TYPES.LAST_7_DAYS);
|
||||
const businessHoursSelected = ref(false);
|
||||
|
||||
const updateURLParams = () => {
|
||||
const params = generateReportURLParams({
|
||||
from: getUnixStartOfDay(customDateRange.value[0]),
|
||||
to: getUnixEndOfDay(customDateRange.value[1]),
|
||||
businessHours: businessHoursSelected.value,
|
||||
range: selectedDateRange.value,
|
||||
});
|
||||
|
||||
router.replace({ query: { ...params } });
|
||||
};
|
||||
|
||||
const emitChange = () => {
|
||||
updateURLParams();
|
||||
emit('filterChange', {
|
||||
from: getUnixStartOfDay(customDateRange.value[0]),
|
||||
to: getUnixEndOfDay(customDateRange.value[1]),
|
||||
@@ -19,7 +41,9 @@ const emitChange = () => {
|
||||
};
|
||||
|
||||
const onDateRangeChange = value => {
|
||||
customDateRange.value = value;
|
||||
const [startDate, endDate, rangeType] = value;
|
||||
customDateRange.value = [startDate, endDate];
|
||||
selectedDateRange.value = rangeType || DATE_RANGE_TYPES.CUSTOM_RANGE;
|
||||
emitChange();
|
||||
};
|
||||
|
||||
@@ -27,7 +51,29 @@ const onBusinessHoursToggle = () => {
|
||||
emitChange();
|
||||
};
|
||||
|
||||
const initializeFromURL = () => {
|
||||
const urlParams = parseReportURLParams(route.query);
|
||||
|
||||
// Set the range type first
|
||||
if (urlParams.range) {
|
||||
selectedDateRange.value = urlParams.range;
|
||||
}
|
||||
|
||||
// Restore dates from URL if available
|
||||
if (urlParams.from && urlParams.to) {
|
||||
customDateRange.value = [
|
||||
new Date(urlParams.from * 1000),
|
||||
new Date(urlParams.to * 1000),
|
||||
];
|
||||
}
|
||||
|
||||
if (urlParams.businessHours) {
|
||||
businessHoursSelected.value = urlParams.businessHours;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
initializeFromURL();
|
||||
emitChange();
|
||||
});
|
||||
</script>
|
||||
@@ -35,7 +81,11 @@ onMounted(() => {
|
||||
<template>
|
||||
<div class="flex flex-col justify-between gap-3 md:flex-row">
|
||||
<div class="flex flex-col flex-wrap items-start gap-2 md:flex-row">
|
||||
<WootDatePicker @date-range-changed="onDateRangeChange" />
|
||||
<WootDatePicker
|
||||
v-model:date-range="customDateRange"
|
||||
v-model:range-type="selectedDateRange"
|
||||
@date-range-changed="onDateRangeChange"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<span class="mx-2 text-sm whitespace-nowrap">
|
||||
|
||||
+87
-11
@@ -2,6 +2,7 @@
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useStore } from 'vuex';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { getUnixStartOfDay, getUnixEndOfDay } from 'helpers/DateHelper';
|
||||
import subDays from 'date-fns/subDays';
|
||||
import differenceInDays from 'date-fns/differenceInDays';
|
||||
@@ -9,6 +10,11 @@ import ActiveFilterChip from './Filters/v3/ActiveFilterChip.vue';
|
||||
import WootDatePicker from 'dashboard/components/ui/DatePicker/DatePicker.vue';
|
||||
import ToggleSwitch from 'dashboard/components-next/switch/Switch.vue';
|
||||
import { GROUP_BY_FILTER } from '../constants';
|
||||
import { DATE_RANGE_TYPES } from 'dashboard/components/ui/DatePicker/helpers/DatePickerHelper';
|
||||
import {
|
||||
generateReportURLParams,
|
||||
parseReportURLParams,
|
||||
} from '../helpers/reportFilterHelper';
|
||||
|
||||
const props = defineProps({
|
||||
filterType: {
|
||||
@@ -40,6 +46,8 @@ const emit = defineEmits(['filterChange']);
|
||||
|
||||
const { t } = useI18n();
|
||||
const store = useStore();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const buildReportFilterList = (items, type) => {
|
||||
if (!Array.isArray(items)) return [];
|
||||
@@ -56,7 +64,7 @@ const getReportFilterKey = filterType => {
|
||||
teams: 'team_id',
|
||||
inboxes: 'inbox_id',
|
||||
labels: 'label_id',
|
||||
agents: 'agent_ids',
|
||||
agents: 'agent_id',
|
||||
};
|
||||
return keyMap[filterType] || '';
|
||||
};
|
||||
@@ -67,6 +75,7 @@ const showSubDropdownMenu = ref(false);
|
||||
const showGroupByDropdown = ref(false);
|
||||
const activeFilterType = ref('');
|
||||
const customDateRange = ref([subDays(new Date(), 6), new Date()]);
|
||||
const selectedDateRange = ref(DATE_RANGE_TYPES.LAST_7_DAYS);
|
||||
const businessHoursSelected = ref(false);
|
||||
const groupBy = ref(GROUP_BY_FILTER[1]);
|
||||
const groupByfilterItemsList = ref([{ id: 1, name: 'Day' }]);
|
||||
@@ -159,6 +168,18 @@ const selectedFilterName = computed(() => {
|
||||
return selectedItem?.name || defaultFilterLabel.value;
|
||||
});
|
||||
|
||||
const updateURLParams = () => {
|
||||
const params = generateReportURLParams({
|
||||
from: from.value,
|
||||
to: to.value,
|
||||
businessHours: businessHoursSelected.value,
|
||||
groupBy: isGroupByPossible.value ? groupBy.value.id : null,
|
||||
range: selectedDateRange.value,
|
||||
});
|
||||
|
||||
router.replace({ query: { ...params } });
|
||||
};
|
||||
|
||||
const emitChange = () => {
|
||||
const payload = {
|
||||
from: from.value,
|
||||
@@ -166,8 +187,11 @@ const emitChange = () => {
|
||||
businessHours: businessHoursSelected.value,
|
||||
};
|
||||
|
||||
if (props.showGroupBy && isGroupByPossible.value) {
|
||||
payload.groupBy = groupBy.value;
|
||||
if (props.showGroupBy) {
|
||||
// Always emit groupBy, default to day when range is too short
|
||||
payload.groupBy = isGroupByPossible.value
|
||||
? groupBy.value
|
||||
: GROUP_BY_FILTER[1];
|
||||
}
|
||||
|
||||
if (props.showEntityFilter) {
|
||||
@@ -182,6 +206,7 @@ const emitChange = () => {
|
||||
}
|
||||
}
|
||||
|
||||
updateURLParams();
|
||||
emit('filterChange', payload);
|
||||
};
|
||||
|
||||
@@ -201,16 +226,29 @@ const addFilter = item => {
|
||||
appliedFilters.value[filterKey] = item.id;
|
||||
closeActiveFilterDropdown();
|
||||
emitChange();
|
||||
};
|
||||
|
||||
const removeFilter = () => {
|
||||
const filterKey = getFilterKey();
|
||||
appliedFilters.value[filterKey] = null;
|
||||
emitChange();
|
||||
// Navigate to the new entity's route
|
||||
const routeNameMap = {
|
||||
teams: 'team_reports_show',
|
||||
inboxes: 'inbox_reports_show',
|
||||
labels: 'label_reports_show',
|
||||
agents: 'agent_reports_show',
|
||||
};
|
||||
|
||||
const routeName = routeNameMap[props.filterType];
|
||||
if (routeName) {
|
||||
router.push({
|
||||
name: routeName,
|
||||
params: { ...route.params, id: item.id },
|
||||
query: route.query,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onDateRangeChange = value => {
|
||||
customDateRange.value = value;
|
||||
const [startDate, endDate, rangeType] = value;
|
||||
customDateRange.value = [startDate, endDate];
|
||||
selectedDateRange.value = rangeType || DATE_RANGE_TYPES.CUSTOM_RANGE;
|
||||
groupByfilterItemsList.value = fetchFilterItems();
|
||||
const filterItems = groupByfilterItemsList.value.filter(
|
||||
item => item.id === groupBy.value.id
|
||||
@@ -239,7 +277,42 @@ const closeGroupByDropdown = () => {
|
||||
showGroupByDropdown.value = false;
|
||||
};
|
||||
|
||||
const initializeFromURL = () => {
|
||||
const urlParams = parseReportURLParams(route.query);
|
||||
|
||||
// Set the range type first
|
||||
if (urlParams.range) {
|
||||
selectedDateRange.value = urlParams.range;
|
||||
}
|
||||
|
||||
// Restore dates from URL if available
|
||||
if (urlParams.from && urlParams.to) {
|
||||
customDateRange.value = [
|
||||
new Date(urlParams.from * 1000),
|
||||
new Date(urlParams.to * 1000),
|
||||
];
|
||||
}
|
||||
|
||||
if (urlParams.businessHours) {
|
||||
businessHoursSelected.value = urlParams.businessHours;
|
||||
}
|
||||
|
||||
if (urlParams.groupBy) {
|
||||
const groupByValue = GROUP_BY_FILTER[urlParams.groupBy];
|
||||
if (groupByValue) {
|
||||
groupBy.value = groupByValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize entity filter from route params (not URL query)
|
||||
if (props.showEntityFilter && route.params.id) {
|
||||
const filterKey = getFilterKey();
|
||||
appliedFilters.value[filterKey] = Number(route.params.id);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
initializeFromURL();
|
||||
groupByfilterItemsList.value = fetchFilterItems();
|
||||
emitChange();
|
||||
});
|
||||
@@ -247,7 +320,11 @@ onMounted(() => {
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col w-full gap-3 lg:flex-row">
|
||||
<WootDatePicker @date-range-changed="onDateRangeChange" />
|
||||
<WootDatePicker
|
||||
v-model:date-range="customDateRange"
|
||||
v-model:range-type="selectedDateRange"
|
||||
@date-range-changed="onDateRangeChange"
|
||||
/>
|
||||
|
||||
<div class="flex gap-2 items-center w-full">
|
||||
<ActiveFilterChip
|
||||
@@ -264,7 +341,6 @@ onMounted(() => {
|
||||
@toggle-dropdown="openActiveFilterDropdown"
|
||||
@close-dropdown="closeActiveFilterDropdown"
|
||||
@add-filter="addFilter"
|
||||
@remove-filter="removeFilter"
|
||||
/>
|
||||
|
||||
<ActiveFilterChip
|
||||
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
export const generateReportURLParams = ({
|
||||
from,
|
||||
to,
|
||||
businessHours,
|
||||
groupBy,
|
||||
range,
|
||||
}) => {
|
||||
const params = {};
|
||||
|
||||
// Always include from/to dates
|
||||
if (from) params.from = from;
|
||||
if (to) params.to = to;
|
||||
|
||||
if (businessHours) params.business_hours = 'true';
|
||||
if (groupBy) params.group_by = groupBy;
|
||||
|
||||
// Include range type (last7days, last3months, custom, etc.)
|
||||
if (range) params.range = range;
|
||||
|
||||
return params;
|
||||
};
|
||||
|
||||
export const parseReportURLParams = query => {
|
||||
const { from, to, business_hours, group_by, range } = query;
|
||||
|
||||
return {
|
||||
from: from ? Number(from) : null,
|
||||
to: to ? Number(to) : null,
|
||||
businessHours: business_hours === 'true',
|
||||
groupBy: group_by ? Number(group_by) : null,
|
||||
range: range || null,
|
||||
};
|
||||
};
|
||||
+213
@@ -0,0 +1,213 @@
|
||||
import {
|
||||
generateReportURLParams,
|
||||
parseReportURLParams,
|
||||
} from './reportFilterHelper';
|
||||
|
||||
describe('reportFilterHelper', () => {
|
||||
describe('generateReportURLParams', () => {
|
||||
it('generates URL params with from and to dates', () => {
|
||||
const params = generateReportURLParams({
|
||||
from: 1738607400,
|
||||
to: 1770229799,
|
||||
});
|
||||
|
||||
expect(params).toEqual({
|
||||
from: 1738607400,
|
||||
to: 1770229799,
|
||||
});
|
||||
});
|
||||
|
||||
it('includes business hours when true', () => {
|
||||
const params = generateReportURLParams({
|
||||
from: 1738607400,
|
||||
to: 1770229799,
|
||||
businessHours: true,
|
||||
});
|
||||
|
||||
expect(params).toEqual({
|
||||
from: 1738607400,
|
||||
to: 1770229799,
|
||||
business_hours: 'true',
|
||||
});
|
||||
});
|
||||
|
||||
it('excludes business hours when false', () => {
|
||||
const params = generateReportURLParams({
|
||||
from: 1738607400,
|
||||
to: 1770229799,
|
||||
businessHours: false,
|
||||
});
|
||||
|
||||
expect(params).toEqual({
|
||||
from: 1738607400,
|
||||
to: 1770229799,
|
||||
});
|
||||
});
|
||||
|
||||
it('includes group by parameter', () => {
|
||||
const params = generateReportURLParams({
|
||||
from: 1738607400,
|
||||
to: 1770229799,
|
||||
groupBy: 3,
|
||||
});
|
||||
|
||||
expect(params).toEqual({
|
||||
from: 1738607400,
|
||||
to: 1770229799,
|
||||
group_by: 3,
|
||||
});
|
||||
});
|
||||
|
||||
it('includes range type', () => {
|
||||
const params = generateReportURLParams({
|
||||
from: 1738607400,
|
||||
to: 1770229799,
|
||||
range: 'last7days',
|
||||
});
|
||||
|
||||
expect(params).toEqual({
|
||||
from: 1738607400,
|
||||
to: 1770229799,
|
||||
range: 'last7days',
|
||||
});
|
||||
});
|
||||
|
||||
it('generates complete URL params with all options', () => {
|
||||
const params = generateReportURLParams({
|
||||
from: 1738607400,
|
||||
to: 1770229799,
|
||||
businessHours: true,
|
||||
groupBy: 3,
|
||||
range: 'lastYear',
|
||||
});
|
||||
|
||||
expect(params).toEqual({
|
||||
from: 1738607400,
|
||||
to: 1770229799,
|
||||
business_hours: 'true',
|
||||
group_by: 3,
|
||||
range: 'lastYear',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseReportURLParams', () => {
|
||||
it('parses from and to dates as numbers', () => {
|
||||
const result = parseReportURLParams({
|
||||
from: '1738607400',
|
||||
to: '1770229799',
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
from: 1738607400,
|
||||
to: 1770229799,
|
||||
businessHours: false,
|
||||
groupBy: null,
|
||||
range: null,
|
||||
});
|
||||
});
|
||||
|
||||
it('parses business hours as boolean', () => {
|
||||
const result = parseReportURLParams({
|
||||
from: '1738607400',
|
||||
to: '1770229799',
|
||||
business_hours: 'true',
|
||||
});
|
||||
|
||||
expect(result.businessHours).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false for business hours when not "true"', () => {
|
||||
const result = parseReportURLParams({
|
||||
from: '1738607400',
|
||||
to: '1770229799',
|
||||
business_hours: 'false',
|
||||
});
|
||||
|
||||
expect(result.businessHours).toBe(false);
|
||||
});
|
||||
|
||||
it('parses group by as number', () => {
|
||||
const result = parseReportURLParams({
|
||||
from: '1738607400',
|
||||
to: '1770229799',
|
||||
group_by: '3',
|
||||
});
|
||||
|
||||
expect(result.groupBy).toBe(3);
|
||||
});
|
||||
|
||||
it('parses range type', () => {
|
||||
const result = parseReportURLParams({
|
||||
from: '1738607400',
|
||||
to: '1770229799',
|
||||
range: 'last7days',
|
||||
});
|
||||
|
||||
expect(result.range).toBe('last7days');
|
||||
});
|
||||
|
||||
it('returns null for missing parameters', () => {
|
||||
const result = parseReportURLParams({});
|
||||
|
||||
expect(result).toEqual({
|
||||
from: null,
|
||||
to: null,
|
||||
businessHours: false,
|
||||
groupBy: null,
|
||||
range: null,
|
||||
});
|
||||
});
|
||||
|
||||
it('parses complete URL params with all options', () => {
|
||||
const result = parseReportURLParams({
|
||||
from: '1738607400',
|
||||
to: '1770229799',
|
||||
business_hours: 'true',
|
||||
group_by: '3',
|
||||
range: 'lastYear',
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
from: 1738607400,
|
||||
to: 1770229799,
|
||||
businessHours: true,
|
||||
groupBy: 3,
|
||||
range: 'lastYear',
|
||||
});
|
||||
});
|
||||
|
||||
it('handles numeric values correctly', () => {
|
||||
const result = parseReportURLParams({
|
||||
from: 1738607400,
|
||||
to: 1770229799,
|
||||
group_by: 3,
|
||||
});
|
||||
|
||||
expect(result.from).toBe(1738607400);
|
||||
expect(result.to).toBe(1770229799);
|
||||
expect(result.groupBy).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('round-trip conversion', () => {
|
||||
it('maintains data integrity through generate and parse cycle', () => {
|
||||
const original = {
|
||||
from: 1738607400,
|
||||
to: 1770229799,
|
||||
businessHours: true,
|
||||
groupBy: 3,
|
||||
range: 'lastYear',
|
||||
};
|
||||
|
||||
const urlParams = generateReportURLParams(original);
|
||||
const parsed = parseReportURLParams(urlParams);
|
||||
|
||||
expect(parsed.from).toBe(original.from);
|
||||
expect(parsed.to).toBe(original.to);
|
||||
expect(parsed.businessHours).toBe(original.businessHours);
|
||||
expect(parsed.groupBy).toBe(original.groupBy);
|
||||
expect(parsed.range).toBe(original.range);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user