kmod-cli 1.0.10
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 +53 -0
- package/bin/gen-components.js +68 -0
- package/bin/index.js +153 -0
- package/component-templates/components/access-denied.tsx +130 -0
- package/component-templates/components/breadcumb.tsx +42 -0
- package/component-templates/components/count-down.tsx +94 -0
- package/component-templates/components/count-input.tsx +221 -0
- package/component-templates/components/date-range-calendar/button.tsx +61 -0
- package/component-templates/components/date-range-calendar/calendar.tsx +132 -0
- package/component-templates/components/date-range-calendar/date-input.tsx +259 -0
- package/component-templates/components/date-range-calendar/date-range-picker.tsx +594 -0
- package/component-templates/components/date-range-calendar/label.tsx +31 -0
- package/component-templates/components/date-range-calendar/popover.tsx +32 -0
- package/component-templates/components/date-range-calendar/select.tsx +125 -0
- package/component-templates/components/date-range-calendar/switch.tsx +30 -0
- package/component-templates/components/datetime-picker/button.tsx +61 -0
- package/component-templates/components/datetime-picker/calendar.tsx +156 -0
- package/component-templates/components/datetime-picker/datetime-picker.tsx +75 -0
- package/component-templates/components/datetime-picker/input.tsx +20 -0
- package/component-templates/components/datetime-picker/label.tsx +18 -0
- package/component-templates/components/datetime-picker/period-input.tsx +62 -0
- package/component-templates/components/datetime-picker/popover.tsx +32 -0
- package/component-templates/components/datetime-picker/select.tsx +125 -0
- package/component-templates/components/datetime-picker/time-picker-input.tsx +131 -0
- package/component-templates/components/datetime-picker/time-picker-utils.tsx +204 -0
- package/component-templates/components/datetime-picker/time-picker.tsx +59 -0
- package/component-templates/components/gradient-outline.tsx +233 -0
- package/component-templates/components/gradient-svg.tsx +157 -0
- package/component-templates/components/grid-layout.tsx +69 -0
- package/component-templates/components/hydrate-guard.tsx +40 -0
- package/component-templates/components/image.tsx +92 -0
- package/component-templates/components/loader-slash-gradient.tsx +85 -0
- package/component-templates/components/masonry-gallery.tsx +221 -0
- package/component-templates/components/modal.tsx +110 -0
- package/component-templates/components/multi-select.tsx +447 -0
- package/component-templates/components/non-hydration.tsx +27 -0
- package/component-templates/components/portal.tsx +34 -0
- package/component-templates/components/segments-circle.tsx +235 -0
- package/component-templates/components/single-select.tsx +248 -0
- package/component-templates/components/stroke-circle.tsx +57 -0
- package/component-templates/components/table/column-table.tsx +15 -0
- package/component-templates/components/table/data-table.tsx +339 -0
- package/component-templates/components/table/readme.tsx +95 -0
- package/component-templates/components/table/table.tsx +60 -0
- package/component-templates/components/text-hover-effect.tsx +120 -0
- package/component-templates/components/timout-loader.tsx +52 -0
- package/component-templates/components/toast.tsx +994 -0
- package/component-templates/configs/config.ts +33 -0
- package/component-templates/configs/feature-config.tsx +432 -0
- package/component-templates/configs/keys.ts +7 -0
- package/component-templates/core/api-service.ts +202 -0
- package/component-templates/core/calculate.ts +18 -0
- package/component-templates/core/idb.ts +166 -0
- package/component-templates/core/storage.ts +213 -0
- package/component-templates/hooks/count-down.ts +38 -0
- package/component-templates/hooks/fade-on-scroll.ts +52 -0
- package/component-templates/hooks/safe-action.ts +59 -0
- package/component-templates/hooks/spam-guard.ts +31 -0
- package/component-templates/lib/utils.ts +6 -0
- package/component-templates/providers/feature-guard.tsx +432 -0
- package/component-templates/queries/query.tsx +775 -0
- package/component-templates/utils/colors/color-by-text.ts +307 -0
- package/component-templates/utils/colors/stripe-effect.ts +100 -0
- package/component-templates/utils/hash/hash-aes.ts +35 -0
- package/components.json +348 -0
- package/package.json +60 -0
|
@@ -0,0 +1,594 @@
|
|
|
1
|
+
/* eslint-disable max-lines */
|
|
2
|
+
'use client'
|
|
3
|
+
|
|
4
|
+
import React, {
|
|
5
|
+
type FC,
|
|
6
|
+
JSX,
|
|
7
|
+
useEffect,
|
|
8
|
+
useRef,
|
|
9
|
+
useState,
|
|
10
|
+
} from 'react';
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
CheckIcon,
|
|
14
|
+
ChevronDownIcon,
|
|
15
|
+
ChevronUpIcon,
|
|
16
|
+
} from '@radix-ui/react-icons';
|
|
17
|
+
|
|
18
|
+
import { cn } from '../../lib/utils';
|
|
19
|
+
import { Button } from './button';
|
|
20
|
+
import { Calendar } from './calendar';
|
|
21
|
+
import { DateInput } from './date-input';
|
|
22
|
+
import { Label } from './label';
|
|
23
|
+
import {
|
|
24
|
+
Popover,
|
|
25
|
+
PopoverContent,
|
|
26
|
+
PopoverTrigger,
|
|
27
|
+
} from './popover';
|
|
28
|
+
import {
|
|
29
|
+
Select,
|
|
30
|
+
SelectContent,
|
|
31
|
+
SelectItem,
|
|
32
|
+
SelectTrigger,
|
|
33
|
+
SelectValue,
|
|
34
|
+
} from './select';
|
|
35
|
+
import { Switch } from './switch';
|
|
36
|
+
|
|
37
|
+
export interface DateRangePickerProps {
|
|
38
|
+
/** Click handler for applying the updates from DateRangePicker. */
|
|
39
|
+
onUpdate?: (values: { range: DateRange, rangeCompare?: DateRange }) => void
|
|
40
|
+
/** Initial value for start date */
|
|
41
|
+
initialDateFrom?: Date | string
|
|
42
|
+
/** Initial value for end date */
|
|
43
|
+
initialDateTo?: Date | string
|
|
44
|
+
/** Initial value for start date for compare */
|
|
45
|
+
initialCompareFrom?: Date | string
|
|
46
|
+
/** Initial value for end date for compare */
|
|
47
|
+
initialCompareTo?: Date | string
|
|
48
|
+
/** Alignment of popover */
|
|
49
|
+
align?: 'start' | 'center' | 'end'
|
|
50
|
+
/** Option for locale */
|
|
51
|
+
locale?: string
|
|
52
|
+
/** Option for showing compare feature */
|
|
53
|
+
showCompare?: boolean
|
|
54
|
+
/** Class of trigger button */
|
|
55
|
+
className?: string
|
|
56
|
+
/** Class of content popover */
|
|
57
|
+
classNameContent?: string
|
|
58
|
+
/** Clear handler */
|
|
59
|
+
onClear?: () => void
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const formatDate = (date: Date, locale: string = 'en-us'): string => {
|
|
63
|
+
return date.toLocaleDateString(locale, {
|
|
64
|
+
month: 'short',
|
|
65
|
+
day: 'numeric',
|
|
66
|
+
year: 'numeric'
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const getDateAdjustedForTimezone = (dateInput: Date | string): Date => {
|
|
71
|
+
if (typeof dateInput === 'string') {
|
|
72
|
+
// Split the date string to get year, month, and day parts
|
|
73
|
+
const parts = dateInput.split('-').map((part) => parseInt(part, 10))
|
|
74
|
+
// Create a new Date object using the local timezone
|
|
75
|
+
// Note: Month is 0-indexed, so subtract 1 from the month part
|
|
76
|
+
const date = new Date(parts[0], parts[1] - 1, parts[2])
|
|
77
|
+
return date
|
|
78
|
+
} else {
|
|
79
|
+
// If dateInput is already a Date object, return it directly
|
|
80
|
+
return dateInput
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
interface DateRange {
|
|
85
|
+
from: Date
|
|
86
|
+
to: Date | undefined
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
interface Preset {
|
|
90
|
+
name: string
|
|
91
|
+
label: string
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Define presets
|
|
95
|
+
const PRESETS: Preset[] = [
|
|
96
|
+
{ name: 'today', label: 'Today' },
|
|
97
|
+
{ name: 'yesterday', label: 'Yesterday' },
|
|
98
|
+
{ name: 'last7', label: 'Last 7 days' },
|
|
99
|
+
{ name: 'last14', label: 'Last 14 days' },
|
|
100
|
+
{ name: 'last30', label: 'Last 30 days' },
|
|
101
|
+
{ name: 'thisWeek', label: 'This Week' },
|
|
102
|
+
{ name: 'lastWeek', label: 'Last Week' },
|
|
103
|
+
{ name: 'thisMonth', label: 'This Month' },
|
|
104
|
+
{ name: 'lastMonth', label: 'Last Month' }
|
|
105
|
+
]
|
|
106
|
+
|
|
107
|
+
/** The DateRangePicker component allows a user to select a range of dates */
|
|
108
|
+
export const DateRangePicker: FC<DateRangePickerProps> & {
|
|
109
|
+
filePath: string
|
|
110
|
+
} = ({
|
|
111
|
+
initialDateFrom = new Date(new Date().setHours(0, 0, 0, 0)),
|
|
112
|
+
initialDateTo,
|
|
113
|
+
initialCompareFrom,
|
|
114
|
+
initialCompareTo,
|
|
115
|
+
onUpdate,
|
|
116
|
+
align = 'end',
|
|
117
|
+
locale = 'en-US',
|
|
118
|
+
showCompare = true,
|
|
119
|
+
className,
|
|
120
|
+
classNameContent,
|
|
121
|
+
onClear
|
|
122
|
+
}): JSX.Element => {
|
|
123
|
+
const [isOpen, setIsOpen] = useState(false)
|
|
124
|
+
|
|
125
|
+
const [range, setRange] = useState<DateRange>({
|
|
126
|
+
from: getDateAdjustedForTimezone(initialDateFrom),
|
|
127
|
+
to: initialDateTo
|
|
128
|
+
? getDateAdjustedForTimezone(initialDateTo)
|
|
129
|
+
: getDateAdjustedForTimezone(initialDateFrom)
|
|
130
|
+
})
|
|
131
|
+
const [rangeCompare, setRangeCompare] = useState<DateRange | undefined>(
|
|
132
|
+
initialCompareFrom
|
|
133
|
+
? {
|
|
134
|
+
from: new Date(new Date(initialCompareFrom).setHours(0, 0, 0, 0)),
|
|
135
|
+
to: initialCompareTo
|
|
136
|
+
? new Date(new Date(initialCompareTo).setHours(0, 0, 0, 0))
|
|
137
|
+
: new Date(new Date(initialCompareFrom).setHours(0, 0, 0, 0))
|
|
138
|
+
}
|
|
139
|
+
: undefined
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
// Refs to store the values of range and rangeCompare when the date picker is opened
|
|
143
|
+
const openedRangeRef = useRef<DateRange | undefined>(undefined)
|
|
144
|
+
const openedRangeCompareRef = useRef<DateRange | undefined>(undefined)
|
|
145
|
+
|
|
146
|
+
const [selectedPreset, setSelectedPreset] = useState<string | undefined>(undefined)
|
|
147
|
+
|
|
148
|
+
const [isSmallScreen, setIsSmallScreen] = useState(
|
|
149
|
+
typeof window !== 'undefined' ? window.innerWidth < 960 : false
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
useEffect(() => {
|
|
153
|
+
const handleResize = (): void => {
|
|
154
|
+
setIsSmallScreen(window.innerWidth < 960)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
window.addEventListener('resize', handleResize)
|
|
158
|
+
|
|
159
|
+
// Clean up event listener on unmount
|
|
160
|
+
return () => {
|
|
161
|
+
window.removeEventListener('resize', handleResize)
|
|
162
|
+
}
|
|
163
|
+
}, [])
|
|
164
|
+
|
|
165
|
+
const getPresetRange = (presetName: string): DateRange => {
|
|
166
|
+
const preset = PRESETS.find(({ name }) => name === presetName)
|
|
167
|
+
if (!preset) throw new Error(`Unknown date range preset: ${presetName}`)
|
|
168
|
+
const from = new Date()
|
|
169
|
+
const to = new Date()
|
|
170
|
+
const first = from.getDate() - from.getDay()
|
|
171
|
+
|
|
172
|
+
switch (preset.name) {
|
|
173
|
+
case 'today':
|
|
174
|
+
from.setHours(0, 0, 0, 0)
|
|
175
|
+
to.setHours(23, 59, 59, 999)
|
|
176
|
+
break
|
|
177
|
+
case 'yesterday':
|
|
178
|
+
from.setDate(from.getDate() - 1)
|
|
179
|
+
from.setHours(0, 0, 0, 0)
|
|
180
|
+
to.setDate(to.getDate() - 1)
|
|
181
|
+
to.setHours(23, 59, 59, 999)
|
|
182
|
+
break
|
|
183
|
+
case 'last7':
|
|
184
|
+
from.setDate(from.getDate() - 6)
|
|
185
|
+
from.setHours(0, 0, 0, 0)
|
|
186
|
+
to.setHours(23, 59, 59, 999)
|
|
187
|
+
break
|
|
188
|
+
case 'last14':
|
|
189
|
+
from.setDate(from.getDate() - 13)
|
|
190
|
+
from.setHours(0, 0, 0, 0)
|
|
191
|
+
to.setHours(23, 59, 59, 999)
|
|
192
|
+
break
|
|
193
|
+
case 'last30':
|
|
194
|
+
from.setDate(from.getDate() - 29)
|
|
195
|
+
from.setHours(0, 0, 0, 0)
|
|
196
|
+
to.setHours(23, 59, 59, 999)
|
|
197
|
+
break
|
|
198
|
+
case 'thisWeek':
|
|
199
|
+
from.setDate(first)
|
|
200
|
+
from.setHours(0, 0, 0, 0)
|
|
201
|
+
to.setHours(23, 59, 59, 999)
|
|
202
|
+
break
|
|
203
|
+
case 'lastWeek':
|
|
204
|
+
from.setDate(from.getDate() - 7 - from.getDay())
|
|
205
|
+
to.setDate(to.getDate() - to.getDay() - 1)
|
|
206
|
+
from.setHours(0, 0, 0, 0)
|
|
207
|
+
to.setHours(23, 59, 59, 999)
|
|
208
|
+
break
|
|
209
|
+
case 'thisMonth':
|
|
210
|
+
from.setDate(1)
|
|
211
|
+
from.setHours(0, 0, 0, 0)
|
|
212
|
+
to.setHours(23, 59, 59, 999)
|
|
213
|
+
break
|
|
214
|
+
case 'lastMonth':
|
|
215
|
+
from.setMonth(from.getMonth() - 1)
|
|
216
|
+
from.setDate(1)
|
|
217
|
+
from.setHours(0, 0, 0, 0)
|
|
218
|
+
to.setDate(0)
|
|
219
|
+
to.setHours(23, 59, 59, 999)
|
|
220
|
+
break
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return { from, to }
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const setPreset = (preset: string): void => {
|
|
227
|
+
const range = getPresetRange(preset)
|
|
228
|
+
setRange(range)
|
|
229
|
+
if (rangeCompare) {
|
|
230
|
+
const rangeCompare = {
|
|
231
|
+
from: new Date(
|
|
232
|
+
range.from.getFullYear() - 1,
|
|
233
|
+
range.from.getMonth(),
|
|
234
|
+
range.from.getDate()
|
|
235
|
+
),
|
|
236
|
+
to: range.to
|
|
237
|
+
? new Date(
|
|
238
|
+
range.to.getFullYear() - 1,
|
|
239
|
+
range.to.getMonth(),
|
|
240
|
+
range.to.getDate()
|
|
241
|
+
)
|
|
242
|
+
: undefined
|
|
243
|
+
}
|
|
244
|
+
setRangeCompare(rangeCompare)
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const checkPreset = (): void => {
|
|
249
|
+
for (const preset of PRESETS) {
|
|
250
|
+
const presetRange = getPresetRange(preset.name)
|
|
251
|
+
|
|
252
|
+
const normalizedRangeFrom = new Date(range.from);
|
|
253
|
+
normalizedRangeFrom.setHours(0, 0, 0, 0);
|
|
254
|
+
const normalizedPresetFrom = new Date(
|
|
255
|
+
presetRange.from.setHours(0, 0, 0, 0)
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
const normalizedRangeTo = new Date(range.to ?? 0);
|
|
259
|
+
normalizedRangeTo.setHours(0, 0, 0, 0);
|
|
260
|
+
const normalizedPresetTo = new Date(
|
|
261
|
+
presetRange.to?.setHours(0, 0, 0, 0) ?? 0
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
if (
|
|
265
|
+
normalizedRangeFrom.getTime() === normalizedPresetFrom.getTime() &&
|
|
266
|
+
normalizedRangeTo.getTime() === normalizedPresetTo.getTime()
|
|
267
|
+
) {
|
|
268
|
+
setSelectedPreset(preset.name)
|
|
269
|
+
return
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
setSelectedPreset(undefined)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const resetValues = (): void => {
|
|
277
|
+
setRange({
|
|
278
|
+
from:
|
|
279
|
+
typeof initialDateFrom === 'string'
|
|
280
|
+
? getDateAdjustedForTimezone(initialDateFrom)
|
|
281
|
+
: initialDateFrom,
|
|
282
|
+
to: initialDateTo
|
|
283
|
+
? typeof initialDateTo === 'string'
|
|
284
|
+
? getDateAdjustedForTimezone(initialDateTo)
|
|
285
|
+
: initialDateTo
|
|
286
|
+
: typeof initialDateFrom === 'string'
|
|
287
|
+
? getDateAdjustedForTimezone(initialDateFrom)
|
|
288
|
+
: initialDateFrom
|
|
289
|
+
})
|
|
290
|
+
setRangeCompare(
|
|
291
|
+
initialCompareFrom
|
|
292
|
+
? {
|
|
293
|
+
from:
|
|
294
|
+
typeof initialCompareFrom === 'string'
|
|
295
|
+
? getDateAdjustedForTimezone(initialCompareFrom)
|
|
296
|
+
: initialCompareFrom,
|
|
297
|
+
to: initialCompareTo
|
|
298
|
+
? typeof initialCompareTo === 'string'
|
|
299
|
+
? getDateAdjustedForTimezone(initialCompareTo)
|
|
300
|
+
: initialCompareTo
|
|
301
|
+
: typeof initialCompareFrom === 'string'
|
|
302
|
+
? getDateAdjustedForTimezone(initialCompareFrom)
|
|
303
|
+
: initialCompareFrom
|
|
304
|
+
}
|
|
305
|
+
: undefined
|
|
306
|
+
)
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
useEffect(() => {
|
|
310
|
+
checkPreset()
|
|
311
|
+
}, [range])
|
|
312
|
+
|
|
313
|
+
const PresetButton = ({
|
|
314
|
+
preset,
|
|
315
|
+
label,
|
|
316
|
+
isSelected
|
|
317
|
+
}: {
|
|
318
|
+
preset: string
|
|
319
|
+
label: string
|
|
320
|
+
isSelected: boolean
|
|
321
|
+
}): JSX.Element => (
|
|
322
|
+
<Button
|
|
323
|
+
className={cn(isSelected && 'bg-muted pointer-events-none')}
|
|
324
|
+
variant="ghost"
|
|
325
|
+
onClick={() => {
|
|
326
|
+
setPreset(preset)
|
|
327
|
+
}}
|
|
328
|
+
>
|
|
329
|
+
<>
|
|
330
|
+
<span className={cn('pr-2 opacity-0', isSelected && 'opacity-70')}>
|
|
331
|
+
<CheckIcon width={18} height={18} />
|
|
332
|
+
</span>
|
|
333
|
+
{label}
|
|
334
|
+
</>
|
|
335
|
+
</Button>
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
// Helper function to check if two date ranges are equal
|
|
339
|
+
const areRangesEqual = (a?: DateRange, b?: DateRange): boolean => {
|
|
340
|
+
if (!a || !b) return a === b // If either is undefined, return true if both are undefined
|
|
341
|
+
return (
|
|
342
|
+
a.from.getTime() === b.from.getTime() &&
|
|
343
|
+
(!a.to || !b.to || a.to.getTime() === b.to.getTime())
|
|
344
|
+
)
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
useEffect(() => {
|
|
348
|
+
if (isOpen) {
|
|
349
|
+
openedRangeRef.current = range
|
|
350
|
+
openedRangeCompareRef.current = rangeCompare
|
|
351
|
+
}
|
|
352
|
+
}, [isOpen])
|
|
353
|
+
|
|
354
|
+
return (
|
|
355
|
+
<Popover
|
|
356
|
+
modal={true}
|
|
357
|
+
open={isOpen}
|
|
358
|
+
onOpenChange={(open: boolean) => {
|
|
359
|
+
if (!open) {
|
|
360
|
+
resetValues()
|
|
361
|
+
}
|
|
362
|
+
setIsOpen(open)
|
|
363
|
+
}}
|
|
364
|
+
>
|
|
365
|
+
<PopoverTrigger asChild>
|
|
366
|
+
<Button variant="outline" className={cn("rounded-md flex flex-nowrap text-nowrap",className)}>
|
|
367
|
+
<div className="text-right flex flex-nowrap text-nowrap">
|
|
368
|
+
<div className="py-1 flex flex-nowrap text-nowrap">
|
|
369
|
+
<div>{`${formatDate(range.from, locale)}${
|
|
370
|
+
range.to != null ? ' - ' + formatDate(range.to, locale) : ''
|
|
371
|
+
}`}</div>
|
|
372
|
+
</div>
|
|
373
|
+
{rangeCompare != null && (
|
|
374
|
+
<div className="opacity-60 text-xs -mt-1 flex flex-nowrap text-nowrap">
|
|
375
|
+
<>
|
|
376
|
+
vs. {formatDate(rangeCompare.from, locale)}
|
|
377
|
+
{rangeCompare.to != null
|
|
378
|
+
? ` - ${formatDate(rangeCompare.to, locale)}`
|
|
379
|
+
: ''}
|
|
380
|
+
</>
|
|
381
|
+
</div>
|
|
382
|
+
)}
|
|
383
|
+
</div>
|
|
384
|
+
<div className="pl-1 opacity-60 -mr-2 scale-125">
|
|
385
|
+
{isOpen ? (<ChevronUpIcon width={24} />) : (<ChevronDownIcon width={24} />)}
|
|
386
|
+
</div>
|
|
387
|
+
</Button>
|
|
388
|
+
</PopoverTrigger>
|
|
389
|
+
<PopoverContent align={align} className={cn("w-auto z-[101]", classNameContent)}>
|
|
390
|
+
<div className="flex py-2">
|
|
391
|
+
<div className="flex">
|
|
392
|
+
<div className="flex flex-col">
|
|
393
|
+
<div className="flex flex-col lg:flex-row gap-2 px-3 justify-end items-center lg:items-start pb-4 lg:pb-0">
|
|
394
|
+
{showCompare && (
|
|
395
|
+
<div className="flex items-center space-x-2 pr-4 py-1">
|
|
396
|
+
<Switch
|
|
397
|
+
defaultChecked={Boolean(rangeCompare)}
|
|
398
|
+
onCheckedChange={(checked: boolean) => {
|
|
399
|
+
if (checked) {
|
|
400
|
+
if (!range.to) {
|
|
401
|
+
setRange({
|
|
402
|
+
from: range.from,
|
|
403
|
+
to: range.from
|
|
404
|
+
})
|
|
405
|
+
}
|
|
406
|
+
setRangeCompare({
|
|
407
|
+
from: new Date(
|
|
408
|
+
range.from.getFullYear(),
|
|
409
|
+
range.from.getMonth(),
|
|
410
|
+
range.from.getDate() - 365
|
|
411
|
+
),
|
|
412
|
+
to: range.to
|
|
413
|
+
? new Date(
|
|
414
|
+
range.to.getFullYear() - 1,
|
|
415
|
+
range.to.getMonth(),
|
|
416
|
+
range.to.getDate()
|
|
417
|
+
)
|
|
418
|
+
: new Date(
|
|
419
|
+
range.from.getFullYear() - 1,
|
|
420
|
+
range.from.getMonth(),
|
|
421
|
+
range.from.getDate()
|
|
422
|
+
)
|
|
423
|
+
})
|
|
424
|
+
} else {
|
|
425
|
+
setRangeCompare(undefined)
|
|
426
|
+
}
|
|
427
|
+
}}
|
|
428
|
+
id="compare-mode"
|
|
429
|
+
/>
|
|
430
|
+
<Label htmlFor="compare-mode">Compare</Label>
|
|
431
|
+
</div>
|
|
432
|
+
)}
|
|
433
|
+
<div className="flex flex-col gap-2">
|
|
434
|
+
<div className="flex gap-2">
|
|
435
|
+
<DateInput
|
|
436
|
+
value={range.from}
|
|
437
|
+
onChange={(date) => {
|
|
438
|
+
const toDate =
|
|
439
|
+
range.to == null || date > range.to ? date : range.to
|
|
440
|
+
setRange((prevRange) => ({
|
|
441
|
+
...prevRange,
|
|
442
|
+
from: date,
|
|
443
|
+
to: toDate
|
|
444
|
+
}))
|
|
445
|
+
}}
|
|
446
|
+
/>
|
|
447
|
+
<div className="py-1">-</div>
|
|
448
|
+
<DateInput
|
|
449
|
+
value={range.to}
|
|
450
|
+
onChange={(date) => {
|
|
451
|
+
const fromDate = date < range.from ? date : range.from
|
|
452
|
+
setRange((prevRange) => ({
|
|
453
|
+
...prevRange,
|
|
454
|
+
from: fromDate,
|
|
455
|
+
to: date
|
|
456
|
+
}))
|
|
457
|
+
}}
|
|
458
|
+
/>
|
|
459
|
+
</div>
|
|
460
|
+
{rangeCompare != null && (
|
|
461
|
+
<div className="flex gap-2">
|
|
462
|
+
<DateInput
|
|
463
|
+
value={rangeCompare?.from}
|
|
464
|
+
onChange={(date) => {
|
|
465
|
+
if (rangeCompare) {
|
|
466
|
+
const compareToDate =
|
|
467
|
+
rangeCompare.to == null || date > rangeCompare.to
|
|
468
|
+
? date
|
|
469
|
+
: rangeCompare.to
|
|
470
|
+
setRangeCompare((prevRangeCompare) => ({
|
|
471
|
+
...prevRangeCompare,
|
|
472
|
+
from: date,
|
|
473
|
+
to: compareToDate
|
|
474
|
+
}))
|
|
475
|
+
} else {
|
|
476
|
+
setRangeCompare({
|
|
477
|
+
from: date,
|
|
478
|
+
to: new Date()
|
|
479
|
+
})
|
|
480
|
+
}
|
|
481
|
+
}}
|
|
482
|
+
/>
|
|
483
|
+
<div className="py-1">-</div>
|
|
484
|
+
<DateInput
|
|
485
|
+
value={rangeCompare?.to}
|
|
486
|
+
onChange={(date) => {
|
|
487
|
+
if (rangeCompare && rangeCompare.from) {
|
|
488
|
+
const compareFromDate =
|
|
489
|
+
date < rangeCompare.from
|
|
490
|
+
? date
|
|
491
|
+
: rangeCompare.from
|
|
492
|
+
setRangeCompare({
|
|
493
|
+
...rangeCompare,
|
|
494
|
+
from: compareFromDate,
|
|
495
|
+
to: date
|
|
496
|
+
})
|
|
497
|
+
}
|
|
498
|
+
}}
|
|
499
|
+
/>
|
|
500
|
+
</div>
|
|
501
|
+
)}
|
|
502
|
+
</div>
|
|
503
|
+
</div>
|
|
504
|
+
{ isSmallScreen && (
|
|
505
|
+
<Select defaultValue={selectedPreset} onValueChange={(value) => { setPreset(value) }}>
|
|
506
|
+
<SelectTrigger className="w-[180px] mx-auto mb-2">
|
|
507
|
+
<SelectValue placeholder="Select..." />
|
|
508
|
+
</SelectTrigger>
|
|
509
|
+
<SelectContent>
|
|
510
|
+
{PRESETS.map((preset) => (
|
|
511
|
+
<SelectItem key={preset.name} value={preset.name}>
|
|
512
|
+
{preset.label}
|
|
513
|
+
</SelectItem>
|
|
514
|
+
))}
|
|
515
|
+
</SelectContent>
|
|
516
|
+
</Select>
|
|
517
|
+
)}
|
|
518
|
+
<div>
|
|
519
|
+
<Calendar
|
|
520
|
+
mode="range"
|
|
521
|
+
onSelect={(value: { from?: Date, to?: Date } | undefined) => {
|
|
522
|
+
if (value?.from != null) {
|
|
523
|
+
setRange({ from: value.from, to: value?.to })
|
|
524
|
+
}
|
|
525
|
+
}}
|
|
526
|
+
selected={range}
|
|
527
|
+
numberOfMonths={isSmallScreen ? 1 : 2}
|
|
528
|
+
defaultMonth={
|
|
529
|
+
new Date(
|
|
530
|
+
new Date().setMonth(
|
|
531
|
+
new Date().getMonth() - (isSmallScreen ? 0 : 1)
|
|
532
|
+
)
|
|
533
|
+
)
|
|
534
|
+
}
|
|
535
|
+
/>
|
|
536
|
+
</div>
|
|
537
|
+
</div>
|
|
538
|
+
</div>
|
|
539
|
+
{!isSmallScreen && (
|
|
540
|
+
<div className="flex flex-col items-end gap-1 pr-2 pl-6 pb-6">
|
|
541
|
+
<div className="flex w-full flex-col items-end gap-1 pr-2 pl-6 pb-6">
|
|
542
|
+
{PRESETS.map((preset) => (
|
|
543
|
+
<PresetButton
|
|
544
|
+
key={preset.name}
|
|
545
|
+
preset={preset.name}
|
|
546
|
+
label={preset.label}
|
|
547
|
+
isSelected={selectedPreset === preset.name}
|
|
548
|
+
/>
|
|
549
|
+
))}
|
|
550
|
+
</div>
|
|
551
|
+
</div>
|
|
552
|
+
)}
|
|
553
|
+
</div>
|
|
554
|
+
<div className="flex justify-end gap-2 py-2 pr-4">
|
|
555
|
+
<Button
|
|
556
|
+
onClick={() => {
|
|
557
|
+
onClear?.()
|
|
558
|
+
}}
|
|
559
|
+
variant="ghost"
|
|
560
|
+
>
|
|
561
|
+
Clear
|
|
562
|
+
</Button>
|
|
563
|
+
<Button
|
|
564
|
+
onClick={() => {
|
|
565
|
+
setIsOpen(false)
|
|
566
|
+
resetValues()
|
|
567
|
+
}}
|
|
568
|
+
variant="ghost"
|
|
569
|
+
>
|
|
570
|
+
Cancel
|
|
571
|
+
</Button>
|
|
572
|
+
<Button
|
|
573
|
+
className="!text-background"
|
|
574
|
+
onClick={() => {
|
|
575
|
+
setIsOpen(false)
|
|
576
|
+
if (
|
|
577
|
+
!areRangesEqual(range, openedRangeRef.current) ||
|
|
578
|
+
!areRangesEqual(rangeCompare, openedRangeCompareRef.current)
|
|
579
|
+
) {
|
|
580
|
+
onUpdate?.({ range, rangeCompare })
|
|
581
|
+
}
|
|
582
|
+
}}
|
|
583
|
+
>
|
|
584
|
+
Update
|
|
585
|
+
</Button>
|
|
586
|
+
</div>
|
|
587
|
+
</PopoverContent>
|
|
588
|
+
</Popover>
|
|
589
|
+
)
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
DateRangePicker.displayName = 'DateRangePicker'
|
|
593
|
+
DateRangePicker.filePath =
|
|
594
|
+
'libs/shared/ui-kit/src/lib/date-range-picker/date-range-picker.tsx'
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
cva,
|
|
7
|
+
type VariantProps,
|
|
8
|
+
} from 'class-variance-authority';
|
|
9
|
+
|
|
10
|
+
import * as LabelPrimitive from '@radix-ui/react-label';
|
|
11
|
+
|
|
12
|
+
import { cn } from '../../lib/utils';
|
|
13
|
+
|
|
14
|
+
const labelVariants = cva(
|
|
15
|
+
'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70'
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
const Label = React.forwardRef<
|
|
19
|
+
React.ElementRef<typeof LabelPrimitive.Root>,
|
|
20
|
+
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
|
|
21
|
+
VariantProps<typeof labelVariants>
|
|
22
|
+
>(({ className, ...props }, ref) => (
|
|
23
|
+
<LabelPrimitive.Root
|
|
24
|
+
ref={ref}
|
|
25
|
+
className={cn(labelVariants(), className)}
|
|
26
|
+
{...props}
|
|
27
|
+
/>
|
|
28
|
+
))
|
|
29
|
+
Label.displayName = LabelPrimitive.Root.displayName
|
|
30
|
+
|
|
31
|
+
export { Label };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
|
|
5
|
+
import * as PopoverPrimitive from '@radix-ui/react-popover';
|
|
6
|
+
|
|
7
|
+
import { cn } from '../../lib/utils';
|
|
8
|
+
|
|
9
|
+
const Popover = PopoverPrimitive.Root
|
|
10
|
+
|
|
11
|
+
const PopoverTrigger = PopoverPrimitive.Trigger
|
|
12
|
+
|
|
13
|
+
const PopoverContent = React.forwardRef<
|
|
14
|
+
React.ElementRef<typeof PopoverPrimitive.Content>,
|
|
15
|
+
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
|
|
16
|
+
>(({ className, align = 'center', sideOffset = 4, ...props }, ref) => (
|
|
17
|
+
<PopoverPrimitive.Portal>
|
|
18
|
+
<PopoverPrimitive.Content
|
|
19
|
+
ref={ref}
|
|
20
|
+
align={align}
|
|
21
|
+
sideOffset={sideOffset}
|
|
22
|
+
className={cn(
|
|
23
|
+
'z-50 w-72 rounded-md border bg-white p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
|
24
|
+
className
|
|
25
|
+
)}
|
|
26
|
+
{...props}
|
|
27
|
+
/>
|
|
28
|
+
</PopoverPrimitive.Portal>
|
|
29
|
+
))
|
|
30
|
+
PopoverContent.displayName = PopoverPrimitive.Content.displayName
|
|
31
|
+
|
|
32
|
+
export { Popover, PopoverContent, PopoverTrigger };
|