From 72d4d5495bb1441c0f19e6612be3991635d01c59 Mon Sep 17 00:00:00 2001 From: "nick.yi" Date: Fri, 30 Jan 2026 21:40:06 +0800 Subject: [PATCH 1/5] feat: add custom date range picker with floating tabbed interface --- src/lib/helpers/types/conversationTypes.js | 3 +- src/lib/helpers/utils/common.js | 12 +- src/routes/page/conversation/+page.svelte | 250 ++++++++++++++++--- src/routes/page/instruction/log/+page.svelte | 246 +++++++++++++++--- 4 files changed, 447 insertions(+), 64 deletions(-) diff --git a/src/lib/helpers/types/conversationTypes.js b/src/lib/helpers/types/conversationTypes.js index 67ed1654..d7b106a7 100644 --- a/src/lib/helpers/types/conversationTypes.js +++ b/src/lib/helpers/types/conversationTypes.js @@ -324,7 +324,8 @@ IRichContent.prototype.language; * @property {UserStateDetailModel[]} states * @property {string[]} tags * @property {string?} [timeRange] - * @property {string} [specificDate] - When timeRange is SpecificDay, date in YYYY-MM-DD (e.g. 2026-01-25) + * @property {string} [startDate] - When timeRange is SpecificDay, start date in YYYY-MM-DD format (e.g. 2026-01-25) + * @property {string} [endDate] - When timeRange is SpecificDay, end date in YYYY-MM-DD format (e.g. 2026-01-30). Defaults to startDate if not provided */ /** diff --git a/src/lib/helpers/utils/common.js b/src/lib/helpers/utils/common.js index 23bad793..4a274bc0 100644 --- a/src/lib/helpers/utils/common.js +++ b/src/lib/helpers/utils/common.js @@ -192,10 +192,11 @@ export function getCleanUrl(url) { /** * @param {string} timeRange - * @param {string} [specificDate] - When timeRange is SpecificDay, date in YYYY-MM-DD format (e.g. 2026-01-25) + * @param {string} [startDate] - When timeRange is SpecificDay, start date in YYYY-MM-DD format (e.g. 2026-01-25) + * @param {string} [endDate] - When timeRange is SpecificDay, end date in YYYY-MM-DD format (e.g. 2026-01-30). If not provided, uses startDate * @returns {{ startTime: string | null, endTime: string | null }} */ -export function convertTimeRange(timeRange, specificDate) { +export function convertTimeRange(timeRange, startDate, endDate) { let ret = { startTime: null, endTime: null }; if (!timeRange) { @@ -242,13 +243,14 @@ export function convertTimeRange(timeRange, specificDate) { }; break; case TimeRange.SpecificDay: - if (specificDate && moment(specificDate).isValid()) { + if (startDate && moment(startDate).isValid()) { + const endDateToUse = endDate && moment(endDate).isValid() ? endDate : startDate; ret = { ...ret, // @ts-ignore - startTime: moment(specificDate).startOf('day').utc().format(), + startTime: moment(startDate).startOf('day').utc().format(), // @ts-ignore - endTime: moment(specificDate).endOf('day').utc().format() + endTime: moment(endDateToUse).endOf('day').utc().format() }; } break; diff --git a/src/routes/page/conversation/+page.svelte b/src/routes/page/conversation/+page.svelte index 923d3c06..87d513c0 100644 --- a/src/routes/page/conversation/+page.svelte +++ b/src/routes/page/conversation/+page.svelte @@ -23,6 +23,7 @@ import { utcToLocal } from '$lib/helpers/datetime'; import { ConversationChannel, TimeRange } from '$lib/helpers/enums'; import { TIME_RANGE_OPTIONS } from '$lib/helpers/constants'; + import { clickoutsideDirective } from '$lib/helpers/directives'; import { getConversations, deleteConversation, @@ -46,12 +47,71 @@ return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0'); }; + // Get yesterday's date in YYYY-MM-DD format + const getYesterdayStr = () => { + const d = new Date(); + d.setDate(d.getDate() - 1); + return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0'); + }; + + // Format date for display (YYYY-MM-DD -> MM/DD/YYYY) + const formatDateForDisplay = (/** @type {string} */ dateStr) => { + if (!dateStr) return ''; + const [year, month, day] = dateStr.split('-'); + return `${month}/${day}/${year}`; + }; + + // Get time range display text + const getTimeRangeDisplayText = () => { + if (searchOption.timeRange === TimeRange.SpecificDay) { + if (searchOption.startDate && searchOption.endDate) { + const start = formatDateForDisplay(searchOption.startDate); + const end = formatDateForDisplay(searchOption.endDate); + if (start === end) { + return start; + } + return `${start} - ${end}`; + } + return 'Custom'; + } + // Find the label for the selected time range + const selected = presetTimeRangeOptions.find(x => x.value === searchOption.timeRange); + return selected ? selected.label : ''; + }; + /** @type {boolean} */ let isLoading = false; let isComplete = false; let showStateSearch = false; let isPageMounted = false; + /** @type {boolean} */ + let showDatePicker = false; + + /** @type {HTMLDivElement | null} */ + let datePickerRef = null; + + /** @type {string} */ + let timeRangeDisplayText = ''; + + // Update time range display text reactively + $: { + if (searchOption.timeRange === TimeRange.SpecificDay && searchOption.startDate && searchOption.endDate) { + const start = formatDateForDisplay(searchOption.startDate); + const end = formatDateForDisplay(searchOption.endDate); + if (start === end) { + timeRangeDisplayText = start; + } else { + timeRangeDisplayText = `${start} - ${end}`; + } + } else if (searchOption.timeRange === TimeRange.SpecificDay) { + timeRangeDisplayText = 'Custom'; + } else { + const selected = presetTimeRangeOptions.find(x => x.value === searchOption.timeRange); + timeRangeDisplayText = selected ? selected.label : ''; + } + } + /** @type {import('$commonTypes').PagedItems} */ let conversations = { count: 0, items: [] }; @@ -83,10 +143,16 @@ { value: k.toLowerCase(), label: v } )); - const timeRangeOptions = TIME_RANGE_OPTIONS.map(x => ({ - label: x.label, - value: x.value - })); + // Preset time range options (excluding SpecificDay) + const presetTimeRangeOptions = TIME_RANGE_OPTIONS + .filter(x => x.value !== TimeRange.SpecificDay) + .map(x => ({ + label: x.label, + value: x.value + })); + + /** @type {string} */ + let datePickerTab = 'relative'; /** @type {{ startTime: string | null, endTime: string | null }} */ let innerTimeRange = { @@ -102,7 +168,8 @@ status: null, taskId: null, timeRange: TimeRange.Last12Hours, - specificDate: '', + startDate: '', + endDate: '', states: [], tags: [] }; @@ -118,7 +185,7 @@ page: $page.url.searchParams.get("page"), pageSize: $page.url.searchParams.get("pageSize") }, { defaultPageSize: pageSize }); - innerTimeRange = convertTimeRange(searchOption.timeRange || '', searchOption.specificDate); + innerTimeRange = convertTimeRange(searchOption.timeRange || '', searchOption.startDate, searchOption.endDate); filter = { ...filter, @@ -305,7 +372,7 @@ function refreshFilter() { const searchStates = getSearchStates(); - innerTimeRange = convertTimeRange(searchOption.timeRange || '', searchOption.specificDate); + innerTimeRange = convertTimeRange(searchOption.timeRange || '', searchOption.startDate, searchOption.endDate); filter = { ...filter, @@ -405,13 +472,7 @@ tags: e.target.value?.length > 0 ? [e.target.value] : [] }; } else if (type === 'timeRange') { - const timeRange = selectedValues.length > 0 ? selectedValues[0] : null; - const isSpecificDay = timeRange === TimeRange.SpecificDay; - searchOption = { - ...searchOption, - timeRange, - specificDate: isSpecificDay ? (searchOption.specificDate || getTodayStr()) : '' - }; + // This handler is no longer used, but kept for compatibility } } @@ -518,21 +579,152 @@ on:input={e => changeOption(e, "tags")} /> - - + + + {#if showDatePicker} +
{ + if (e.detail && e.detail.targetNode && datePickerRef) { + if (!datePickerRef.contains(e.detail.targetNode)) { + showDatePicker = false; + } + } + }} + class="position-absolute top-100 start-0 mt-1 bg-white border rounded shadow-lg" + style="z-index: 1050; min-width: 320px; max-width: 350px;" + > + + +
+ {#if datePickerTab === 'relative'} +
+ {#each presetTimeRangeOptions as option} + + {/each} +
+ {:else if datePickerTab === 'custom'} +
+ + +
+
+ + +
+
+ + +
+ {/if} +
+
{/if} diff --git a/src/routes/page/instruction/log/+page.svelte b/src/routes/page/instruction/log/+page.svelte index 174418fb..11041612 100644 --- a/src/routes/page/instruction/log/+page.svelte +++ b/src/routes/page/instruction/log/+page.svelte @@ -22,6 +22,7 @@ } from '$lib/helpers/utils/common'; import { TimeRange } from '$lib/helpers/enums'; import { TIME_RANGE_OPTIONS } from '$lib/helpers/constants'; + import { clickoutsideDirective } from '$lib/helpers/directives'; import LogItem from './log-item.svelte'; const firstPage = 1; @@ -31,10 +32,16 @@ const initPager = { page: firstPage, size: pageSize }; - const timeRangeOptions = TIME_RANGE_OPTIONS.map(x => ({ - label: x.label, - value: x.value - })); + // Preset time range options (excluding SpecificDay) + const presetTimeRangeOptions = TIME_RANGE_OPTIONS + .filter(x => x.value !== TimeRange.SpecificDay) + .map(x => ({ + label: x.label, + value: x.value + })); + + /** @type {string} */ + let datePickerTab = 'relative'; // Get today's date in YYYY-MM-DD format const getTodayStr = () => { @@ -42,6 +49,38 @@ return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0'); }; + // Get yesterday's date in YYYY-MM-DD format + const getYesterdayStr = () => { + const d = new Date(); + d.setDate(d.getDate() - 1); + return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0'); + }; + + // Format date for display (YYYY-MM-DD -> MM/DD/YYYY) + const formatDateForDisplay = (/** @type {string} */ dateStr) => { + if (!dateStr) return ''; + const [year, month, day] = dateStr.split('-'); + return `${month}/${day}/${year}`; + }; + + // Get time range display text + const getTimeRangeDisplayText = () => { + if (searchOption.timeRange === TimeRange.SpecificDay) { + if (searchOption.startDate && searchOption.endDate) { + const start = formatDateForDisplay(searchOption.startDate); + const end = formatDateForDisplay(searchOption.endDate); + if (start === end) { + return start; + } + return `${start} - ${end}`; + } + return 'Custom'; + } + // Find the label for the selected time range + const selected = presetTimeRangeOptions.find(x => x.value === searchOption.timeRange); + return selected ? selected.label : ''; + }; + /** @type {import('$commonTypes').Pagination} */ let pager = { page: firstPage, size: pageSize, count: 0 } @@ -70,7 +109,8 @@ models: [], template: '', timeRange: TimeRange.Today, - specificDate: '', + startDate: '', + endDate: '', states: [] }; @@ -83,6 +123,33 @@ /** @type {boolean} */ let showStateSearch = false; + /** @type {string} */ + let timeRangeDisplayText = ''; + + // Update time range display text reactively + $: { + if (searchOption.timeRange === TimeRange.SpecificDay && searchOption.startDate && searchOption.endDate) { + const start = formatDateForDisplay(searchOption.startDate); + const end = formatDateForDisplay(searchOption.endDate); + if (start === end) { + timeRangeDisplayText = start; + } else { + timeRangeDisplayText = `${start} - ${end}`; + } + } else if (searchOption.timeRange === TimeRange.SpecificDay) { + timeRangeDisplayText = 'Custom'; + } else { + const selected = presetTimeRangeOptions.find(x => x.value === searchOption.timeRange); + timeRangeDisplayText = selected ? selected.label : ''; + } + } + + /** @type {boolean} */ + let showDatePicker = false; + + /** @type {HTMLDivElement | null} */ + let datePickerRef = null; + /** @type {{key: string, value: string | null}[]} */ let states = [ { key: '', value: ''} @@ -94,7 +161,7 @@ page: $page.url.searchParams.get("page"), pageSize: $page.url.searchParams.get("pageSize") }, { defaultPageSize: pageSize }); - innerTimeRange = convertTimeRange(searchOption.timeRange, searchOption.specificDate); + innerTimeRange = convertTimeRange(searchOption.timeRange, searchOption.startDate, searchOption.endDate); filter = { ...filter, @@ -230,13 +297,7 @@ models: selectedValues }; } else if (type === 'timeRange') { - const timeRange = selectedValues.length > 0 ? selectedValues[0] : null; - const isSpecificDay = timeRange === TimeRange.SpecificDay; - searchOption = { - ...searchOption, - timeRange, - specificDate: isSpecificDay ? (searchOption.specificDate || getTodayStr()) : '' - }; + // This handler is no longer used, but kept for compatibility } } @@ -252,7 +313,7 @@ const models = searchOption.models; const template = util.trim(searchOption.template) || null; const states = getSearchStates(); - innerTimeRange = convertTimeRange(searchOption.timeRange, searchOption.specificDate); + innerTimeRange = convertTimeRange(searchOption.timeRange, searchOption.startDate, searchOption.endDate); filter = { ...filter, @@ -383,21 +444,148 @@ - - + + + {#if showDatePicker} +
{ + if (e.detail && e.detail.targetNode && datePickerRef) { + if (!datePickerRef.contains(e.detail.targetNode)) { + showDatePicker = false; + } + } + }} + class="position-absolute top-100 start-0 mt-1 bg-white border rounded shadow-lg" + style="z-index: 1050; min-width: 320px; max-width: 350px;" + > + + +
+ {#if datePickerTab === 'relative'} +
+ {#each presetTimeRangeOptions as option} + + {/each} +
+ {:else if datePickerTab === 'custom'} +
+ + +
+
+ + +
+
+ + +
+ {/if} +
+
{/if} From 1369ad72d24674682172fbe4347f89d9c836fe50 Mon Sep 17 00:00:00 2001 From: "nick.yi" Date: Fri, 30 Jan 2026 21:48:11 +0800 Subject: [PATCH 2/5] optimize TimeRange --- src/lib/helpers/constants.js | 4 +++- src/lib/helpers/enums.js | 1 - src/lib/helpers/types/conversationTypes.js | 4 ++-- src/lib/helpers/utils/common.js | 8 ++++---- src/routes/page/conversation/+page.svelte | 17 +++++++++-------- src/routes/page/instruction/log/+page.svelte | 17 +++++++++-------- 6 files changed, 27 insertions(+), 24 deletions(-) diff --git a/src/lib/helpers/constants.js b/src/lib/helpers/constants.js index 1924c931..20d445aa 100644 --- a/src/lib/helpers/constants.js +++ b/src/lib/helpers/constants.js @@ -49,6 +49,9 @@ export const IMAGE_DATA_PREFIX = 'data:image'; export const INTEGER_REGEX = "[0-9]+"; export const DECIMAL_REGEX = "[0-9.]+"; +// Custom date range identifier (not in TimeRange enum) +export const CUSTOM_DATE_RANGE = "Custom date"; + export const TIME_RANGE_OPTIONS = [ { label: TimeRange.Last15Minutes, value: TimeRange.Last15Minutes, qty: 15, unit: 'minutes' }, { label: TimeRange.Last30Minutes, value: TimeRange.Last30Minutes, qty: 30, unit: 'minutes' }, @@ -57,7 +60,6 @@ export const TIME_RANGE_OPTIONS = [ { label: TimeRange.Last12Hours, value: TimeRange.Last12Hours, qty: 12, unit: 'hours' }, { label: TimeRange.Today, value: TimeRange.Today, qty: 1, unit: 'days' }, { label: TimeRange.Yesterday, value: TimeRange.Yesterday, qty: 1, unit: 'days' }, - { label: TimeRange.SpecificDay, value: TimeRange.SpecificDay, isSpecificDay: true }, { label: TimeRange.Last3Days, value: TimeRange.Last3Days, qty: 3, unit: 'days' }, { label: TimeRange.Last7Days, value: TimeRange.Last7Days, qty: 7, unit: 'days' }, { label: TimeRange.Last30Days, value: TimeRange.Last30Days, qty: 30, unit: 'days' }, diff --git a/src/lib/helpers/enums.js b/src/lib/helpers/enums.js index 01dd347a..001111ac 100644 --- a/src/lib/helpers/enums.js +++ b/src/lib/helpers/enums.js @@ -257,7 +257,6 @@ const timeRange = { Last12Hours: "Last 12 hours", Today: "Today", Yesterday: "Yesterday", - SpecificDay: "Specific day", Last3Days: "Last 3 days", Last7Days: "Last 7 days", Last30Days: "Last 30 days", diff --git a/src/lib/helpers/types/conversationTypes.js b/src/lib/helpers/types/conversationTypes.js index d7b106a7..54879042 100644 --- a/src/lib/helpers/types/conversationTypes.js +++ b/src/lib/helpers/types/conversationTypes.js @@ -324,8 +324,8 @@ IRichContent.prototype.language; * @property {UserStateDetailModel[]} states * @property {string[]} tags * @property {string?} [timeRange] - * @property {string} [startDate] - When timeRange is SpecificDay, start date in YYYY-MM-DD format (e.g. 2026-01-25) - * @property {string} [endDate] - When timeRange is SpecificDay, end date in YYYY-MM-DD format (e.g. 2026-01-30). Defaults to startDate if not provided + * @property {string} [startDate] - When timeRange is "Custom date", start date in YYYY-MM-DD format (e.g. 2026-01-25) + * @property {string} [endDate] - When timeRange is "Custom date", end date in YYYY-MM-DD format (e.g. 2026-01-30). Defaults to startDate if not provided */ /** diff --git a/src/lib/helpers/utils/common.js b/src/lib/helpers/utils/common.js index 4a274bc0..52940de2 100644 --- a/src/lib/helpers/utils/common.js +++ b/src/lib/helpers/utils/common.js @@ -1,6 +1,6 @@ import { goto } from '$app/navigation'; import moment from 'moment'; -import { TIME_RANGE_OPTIONS } from '../constants'; +import { TIME_RANGE_OPTIONS, CUSTOM_DATE_RANGE } from '../constants'; import { TimeRange } from '../enums'; export function range(size = 3, startAt = 0) { @@ -192,8 +192,8 @@ export function getCleanUrl(url) { /** * @param {string} timeRange - * @param {string} [startDate] - When timeRange is SpecificDay, start date in YYYY-MM-DD format (e.g. 2026-01-25) - * @param {string} [endDate] - When timeRange is SpecificDay, end date in YYYY-MM-DD format (e.g. 2026-01-30). If not provided, uses startDate + * @param {string} [startDate] - When timeRange is "Custom date", start date in YYYY-MM-DD format (e.g. 2026-01-25) + * @param {string} [endDate] - When timeRange is "Custom date", end date in YYYY-MM-DD format (e.g. 2026-01-30). If not provided, uses startDate * @returns {{ startTime: string | null, endTime: string | null }} */ export function convertTimeRange(timeRange, startDate, endDate) { @@ -242,7 +242,7 @@ export function convertTimeRange(timeRange, startDate, endDate) { endTime: moment().subtract(1, 'days').endOf('day').utc().format() }; break; - case TimeRange.SpecificDay: + case CUSTOM_DATE_RANGE: if (startDate && moment(startDate).isValid()) { const endDateToUse = endDate && moment(endDate).isValid() ? endDate : startDate; ret = { diff --git a/src/routes/page/conversation/+page.svelte b/src/routes/page/conversation/+page.svelte index 87d513c0..405bb4b4 100644 --- a/src/routes/page/conversation/+page.svelte +++ b/src/routes/page/conversation/+page.svelte @@ -22,7 +22,7 @@ import { getAgentOptions } from '$lib/services/agent-service'; import { utcToLocal } from '$lib/helpers/datetime'; import { ConversationChannel, TimeRange } from '$lib/helpers/enums'; - import { TIME_RANGE_OPTIONS } from '$lib/helpers/constants'; + import { TIME_RANGE_OPTIONS, CUSTOM_DATE_RANGE } from '$lib/helpers/constants'; import { clickoutsideDirective } from '$lib/helpers/directives'; import { getConversations, @@ -63,7 +63,7 @@ // Get time range display text const getTimeRangeDisplayText = () => { - if (searchOption.timeRange === TimeRange.SpecificDay) { + if (searchOption.timeRange === CUSTOM_DATE_RANGE) { if (searchOption.startDate && searchOption.endDate) { const start = formatDateForDisplay(searchOption.startDate); const end = formatDateForDisplay(searchOption.endDate); @@ -96,7 +96,7 @@ // Update time range display text reactively $: { - if (searchOption.timeRange === TimeRange.SpecificDay && searchOption.startDate && searchOption.endDate) { + if (searchOption.timeRange === CUSTOM_DATE_RANGE && searchOption.startDate && searchOption.endDate) { const start = formatDateForDisplay(searchOption.startDate); const end = formatDateForDisplay(searchOption.endDate); if (start === end) { @@ -104,7 +104,7 @@ } else { timeRangeDisplayText = `${start} - ${end}`; } - } else if (searchOption.timeRange === TimeRange.SpecificDay) { + } else if (searchOption.timeRange === CUSTOM_DATE_RANGE) { timeRangeDisplayText = 'Custom'; } else { const selected = presetTimeRangeOptions.find(x => x.value === searchOption.timeRange); @@ -143,9 +143,9 @@ { value: k.toLowerCase(), label: v } )); - // Preset time range options (excluding SpecificDay) + // Preset time range options (excluding custom date) const presetTimeRangeOptions = TIME_RANGE_OPTIONS - .filter(x => x.value !== TimeRange.SpecificDay) + .filter(x => x.value !== CUSTOM_DATE_RANGE) .map(x => ({ label: x.label, value: x.value @@ -586,7 +586,8 @@ on:click={() => { showDatePicker = !showDatePicker; if (showDatePicker) { - datePickerTab = 'relative'; + // If custom date is selected, switch to custom tab; otherwise use relative tab + datePickerTab = searchOption.timeRange === CUSTOM_DATE_RANGE ? 'custom' : 'relative'; } }} style="cursor: pointer;" @@ -710,7 +711,7 @@ // Force reactivity by reassigning the object searchOption = { ...searchOption, - timeRange: TimeRange.SpecificDay + timeRange: CUSTOM_DATE_RANGE }; } showDatePicker = false; diff --git a/src/routes/page/instruction/log/+page.svelte b/src/routes/page/instruction/log/+page.svelte index 11041612..32856e22 100644 --- a/src/routes/page/instruction/log/+page.svelte +++ b/src/routes/page/instruction/log/+page.svelte @@ -21,7 +21,7 @@ goToUrl } from '$lib/helpers/utils/common'; import { TimeRange } from '$lib/helpers/enums'; - import { TIME_RANGE_OPTIONS } from '$lib/helpers/constants'; + import { TIME_RANGE_OPTIONS, CUSTOM_DATE_RANGE } from '$lib/helpers/constants'; import { clickoutsideDirective } from '$lib/helpers/directives'; import LogItem from './log-item.svelte'; @@ -32,9 +32,9 @@ const initPager = { page: firstPage, size: pageSize }; - // Preset time range options (excluding SpecificDay) + // Preset time range options (excluding custom date) const presetTimeRangeOptions = TIME_RANGE_OPTIONS - .filter(x => x.value !== TimeRange.SpecificDay) + .filter(x => x.value !== CUSTOM_DATE_RANGE) .map(x => ({ label: x.label, value: x.value @@ -65,7 +65,7 @@ // Get time range display text const getTimeRangeDisplayText = () => { - if (searchOption.timeRange === TimeRange.SpecificDay) { + if (searchOption.timeRange === CUSTOM_DATE_RANGE) { if (searchOption.startDate && searchOption.endDate) { const start = formatDateForDisplay(searchOption.startDate); const end = formatDateForDisplay(searchOption.endDate); @@ -128,7 +128,7 @@ // Update time range display text reactively $: { - if (searchOption.timeRange === TimeRange.SpecificDay && searchOption.startDate && searchOption.endDate) { + if (searchOption.timeRange === CUSTOM_DATE_RANGE && searchOption.startDate && searchOption.endDate) { const start = formatDateForDisplay(searchOption.startDate); const end = formatDateForDisplay(searchOption.endDate); if (start === end) { @@ -136,7 +136,7 @@ } else { timeRangeDisplayText = `${start} - ${end}`; } - } else if (searchOption.timeRange === TimeRange.SpecificDay) { + } else if (searchOption.timeRange === CUSTOM_DATE_RANGE) { timeRangeDisplayText = 'Custom'; } else { const selected = presetTimeRangeOptions.find(x => x.value === searchOption.timeRange); @@ -451,7 +451,8 @@ on:click={() => { showDatePicker = !showDatePicker; if (showDatePicker) { - datePickerTab = 'relative'; + // If custom date is selected, switch to custom tab; otherwise use relative tab + datePickerTab = searchOption.timeRange === CUSTOM_DATE_RANGE ? 'custom' : 'relative'; } }} style="cursor: pointer;" @@ -573,7 +574,7 @@ // Force reactivity by reassigning the object searchOption = { ...searchOption, - timeRange: TimeRange.SpecificDay + timeRange: CUSTOM_DATE_RANGE }; } showDatePicker = false; From 611a6efd3e17d1ec2eeeeb8eb42c909bc1c0819a Mon Sep 17 00:00:00 2001 From: "nick.yi" Date: Fri, 30 Jan 2026 21:55:28 +0800 Subject: [PATCH 3/5] optimize presetTimeRangeOptions --- src/routes/page/conversation/+page.svelte | 6 ++---- src/routes/page/instruction/log/+page.svelte | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/routes/page/conversation/+page.svelte b/src/routes/page/conversation/+page.svelte index 405bb4b4..d1407237 100644 --- a/src/routes/page/conversation/+page.svelte +++ b/src/routes/page/conversation/+page.svelte @@ -143,10 +143,8 @@ { value: k.toLowerCase(), label: v } )); - // Preset time range options (excluding custom date) - const presetTimeRangeOptions = TIME_RANGE_OPTIONS - .filter(x => x.value !== CUSTOM_DATE_RANGE) - .map(x => ({ + // Preset time range options + const presetTimeRangeOptions = TIME_RANGE_OPTIONS.map(x => ({ label: x.label, value: x.value })); diff --git a/src/routes/page/instruction/log/+page.svelte b/src/routes/page/instruction/log/+page.svelte index 32856e22..fd2a3d72 100644 --- a/src/routes/page/instruction/log/+page.svelte +++ b/src/routes/page/instruction/log/+page.svelte @@ -32,10 +32,8 @@ const initPager = { page: firstPage, size: pageSize }; - // Preset time range options (excluding custom date) - const presetTimeRangeOptions = TIME_RANGE_OPTIONS - .filter(x => x.value !== CUSTOM_DATE_RANGE) - .map(x => ({ + // Preset time range options + const presetTimeRangeOptions = TIME_RANGE_OPTIONS.map(x => ({ label: x.label, value: x.value })); From ff630660daad2f3bbf10f225e09c18d2377a1c19 Mon Sep 17 00:00:00 2001 From: "nick.yi" Date: Fri, 30 Jan 2026 22:01:08 +0800 Subject: [PATCH 4/5] refactor: extract time range picker into reusable componen --- src/lib/common/shared/TimeRangePicker.svelte | 229 ++++++++++++++++++ src/routes/page/conversation/+page.svelte | 237 ++----------------- src/routes/page/instruction/log/+page.svelte | 230 +----------------- 3 files changed, 256 insertions(+), 440 deletions(-) create mode 100644 src/lib/common/shared/TimeRangePicker.svelte diff --git a/src/lib/common/shared/TimeRangePicker.svelte b/src/lib/common/shared/TimeRangePicker.svelte new file mode 100644 index 00000000..046f6663 --- /dev/null +++ b/src/lib/common/shared/TimeRangePicker.svelte @@ -0,0 +1,229 @@ + + +
+ + {#if showDatePicker} +
{ + if (e.detail && e.detail.targetNode && datePickerRef) { + if (!datePickerRef.contains(e.detail.targetNode)) { + showDatePicker = false; + } + } + }} + class="position-absolute top-100 start-0 mt-1 bg-white border rounded shadow-lg" + style="z-index: 1050; min-width: 320px; max-width: 350px;" + > + + +
+ {#if datePickerTab === 'relative'} +
+ {#each presetTimeRangeOptions as option} + + {/each} +
+ {:else if datePickerTab === 'custom'} +
+ + +
+
+ + +
+
+ + +
+ {/if} +
+
+ {/if} +
diff --git a/src/routes/page/conversation/+page.svelte b/src/routes/page/conversation/+page.svelte index d1407237..cfcb4606 100644 --- a/src/routes/page/conversation/+page.svelte +++ b/src/routes/page/conversation/+page.svelte @@ -19,11 +19,11 @@ import TablePagination from '$lib/common/shared/TablePagination.svelte'; import LoadingToComplete from '$lib/common/spinners/LoadingToComplete.svelte'; import Select from '$lib/common/dropdowns/Select.svelte'; + import TimeRangePicker from '$lib/common/shared/TimeRangePicker.svelte'; import { getAgentOptions } from '$lib/services/agent-service'; import { utcToLocal } from '$lib/helpers/datetime'; import { ConversationChannel, TimeRange } from '$lib/helpers/enums'; - import { TIME_RANGE_OPTIONS, CUSTOM_DATE_RANGE } from '$lib/helpers/constants'; - import { clickoutsideDirective } from '$lib/helpers/directives'; + import { CUSTOM_DATE_RANGE } from '$lib/helpers/constants'; import { getConversations, deleteConversation, @@ -41,77 +41,12 @@ const firstPage = 1; const pageSize = 15; - // Get today's date in YYYY-MM-DD format - const getTodayStr = () => { - const d = new Date(); - return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0'); - }; - - // Get yesterday's date in YYYY-MM-DD format - const getYesterdayStr = () => { - const d = new Date(); - d.setDate(d.getDate() - 1); - return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0'); - }; - - // Format date for display (YYYY-MM-DD -> MM/DD/YYYY) - const formatDateForDisplay = (/** @type {string} */ dateStr) => { - if (!dateStr) return ''; - const [year, month, day] = dateStr.split('-'); - return `${month}/${day}/${year}`; - }; - - // Get time range display text - const getTimeRangeDisplayText = () => { - if (searchOption.timeRange === CUSTOM_DATE_RANGE) { - if (searchOption.startDate && searchOption.endDate) { - const start = formatDateForDisplay(searchOption.startDate); - const end = formatDateForDisplay(searchOption.endDate); - if (start === end) { - return start; - } - return `${start} - ${end}`; - } - return 'Custom'; - } - // Find the label for the selected time range - const selected = presetTimeRangeOptions.find(x => x.value === searchOption.timeRange); - return selected ? selected.label : ''; - }; - /** @type {boolean} */ let isLoading = false; let isComplete = false; let showStateSearch = false; let isPageMounted = false; - /** @type {boolean} */ - let showDatePicker = false; - - /** @type {HTMLDivElement | null} */ - let datePickerRef = null; - - /** @type {string} */ - let timeRangeDisplayText = ''; - - // Update time range display text reactively - $: { - if (searchOption.timeRange === CUSTOM_DATE_RANGE && searchOption.startDate && searchOption.endDate) { - const start = formatDateForDisplay(searchOption.startDate); - const end = formatDateForDisplay(searchOption.endDate); - if (start === end) { - timeRangeDisplayText = start; - } else { - timeRangeDisplayText = `${start} - ${end}`; - } - } else if (searchOption.timeRange === CUSTOM_DATE_RANGE) { - timeRangeDisplayText = 'Custom'; - } else { - const selected = presetTimeRangeOptions.find(x => x.value === searchOption.timeRange); - timeRangeDisplayText = selected ? selected.label : ''; - } - } - /** @type {import('$commonTypes').PagedItems} */ let conversations = { count: 0, items: [] }; @@ -143,14 +78,6 @@ { value: k.toLowerCase(), label: v } )); - // Preset time range options - const presetTimeRangeOptions = TIME_RANGE_OPTIONS.map(x => ({ - label: x.label, - value: x.value - })); - - /** @type {string} */ - let datePickerTab = 'relative'; /** @type {{ startTime: string | null, endTime: string | null }} */ let innerTimeRange = { @@ -577,154 +504,20 @@ on:input={e => changeOption(e, "tags")} /> - - - {#if showDatePicker} -
{ - if (e.detail && e.detail.targetNode && datePickerRef) { - if (!datePickerRef.contains(e.detail.targetNode)) { - showDatePicker = false; - } - } - }} - class="position-absolute top-100 start-0 mt-1 bg-white border rounded shadow-lg" - style="z-index: 1050; min-width: 320px; max-width: 350px;" - > - - -
- {#if datePickerTab === 'relative'} -
- {#each presetTimeRangeOptions as option} - - {/each} -
- {:else if datePickerTab === 'custom'} -
- - -
-
- - -
-
- - -
- {/if} -
-
- {/if} + /> - {#if showDatePicker} -
{ - if (e.detail && e.detail.targetNode && datePickerRef) { - if (!datePickerRef.contains(e.detail.targetNode)) { - showDatePicker = false; - } - } - }} - class="position-absolute top-100 start-0 mt-1 bg-white border rounded shadow-lg" - style="z-index: 1050; min-width: 320px; max-width: 350px;" - > - - -
- {#if datePickerTab === 'relative'} -
- {#each presetTimeRangeOptions as option} - - {/each} -
- {:else if datePickerTab === 'custom'} -
- - -
-
- - -
-
- - -
- {/if} -
-
- {/if} + />