diff --git a/frontend/src/components/common/DateRangePicker.vue b/frontend/src/components/common/DateRangePicker.vue
index b116dff2e3..a2af52de9f 100644
--- a/frontend/src/components/common/DateRangePicker.vue
+++ b/frontend/src/components/common/DateRangePicker.vue
@@ -43,7 +43,7 @@
@@ -57,7 +57,7 @@
type="date"
v-model="localEndDate"
:min="localStartDate"
- :max="tomorrow"
+ :max="tomorrow()"
class="date-picker-input"
@change="onDateChange"
/>
@@ -108,22 +108,15 @@ const localStartDate = ref(props.startDate)
const localEndDate = ref(props.endDate)
const activePreset = ref('last24Hours')
-const today = computed(() => {
- // Use local timezone to avoid UTC timezone issues
- const now = new Date()
- const year = now.getFullYear()
- const month = String(now.getMonth() + 1).padStart(2, '0')
- const day = String(now.getDate()).padStart(2, '0')
- return `${year}-${month}-${day}`
-})
+const today = () => formatDateToString(new Date())
// Tomorrow's date - used for max date to handle timezone differences
// When user is in a timezone behind the server, "today" on server might be "tomorrow" locally
-const tomorrow = computed(() => {
+const tomorrow = () => {
const d = new Date()
d.setDate(d.getDate() + 1)
return formatDateToString(d)
-})
+}
// Helper function to format date to YYYY-MM-DD using local timezone
const formatDateToString = (date: Date): string => {
@@ -138,7 +131,7 @@ const presets: DatePreset[] = [
labelKey: 'dates.today',
value: 'today',
getRange: () => {
- const t = today.value
+ const t = today()
return { start: t, end: t }
}
},
@@ -168,7 +161,7 @@ const presets: DatePreset[] = [
labelKey: 'dates.last7Days',
value: '7days',
getRange: () => {
- const end = today.value
+ const end = today()
const d = new Date()
d.setDate(d.getDate() - 6)
const start = formatDateToString(d)
@@ -179,7 +172,7 @@ const presets: DatePreset[] = [
labelKey: 'dates.last14Days',
value: '14days',
getRange: () => {
- const end = today.value
+ const end = today()
const d = new Date()
d.setDate(d.getDate() - 13)
const start = formatDateToString(d)
@@ -190,7 +183,7 @@ const presets: DatePreset[] = [
labelKey: 'dates.last30Days',
value: '30days',
getRange: () => {
- const end = today.value
+ const end = today()
const d = new Date()
d.setDate(d.getDate() - 29)
const start = formatDateToString(d)
@@ -203,7 +196,7 @@ const presets: DatePreset[] = [
getRange: () => {
const now = new Date()
const start = formatDateToString(new Date(now.getFullYear(), now.getMonth(), 1))
- return { start, end: today.value }
+ return { start, end: today() }
}
},
{
diff --git a/frontend/src/components/common/__tests__/DateRangePicker.midnight.spec.ts b/frontend/src/components/common/__tests__/DateRangePicker.midnight.spec.ts
new file mode 100644
index 0000000000..79e2404564
--- /dev/null
+++ b/frontend/src/components/common/__tests__/DateRangePicker.midnight.spec.ts
@@ -0,0 +1,33 @@
+import { enableAutoUnmount, mount } from '@vue/test-utils'
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
+import { ref } from 'vue'
+import DateRangePicker from '../DateRangePicker.vue'
+vi.mock('vue-i18n', () => ({ useI18n: () => ({ t: (key: string) => key, locale: ref('en') }) }))
+enableAutoUnmount(afterEach)
+beforeEach(() => { vi.useFakeTimers(); vi.setSystemTime(new Date(2026, 8, 30, 23, 59)) })
+afterEach(() => vi.useRealTimers())
+async function reopenNextDay() {
+ const w = mount(DateRangePicker, { props: { startDate: '2026-09-30', endDate: '2026-09-30' }, global: { stubs: { Icon: true } } })
+ await w.get('.date-picker-trigger').trigger('click')
+ expect(w.findAll('input[type="date"]')[1].attributes('max')).toBe('2026-10-01')
+ await w.get('.date-picker-trigger').trigger('click')
+ vi.setSystemTime(new Date(2026, 9, 1, 0, 1))
+ await w.get('.date-picker-trigger').trigger('click')
+ return w
+}
+describe('date presets after midnight', () => {
+ it.each([
+ ['dates.today', '2026-10-01'],
+ ['dates.last7Days', '2026-09-25'],
+ ['dates.thisMonth', '2026-10-01'],
+ ])('refreshes %s when the page stays mounted overnight', async (label, startDate) => {
+ const w = await reopenNextDay()
+ await w.findAll('.date-picker-preset').find(b => b.text() === label)!.trigger('click')
+ await w.get('.date-picker-apply').trigger('click')
+ expect(w.emitted('change')?.[0]?.[0]).toMatchObject({ startDate, endDate: '2026-10-01' })
+ })
+ it('updates the maximum selectable date when reopened', async () => {
+ const w = await reopenNextDay()
+ expect(w.findAll('input[type="date"]')[1].attributes('max')).toBe('2026-10-02')
+ })
+})