@datum-cloud/datum-ui 0.2.0-alpha.2 → 0.2.0-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +336 -0
- package/dist/alert/index.mjs +3 -0
- package/dist/alert-BC2Mccfo.mjs +95 -0
- package/dist/autocomplete/index.mjs +7 -0
- package/dist/autocomplete-DZtI97HP.mjs +295 -0
- package/dist/avatar-stack/index.mjs +5 -0
- package/dist/avatar-stack-JCfBlPB9.mjs +80 -0
- package/dist/badge/index.mjs +3 -0
- package/dist/badge-bFgeYceE.mjs +185 -0
- package/dist/breadcrumb/index.mjs +4 -0
- package/dist/breadcrumb-BGYJgom_.mjs +71 -0
- package/dist/button/index.mjs +4 -0
- package/dist/button-AzpnV-WB.mjs +49 -0
- package/dist/button-C1wRfGtT.mjs +230 -0
- package/dist/button-group/index.mjs +5 -0
- package/dist/button-group-C1IB2K5s.mjs +40 -0
- package/dist/calendar/index.mjs +5 -0
- package/dist/calendar-DlIHeWb0.mjs +113 -0
- package/dist/card/index.mjs +4 -0
- package/dist/card-3Kd0VdNf.mjs +63 -0
- package/dist/chart/index.mjs +4 -0
- package/dist/chart-BZqUKpkh.mjs +143 -0
- package/dist/checkbox/index.mjs +4 -0
- package/dist/checkbox-LG1OKTpG.mjs +34 -0
- package/dist/col-lrLMZaTJ.mjs +184 -0
- package/dist/collapsible/index.mjs +3 -0
- package/dist/collapsible-Bt9UYfv3.mjs +9 -0
- package/dist/command/index.mjs +5 -0
- package/dist/command-s0Yv3abE.mjs +86 -0
- package/dist/components/features/date-picker/index.d.ts +3 -0
- package/dist/components/features/date-picker/index.d.ts.map +1 -0
- package/dist/components/features/dropzone/index.d.ts +1 -0
- package/dist/components/features/dropzone/index.d.ts.map +1 -1
- package/dist/date-picker/index.mjs +9 -0
- package/dist/{datum.provider-D6VMjSV0.mjs → datum.provider-B77goJgl.mjs} +1 -1
- package/dist/dialog/index.mjs +5 -0
- package/dist/dialog-DXBaT9gA.mjs +86 -0
- package/dist/dialog-bnMMf9GD.mjs +73 -0
- package/dist/dropdown/index.mjs +3 -0
- package/dist/dropdown-DtSa_lqc.mjs +112 -0
- package/dist/dropzone/index.mjs +5 -0
- package/dist/dropzone-BkOnwrS4.mjs +221 -0
- package/dist/empty-content/index.mjs +3 -0
- package/dist/empty-content-BM9rzI13.mjs +196 -0
- package/dist/exports/map.d.ts +3 -0
- package/dist/exports/map.d.ts.map +1 -0
- package/dist/form/index.mjs +146 -0
- package/dist/grid/index.mjs +3 -0
- package/dist/hooks/index.mjs +2 -3
- package/dist/hover-card/index.mjs +4 -0
- package/dist/hover-card-CUPfFUqE.mjs +33 -0
- package/dist/icon-wrapper-9ticVbRL.mjs +14 -0
- package/dist/icons/index.mjs +3 -3
- package/dist/index.mjs +66 -8
- package/dist/input/index.mjs +5 -0
- package/dist/input-DuyjEKEW.mjs +17 -0
- package/dist/input-fzXBheCN.mjs +17 -0
- package/dist/input-group/index.mjs +7 -0
- package/dist/input-group-CPaFSTEV.mjs +80 -0
- package/dist/input-number/index.mjs +6 -0
- package/dist/input-number-9o62JHRl.mjs +106 -0
- package/dist/input-with-addons/index.mjs +3 -0
- package/dist/input-with-addons-BQn7KCTU.mjs +30 -0
- package/dist/label/index.mjs +4 -0
- package/dist/label-_ste_Re3.mjs +44 -0
- package/dist/link-button-TIF2Zdrk.mjs +36 -0
- package/dist/loader-overlay/index.mjs +3 -0
- package/dist/loader-overlay-DUaQSZQP.mjs +17 -0
- package/dist/map/index.mjs +13 -0
- package/dist/map-Df8QMcX0.mjs +1094 -0
- package/dist/more-actions/index.mjs +5 -0
- package/dist/more-actions-Ch1f6Mh3.mjs +54 -0
- package/dist/nprogress/index.mjs +32 -0
- package/dist/page-title/index.mjs +3 -0
- package/dist/page-title-BJuo81rT.mjs +26 -0
- package/dist/popover/index.mjs +4 -0
- package/dist/popover-SQlKSz6L.mjs +36 -0
- package/dist/provider/index.mjs +4 -0
- package/dist/radio-group/index.mjs +4 -0
- package/dist/radio-group-Oshv0b-U.mjs +49 -0
- package/dist/select/index.mjs +4 -0
- package/dist/select-DVlEzD2W.mjs +166 -0
- package/dist/separator/index.mjs +4 -0
- package/dist/separator-T2ppyD-8.mjs +18 -0
- package/dist/sheet/index.mjs +5 -0
- package/dist/sheet-BKiCwtNO.mjs +45 -0
- package/dist/sheet-CtnP6gTD.mjs +77 -0
- package/dist/sidebar/index.mjs +11 -0
- package/dist/sidebar-DfqezV8t.mjs +945 -0
- package/dist/skeleton/index.mjs +4 -0
- package/dist/skeleton-vzbxA-DQ.mjs +13 -0
- package/dist/spinner/index.mjs +4 -0
- package/dist/spinner-BE7k2bAD.mjs +16 -0
- package/dist/{icon-wrapper-BgPkifId.mjs → spinner.icon-Bg8zgGh0.mjs} +1 -12
- package/dist/stepper/index.mjs +5 -0
- package/dist/stepper-SWB-u_nM.mjs +323 -0
- package/dist/style.css +3 -0
- package/dist/switch/index.mjs +4 -0
- package/dist/switch-Calk7Gyw.mjs +32 -0
- package/dist/table/index.mjs +4 -0
- package/dist/table-CsXBcQLI.mjs +68 -0
- package/dist/tabs/index.mjs +3 -0
- package/dist/tabs-D8n-dqnw.mjs +52 -0
- package/dist/tag-input/index.mjs +5 -0
- package/dist/tag-input-Di7SDNbK.mjs +284 -0
- package/dist/task-queue/index.mjs +7 -0
- package/dist/task-queue-dropdown-DW72ikDH.mjs +1356 -0
- package/dist/textarea/index.mjs +5 -0
- package/dist/textarea-CxE3YbC7.mjs +17 -0
- package/dist/textarea-QYRcDEpK.mjs +15 -0
- package/dist/theme/index.mjs +4 -0
- package/dist/theme-script-XBouzsNR.mjs +66 -0
- package/dist/to-api-format-C2xjQUcI.mjs +1506 -0
- package/dist/toast/index.mjs +3 -0
- package/dist/tooltip/index.mjs +4 -0
- package/dist/tooltip-Dd3ActSS.mjs +74 -0
- package/dist/typography/index.mjs +3 -0
- package/dist/typography-UA7ZZvgJ.mjs +200 -0
- package/dist/use-copy-to-clipboard-ki-WoTml.mjs +31 -0
- package/dist/use-stepper-BaToCYMs.mjs +2017 -0
- package/dist/{use-copy-to-clipboard-BfrpD6G8.mjs → use-toast-mdn_CqRY.mjs} +34 -27
- package/dist/utils/index.mjs +0 -1
- package/dist/utils-Bfgoe-Gm.mjs +20 -0
- package/dist/visually-hidden/index.mjs +3 -0
- package/dist/visuallyhidden-aaTUk4Yo.mjs +7 -0
- package/package.json +208 -8
- package/dist/components/index.mjs +0 -8
- package/dist/providers/index.mjs +0 -4
- package/dist/theme-script-DHyLk25i.mjs +0 -11128
- /package/dist/{close.icon-chkXPAUC.mjs → close.icon-CMNMoXM_.mjs} +0 -0
- /package/dist/{map-leaflet-imports-OKaoesjZ.mjs → map-leaflet-imports-CdzvEnzY.mjs} +0 -0
- /package/dist/{theme.provider-DpFLwtHe.mjs → theme.provider-DgGshapa.mjs} +0 -0
- /package/dist/{use-debounce-BYB-jPeX.mjs → use-debounce-DQ1tmxOL.mjs} +0 -0
|
@@ -0,0 +1,1506 @@
|
|
|
1
|
+
import { t as cn } from "./cn-DWCc1QRE.mjs";
|
|
2
|
+
import { t as Button } from "./button-C1wRfGtT.mjs";
|
|
3
|
+
import { t as Separator } from "./separator-T2ppyD-8.mjs";
|
|
4
|
+
import { t as Button$1 } from "./button-AzpnV-WB.mjs";
|
|
5
|
+
import { t as Icon } from "./icon-wrapper-9ticVbRL.mjs";
|
|
6
|
+
import { t as Calendar$1 } from "./calendar-DlIHeWb0.mjs";
|
|
7
|
+
import { t as Input } from "./input-fzXBheCN.mjs";
|
|
8
|
+
import { i as PopoverTrigger, r as PopoverContent, t as Popover } from "./popover-SQlKSz6L.mjs";
|
|
9
|
+
import { i as SelectItem, l as SelectTrigger, n as SelectContent, t as Select, u as SelectValue } from "./select-DVlEzD2W.mjs";
|
|
10
|
+
import { cva } from "class-variance-authority";
|
|
11
|
+
import { Calendar, CalendarIcon, Globe, X } from "lucide-react";
|
|
12
|
+
import * as React$1 from "react";
|
|
13
|
+
import { useCallback, useEffect, useId, useMemo, useRef, useState } from "react";
|
|
14
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
15
|
+
import { endOfDay, endOfMonth, endOfWeek, endOfYear, format, isSameDay, isSameYear, parse, startOfDay, startOfMonth, startOfWeek, startOfYear, subDays, subHours, subMinutes } from "date-fns";
|
|
16
|
+
import { formatInTimeZone, fromZonedTime, toDate, toZonedTime } from "date-fns-tz";
|
|
17
|
+
|
|
18
|
+
//#region src/components/features/calendar-date-picker/calendar-date-picker.tsx
|
|
19
|
+
const months = [
|
|
20
|
+
"January",
|
|
21
|
+
"February",
|
|
22
|
+
"March",
|
|
23
|
+
"April",
|
|
24
|
+
"May",
|
|
25
|
+
"June",
|
|
26
|
+
"July",
|
|
27
|
+
"August",
|
|
28
|
+
"September",
|
|
29
|
+
"October",
|
|
30
|
+
"November",
|
|
31
|
+
"December"
|
|
32
|
+
];
|
|
33
|
+
const multiSelectVariants = cva("flex font-normal shadow-none items-center justify-center whitespace-nowrap rounded-md text-sm text-foreground ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", {
|
|
34
|
+
variants: { variant: {
|
|
35
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
36
|
+
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
|
37
|
+
outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
|
38
|
+
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
39
|
+
ghost: "hover:bg-accent hover:text-accent-foreground text-background",
|
|
40
|
+
link: "text-primary underline-offset-4 hover:underline text-background"
|
|
41
|
+
} },
|
|
42
|
+
defaultVariants: { variant: "default" }
|
|
43
|
+
});
|
|
44
|
+
function CalendarDatePicker({ ref, id = "calendar-date-picker", className, triggerClassName, date, closeOnSelect = false, numberOfMonths = 2, yearsRange = 10, onDateSelect, variant, placeholder, excludePresets, customPresets, minDate, maxDate, disableFuture = false, disablePast = false, maxRange, ...props }) {
|
|
45
|
+
const [isPopoverOpen, setIsPopoverOpen] = React$1.useState(false);
|
|
46
|
+
const [selectedRange, setSelectedRange] = React$1.useState(numberOfMonths === 2 ? "This Year" : "Today");
|
|
47
|
+
const [monthFrom, setMonthFrom] = React$1.useState(date?.from);
|
|
48
|
+
const [yearFrom, setYearFrom] = React$1.useState(date?.from?.getFullYear());
|
|
49
|
+
const [monthTo, setMonthTo] = React$1.useState(numberOfMonths === 2 ? date?.to : date?.from);
|
|
50
|
+
const [yearTo, setYearTo] = React$1.useState(numberOfMonths === 2 ? date?.to?.getFullYear() : date?.from?.getFullYear());
|
|
51
|
+
const [highlightedPart, setHighlightedPart] = React$1.useState(null);
|
|
52
|
+
const [pendingDate, setPendingDate] = React$1.useState(date);
|
|
53
|
+
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
54
|
+
const today = /* @__PURE__ */ new Date();
|
|
55
|
+
const effectiveMinDate = React$1.useMemo(() => {
|
|
56
|
+
let min = minDate;
|
|
57
|
+
if (disablePast) {
|
|
58
|
+
const todayStart = startOfDay(today);
|
|
59
|
+
min = min ? min > todayStart ? min : todayStart : todayStart;
|
|
60
|
+
}
|
|
61
|
+
return min;
|
|
62
|
+
}, [
|
|
63
|
+
minDate,
|
|
64
|
+
disablePast,
|
|
65
|
+
today
|
|
66
|
+
]);
|
|
67
|
+
const effectiveMaxDate = React$1.useMemo(() => {
|
|
68
|
+
let max = maxDate;
|
|
69
|
+
if (disableFuture) {
|
|
70
|
+
const todayEnd = endOfDay(today);
|
|
71
|
+
max = max ? max < todayEnd ? max : todayEnd : todayEnd;
|
|
72
|
+
}
|
|
73
|
+
return max;
|
|
74
|
+
}, [
|
|
75
|
+
maxDate,
|
|
76
|
+
disableFuture,
|
|
77
|
+
today
|
|
78
|
+
]);
|
|
79
|
+
const isDateDisabled = React$1.useCallback((date) => {
|
|
80
|
+
if (effectiveMinDate && date < effectiveMinDate) return true;
|
|
81
|
+
if (effectiveMaxDate && date > effectiveMaxDate) return true;
|
|
82
|
+
return false;
|
|
83
|
+
}, [effectiveMinDate, effectiveMaxDate]);
|
|
84
|
+
const getDaysDifference = React$1.useCallback((from, to) => {
|
|
85
|
+
const timeDiff = Math.abs(to.getTime() - from.getTime());
|
|
86
|
+
return Math.ceil(timeDiff / (1e3 * 60 * 60 * 24));
|
|
87
|
+
}, []);
|
|
88
|
+
const isRangeValid = React$1.useCallback((from, to) => {
|
|
89
|
+
if (!maxRange) return true;
|
|
90
|
+
if (numberOfMonths === 1) return true;
|
|
91
|
+
return getDaysDifference(from, to) <= maxRange;
|
|
92
|
+
}, [
|
|
93
|
+
maxRange,
|
|
94
|
+
numberOfMonths,
|
|
95
|
+
getDaysDifference
|
|
96
|
+
]);
|
|
97
|
+
const adjustRangeToMaxRange = React$1.useCallback((from, to) => {
|
|
98
|
+
if (!maxRange || numberOfMonths === 1) return {
|
|
99
|
+
from,
|
|
100
|
+
to
|
|
101
|
+
};
|
|
102
|
+
if (getDaysDifference(from, to) <= maxRange) return {
|
|
103
|
+
from,
|
|
104
|
+
to
|
|
105
|
+
};
|
|
106
|
+
const adjustedTo = new Date(from.getTime() + maxRange * 24 * 60 * 60 * 1e3);
|
|
107
|
+
if (effectiveMaxDate && adjustedTo > effectiveMaxDate) {
|
|
108
|
+
const adjustedFrom = /* @__PURE__ */ new Date(to.getTime() - maxRange * 24 * 60 * 60 * 1e3);
|
|
109
|
+
if (effectiveMinDate && adjustedFrom < effectiveMinDate) return {
|
|
110
|
+
from,
|
|
111
|
+
to
|
|
112
|
+
};
|
|
113
|
+
return {
|
|
114
|
+
from: adjustedFrom,
|
|
115
|
+
to
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
from,
|
|
120
|
+
to: adjustedTo
|
|
121
|
+
};
|
|
122
|
+
}, [
|
|
123
|
+
maxRange,
|
|
124
|
+
numberOfMonths,
|
|
125
|
+
getDaysDifference,
|
|
126
|
+
effectiveMinDate,
|
|
127
|
+
effectiveMaxDate
|
|
128
|
+
]);
|
|
129
|
+
React$1.useEffect(() => {
|
|
130
|
+
setPendingDate(date);
|
|
131
|
+
}, [date]);
|
|
132
|
+
const handleClose = () => setIsPopoverOpen(false);
|
|
133
|
+
const handleTogglePopover = () => setIsPopoverOpen((prev) => !prev);
|
|
134
|
+
const handleClear = (e) => {
|
|
135
|
+
e.stopPropagation();
|
|
136
|
+
onDateSelect(void 0);
|
|
137
|
+
setPendingDate({
|
|
138
|
+
from: void 0,
|
|
139
|
+
to: void 0
|
|
140
|
+
});
|
|
141
|
+
setSelectedRange(null);
|
|
142
|
+
};
|
|
143
|
+
const handleApply = () => {
|
|
144
|
+
if (pendingDate?.from) onDateSelect({
|
|
145
|
+
from: pendingDate.from,
|
|
146
|
+
to: pendingDate.to || pendingDate.from
|
|
147
|
+
});
|
|
148
|
+
setIsPopoverOpen(false);
|
|
149
|
+
};
|
|
150
|
+
const handleReset = () => {
|
|
151
|
+
setPendingDate(date);
|
|
152
|
+
setMonthFrom(date?.from);
|
|
153
|
+
setYearFrom(date?.from?.getFullYear());
|
|
154
|
+
setMonthTo(numberOfMonths === 2 ? date?.to : date?.from);
|
|
155
|
+
setYearTo(numberOfMonths === 2 ? date?.to?.getFullYear() : date?.from?.getFullYear());
|
|
156
|
+
setSelectedRange(null);
|
|
157
|
+
};
|
|
158
|
+
const selectDateRange = (from, to, range) => {
|
|
159
|
+
const startDate = startOfDay(toDate(from, { timeZone }));
|
|
160
|
+
const endDate = numberOfMonths === 2 ? endOfDay(toDate(to, { timeZone })) : startDate;
|
|
161
|
+
if (isDateDisabled(startDate) || isDateDisabled(endDate)) return;
|
|
162
|
+
let finalDates = {
|
|
163
|
+
from: startDate,
|
|
164
|
+
to: endDate
|
|
165
|
+
};
|
|
166
|
+
if (numberOfMonths === 2 && !isRangeValid(startDate, endDate)) {
|
|
167
|
+
finalDates = adjustRangeToMaxRange(startDate, endDate);
|
|
168
|
+
if (!isRangeValid(finalDates.from, finalDates.to)) return;
|
|
169
|
+
}
|
|
170
|
+
setPendingDate({
|
|
171
|
+
from: finalDates.from,
|
|
172
|
+
to: finalDates.to
|
|
173
|
+
});
|
|
174
|
+
setSelectedRange(range);
|
|
175
|
+
setMonthFrom(finalDates.from);
|
|
176
|
+
setYearFrom(finalDates.from.getFullYear());
|
|
177
|
+
setMonthTo(finalDates.to);
|
|
178
|
+
setYearTo(finalDates.to.getFullYear());
|
|
179
|
+
if (closeOnSelect) {
|
|
180
|
+
onDateSelect({
|
|
181
|
+
from: finalDates.from,
|
|
182
|
+
to: finalDates.to
|
|
183
|
+
});
|
|
184
|
+
setIsPopoverOpen(false);
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
const handleDateSelect = (range) => {
|
|
188
|
+
if (range) {
|
|
189
|
+
let from = startOfDay(toDate(range.from, { timeZone }));
|
|
190
|
+
let to = range.to ? endOfDay(toDate(range.to, { timeZone })) : from;
|
|
191
|
+
if (numberOfMonths === 1) if (range.from !== pendingDate?.from) to = from;
|
|
192
|
+
else from = startOfDay(toDate(range.to, { timeZone }));
|
|
193
|
+
if (isDateDisabled(from) || isDateDisabled(to)) return;
|
|
194
|
+
let finalDates = {
|
|
195
|
+
from,
|
|
196
|
+
to
|
|
197
|
+
};
|
|
198
|
+
if (numberOfMonths === 2 && !isRangeValid(from, to)) {
|
|
199
|
+
finalDates = adjustRangeToMaxRange(from, to);
|
|
200
|
+
if (!isRangeValid(finalDates.from, finalDates.to)) return;
|
|
201
|
+
}
|
|
202
|
+
setPendingDate({
|
|
203
|
+
from: finalDates.from,
|
|
204
|
+
to: finalDates.to
|
|
205
|
+
});
|
|
206
|
+
setMonthFrom(finalDates.from);
|
|
207
|
+
setYearFrom(finalDates.from.getFullYear());
|
|
208
|
+
setMonthTo(finalDates.to);
|
|
209
|
+
setYearTo(finalDates.to.getFullYear());
|
|
210
|
+
if (closeOnSelect) {
|
|
211
|
+
onDateSelect({
|
|
212
|
+
from: finalDates.from,
|
|
213
|
+
to: finalDates.to
|
|
214
|
+
});
|
|
215
|
+
setIsPopoverOpen(false);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
setSelectedRange(null);
|
|
219
|
+
};
|
|
220
|
+
const handleMonthChange = (newMonthIndex, part) => {
|
|
221
|
+
setSelectedRange(null);
|
|
222
|
+
if (part === "from") {
|
|
223
|
+
if (yearFrom !== void 0) {
|
|
224
|
+
if (newMonthIndex < 0 || newMonthIndex > yearsRange + 1) return;
|
|
225
|
+
const newMonth = new Date(yearFrom, newMonthIndex, 1);
|
|
226
|
+
const from = numberOfMonths === 2 ? startOfMonth(toDate(newMonth, { timeZone })) : pendingDate?.from ? new Date(pendingDate.from.getFullYear(), newMonth.getMonth(), pendingDate.from.getDate()) : newMonth;
|
|
227
|
+
const to = numberOfMonths === 2 ? pendingDate?.to ? endOfDay(toDate(pendingDate.to, { timeZone })) : endOfMonth(toDate(newMonth, { timeZone })) : from;
|
|
228
|
+
if (from <= to) {
|
|
229
|
+
setPendingDate({
|
|
230
|
+
from,
|
|
231
|
+
to
|
|
232
|
+
});
|
|
233
|
+
setMonthFrom(newMonth);
|
|
234
|
+
setMonthTo(pendingDate?.to);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
} else if (yearTo !== void 0) {
|
|
238
|
+
if (newMonthIndex < 0 || newMonthIndex > yearsRange + 1) return;
|
|
239
|
+
const newMonth = new Date(yearTo, newMonthIndex, 1);
|
|
240
|
+
const from = pendingDate?.from ? startOfDay(toDate(pendingDate.from, { timeZone })) : startOfMonth(toDate(newMonth, { timeZone }));
|
|
241
|
+
const to = numberOfMonths === 2 ? endOfMonth(toDate(newMonth, { timeZone })) : from;
|
|
242
|
+
if (from <= to) {
|
|
243
|
+
setPendingDate({
|
|
244
|
+
from,
|
|
245
|
+
to
|
|
246
|
+
});
|
|
247
|
+
setMonthTo(newMonth);
|
|
248
|
+
setMonthFrom(pendingDate?.from);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
const years = Array.from({ length: yearsRange + 1 }, (_, i) => today.getFullYear() - yearsRange / 2 + i);
|
|
253
|
+
const handleYearChange = (newYear, part) => {
|
|
254
|
+
setSelectedRange(null);
|
|
255
|
+
if (part === "from") {
|
|
256
|
+
if (years.includes(newYear)) {
|
|
257
|
+
const newMonth = monthFrom ? new Date(newYear, monthFrom ? monthFrom.getMonth() : 0, 1) : new Date(newYear, 0, 1);
|
|
258
|
+
const from = numberOfMonths === 2 ? startOfMonth(toDate(newMonth, { timeZone })) : pendingDate?.from ? new Date(newYear, newMonth.getMonth(), pendingDate.from.getDate()) : newMonth;
|
|
259
|
+
const to = numberOfMonths === 2 ? pendingDate?.to ? endOfDay(toDate(pendingDate.to, { timeZone })) : endOfMonth(toDate(newMonth, { timeZone })) : from;
|
|
260
|
+
if (from <= to) {
|
|
261
|
+
setPendingDate({
|
|
262
|
+
from,
|
|
263
|
+
to
|
|
264
|
+
});
|
|
265
|
+
setYearFrom(newYear);
|
|
266
|
+
setMonthFrom(newMonth);
|
|
267
|
+
setYearTo(pendingDate?.to?.getFullYear());
|
|
268
|
+
setMonthTo(pendingDate?.to);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
} else if (years.includes(newYear)) {
|
|
272
|
+
const newMonth = monthTo ? new Date(newYear, monthTo.getMonth(), 1) : new Date(newYear, 0, 1);
|
|
273
|
+
const from = pendingDate?.from ? startOfDay(toDate(pendingDate.from, { timeZone })) : startOfMonth(toDate(newMonth, { timeZone }));
|
|
274
|
+
const to = numberOfMonths === 2 ? endOfMonth(toDate(newMonth, { timeZone })) : from;
|
|
275
|
+
if (from <= to) {
|
|
276
|
+
setPendingDate({
|
|
277
|
+
from,
|
|
278
|
+
to
|
|
279
|
+
});
|
|
280
|
+
setYearTo(newYear);
|
|
281
|
+
setMonthTo(newMonth);
|
|
282
|
+
setYearFrom(pendingDate?.from?.getFullYear());
|
|
283
|
+
setMonthFrom(pendingDate?.from);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
const defaultDateRangePresets = [
|
|
288
|
+
{
|
|
289
|
+
key: "today",
|
|
290
|
+
label: "Today",
|
|
291
|
+
start: today,
|
|
292
|
+
end: today
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
key: "yesterday",
|
|
296
|
+
label: "Yesterday",
|
|
297
|
+
start: subDays(today, 1),
|
|
298
|
+
end: subDays(today, 1)
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
key: "thisWeek",
|
|
302
|
+
label: "This Week",
|
|
303
|
+
start: startOfWeek(today, { weekStartsOn: 1 }),
|
|
304
|
+
end: endOfWeek(today, { weekStartsOn: 1 })
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
key: "lastWeek",
|
|
308
|
+
label: "Last Week",
|
|
309
|
+
start: subDays(startOfWeek(today, { weekStartsOn: 1 }), 7),
|
|
310
|
+
end: subDays(endOfWeek(today, { weekStartsOn: 1 }), 7)
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
key: "last7Days",
|
|
314
|
+
label: "Last 7 Days",
|
|
315
|
+
start: subDays(today, 6),
|
|
316
|
+
end: today
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
key: "thisMonth",
|
|
320
|
+
label: "This Month",
|
|
321
|
+
start: startOfMonth(today),
|
|
322
|
+
end: endOfMonth(today)
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
key: "lastMonth",
|
|
326
|
+
label: "Last Month",
|
|
327
|
+
start: startOfMonth(subDays(today, today.getDate())),
|
|
328
|
+
end: endOfMonth(subDays(today, today.getDate()))
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
key: "thisYear",
|
|
332
|
+
label: "This Year",
|
|
333
|
+
start: startOfYear(today),
|
|
334
|
+
end: endOfYear(today)
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
key: "lastYear",
|
|
338
|
+
label: "Last Year",
|
|
339
|
+
start: startOfYear(subDays(today, 365)),
|
|
340
|
+
end: endOfYear(subDays(today, 365))
|
|
341
|
+
}
|
|
342
|
+
];
|
|
343
|
+
const allDateRangePresets = customPresets || defaultDateRangePresets;
|
|
344
|
+
const dateRanges = React$1.useMemo(() => {
|
|
345
|
+
return allDateRangePresets.filter((preset) => {
|
|
346
|
+
if (excludePresets?.includes(preset.key)) return false;
|
|
347
|
+
const startValid = !isDateDisabled(preset.start);
|
|
348
|
+
const endValid = !isDateDisabled(preset.end);
|
|
349
|
+
const rangeValid = numberOfMonths === 1 || isRangeValid(preset.start, preset.end);
|
|
350
|
+
return startValid && endValid && rangeValid;
|
|
351
|
+
});
|
|
352
|
+
}, [
|
|
353
|
+
isDateDisabled,
|
|
354
|
+
excludePresets,
|
|
355
|
+
numberOfMonths,
|
|
356
|
+
isRangeValid
|
|
357
|
+
]);
|
|
358
|
+
const handleMouseOver = (part) => {
|
|
359
|
+
setHighlightedPart(part);
|
|
360
|
+
};
|
|
361
|
+
const handleMouseLeave = () => {
|
|
362
|
+
setHighlightedPart(null);
|
|
363
|
+
};
|
|
364
|
+
const handleWheel = (event) => {
|
|
365
|
+
event.preventDefault();
|
|
366
|
+
setSelectedRange(null);
|
|
367
|
+
if (highlightedPart === "firstDay") {
|
|
368
|
+
const newDate = new Date(pendingDate?.from);
|
|
369
|
+
const increment = event.deltaY > 0 ? -1 : 1;
|
|
370
|
+
newDate.setDate(newDate.getDate() + increment);
|
|
371
|
+
if (newDate <= pendingDate?.to) {
|
|
372
|
+
numberOfMonths === 2 ? setPendingDate({
|
|
373
|
+
from: newDate,
|
|
374
|
+
to: new Date(pendingDate?.to)
|
|
375
|
+
}) : setPendingDate({
|
|
376
|
+
from: newDate,
|
|
377
|
+
to: newDate
|
|
378
|
+
});
|
|
379
|
+
setMonthFrom(newDate);
|
|
380
|
+
} else if (newDate > pendingDate?.to && numberOfMonths === 1) {
|
|
381
|
+
setPendingDate({
|
|
382
|
+
from: newDate,
|
|
383
|
+
to: newDate
|
|
384
|
+
});
|
|
385
|
+
setMonthFrom(newDate);
|
|
386
|
+
}
|
|
387
|
+
} else if (highlightedPart === "firstMonth") handleMonthChange((monthFrom ? monthFrom.getMonth() : 0) + (event.deltaY > 0 ? -1 : 1), "from");
|
|
388
|
+
else if (highlightedPart === "firstYear" && yearFrom !== void 0) handleYearChange(yearFrom + (event.deltaY > 0 ? -1 : 1), "from");
|
|
389
|
+
else if (highlightedPart === "secondDay") {
|
|
390
|
+
const newDate = new Date(pendingDate?.to);
|
|
391
|
+
const increment = event.deltaY > 0 ? -1 : 1;
|
|
392
|
+
newDate.setDate(newDate.getDate() + increment);
|
|
393
|
+
if (newDate >= pendingDate?.from) {
|
|
394
|
+
setPendingDate({
|
|
395
|
+
from: new Date(pendingDate?.from),
|
|
396
|
+
to: newDate
|
|
397
|
+
});
|
|
398
|
+
setMonthTo(newDate);
|
|
399
|
+
}
|
|
400
|
+
} else if (highlightedPart === "secondMonth") handleMonthChange((monthTo ? monthTo.getMonth() : 0) + (event.deltaY > 0 ? -1 : 1), "to");
|
|
401
|
+
else if (highlightedPart === "secondYear" && yearTo !== void 0) handleYearChange(yearTo + (event.deltaY > 0 ? -1 : 1), "to");
|
|
402
|
+
};
|
|
403
|
+
React$1.useEffect(() => {
|
|
404
|
+
const elements = [
|
|
405
|
+
document.getElementById(`firstDay-${id}`),
|
|
406
|
+
document.getElementById(`firstMonth-${id}`),
|
|
407
|
+
document.getElementById(`firstYear-${id}`),
|
|
408
|
+
document.getElementById(`secondDay-${id}`),
|
|
409
|
+
document.getElementById(`secondMonth-${id}`),
|
|
410
|
+
document.getElementById(`secondYear-${id}`)
|
|
411
|
+
];
|
|
412
|
+
const addPassiveEventListener = (element) => {
|
|
413
|
+
if (element) element.addEventListener("wheel", handleWheel, { passive: false });
|
|
414
|
+
};
|
|
415
|
+
elements.forEach(addPassiveEventListener);
|
|
416
|
+
return () => {
|
|
417
|
+
elements.forEach((element) => {
|
|
418
|
+
if (element) element.removeEventListener("wheel", handleWheel);
|
|
419
|
+
});
|
|
420
|
+
};
|
|
421
|
+
}, [highlightedPart, pendingDate]);
|
|
422
|
+
const formatWithTz = (date, fmt) => formatInTimeZone(date, timeZone, fmt);
|
|
423
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("style", { children: `
|
|
424
|
+
.date-part {
|
|
425
|
+
touch-action: none;
|
|
426
|
+
}
|
|
427
|
+
` }), /* @__PURE__ */ jsxs(Popover, {
|
|
428
|
+
open: isPopoverOpen,
|
|
429
|
+
onOpenChange: setIsPopoverOpen,
|
|
430
|
+
children: [/* @__PURE__ */ jsx(PopoverTrigger, {
|
|
431
|
+
asChild: true,
|
|
432
|
+
children: /* @__PURE__ */ jsx(Button$1, {
|
|
433
|
+
id: "date",
|
|
434
|
+
ref,
|
|
435
|
+
...props,
|
|
436
|
+
className: cn("w-full", triggerClassName, multiSelectVariants({
|
|
437
|
+
variant,
|
|
438
|
+
className
|
|
439
|
+
})),
|
|
440
|
+
onClick: handleTogglePopover,
|
|
441
|
+
suppressHydrationWarning: true,
|
|
442
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
443
|
+
className: "flex w-full items-center justify-between gap-2",
|
|
444
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
445
|
+
className: "flex items-center gap-2",
|
|
446
|
+
children: [/* @__PURE__ */ jsx(CalendarIcon, { className: "text-muted-foreground h-4 w-4" }), /* @__PURE__ */ jsx("span", { children: date?.from ? date.to ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
447
|
+
/* @__PURE__ */ jsx("span", {
|
|
448
|
+
id: `firstDay-${id}`,
|
|
449
|
+
className: cn("date-part", highlightedPart === "firstDay" && "font-bold underline"),
|
|
450
|
+
onMouseOver: () => handleMouseOver("firstDay"),
|
|
451
|
+
onMouseLeave: handleMouseLeave,
|
|
452
|
+
children: formatWithTz(date.from, "dd")
|
|
453
|
+
}),
|
|
454
|
+
" ",
|
|
455
|
+
/* @__PURE__ */ jsx("span", {
|
|
456
|
+
id: `firstMonth-${id}`,
|
|
457
|
+
className: cn("date-part", highlightedPart === "firstMonth" && "font-bold underline"),
|
|
458
|
+
onMouseOver: () => handleMouseOver("firstMonth"),
|
|
459
|
+
onMouseLeave: handleMouseLeave,
|
|
460
|
+
children: formatWithTz(date.from, "LLL")
|
|
461
|
+
}),
|
|
462
|
+
",",
|
|
463
|
+
" ",
|
|
464
|
+
/* @__PURE__ */ jsx("span", {
|
|
465
|
+
id: `firstYear-${id}`,
|
|
466
|
+
className: cn("date-part", highlightedPart === "firstYear" && "font-bold underline"),
|
|
467
|
+
onMouseOver: () => handleMouseOver("firstYear"),
|
|
468
|
+
onMouseLeave: handleMouseLeave,
|
|
469
|
+
children: formatWithTz(date.from, "y")
|
|
470
|
+
}),
|
|
471
|
+
numberOfMonths === 2 && /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
472
|
+
" - ",
|
|
473
|
+
/* @__PURE__ */ jsx("span", {
|
|
474
|
+
id: `secondDay-${id}`,
|
|
475
|
+
className: cn("date-part", highlightedPart === "secondDay" && "font-bold underline"),
|
|
476
|
+
onMouseOver: () => handleMouseOver("secondDay"),
|
|
477
|
+
onMouseLeave: handleMouseLeave,
|
|
478
|
+
children: formatWithTz(date.to, "dd")
|
|
479
|
+
}),
|
|
480
|
+
" ",
|
|
481
|
+
/* @__PURE__ */ jsx("span", {
|
|
482
|
+
id: `secondMonth-${id}`,
|
|
483
|
+
className: cn("date-part", highlightedPart === "secondMonth" && "font-bold underline"),
|
|
484
|
+
onMouseOver: () => handleMouseOver("secondMonth"),
|
|
485
|
+
onMouseLeave: handleMouseLeave,
|
|
486
|
+
children: formatWithTz(date.to, "LLL")
|
|
487
|
+
}),
|
|
488
|
+
",",
|
|
489
|
+
" ",
|
|
490
|
+
/* @__PURE__ */ jsx("span", {
|
|
491
|
+
id: `secondYear-${id}`,
|
|
492
|
+
className: cn("date-part", highlightedPart === "secondYear" && "font-bold underline"),
|
|
493
|
+
onMouseOver: () => handleMouseOver("secondYear"),
|
|
494
|
+
onMouseLeave: handleMouseLeave,
|
|
495
|
+
children: formatWithTz(date.to, "y")
|
|
496
|
+
})
|
|
497
|
+
] })
|
|
498
|
+
] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
499
|
+
/* @__PURE__ */ jsx("span", {
|
|
500
|
+
id: "day",
|
|
501
|
+
className: cn("date-part", highlightedPart === "day" && "font-bold underline"),
|
|
502
|
+
onMouseOver: () => handleMouseOver("day"),
|
|
503
|
+
onMouseLeave: handleMouseLeave,
|
|
504
|
+
children: formatWithTz(date.from, "dd")
|
|
505
|
+
}),
|
|
506
|
+
" ",
|
|
507
|
+
/* @__PURE__ */ jsx("span", {
|
|
508
|
+
id: "month",
|
|
509
|
+
className: cn("date-part", highlightedPart === "month" && "font-bold underline"),
|
|
510
|
+
onMouseOver: () => handleMouseOver("month"),
|
|
511
|
+
onMouseLeave: handleMouseLeave,
|
|
512
|
+
children: formatWithTz(date.from, "LLL")
|
|
513
|
+
}),
|
|
514
|
+
",",
|
|
515
|
+
" ",
|
|
516
|
+
/* @__PURE__ */ jsx("span", {
|
|
517
|
+
id: "year",
|
|
518
|
+
className: cn("date-part", highlightedPart === "year" && "font-bold underline"),
|
|
519
|
+
onMouseOver: () => handleMouseOver("year"),
|
|
520
|
+
onMouseLeave: handleMouseLeave,
|
|
521
|
+
children: formatWithTz(date.from, "y")
|
|
522
|
+
})
|
|
523
|
+
] }) : /* @__PURE__ */ jsx("span", {
|
|
524
|
+
className: "text-muted-foreground",
|
|
525
|
+
children: placeholder || "Pick a date"
|
|
526
|
+
}) })]
|
|
527
|
+
}), date?.from && /* @__PURE__ */ jsxs("div", {
|
|
528
|
+
onClick: handleClear,
|
|
529
|
+
className: "text-muted-foreground hover:text-primary size-4 p-0 hover:bg-transparent",
|
|
530
|
+
children: [/* @__PURE__ */ jsx(X, { size: 14 }), /* @__PURE__ */ jsx("span", {
|
|
531
|
+
className: "sr-only",
|
|
532
|
+
children: "Clear date"
|
|
533
|
+
})]
|
|
534
|
+
})]
|
|
535
|
+
})
|
|
536
|
+
})
|
|
537
|
+
}), isPopoverOpen && /* @__PURE__ */ jsx(PopoverContent, {
|
|
538
|
+
className: "w-auto",
|
|
539
|
+
align: "center",
|
|
540
|
+
avoidCollisions: false,
|
|
541
|
+
onInteractOutside: handleClose,
|
|
542
|
+
onEscapeKeyDown: handleClose,
|
|
543
|
+
style: {
|
|
544
|
+
maxHeight: "var(--radix-popover-content-available-height)",
|
|
545
|
+
overflowY: "auto"
|
|
546
|
+
},
|
|
547
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
548
|
+
className: "flex",
|
|
549
|
+
children: [numberOfMonths === 2 && /* @__PURE__ */ jsx("div", {
|
|
550
|
+
className: "border-foreground/10 hidden flex-col gap-1 border-r pr-4 text-left md:flex",
|
|
551
|
+
children: dateRanges.map(({ key, label, start, end }) => /* @__PURE__ */ jsx(Button$1, {
|
|
552
|
+
variant: "ghost",
|
|
553
|
+
size: "sm",
|
|
554
|
+
className: cn("hover:bg-primary/90 hover:text-background justify-start", selectedRange === label && "bg-primary text-background hover:bg-primary/90 hover:text-background"),
|
|
555
|
+
onClick: () => {
|
|
556
|
+
selectDateRange(start, end, label);
|
|
557
|
+
setMonthFrom(start);
|
|
558
|
+
setYearFrom(start.getFullYear());
|
|
559
|
+
setMonthTo(end);
|
|
560
|
+
setYearTo(end.getFullYear());
|
|
561
|
+
},
|
|
562
|
+
children: label
|
|
563
|
+
}, key))
|
|
564
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
565
|
+
className: "flex flex-col",
|
|
566
|
+
children: [
|
|
567
|
+
/* @__PURE__ */ jsxs("div", {
|
|
568
|
+
className: "flex items-center gap-4",
|
|
569
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
570
|
+
className: "ml-3 flex gap-2",
|
|
571
|
+
children: [/* @__PURE__ */ jsxs(Select, {
|
|
572
|
+
onValueChange: (value) => {
|
|
573
|
+
handleMonthChange(months.indexOf(value), "from");
|
|
574
|
+
setSelectedRange(null);
|
|
575
|
+
},
|
|
576
|
+
value: monthFrom ? months[monthFrom.getMonth()] : void 0,
|
|
577
|
+
children: [/* @__PURE__ */ jsx(SelectTrigger, {
|
|
578
|
+
className: "hover:bg-accent hover:text-accent-foreground hidden w-[122px] font-medium focus:ring-0 focus:ring-offset-0 sm:flex",
|
|
579
|
+
children: /* @__PURE__ */ jsx(SelectValue, { placeholder: "Month" })
|
|
580
|
+
}), /* @__PURE__ */ jsx(SelectContent, { children: months.map((month, idx) => /* @__PURE__ */ jsx(SelectItem, {
|
|
581
|
+
value: month,
|
|
582
|
+
children: month
|
|
583
|
+
}, idx)) })]
|
|
584
|
+
}), /* @__PURE__ */ jsxs(Select, {
|
|
585
|
+
onValueChange: (value) => {
|
|
586
|
+
handleYearChange(Number(value), "from");
|
|
587
|
+
setSelectedRange(null);
|
|
588
|
+
},
|
|
589
|
+
value: yearFrom ? yearFrom.toString() : void 0,
|
|
590
|
+
children: [/* @__PURE__ */ jsx(SelectTrigger, {
|
|
591
|
+
className: "hover:bg-accent hover:text-accent-foreground hidden w-[122px] font-medium focus:ring-0 focus:ring-offset-0 sm:flex",
|
|
592
|
+
children: /* @__PURE__ */ jsx(SelectValue, { placeholder: "Year" })
|
|
593
|
+
}), /* @__PURE__ */ jsx(SelectContent, { children: years.map((year, idx) => /* @__PURE__ */ jsx(SelectItem, {
|
|
594
|
+
value: year.toString(),
|
|
595
|
+
children: year
|
|
596
|
+
}, idx)) })]
|
|
597
|
+
})]
|
|
598
|
+
}), numberOfMonths === 2 && /* @__PURE__ */ jsxs("div", {
|
|
599
|
+
className: "flex gap-2",
|
|
600
|
+
children: [/* @__PURE__ */ jsxs(Select, {
|
|
601
|
+
onValueChange: (value) => {
|
|
602
|
+
handleMonthChange(months.indexOf(value), "to");
|
|
603
|
+
setSelectedRange(null);
|
|
604
|
+
},
|
|
605
|
+
value: monthTo ? months[monthTo.getMonth()] : void 0,
|
|
606
|
+
children: [/* @__PURE__ */ jsx(SelectTrigger, {
|
|
607
|
+
className: "hover:bg-accent hover:text-accent-foreground hidden w-[122px] font-medium focus:ring-0 focus:ring-offset-0 sm:flex",
|
|
608
|
+
children: /* @__PURE__ */ jsx(SelectValue, { placeholder: "Month" })
|
|
609
|
+
}), /* @__PURE__ */ jsx(SelectContent, { children: months.map((month, idx) => /* @__PURE__ */ jsx(SelectItem, {
|
|
610
|
+
value: month,
|
|
611
|
+
children: month
|
|
612
|
+
}, idx)) })]
|
|
613
|
+
}), /* @__PURE__ */ jsxs(Select, {
|
|
614
|
+
onValueChange: (value) => {
|
|
615
|
+
handleYearChange(Number(value), "to");
|
|
616
|
+
setSelectedRange(null);
|
|
617
|
+
},
|
|
618
|
+
value: yearTo ? yearTo.toString() : void 0,
|
|
619
|
+
children: [/* @__PURE__ */ jsx(SelectTrigger, {
|
|
620
|
+
className: "hover:bg-accent hover:text-accent-foreground hidden w-[122px] font-medium focus:ring-0 focus:ring-offset-0 sm:flex",
|
|
621
|
+
children: /* @__PURE__ */ jsx(SelectValue, { placeholder: "Year" })
|
|
622
|
+
}), /* @__PURE__ */ jsx(SelectContent, { children: years.map((year, idx) => /* @__PURE__ */ jsx(SelectItem, {
|
|
623
|
+
value: year.toString(),
|
|
624
|
+
children: year
|
|
625
|
+
}, idx)) })]
|
|
626
|
+
})]
|
|
627
|
+
})]
|
|
628
|
+
}),
|
|
629
|
+
/* @__PURE__ */ jsx("div", {
|
|
630
|
+
className: "flex",
|
|
631
|
+
children: /* @__PURE__ */ jsx(Calendar$1, {
|
|
632
|
+
mode: "range",
|
|
633
|
+
defaultMonth: monthFrom,
|
|
634
|
+
month: monthFrom,
|
|
635
|
+
onMonthChange: setMonthFrom,
|
|
636
|
+
selected: pendingDate,
|
|
637
|
+
onSelect: handleDateSelect,
|
|
638
|
+
numberOfMonths,
|
|
639
|
+
showOutsideDays: false,
|
|
640
|
+
disabled: isDateDisabled,
|
|
641
|
+
fromDate: effectiveMinDate,
|
|
642
|
+
toDate: effectiveMaxDate,
|
|
643
|
+
className
|
|
644
|
+
})
|
|
645
|
+
}),
|
|
646
|
+
!closeOnSelect && /* @__PURE__ */ jsxs("div", {
|
|
647
|
+
className: "flex justify-end gap-2 border-t p-3",
|
|
648
|
+
children: [/* @__PURE__ */ jsx(Button$1, {
|
|
649
|
+
variant: "outline",
|
|
650
|
+
size: "sm",
|
|
651
|
+
onClick: handleReset,
|
|
652
|
+
type: "button",
|
|
653
|
+
children: "Reset"
|
|
654
|
+
}), /* @__PURE__ */ jsx(Button$1, {
|
|
655
|
+
size: "sm",
|
|
656
|
+
onClick: handleApply,
|
|
657
|
+
type: "button",
|
|
658
|
+
children: "Apply"
|
|
659
|
+
})]
|
|
660
|
+
})
|
|
661
|
+
]
|
|
662
|
+
})]
|
|
663
|
+
})
|
|
664
|
+
})]
|
|
665
|
+
})] });
|
|
666
|
+
}
|
|
667
|
+
CalendarDatePicker.displayName = "CalendarDatePicker";
|
|
668
|
+
|
|
669
|
+
//#endregion
|
|
670
|
+
//#region src/components/features/time-range-picker/utils/timezone.ts
|
|
671
|
+
/**
|
|
672
|
+
* Get the UTC offset string for a timezone
|
|
673
|
+
* @example getTimezoneOffset('Asia/Jakarta') => '+07:00'
|
|
674
|
+
*/
|
|
675
|
+
function getTimezoneOffset(timezone) {
|
|
676
|
+
try {
|
|
677
|
+
const now = /* @__PURE__ */ new Date();
|
|
678
|
+
const offsetPart = new Intl.DateTimeFormat("en-US", {
|
|
679
|
+
timeZone: timezone,
|
|
680
|
+
timeZoneName: "shortOffset"
|
|
681
|
+
}).formatToParts(now).find((p) => p.type === "timeZoneName");
|
|
682
|
+
if (offsetPart) {
|
|
683
|
+
const match = offsetPart.value.match(/GMT([+-])(\d+)(?::(\d+))?/);
|
|
684
|
+
if (match) return `${match[1]}${match[2].padStart(2, "0")}:${match[3]?.padStart(2, "0") ?? "00"}`;
|
|
685
|
+
if (offsetPart.value === "GMT") return "+00:00";
|
|
686
|
+
}
|
|
687
|
+
} catch {}
|
|
688
|
+
return "+00:00";
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* Format a timezone for display
|
|
692
|
+
* @example formatTimezoneLabel('Asia/Jakarta') => 'Asia/Jakarta (UTC+07:00)'
|
|
693
|
+
*/
|
|
694
|
+
function formatTimezoneLabel(timezone) {
|
|
695
|
+
const offset = getTimezoneOffset(timezone);
|
|
696
|
+
return `${timezone.replace(/_/g, " ")} (UTC${offset})`;
|
|
697
|
+
}
|
|
698
|
+
/**
|
|
699
|
+
* Create a TimezoneOption from a timezone string
|
|
700
|
+
*/
|
|
701
|
+
function createTimezoneOption(timezone) {
|
|
702
|
+
return {
|
|
703
|
+
value: timezone,
|
|
704
|
+
label: formatTimezoneLabel(timezone),
|
|
705
|
+
offset: getTimezoneOffset(timezone)
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
709
|
+
* Get default timezone options (user's timezone + UTC)
|
|
710
|
+
*/
|
|
711
|
+
function getDefaultTimezoneOptions(userTimezone) {
|
|
712
|
+
const options = [];
|
|
713
|
+
if (userTimezone && userTimezone !== "UTC") options.push(createTimezoneOption(userTimezone));
|
|
714
|
+
options.push({
|
|
715
|
+
value: "UTC",
|
|
716
|
+
label: "UTC (+00:00)",
|
|
717
|
+
offset: "+00:00"
|
|
718
|
+
});
|
|
719
|
+
return options;
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* Get short offset display (e.g., "UTC+7")
|
|
723
|
+
*/
|
|
724
|
+
function getShortTimezoneDisplay(timezone) {
|
|
725
|
+
const offset = getTimezoneOffset(timezone);
|
|
726
|
+
const match = offset.match(/([+-])(\d+):(\d+)/);
|
|
727
|
+
if (match) {
|
|
728
|
+
const sign = match[1];
|
|
729
|
+
const hours = Number.parseInt(match[2], 10);
|
|
730
|
+
const minutes = match[3];
|
|
731
|
+
if (minutes === "00") return `UTC${sign}${hours}`;
|
|
732
|
+
return `UTC${sign}${hours}:${minutes}`;
|
|
733
|
+
}
|
|
734
|
+
return `UTC${offset}`;
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* Get the browser's timezone
|
|
738
|
+
*/
|
|
739
|
+
function getBrowserTimezone() {
|
|
740
|
+
try {
|
|
741
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
742
|
+
} catch {
|
|
743
|
+
return "UTC";
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
/**
|
|
747
|
+
* Convert a UTC Date to a local datetime-local input string in the given timezone
|
|
748
|
+
* @example utcToLocalInputString(new Date('2026-01-16T04:00:00Z'), 'Asia/Jakarta') => '2026-01-16T11:00'
|
|
749
|
+
*/
|
|
750
|
+
function utcToLocalInputString(utcDate, timezone) {
|
|
751
|
+
return format(toZonedTime(utcDate, timezone), "yyyy-MM-dd'T'HH:mm");
|
|
752
|
+
}
|
|
753
|
+
/**
|
|
754
|
+
* Convert a local datetime-local input string to a UTC Date
|
|
755
|
+
* @example localInputStringToUtc('2026-01-16T11:00', 'Asia/Jakarta') => Date('2026-01-16T04:00:00Z')
|
|
756
|
+
*/
|
|
757
|
+
function localInputStringToUtc(localString, timezone) {
|
|
758
|
+
return fromZonedTime(parse(localString, "yyyy-MM-dd'T'HH:mm", /* @__PURE__ */ new Date()), timezone);
|
|
759
|
+
}
|
|
760
|
+
/**
|
|
761
|
+
* Convert a UTC ISO string to a Date for display in user's timezone
|
|
762
|
+
*/
|
|
763
|
+
function utcStringToZonedDate(utcString, timezone) {
|
|
764
|
+
return toZonedTime(new Date(utcString), timezone);
|
|
765
|
+
}
|
|
766
|
+
/**
|
|
767
|
+
* Convert a zoned Date to UTC ISO string
|
|
768
|
+
*/
|
|
769
|
+
function zonedDateToUtcString(zonedDate, timezone) {
|
|
770
|
+
return fromZonedTime(zonedDate, timezone).toISOString();
|
|
771
|
+
}
|
|
772
|
+
/**
|
|
773
|
+
* Format a UTC ISO string for display in user's timezone
|
|
774
|
+
* @example formatUtcForDisplay('2026-01-16T04:00:00Z', 'Asia/Jakarta') => 'Jan 16, 2026 11:00'
|
|
775
|
+
*/
|
|
776
|
+
function formatUtcForDisplay(utcString, timezone, formatString = "MMM d, yyyy HH:mm") {
|
|
777
|
+
return format(utcStringToZonedDate(utcString, timezone), formatString);
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
//#endregion
|
|
781
|
+
//#region src/components/features/time-range-picker/components/absolute-range-panel.tsx
|
|
782
|
+
/**
|
|
783
|
+
* Parse a zoned date into separate date and time parts
|
|
784
|
+
*/
|
|
785
|
+
function parseDateTimeParts(zonedDate) {
|
|
786
|
+
return {
|
|
787
|
+
date: zonedDate,
|
|
788
|
+
time: `${zonedDate.getHours().toString().padStart(2, "0")}:${zonedDate.getMinutes().toString().padStart(2, "0")}`
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
/**
|
|
792
|
+
* Combine a date and time string into a single Date
|
|
793
|
+
*/
|
|
794
|
+
function combineDateAndTime(date, time) {
|
|
795
|
+
const [hours, minutes] = time.split(":").map(Number);
|
|
796
|
+
const combined = new Date(date);
|
|
797
|
+
combined.setHours(hours || 0, minutes || 0, 0, 0);
|
|
798
|
+
return combined;
|
|
799
|
+
}
|
|
800
|
+
/**
|
|
801
|
+
* Validate time string format (HH:mm)
|
|
802
|
+
*/
|
|
803
|
+
function isValidTime(time) {
|
|
804
|
+
const match = time.match(/^(\d{2}):(\d{2})$/);
|
|
805
|
+
if (!match) return false;
|
|
806
|
+
const hours = Number.parseInt(match[1], 10);
|
|
807
|
+
const minutes = Number.parseInt(match[2], 10);
|
|
808
|
+
return hours >= 0 && hours <= 23 && minutes >= 0 && minutes <= 59;
|
|
809
|
+
}
|
|
810
|
+
function CustomRangePanel({ fromUtc, toUtc, timezone, onRangeChange, disableFuture = false, debounceMs = 600, className }) {
|
|
811
|
+
const startDateId = useId();
|
|
812
|
+
const startTimeId = useId();
|
|
813
|
+
const endDateId = useId();
|
|
814
|
+
const endTimeId = useId();
|
|
815
|
+
const userHasEditedTimeRef = useRef(false);
|
|
816
|
+
const prevFromUtcRef = useRef(fromUtc);
|
|
817
|
+
const prevToUtcRef = useRef(toUtc);
|
|
818
|
+
const debounceTimerRef = useRef(null);
|
|
819
|
+
const [startDateOpen, setStartDateOpen] = useState(false);
|
|
820
|
+
const [endDateOpen, setEndDateOpen] = useState(false);
|
|
821
|
+
const initialFrom = parseDateTimeParts(utcStringToZonedDate(fromUtc, timezone));
|
|
822
|
+
const initialTo = parseDateTimeParts(utcStringToZonedDate(toUtc, timezone));
|
|
823
|
+
const [startDate, setStartDate] = useState(initialFrom.date);
|
|
824
|
+
const [startTime, setStartTime] = useState(initialFrom.time);
|
|
825
|
+
const [endDate, setEndDate] = useState(initialTo.date);
|
|
826
|
+
const [endTime, setEndTime] = useState(initialTo.time);
|
|
827
|
+
useEffect(() => {
|
|
828
|
+
return () => {
|
|
829
|
+
if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current);
|
|
830
|
+
};
|
|
831
|
+
}, []);
|
|
832
|
+
useEffect(() => {
|
|
833
|
+
const fromChanged = fromUtc !== prevFromUtcRef.current;
|
|
834
|
+
const toChanged = toUtc !== prevToUtcRef.current;
|
|
835
|
+
if (fromChanged) {
|
|
836
|
+
try {
|
|
837
|
+
const parsed = parseDateTimeParts(utcStringToZonedDate(fromUtc, timezone));
|
|
838
|
+
setStartDate(parsed.date);
|
|
839
|
+
setStartTime(parsed.time);
|
|
840
|
+
userHasEditedTimeRef.current = false;
|
|
841
|
+
} catch {}
|
|
842
|
+
prevFromUtcRef.current = fromUtc;
|
|
843
|
+
}
|
|
844
|
+
if (toChanged) {
|
|
845
|
+
try {
|
|
846
|
+
const parsed = parseDateTimeParts(utcStringToZonedDate(toUtc, timezone));
|
|
847
|
+
setEndDate(parsed.date);
|
|
848
|
+
setEndTime(parsed.time);
|
|
849
|
+
userHasEditedTimeRef.current = false;
|
|
850
|
+
} catch {}
|
|
851
|
+
prevToUtcRef.current = toUtc;
|
|
852
|
+
}
|
|
853
|
+
}, [
|
|
854
|
+
fromUtc,
|
|
855
|
+
toUtc,
|
|
856
|
+
timezone
|
|
857
|
+
]);
|
|
858
|
+
const notifyChangeImmediate = useCallback((newStartDate, newStartTime, newEndDate, newEndTime) => {
|
|
859
|
+
if (!isValidTime(newStartTime) || !isValidTime(newEndTime)) return;
|
|
860
|
+
const startCombined = combineDateAndTime(newStartDate, newStartTime);
|
|
861
|
+
const endCombined = combineDateAndTime(newEndDate, newEndTime);
|
|
862
|
+
if (startCombined >= endCombined) return;
|
|
863
|
+
const newFromUtc = zonedDateToUtcString(startCombined, timezone);
|
|
864
|
+
const newToUtc = zonedDateToUtcString(endCombined, timezone);
|
|
865
|
+
if (newFromUtc !== fromUtc || newToUtc !== toUtc) onRangeChange(newFromUtc, newToUtc);
|
|
866
|
+
}, [
|
|
867
|
+
timezone,
|
|
868
|
+
fromUtc,
|
|
869
|
+
toUtc,
|
|
870
|
+
onRangeChange
|
|
871
|
+
]);
|
|
872
|
+
const notifyChangeDebounced = useCallback((newStartDate, newStartTime, newEndDate, newEndTime) => {
|
|
873
|
+
if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current);
|
|
874
|
+
debounceTimerRef.current = setTimeout(() => {
|
|
875
|
+
notifyChangeImmediate(newStartDate, newStartTime, newEndDate, newEndTime);
|
|
876
|
+
}, debounceMs);
|
|
877
|
+
}, [notifyChangeImmediate, debounceMs]);
|
|
878
|
+
const handleStartDateSelect = useCallback((date) => {
|
|
879
|
+
if (!date) return;
|
|
880
|
+
setStartDate(date);
|
|
881
|
+
setStartDateOpen(false);
|
|
882
|
+
notifyChangeImmediate(date, startTime, endDate, endTime);
|
|
883
|
+
}, [
|
|
884
|
+
startTime,
|
|
885
|
+
endDate,
|
|
886
|
+
endTime,
|
|
887
|
+
notifyChangeImmediate
|
|
888
|
+
]);
|
|
889
|
+
const handleStartTimeChange = useCallback((e) => {
|
|
890
|
+
const newTime = e.target.value;
|
|
891
|
+
userHasEditedTimeRef.current = true;
|
|
892
|
+
setStartTime(newTime);
|
|
893
|
+
if (isValidTime(newTime)) notifyChangeDebounced(startDate, newTime, endDate, endTime);
|
|
894
|
+
}, [
|
|
895
|
+
startDate,
|
|
896
|
+
endDate,
|
|
897
|
+
endTime,
|
|
898
|
+
notifyChangeDebounced
|
|
899
|
+
]);
|
|
900
|
+
const handleEndDateSelect = useCallback((date) => {
|
|
901
|
+
if (!date) return;
|
|
902
|
+
setEndDate(date);
|
|
903
|
+
setEndDateOpen(false);
|
|
904
|
+
notifyChangeImmediate(startDate, startTime, date, endTime);
|
|
905
|
+
}, [
|
|
906
|
+
startDate,
|
|
907
|
+
startTime,
|
|
908
|
+
endTime,
|
|
909
|
+
notifyChangeImmediate
|
|
910
|
+
]);
|
|
911
|
+
const handleEndTimeChange = useCallback((e) => {
|
|
912
|
+
const newTime = e.target.value;
|
|
913
|
+
userHasEditedTimeRef.current = true;
|
|
914
|
+
setEndTime(newTime);
|
|
915
|
+
if (isValidTime(newTime)) notifyChangeDebounced(startDate, startTime, endDate, newTime);
|
|
916
|
+
}, [
|
|
917
|
+
startDate,
|
|
918
|
+
startTime,
|
|
919
|
+
endDate,
|
|
920
|
+
notifyChangeDebounced
|
|
921
|
+
]);
|
|
922
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
923
|
+
className: cn("flex flex-col gap-2", className),
|
|
924
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
925
|
+
className: "text-muted-foreground text-xs font-medium",
|
|
926
|
+
children: "Custom Range"
|
|
927
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
928
|
+
className: "flex items-center gap-2",
|
|
929
|
+
children: [
|
|
930
|
+
/* @__PURE__ */ jsxs("div", {
|
|
931
|
+
className: "flex items-center gap-1.5",
|
|
932
|
+
children: [/* @__PURE__ */ jsxs(Popover, {
|
|
933
|
+
open: startDateOpen,
|
|
934
|
+
onOpenChange: setStartDateOpen,
|
|
935
|
+
children: [/* @__PURE__ */ jsx(PopoverTrigger, {
|
|
936
|
+
asChild: true,
|
|
937
|
+
children: /* @__PURE__ */ jsxs(Button, {
|
|
938
|
+
type: "quaternary",
|
|
939
|
+
theme: "outline",
|
|
940
|
+
id: startDateId,
|
|
941
|
+
className: "h-8 w-full justify-start gap-1.5 px-2 text-xs font-normal",
|
|
942
|
+
children: [/* @__PURE__ */ jsx(CalendarIcon, { className: "h-3.5 w-3.5 shrink-0 opacity-50" }), /* @__PURE__ */ jsx("span", {
|
|
943
|
+
className: "truncate",
|
|
944
|
+
children: format(startDate, "MMM d, yyyy")
|
|
945
|
+
})]
|
|
946
|
+
})
|
|
947
|
+
}), /* @__PURE__ */ jsx(PopoverContent, {
|
|
948
|
+
className: "w-auto p-0",
|
|
949
|
+
align: "start",
|
|
950
|
+
children: /* @__PURE__ */ jsx(Calendar$1, {
|
|
951
|
+
mode: "single",
|
|
952
|
+
selected: startDate,
|
|
953
|
+
onSelect: handleStartDateSelect,
|
|
954
|
+
disabled: disableFuture ? (date) => date > /* @__PURE__ */ new Date() : void 0,
|
|
955
|
+
initialFocus: true
|
|
956
|
+
})
|
|
957
|
+
})]
|
|
958
|
+
}), /* @__PURE__ */ jsx(Input, {
|
|
959
|
+
type: "time",
|
|
960
|
+
id: startTimeId,
|
|
961
|
+
value: startTime,
|
|
962
|
+
onChange: handleStartTimeChange,
|
|
963
|
+
className: cn("h-8 w-[80px] px-2 text-xs md:text-xs", "appearance-none bg-transparent", "[&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none")
|
|
964
|
+
})]
|
|
965
|
+
}),
|
|
966
|
+
/* @__PURE__ */ jsx("span", {
|
|
967
|
+
className: "text-muted-foreground text-sm",
|
|
968
|
+
children: "—"
|
|
969
|
+
}),
|
|
970
|
+
/* @__PURE__ */ jsxs("div", {
|
|
971
|
+
className: "flex items-center gap-1.5",
|
|
972
|
+
children: [/* @__PURE__ */ jsxs(Popover, {
|
|
973
|
+
open: endDateOpen,
|
|
974
|
+
onOpenChange: setEndDateOpen,
|
|
975
|
+
children: [/* @__PURE__ */ jsx(PopoverTrigger, {
|
|
976
|
+
asChild: true,
|
|
977
|
+
children: /* @__PURE__ */ jsxs(Button, {
|
|
978
|
+
type: "quaternary",
|
|
979
|
+
theme: "outline",
|
|
980
|
+
id: endDateId,
|
|
981
|
+
className: "h-8 w-full justify-start gap-1.5 px-2 text-xs font-normal",
|
|
982
|
+
children: [/* @__PURE__ */ jsx(CalendarIcon, { className: "h-3.5 w-3.5 shrink-0 opacity-50" }), /* @__PURE__ */ jsx("span", {
|
|
983
|
+
className: "truncate",
|
|
984
|
+
children: format(endDate, "MMM d, yyyy")
|
|
985
|
+
})]
|
|
986
|
+
})
|
|
987
|
+
}), /* @__PURE__ */ jsx(PopoverContent, {
|
|
988
|
+
className: "w-auto p-0",
|
|
989
|
+
align: "start",
|
|
990
|
+
children: /* @__PURE__ */ jsx(Calendar$1, {
|
|
991
|
+
mode: "single",
|
|
992
|
+
selected: endDate,
|
|
993
|
+
onSelect: handleEndDateSelect,
|
|
994
|
+
disabled: disableFuture ? (date) => date > /* @__PURE__ */ new Date() || date < startDate : (date) => date < startDate,
|
|
995
|
+
initialFocus: true
|
|
996
|
+
})
|
|
997
|
+
})]
|
|
998
|
+
}), /* @__PURE__ */ jsx(Input, {
|
|
999
|
+
type: "time",
|
|
1000
|
+
id: endTimeId,
|
|
1001
|
+
value: endTime,
|
|
1002
|
+
onChange: handleEndTimeChange,
|
|
1003
|
+
className: cn("h-8 w-[80px] px-2 text-xs md:text-xs", "appearance-none bg-transparent", "[&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none")
|
|
1004
|
+
})]
|
|
1005
|
+
})
|
|
1006
|
+
]
|
|
1007
|
+
})]
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
//#endregion
|
|
1012
|
+
//#region src/components/features/time-range-picker/components/quick-ranges-panel.tsx
|
|
1013
|
+
function QuickRangesPanel({ presets, value, onPresetSelect, className }) {
|
|
1014
|
+
const selectedPreset = value?.type === "preset" ? value.preset : void 0;
|
|
1015
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1016
|
+
className: cn("flex flex-col gap-2", className),
|
|
1017
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
1018
|
+
className: "text-muted-foreground px-3 text-xs font-medium",
|
|
1019
|
+
children: "Presets"
|
|
1020
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
1021
|
+
className: "grid gap-1",
|
|
1022
|
+
children: presets.map((preset) => {
|
|
1023
|
+
const isSelected = selectedPreset === preset.key;
|
|
1024
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
1025
|
+
type: "button",
|
|
1026
|
+
onClick: () => onPresetSelect(preset),
|
|
1027
|
+
className: cn("flex items-center justify-between gap-2 rounded-lg px-3 py-1.5 text-xs transition-colors", "hover:bg-accent hover:text-accent-foreground", isSelected ? "bg-accent text-primary border-border border font-medium" : "border border-transparent"),
|
|
1028
|
+
children: [/* @__PURE__ */ jsx("span", { children: preset.label }), /* @__PURE__ */ jsx("kbd", {
|
|
1029
|
+
className: cn("bg-muted pointer-events-none hidden h-5 items-center gap-1 rounded border px-1.5 font-mono text-[10px] font-medium select-none sm:flex", isSelected ? "border-primary" : "bg-muted/50 border-transparent"),
|
|
1030
|
+
children: preset.shortcut
|
|
1031
|
+
})]
|
|
1032
|
+
}, preset.key);
|
|
1033
|
+
})
|
|
1034
|
+
})]
|
|
1035
|
+
});
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
//#endregion
|
|
1039
|
+
//#region src/components/features/time-range-picker/components/timezone-selector.tsx
|
|
1040
|
+
function TimezoneSelector({ value, onChange, options, className }) {
|
|
1041
|
+
const selectedOption = options.find((opt) => opt.value === value);
|
|
1042
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1043
|
+
className: cn("flex items-center gap-2", className),
|
|
1044
|
+
children: [/* @__PURE__ */ jsx(Globe, { className: "text-muted-foreground h-4 w-4 shrink-0" }), /* @__PURE__ */ jsxs(Select, {
|
|
1045
|
+
value,
|
|
1046
|
+
onValueChange: onChange,
|
|
1047
|
+
children: [/* @__PURE__ */ jsx(SelectTrigger, {
|
|
1048
|
+
className: "hover:bg-accent h-8 min-w-[180px] border-0 bg-transparent px-2 text-xs shadow-none",
|
|
1049
|
+
children: /* @__PURE__ */ jsx(SelectValue, {
|
|
1050
|
+
placeholder: "Select timezone",
|
|
1051
|
+
children: selectedOption?.label ?? value
|
|
1052
|
+
})
|
|
1053
|
+
}), /* @__PURE__ */ jsx(SelectContent, { children: options.map((option) => /* @__PURE__ */ jsx(SelectItem, {
|
|
1054
|
+
value: option.value,
|
|
1055
|
+
className: "text-xs",
|
|
1056
|
+
children: option.label
|
|
1057
|
+
}, option.value)) })]
|
|
1058
|
+
})]
|
|
1059
|
+
});
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
//#endregion
|
|
1063
|
+
//#region src/components/features/time-range-picker/presets.ts
|
|
1064
|
+
/**
|
|
1065
|
+
* Get start of day in a specific timezone
|
|
1066
|
+
*/
|
|
1067
|
+
function startOfDayInTimezone(date, timezone) {
|
|
1068
|
+
const zonedDate = toZonedTime(date, timezone);
|
|
1069
|
+
zonedDate.setHours(0, 0, 0, 0);
|
|
1070
|
+
return fromZonedTime(zonedDate, timezone);
|
|
1071
|
+
}
|
|
1072
|
+
/**
|
|
1073
|
+
* Get end of day in a specific timezone
|
|
1074
|
+
*/
|
|
1075
|
+
function endOfDayInTimezone(date, timezone) {
|
|
1076
|
+
const zonedDate = toZonedTime(date, timezone);
|
|
1077
|
+
zonedDate.setHours(23, 59, 59, 999);
|
|
1078
|
+
return fromZonedTime(zonedDate, timezone);
|
|
1079
|
+
}
|
|
1080
|
+
/**
|
|
1081
|
+
* Default time range presets with timezone-aware range calculation
|
|
1082
|
+
* Note: All presets use "now" as end time to avoid future timestamps
|
|
1083
|
+
* (API rejects future times for historical audit logs)
|
|
1084
|
+
*
|
|
1085
|
+
* API constraint: Maximum time range is 720 hours (30 days)
|
|
1086
|
+
*/
|
|
1087
|
+
const DEFAULT_PRESETS = [
|
|
1088
|
+
{
|
|
1089
|
+
key: "15m",
|
|
1090
|
+
label: "Last 15 minutes",
|
|
1091
|
+
shortcut: "1",
|
|
1092
|
+
getRange: (_timezone) => {
|
|
1093
|
+
const now = /* @__PURE__ */ new Date();
|
|
1094
|
+
return {
|
|
1095
|
+
from: subMinutes(now, 15),
|
|
1096
|
+
to: now
|
|
1097
|
+
};
|
|
1098
|
+
}
|
|
1099
|
+
},
|
|
1100
|
+
{
|
|
1101
|
+
key: "30m",
|
|
1102
|
+
label: "Last 30 minutes",
|
|
1103
|
+
shortcut: "3",
|
|
1104
|
+
getRange: (_timezone) => {
|
|
1105
|
+
const now = /* @__PURE__ */ new Date();
|
|
1106
|
+
return {
|
|
1107
|
+
from: subMinutes(now, 30),
|
|
1108
|
+
to: now
|
|
1109
|
+
};
|
|
1110
|
+
}
|
|
1111
|
+
},
|
|
1112
|
+
{
|
|
1113
|
+
key: "1h",
|
|
1114
|
+
label: "Last hour",
|
|
1115
|
+
shortcut: "H",
|
|
1116
|
+
getRange: (_timezone) => {
|
|
1117
|
+
const now = /* @__PURE__ */ new Date();
|
|
1118
|
+
return {
|
|
1119
|
+
from: subHours(now, 1),
|
|
1120
|
+
to: now
|
|
1121
|
+
};
|
|
1122
|
+
}
|
|
1123
|
+
},
|
|
1124
|
+
{
|
|
1125
|
+
key: "today",
|
|
1126
|
+
label: "Today",
|
|
1127
|
+
shortcut: "D",
|
|
1128
|
+
getRange: (timezone) => {
|
|
1129
|
+
const now = /* @__PURE__ */ new Date();
|
|
1130
|
+
return {
|
|
1131
|
+
from: startOfDayInTimezone(now, timezone),
|
|
1132
|
+
to: now
|
|
1133
|
+
};
|
|
1134
|
+
}
|
|
1135
|
+
},
|
|
1136
|
+
{
|
|
1137
|
+
key: "yesterday",
|
|
1138
|
+
label: "Yesterday",
|
|
1139
|
+
shortcut: "Y",
|
|
1140
|
+
getRange: (timezone) => {
|
|
1141
|
+
const yesterday = subDays(/* @__PURE__ */ new Date(), 1);
|
|
1142
|
+
return {
|
|
1143
|
+
from: startOfDayInTimezone(yesterday, timezone),
|
|
1144
|
+
to: endOfDayInTimezone(yesterday, timezone)
|
|
1145
|
+
};
|
|
1146
|
+
}
|
|
1147
|
+
},
|
|
1148
|
+
{
|
|
1149
|
+
key: "7d",
|
|
1150
|
+
label: "Last 7 days",
|
|
1151
|
+
shortcut: "W",
|
|
1152
|
+
getRange: (_timezone) => {
|
|
1153
|
+
const now = /* @__PURE__ */ new Date();
|
|
1154
|
+
return {
|
|
1155
|
+
from: subDays(now, 7),
|
|
1156
|
+
to: now
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1159
|
+
},
|
|
1160
|
+
{
|
|
1161
|
+
key: "30d",
|
|
1162
|
+
label: "Last 30 days",
|
|
1163
|
+
shortcut: "M",
|
|
1164
|
+
getRange: (_timezone) => {
|
|
1165
|
+
const now = /* @__PURE__ */ new Date();
|
|
1166
|
+
return {
|
|
1167
|
+
from: subDays(now, 30),
|
|
1168
|
+
to: now
|
|
1169
|
+
};
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
];
|
|
1173
|
+
/**
|
|
1174
|
+
* Get a preset by key
|
|
1175
|
+
*/
|
|
1176
|
+
function getPresetByKey(key, presets = DEFAULT_PRESETS) {
|
|
1177
|
+
return presets.find((p) => p.key === key);
|
|
1178
|
+
}
|
|
1179
|
+
/**
|
|
1180
|
+
* Get a preset by keyboard shortcut
|
|
1181
|
+
*/
|
|
1182
|
+
function getPresetByShortcut(shortcut, presets = DEFAULT_PRESETS) {
|
|
1183
|
+
return presets.find((p) => p.shortcut.toLowerCase() === shortcut.toLowerCase());
|
|
1184
|
+
}
|
|
1185
|
+
/**
|
|
1186
|
+
* Get the default preset (last 7 days)
|
|
1187
|
+
*/
|
|
1188
|
+
function getDefaultPreset(presets = DEFAULT_PRESETS) {
|
|
1189
|
+
return getPresetByKey("today", presets) ?? presets[0];
|
|
1190
|
+
}
|
|
1191
|
+
/**
|
|
1192
|
+
* Calculate preset range and return UTC ISO strings
|
|
1193
|
+
*/
|
|
1194
|
+
function getPresetRange(preset, timezone) {
|
|
1195
|
+
const range = preset.getRange(timezone);
|
|
1196
|
+
return {
|
|
1197
|
+
from: range.from.toISOString(),
|
|
1198
|
+
to: range.to.toISOString()
|
|
1199
|
+
};
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
//#endregion
|
|
1203
|
+
//#region src/components/features/time-range-picker/utils/format-display.ts
|
|
1204
|
+
/**
|
|
1205
|
+
* Format a TimeRangeValue for display in the trigger button
|
|
1206
|
+
* Always shows the actual date/time range, not preset labels
|
|
1207
|
+
* Converts UTC timestamps to user's timezone for display
|
|
1208
|
+
*/
|
|
1209
|
+
function formatTimeRangeDisplay(value, timezone) {
|
|
1210
|
+
if (!value || !value.from || !value.to) return "Select time range";
|
|
1211
|
+
return formatDateRangeDisplay(value.from, value.to, timezone);
|
|
1212
|
+
}
|
|
1213
|
+
/**
|
|
1214
|
+
* Format a date range for display
|
|
1215
|
+
* Smart formatting based on whether dates are same day/year
|
|
1216
|
+
*/
|
|
1217
|
+
function formatDateRangeDisplay(fromUtc, toUtc, timezone) {
|
|
1218
|
+
try {
|
|
1219
|
+
const fromDate = utcStringToZonedDate(fromUtc, timezone);
|
|
1220
|
+
const toDate = utcStringToZonedDate(toUtc, timezone);
|
|
1221
|
+
const now = /* @__PURE__ */ new Date();
|
|
1222
|
+
if (isSameDay(fromDate, toDate)) return `${isSameYear(fromDate, now) ? format(fromDate, "MMM d") : format(fromDate, "MMM d, yyyy")}, ${format(fromDate, "HH:mm")} - ${format(toDate, "HH:mm")}`;
|
|
1223
|
+
if (isSameYear(fromDate, now) && isSameYear(toDate, now)) return `${format(fromDate, "MMM d, HH:mm")} - ${format(toDate, "MMM d, HH:mm")}`;
|
|
1224
|
+
return `${format(fromDate, "MMM d, yyyy")} - ${format(toDate, "MMM d, yyyy")}`;
|
|
1225
|
+
} catch {
|
|
1226
|
+
return `${formatUtcForDisplay(fromUtc, timezone, "MMM d, HH:mm")} - ${formatUtcForDisplay(toUtc, timezone, "MMM d, HH:mm")}`;
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
/**
|
|
1230
|
+
* Format a UTC ISO string for display in user's timezone
|
|
1231
|
+
*/
|
|
1232
|
+
function formatSingleTimeDisplay(utcString, timezone) {
|
|
1233
|
+
try {
|
|
1234
|
+
return formatUtcForDisplay(utcString, timezone, "MMM d, yyyy HH:mm");
|
|
1235
|
+
} catch {
|
|
1236
|
+
return utcString;
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
/**
|
|
1240
|
+
* Format a Date object for the datetime-local input field
|
|
1241
|
+
*/
|
|
1242
|
+
function formatDateForInput(date) {
|
|
1243
|
+
return format(date, "yyyy-MM-dd'T'HH:mm");
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
//#endregion
|
|
1247
|
+
//#region src/components/features/time-range-picker/time-range-picker.tsx
|
|
1248
|
+
function TimeRangePicker({ value, onChange, onClear, timezone: timezoneProp, presets = DEFAULT_PRESETS, disableFuture = false, maxDate, minDate, className, disabled = false, placeholder = "Select time range", align = "start", side = "bottom" }) {
|
|
1249
|
+
const [open, setOpen] = useState(false);
|
|
1250
|
+
const timezone = timezoneProp ?? getBrowserTimezone();
|
|
1251
|
+
const defaultPreset = getDefaultPreset(presets);
|
|
1252
|
+
const defaultRange = useMemo(() => getPresetRange(defaultPreset, timezone), [defaultPreset, timezone]);
|
|
1253
|
+
const currentFromUtc = value?.from ?? defaultRange.from;
|
|
1254
|
+
const currentToUtc = value?.to ?? defaultRange.to;
|
|
1255
|
+
const calendarRange = useMemo(() => {
|
|
1256
|
+
try {
|
|
1257
|
+
return {
|
|
1258
|
+
from: utcStringToZonedDate(currentFromUtc, timezone),
|
|
1259
|
+
to: utcStringToZonedDate(currentToUtc, timezone)
|
|
1260
|
+
};
|
|
1261
|
+
} catch {
|
|
1262
|
+
return;
|
|
1263
|
+
}
|
|
1264
|
+
}, [
|
|
1265
|
+
currentFromUtc,
|
|
1266
|
+
currentToUtc,
|
|
1267
|
+
timezone
|
|
1268
|
+
]);
|
|
1269
|
+
const effectiveValue = useMemo(() => {
|
|
1270
|
+
if (value?.type === "preset" && value?.preset && (!value?.from || !value?.to)) {
|
|
1271
|
+
const preset = presets.find((p) => p.key === value.preset) ?? defaultPreset;
|
|
1272
|
+
const range = getPresetRange(preset, timezone);
|
|
1273
|
+
return {
|
|
1274
|
+
type: "preset",
|
|
1275
|
+
preset: preset.key,
|
|
1276
|
+
from: range.from,
|
|
1277
|
+
to: range.to
|
|
1278
|
+
};
|
|
1279
|
+
}
|
|
1280
|
+
if (value?.from && value?.to) return value;
|
|
1281
|
+
return {
|
|
1282
|
+
type: "preset",
|
|
1283
|
+
preset: defaultPreset.key,
|
|
1284
|
+
from: defaultRange.from,
|
|
1285
|
+
to: defaultRange.to
|
|
1286
|
+
};
|
|
1287
|
+
}, [
|
|
1288
|
+
value,
|
|
1289
|
+
defaultPreset,
|
|
1290
|
+
defaultRange.from,
|
|
1291
|
+
defaultRange.to,
|
|
1292
|
+
presets,
|
|
1293
|
+
timezone
|
|
1294
|
+
]);
|
|
1295
|
+
const displayText = useMemo(() => formatTimeRangeDisplay(effectiveValue, timezone) || placeholder, [
|
|
1296
|
+
effectiveValue,
|
|
1297
|
+
timezone,
|
|
1298
|
+
placeholder
|
|
1299
|
+
]);
|
|
1300
|
+
const handlePresetSelect = useCallback((preset) => {
|
|
1301
|
+
const range = getPresetRange(preset, timezone);
|
|
1302
|
+
onChange({
|
|
1303
|
+
type: "preset",
|
|
1304
|
+
preset: preset.key,
|
|
1305
|
+
from: range.from,
|
|
1306
|
+
to: range.to
|
|
1307
|
+
});
|
|
1308
|
+
setOpen(false);
|
|
1309
|
+
}, [onChange, timezone]);
|
|
1310
|
+
const userClickedCalendarRef = useRef(false);
|
|
1311
|
+
const handleDayClick = useCallback(() => {
|
|
1312
|
+
userClickedCalendarRef.current = true;
|
|
1313
|
+
}, []);
|
|
1314
|
+
const handleCalendarSelect = useCallback((range) => {
|
|
1315
|
+
if (!userClickedCalendarRef.current) return;
|
|
1316
|
+
userClickedCalendarRef.current = false;
|
|
1317
|
+
if (range?.from && range?.to) {
|
|
1318
|
+
const now = /* @__PURE__ */ new Date();
|
|
1319
|
+
const fromStart = new Date(range.from);
|
|
1320
|
+
fromStart.setHours(0, 0, 0, 0);
|
|
1321
|
+
const toEnd = new Date(range.to);
|
|
1322
|
+
toEnd.setHours(23, 59, 59, 999);
|
|
1323
|
+
const effectiveToEnd = toEnd > now ? now : toEnd;
|
|
1324
|
+
onChange({
|
|
1325
|
+
type: "custom",
|
|
1326
|
+
from: zonedDateToUtcString(fromStart, timezone),
|
|
1327
|
+
to: zonedDateToUtcString(effectiveToEnd, timezone)
|
|
1328
|
+
});
|
|
1329
|
+
} else if (range?.from) {
|
|
1330
|
+
const now = /* @__PURE__ */ new Date();
|
|
1331
|
+
const fromStart = new Date(range.from);
|
|
1332
|
+
fromStart.setHours(0, 0, 0, 0);
|
|
1333
|
+
const toEnd = new Date(range.from);
|
|
1334
|
+
toEnd.setHours(23, 59, 59, 999);
|
|
1335
|
+
const effectiveToEnd = toEnd > now ? now : toEnd;
|
|
1336
|
+
onChange({
|
|
1337
|
+
type: "custom",
|
|
1338
|
+
from: zonedDateToUtcString(fromStart, timezone),
|
|
1339
|
+
to: zonedDateToUtcString(effectiveToEnd, timezone)
|
|
1340
|
+
});
|
|
1341
|
+
}
|
|
1342
|
+
}, [onChange, timezone]);
|
|
1343
|
+
const handleCustomRangeChange = useCallback((fromUtc, toUtc) => {
|
|
1344
|
+
onChange({
|
|
1345
|
+
type: "custom",
|
|
1346
|
+
from: fromUtc,
|
|
1347
|
+
to: toUtc
|
|
1348
|
+
});
|
|
1349
|
+
}, [onChange]);
|
|
1350
|
+
useEffect(() => {
|
|
1351
|
+
if (!open) return;
|
|
1352
|
+
const handleKeyDown = (e) => {
|
|
1353
|
+
const preset = getPresetByShortcut(e.key, presets);
|
|
1354
|
+
if (preset) {
|
|
1355
|
+
e.preventDefault();
|
|
1356
|
+
handlePresetSelect(preset);
|
|
1357
|
+
}
|
|
1358
|
+
};
|
|
1359
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
1360
|
+
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
1361
|
+
}, [
|
|
1362
|
+
open,
|
|
1363
|
+
presets,
|
|
1364
|
+
handlePresetSelect
|
|
1365
|
+
]);
|
|
1366
|
+
const effectiveMaxDate = disableFuture ? /* @__PURE__ */ new Date() : maxDate;
|
|
1367
|
+
const handleClear = useCallback((e) => {
|
|
1368
|
+
e.stopPropagation();
|
|
1369
|
+
onClear?.();
|
|
1370
|
+
}, [onClear]);
|
|
1371
|
+
const showClearButton = value && onClear;
|
|
1372
|
+
return /* @__PURE__ */ jsxs(Popover, {
|
|
1373
|
+
open,
|
|
1374
|
+
onOpenChange: setOpen,
|
|
1375
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
1376
|
+
className: "relative inline-flex",
|
|
1377
|
+
children: /* @__PURE__ */ jsx(PopoverTrigger, {
|
|
1378
|
+
asChild: true,
|
|
1379
|
+
children: /* @__PURE__ */ jsxs(Button, {
|
|
1380
|
+
type: "quaternary",
|
|
1381
|
+
theme: "outline",
|
|
1382
|
+
disabled,
|
|
1383
|
+
className: cn("text-foreground min-w-[200px] items-center justify-between gap-2 px-3 font-normal", className),
|
|
1384
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
1385
|
+
className: "flex flex-1 items-center gap-2",
|
|
1386
|
+
children: [/* @__PURE__ */ jsx(Icon, {
|
|
1387
|
+
icon: Calendar,
|
|
1388
|
+
size: 16
|
|
1389
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
1390
|
+
className: "truncate text-xs",
|
|
1391
|
+
children: displayText
|
|
1392
|
+
})]
|
|
1393
|
+
}), showClearButton && /* @__PURE__ */ jsx("div", {
|
|
1394
|
+
onClick: (e) => {
|
|
1395
|
+
e.stopPropagation();
|
|
1396
|
+
e.preventDefault();
|
|
1397
|
+
handleClear(e);
|
|
1398
|
+
},
|
|
1399
|
+
className: cn("size-[14px] shrink-0 p-0 hover:bg-transparent", "hover:text-destructive text-icon-quaternary hover:bg-transparent dark:text-white", "focus:ring-ring focus:ring-2 focus:ring-offset-1 focus:outline-none", "disabled:pointer-events-none disabled:opacity-50", "transition-colors"),
|
|
1400
|
+
"aria-label": "Clear time range",
|
|
1401
|
+
children: /* @__PURE__ */ jsx(Icon, {
|
|
1402
|
+
icon: X,
|
|
1403
|
+
size: 14
|
|
1404
|
+
})
|
|
1405
|
+
})]
|
|
1406
|
+
})
|
|
1407
|
+
})
|
|
1408
|
+
}), /* @__PURE__ */ jsxs(PopoverContent, {
|
|
1409
|
+
className: "w-auto rounded-xl p-0",
|
|
1410
|
+
align,
|
|
1411
|
+
side,
|
|
1412
|
+
sideOffset: 4,
|
|
1413
|
+
children: [
|
|
1414
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1415
|
+
className: "divide-border flex flex-col divide-x sm:flex-row",
|
|
1416
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
1417
|
+
className: "flex-1 px-0",
|
|
1418
|
+
children: /* @__PURE__ */ jsx(Calendar$1, {
|
|
1419
|
+
className: "w-full",
|
|
1420
|
+
mode: "range",
|
|
1421
|
+
defaultMonth: calendarRange?.from,
|
|
1422
|
+
selected: calendarRange,
|
|
1423
|
+
onSelect: handleCalendarSelect,
|
|
1424
|
+
onDayClick: handleDayClick,
|
|
1425
|
+
numberOfMonths: 1,
|
|
1426
|
+
disabled: (date) => {
|
|
1427
|
+
if (effectiveMaxDate && date > effectiveMaxDate) return true;
|
|
1428
|
+
if (minDate && date < minDate) return true;
|
|
1429
|
+
return false;
|
|
1430
|
+
},
|
|
1431
|
+
initialFocus: true
|
|
1432
|
+
})
|
|
1433
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
1434
|
+
className: "p-3",
|
|
1435
|
+
children: /* @__PURE__ */ jsx(QuickRangesPanel, {
|
|
1436
|
+
presets,
|
|
1437
|
+
value: effectiveValue,
|
|
1438
|
+
onPresetSelect: handlePresetSelect
|
|
1439
|
+
})
|
|
1440
|
+
})]
|
|
1441
|
+
}),
|
|
1442
|
+
/* @__PURE__ */ jsx(Separator, {}),
|
|
1443
|
+
/* @__PURE__ */ jsx("div", {
|
|
1444
|
+
className: "p-3",
|
|
1445
|
+
children: /* @__PURE__ */ jsx(CustomRangePanel, {
|
|
1446
|
+
fromUtc: currentFromUtc,
|
|
1447
|
+
toUtc: currentToUtc,
|
|
1448
|
+
timezone,
|
|
1449
|
+
onRangeChange: handleCustomRangeChange,
|
|
1450
|
+
disableFuture
|
|
1451
|
+
})
|
|
1452
|
+
}),
|
|
1453
|
+
/* @__PURE__ */ jsx(Separator, {}),
|
|
1454
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1455
|
+
className: "text-muted-foreground bg-muted/30 flex items-center gap-2 px-3 py-2 text-xs",
|
|
1456
|
+
children: [/* @__PURE__ */ jsx(Globe, { className: "h-3.5 w-3.5" }), /* @__PURE__ */ jsxs("span", { children: ["Your timezone:", formatTimezoneLabel(timezone)] })]
|
|
1457
|
+
})
|
|
1458
|
+
]
|
|
1459
|
+
})]
|
|
1460
|
+
});
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
//#endregion
|
|
1464
|
+
//#region src/components/features/time-range-picker/utils/to-api-format.ts
|
|
1465
|
+
/**
|
|
1466
|
+
* Convert a TimeRangeValue to API format (startTime/endTime)
|
|
1467
|
+
*
|
|
1468
|
+
* Since TimeRangeValue now always stores UTC timestamps,
|
|
1469
|
+
* this is a simple passthrough.
|
|
1470
|
+
*
|
|
1471
|
+
* @param value - The time range value (stores UTC timestamps)
|
|
1472
|
+
* @param timezone - User's timezone (used only if value needs recalculation)
|
|
1473
|
+
* @param presets - Preset configurations
|
|
1474
|
+
*/
|
|
1475
|
+
function toApiTimeRange(value, timezone, presets = DEFAULT_PRESETS) {
|
|
1476
|
+
const tz = timezone ?? getBrowserTimezone();
|
|
1477
|
+
if (!value) {
|
|
1478
|
+
const range = getPresetRange(getDefaultPreset(), tz);
|
|
1479
|
+
return {
|
|
1480
|
+
startTime: range.from,
|
|
1481
|
+
endTime: range.to
|
|
1482
|
+
};
|
|
1483
|
+
}
|
|
1484
|
+
if (value.from && value.to) return {
|
|
1485
|
+
startTime: value.from,
|
|
1486
|
+
endTime: value.to
|
|
1487
|
+
};
|
|
1488
|
+
if (value.type === "preset" && value.preset) {
|
|
1489
|
+
const preset = presets.find((p) => p.key === value.preset);
|
|
1490
|
+
if (preset) {
|
|
1491
|
+
const range = getPresetRange(preset, tz);
|
|
1492
|
+
return {
|
|
1493
|
+
startTime: range.from,
|
|
1494
|
+
endTime: range.to
|
|
1495
|
+
};
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
const range = getPresetRange(getDefaultPreset(presets), tz);
|
|
1499
|
+
return {
|
|
1500
|
+
startTime: range.from,
|
|
1501
|
+
endTime: range.to
|
|
1502
|
+
};
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
//#endregion
|
|
1506
|
+
export { utcToLocalInputString as C, utcStringToZonedDate as S, CalendarDatePicker as T, getBrowserTimezone as _, formatTimeRangeDisplay as a, getTimezoneOffset as b, getPresetByKey as c, TimezoneSelector as d, QuickRangesPanel as f, formatUtcForDisplay as g, formatTimezoneLabel as h, formatSingleTimeDisplay as i, getPresetByShortcut as l, createTimezoneOption as m, TimeRangePicker as n, DEFAULT_PRESETS as o, CustomRangePanel as p, formatDateForInput as r, getDefaultPreset as s, toApiTimeRange as t, getPresetRange as u, getDefaultTimezoneOptions as v, zonedDateToUtcString as w, localInputStringToUtc as x, getShortTimezoneDisplay as y };
|