Skip to content

Commit 359012c

Browse files
committed
feat: add calendar grid to TimeRangePicker and fix custom date query
1 parent f150584 commit 359012c

2 files changed

Lines changed: 143 additions & 35 deletions

File tree

src/lib/common/shared/TimeRangePicker.svelte

Lines changed: 128 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
<script>
22
import { createEventDispatcher } from 'svelte';
33
import { Button, Input } from '@sveltestrap/sveltestrap';
4+
import Flatpickr from 'svelte-flatpickr';
5+
import 'flatpickr/dist/flatpickr.css';
46
import { TIME_RANGE_OPTIONS, CUSTOM_DATE_RANGE } from '$lib/helpers/constants';
57
import { clickoutsideDirective } from '$lib/helpers/directives';
68
@@ -25,6 +27,86 @@
2527
/** @type {string} */
2628
let datePickerTab = 'relative';
2729
30+
// Temporary dates for custom selection (before confirming)
31+
/** @type {string} */
32+
let tempStartDate = '';
33+
34+
/** @type {string} */
35+
let tempEndDate = '';
36+
37+
// Flatpickr instance reference
38+
/** @type {any} */
39+
let flatpickrInstance = null;
40+
41+
// Format date for flatpickr (Date object to YYYY-MM-DD string)
42+
/** @param {Date} date */
43+
function formatDateForFlatpickr(/** @type {Date} */ date) {
44+
if (!date) return '';
45+
const year = date.getFullYear();
46+
const month = String(date.getMonth() + 1).padStart(2, '0');
47+
const day = String(date.getDate()).padStart(2, '0');
48+
return `${year}-${month}-${day}`;
49+
}
50+
51+
// Flatpickr options for date range selection
52+
/** @type {any} */
53+
let flatpickrOptions = {
54+
mode: 'range',
55+
dateFormat: 'Y-m-d',
56+
inline: true,
57+
allowInput: false,
58+
onChange: (/** @type {Date[]} */ selectedDates, /** @type {string} */ dateStr, /** @type {any} */ instance) => {
59+
if (selectedDates.length === 1) {
60+
// Only start date selected
61+
tempStartDate = formatDateForFlatpickr(selectedDates[0]);
62+
tempEndDate = '';
63+
} else if (selectedDates.length === 2) {
64+
// Both dates selected
65+
tempStartDate = formatDateForFlatpickr(selectedDates[0]);
66+
tempEndDate = formatDateForFlatpickr(selectedDates[1]);
67+
}
68+
}
69+
};
70+
71+
// Handle manual input changes
72+
function handleStartDateChange() {
73+
if (tempStartDate && flatpickrInstance) {
74+
if (tempEndDate) {
75+
flatpickrInstance.setDate([tempStartDate, tempEndDate], false);
76+
} else {
77+
flatpickrInstance.setDate([tempStartDate], false);
78+
}
79+
}
80+
}
81+
82+
function handleEndDateChange() {
83+
if (tempStartDate && tempEndDate && flatpickrInstance) {
84+
flatpickrInstance.setDate([tempStartDate, tempEndDate], false);
85+
}
86+
}
87+
88+
// Initialize temp dates when opening custom tab
89+
function initCustomDates() {
90+
if (startDate) {
91+
tempStartDate = startDate;
92+
}
93+
if (endDate) {
94+
tempEndDate = endDate;
95+
}
96+
if (!tempStartDate && !tempEndDate) {
97+
tempStartDate = getYesterdayStr();
98+
tempEndDate = getTodayStr();
99+
}
100+
// Update flatpickr with initial dates
101+
if (flatpickrInstance) {
102+
if (tempStartDate && tempEndDate) {
103+
flatpickrInstance.setDate([tempStartDate, tempEndDate], false);
104+
} else if (tempStartDate) {
105+
flatpickrInstance.setDate([tempStartDate], false);
106+
}
107+
}
108+
}
109+
28110
// Preset time range options
29111
const presetTimeRangeOptions = TIME_RANGE_OPTIONS.map(x => ({
30112
label: x.label,
@@ -80,17 +162,21 @@
80162
dispatch('change', { timeRange, startDate, endDate });
81163
}
82164
83-
function handleCustomConfirm() {
84-
if (startDate) {
85-
// If endDate is not provided, default to startDate
86-
if (!endDate) {
87-
endDate = startDate;
88-
}
89-
// Force reactivity by reassigning
165+
function handleApply() {
166+
if (tempStartDate) {
167+
const finalEndDate = tempEndDate || tempStartDate;
168+
// Update props through binding (will trigger reactivity)
169+
startDate = tempStartDate;
170+
endDate = finalEndDate;
90171
timeRange = CUSTOM_DATE_RANGE;
172+
// Dispatch change event with updated values
173+
dispatch('change', {
174+
timeRange: CUSTOM_DATE_RANGE,
175+
startDate: tempStartDate,
176+
endDate: finalEndDate
177+
});
91178
}
92179
showDatePicker = false;
93-
dispatch('change', { timeRange, startDate, endDate });
94180
}
95181
96182
function handleCancel() {
@@ -107,6 +193,12 @@
107193
if (showDatePicker) {
108194
// If custom date is selected, switch to custom tab; otherwise use relative tab
109195
datePickerTab = timeRange === CUSTOM_DATE_RANGE ? 'custom' : 'relative';
196+
if (datePickerTab === 'custom') {
197+
// Delay init to ensure flatpickr is mounted
198+
setTimeout(() => {
199+
initCustomDates();
200+
}, 0);
201+
}
110202
}
111203
}}
112204
style="cursor: pointer;"
@@ -148,11 +240,10 @@
148240
style="padding: 0.5rem 0.75rem; {datePickerTab === 'custom' ? 'border-bottom: 2px solid var(--bs-primary) !important; margin-bottom: -1px;' : ''}"
149241
on:click={() => {
150242
datePickerTab = 'custom';
151-
// Set default dates to yesterday and today if not already set
152-
if (!startDate && !endDate) {
153-
startDate = getYesterdayStr();
154-
endDate = getTodayStr();
155-
}
243+
// Delay init to ensure flatpickr is mounted
244+
setTimeout(() => {
245+
initCustomDates();
246+
}, 0);
156247
}}
157248
>
158249
Custom
@@ -178,24 +269,31 @@
178269
{/each}
179270
</div>
180271
{:else if datePickerTab === 'custom'}
272+
<!-- Calendar Grid -->
181273
<div class="mb-3">
182-
<label for="start-date-picker" class="form-label small mb-2">From:</label>
274+
<Flatpickr
275+
options={flatpickrOptions}
276+
bind:flatpickr={flatpickrInstance}
277+
/>
278+
</div>
279+
280+
<!-- Date Input Fields -->
281+
<div class="d-flex align-items-center gap-2 mb-3">
183282
<Input
184-
id="start-date-picker"
185283
type="date"
186-
bind:value={startDate}
284+
bind:value={tempStartDate}
187285
class="form-control form-control-sm"
286+
on:change={handleStartDateChange}
188287
/>
189-
</div>
190-
<div class="mb-4">
191-
<label for="end-date-picker" class="form-label small mb-2">To:</label>
288+
<span class="text-muted small">to</span>
192289
<Input
193-
id="end-date-picker"
194290
type="date"
195-
bind:value={endDate}
291+
bind:value={tempEndDate}
196292
class="form-control form-control-sm"
293+
on:change={handleEndDateChange}
197294
/>
198295
</div>
296+
199297
<div class="d-flex justify-content-end gap-2 mt-3">
200298
<Button
201299
color="secondary"
@@ -216,14 +314,21 @@
216314
on:click={(e) => {
217315
e.preventDefault();
218316
e.stopPropagation();
219-
handleCustomConfirm();
317+
handleApply();
220318
}}
221319
>
222-
Confirm
320+
Apply
223321
</Button>
224322
</div>
225323
{/if}
226324
</div>
227325
</div>
228326
{/if}
229327
</div>
328+
329+
<style>
330+
/* Hide flatpickr's default input field when using inline mode */
331+
:global(.flatpickr-input) {
332+
display: none !important;
333+
}
334+
</style>

src/lib/helpers/utils/common.js

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,21 @@ export function convertTimeRange(timeRange, startDate, endDate) {
203203
return ret;
204204
}
205205

206+
// Handle CUSTOM_DATE_RANGE first, as it's not in TIME_RANGE_OPTIONS
207+
if (timeRange === CUSTOM_DATE_RANGE) {
208+
if (startDate && moment(startDate).isValid()) {
209+
const endDateToUse = endDate && moment(endDate).isValid() ? endDate : startDate;
210+
ret = {
211+
...ret,
212+
// @ts-ignore
213+
startTime: moment(startDate).startOf('day').utc().format(),
214+
// @ts-ignore
215+
endTime: moment(endDateToUse).endOf('day').utc().format()
216+
};
217+
}
218+
return ret;
219+
}
220+
206221
const found = TIME_RANGE_OPTIONS.find(x => x.value === timeRange);
207222
if (!found) {
208223
return ret;
@@ -242,18 +257,6 @@ export function convertTimeRange(timeRange, startDate, endDate) {
242257
endTime: moment().subtract(1, 'days').endOf('day').utc().format()
243258
};
244259
break;
245-
case CUSTOM_DATE_RANGE:
246-
if (startDate && moment(startDate).isValid()) {
247-
const endDateToUse = endDate && moment(endDate).isValid() ? endDate : startDate;
248-
ret = {
249-
...ret,
250-
// @ts-ignore
251-
startTime: moment(startDate).startOf('day').utc().format(),
252-
// @ts-ignore
253-
endTime: moment(endDateToUse).endOf('day').utc().format()
254-
};
255-
}
256-
break;
257260
default:
258261
break;
259262
}

0 commit comments

Comments
 (0)