@snapdragonsnursery/react-components 1.18.4 → 1.19.2
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/package.json +1 -1
- package/src/EmployeeSearchModal.jsx +50 -29
- package/src/components/ui/date-range-picker.jsx +125 -105
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Employee Search Modal Component
|
|
2
2
|
// Provides a modal interface for employee search with advanced filtering and selection capabilities
|
|
3
|
-
// Usage: <EmployeeSearchModal isOpen={isOpen} onClose={onClose} onSelect={handleSelect} />
|
|
3
|
+
// Usage: <EmployeeSearchModal isOpen={isOpen} onClose={onClose} onSelect={handleSelect} showFiltersPanel={false} />
|
|
4
4
|
|
|
5
5
|
import React, { useState, useEffect, useCallback } from "react";
|
|
6
6
|
import { useMsal } from "@azure/msal-react";
|
|
@@ -47,7 +47,7 @@ const EmployeeSearchModal = ({
|
|
|
47
47
|
onClose,
|
|
48
48
|
onSelect,
|
|
49
49
|
title = "Select Employee",
|
|
50
|
-
searchPlaceholder = "Search by name, ID, or email address...",
|
|
50
|
+
searchPlaceholder = "Search by name, Employee ID, or email address...",
|
|
51
51
|
siteId = null,
|
|
52
52
|
siteIds = null,
|
|
53
53
|
sites = null,
|
|
@@ -90,6 +90,8 @@ const EmployeeSearchModal = ({
|
|
|
90
90
|
visibleColumns = null,
|
|
91
91
|
columnLabels = {},
|
|
92
92
|
columnRenderers = {},
|
|
93
|
+
showFiltersPanel = true,
|
|
94
|
+
dataProfile = "full",
|
|
93
95
|
}) => {
|
|
94
96
|
const [searchTerm, setSearchTerm] = useState("");
|
|
95
97
|
const [employees, setEmployees] = useState([]);
|
|
@@ -219,7 +221,9 @@ const EmployeeSearchModal = ({
|
|
|
219
221
|
}
|
|
220
222
|
}
|
|
221
223
|
|
|
222
|
-
console.warn(
|
|
224
|
+
console.warn(
|
|
225
|
+
`Failed to fetch profile picture for ${entraId}: Non-200 response`
|
|
226
|
+
);
|
|
223
227
|
} catch (error) {
|
|
224
228
|
console.warn(`Error fetching profile picture for ${entraId}:`, error);
|
|
225
229
|
} finally {
|
|
@@ -315,7 +319,7 @@ const EmployeeSearchModal = ({
|
|
|
315
319
|
});
|
|
316
320
|
|
|
317
321
|
// Define default data columns (excluding optional select)
|
|
318
|
-
const
|
|
322
|
+
const basicColumns = [
|
|
319
323
|
columnHelper.accessor("full_name", {
|
|
320
324
|
header: createSortableHeader(
|
|
321
325
|
"full_name",
|
|
@@ -327,9 +331,11 @@ const EmployeeSearchModal = ({
|
|
|
327
331
|
) : (
|
|
328
332
|
<div>
|
|
329
333
|
<div className="font-medium">{row.original.full_name}</div>
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
334
|
+
{dataProfile !== "basic" && row.original.employee_id ? (
|
|
335
|
+
<div className="text-sm text-gray-500">
|
|
336
|
+
ID: {row.original.employee_id}
|
|
337
|
+
</div>
|
|
338
|
+
) : null}
|
|
333
339
|
</div>
|
|
334
340
|
),
|
|
335
341
|
}),
|
|
@@ -366,6 +372,9 @@ const EmployeeSearchModal = ({
|
|
|
366
372
|
<span className="text-sm">{row.original.email}</span>
|
|
367
373
|
),
|
|
368
374
|
}),
|
|
375
|
+
];
|
|
376
|
+
|
|
377
|
+
const extendedColumns = [
|
|
369
378
|
columnHelper.accessor("start_date", {
|
|
370
379
|
header: createSortableHeader(
|
|
371
380
|
"start_date",
|
|
@@ -429,6 +438,11 @@ const EmployeeSearchModal = ({
|
|
|
429
438
|
}),
|
|
430
439
|
];
|
|
431
440
|
|
|
441
|
+
const defaultDataColumns =
|
|
442
|
+
dataProfile === "basic"
|
|
443
|
+
? basicColumns
|
|
444
|
+
: [...basicColumns, ...extendedColumns];
|
|
445
|
+
|
|
432
446
|
const selectColumn = columnHelper.display({
|
|
433
447
|
id: "select",
|
|
434
448
|
header: ({ table }) => (
|
|
@@ -608,6 +622,10 @@ const EmployeeSearchModal = ({
|
|
|
608
622
|
bypass_permissions: bypassPermissions.toString(),
|
|
609
623
|
});
|
|
610
624
|
|
|
625
|
+
if (dataProfile && dataProfile !== "full") {
|
|
626
|
+
params.append("data_profile", dataProfile);
|
|
627
|
+
}
|
|
628
|
+
|
|
611
629
|
// Handle site filtering
|
|
612
630
|
if (siteIds && siteIds.length > 0) {
|
|
613
631
|
params.append("site_ids", siteIds.join(","));
|
|
@@ -745,6 +763,7 @@ const EmployeeSearchModal = ({
|
|
|
745
763
|
debouncedAdvancedFilters,
|
|
746
764
|
applicationContext,
|
|
747
765
|
bypassPermissions,
|
|
766
|
+
dataProfile,
|
|
748
767
|
]);
|
|
749
768
|
|
|
750
769
|
// Search when debounced term changes
|
|
@@ -885,28 +904,30 @@ const EmployeeSearchModal = ({
|
|
|
885
904
|
</div>
|
|
886
905
|
|
|
887
906
|
{/* Advanced Filters */}
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
907
|
+
{showFiltersPanel && (
|
|
908
|
+
<EmployeeSearchFilters
|
|
909
|
+
filters={advancedFilters}
|
|
910
|
+
onFiltersChange={setAdvancedFilters}
|
|
911
|
+
onApplyFilters={setAdvancedFilters}
|
|
912
|
+
sites={sites}
|
|
913
|
+
roles={roles}
|
|
914
|
+
managers={managers}
|
|
915
|
+
activeOnly={activeOnly}
|
|
916
|
+
isAdvancedFiltersOpen={isAdvancedFiltersOpen}
|
|
917
|
+
onToggleAdvancedFilters={() =>
|
|
918
|
+
setIsAdvancedFiltersOpen(!isAdvancedFiltersOpen)
|
|
919
|
+
}
|
|
920
|
+
onClearFilters={clearFilters}
|
|
921
|
+
showRoleFilter={showRoleFilter}
|
|
922
|
+
showManagerFilter={showManagerFilter}
|
|
923
|
+
showTermTimeFilter={showTermTimeFilter}
|
|
924
|
+
showMaternityFilter={showMaternityFilter}
|
|
925
|
+
showStartDateFilter={showStartDateFilter}
|
|
926
|
+
showEndDateFilter={showEndDateFilter}
|
|
927
|
+
showDbsFilter={showDbsFilter}
|
|
928
|
+
showWorkingHoursFilter={showWorkingHoursFilter}
|
|
929
|
+
/>
|
|
930
|
+
)}
|
|
910
931
|
|
|
911
932
|
{/* Results */}
|
|
912
933
|
<div className="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700">
|
|
@@ -3,18 +3,29 @@
|
|
|
3
3
|
// Features cycling behavior: start date -> end date -> start date -> end date...
|
|
4
4
|
// Usage: <DateRangePicker selectedRange={range} onSelect={setRange} />
|
|
5
5
|
|
|
6
|
-
import React, { useState, useEffect, useRef } from
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
6
|
+
import React, { useState, useEffect, useRef } from "react";
|
|
7
|
+
import {
|
|
8
|
+
format,
|
|
9
|
+
addDays,
|
|
10
|
+
addMonths,
|
|
11
|
+
endOfMonth,
|
|
12
|
+
endOfYear,
|
|
13
|
+
startOfMonth,
|
|
14
|
+
startOfYear,
|
|
15
|
+
subDays,
|
|
16
|
+
subMonths,
|
|
17
|
+
subYears,
|
|
18
|
+
} from "date-fns";
|
|
19
|
+
import { CalendarIcon } from "@heroicons/react/24/outline";
|
|
20
|
+
import { Popover, PopoverContent, PopoverTrigger } from "./popover";
|
|
21
|
+
import { Calendar } from "./calendar";
|
|
22
|
+
import { Button } from "./button";
|
|
23
|
+
import { cn } from "../../lib/utils";
|
|
13
24
|
|
|
14
25
|
// Optional preset type: { key: string, label: string, getRange: () => ({ from: Date, to: Date }) }
|
|
15
|
-
export function DateRangePicker({
|
|
16
|
-
selectedRange,
|
|
17
|
-
onSelect,
|
|
26
|
+
export function DateRangePicker({
|
|
27
|
+
selectedRange,
|
|
28
|
+
onSelect,
|
|
18
29
|
className,
|
|
19
30
|
placeholder = "Select a date range",
|
|
20
31
|
disabled,
|
|
@@ -26,87 +37,87 @@ export function DateRangePicker({
|
|
|
26
37
|
// New: allow styling popover content and calendar for layout control
|
|
27
38
|
contentClassName,
|
|
28
39
|
calendarClassName,
|
|
29
|
-
...props
|
|
40
|
+
...props
|
|
30
41
|
}) {
|
|
31
|
-
const [isOpen, setIsOpen] = useState(false)
|
|
32
|
-
const [internalRange, setInternalRange] = useState(selectedRange)
|
|
33
|
-
const isSelectingRange = useRef(false)
|
|
42
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
43
|
+
const [internalRange, setInternalRange] = useState(selectedRange);
|
|
44
|
+
const isSelectingRange = useRef(false);
|
|
34
45
|
|
|
35
46
|
const normalizeDate = (date) =>
|
|
36
47
|
date
|
|
37
48
|
? new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()))
|
|
38
|
-
: null
|
|
49
|
+
: null;
|
|
39
50
|
|
|
40
51
|
const datesEqual = (a, b) =>
|
|
41
|
-
a instanceof Date && b instanceof Date && a.getTime() === b.getTime()
|
|
52
|
+
a instanceof Date && b instanceof Date && a.getTime() === b.getTime();
|
|
42
53
|
|
|
43
54
|
const handleOpenChange = (open) => {
|
|
44
55
|
// If we're in the middle of selecting a range and trying to close, prevent it
|
|
45
56
|
if (!open && isSelectingRange.current) {
|
|
46
57
|
// Instead of returning, let's try to keep it open
|
|
47
58
|
setTimeout(() => {
|
|
48
|
-
setIsOpen(true)
|
|
49
|
-
}, 0)
|
|
50
|
-
return
|
|
59
|
+
setIsOpen(true);
|
|
60
|
+
}, 0);
|
|
61
|
+
return;
|
|
51
62
|
}
|
|
52
|
-
|
|
63
|
+
|
|
53
64
|
// When opening, set the selecting range flag based on whether we have a partial selection
|
|
54
65
|
if (open) {
|
|
55
66
|
if (internalRange?.from && !internalRange?.to) {
|
|
56
|
-
isSelectingRange.current = true
|
|
67
|
+
isSelectingRange.current = true;
|
|
57
68
|
} else {
|
|
58
|
-
isSelectingRange.current = false
|
|
69
|
+
isSelectingRange.current = false;
|
|
59
70
|
}
|
|
60
71
|
}
|
|
61
|
-
|
|
62
|
-
setIsOpen(open)
|
|
63
|
-
}
|
|
72
|
+
|
|
73
|
+
setIsOpen(open);
|
|
74
|
+
};
|
|
64
75
|
|
|
65
76
|
// Update internal range when prop changes
|
|
66
77
|
useEffect(() => {
|
|
67
|
-
setInternalRange(selectedRange)
|
|
68
|
-
}, [selectedRange])
|
|
78
|
+
setInternalRange(selectedRange);
|
|
79
|
+
}, [selectedRange]);
|
|
69
80
|
|
|
70
81
|
const handleSelect = (range, selectedDay) => {
|
|
71
82
|
if (!range || (!range.from && !range.to)) {
|
|
72
|
-
return
|
|
83
|
+
return;
|
|
73
84
|
}
|
|
74
85
|
|
|
75
86
|
const normalizedRange = {
|
|
76
87
|
from: normalizeDate(range.from),
|
|
77
88
|
to: normalizeDate(range.to),
|
|
78
|
-
}
|
|
79
|
-
const normalizedSelectedDay = normalizeDate(selectedDay)
|
|
89
|
+
};
|
|
90
|
+
const normalizedSelectedDay = normalizeDate(selectedDay);
|
|
80
91
|
|
|
81
92
|
const hasCompleteCurrentRange = Boolean(
|
|
82
93
|
internalRange?.from && internalRange?.to
|
|
83
|
-
)
|
|
94
|
+
);
|
|
84
95
|
|
|
85
|
-
let newRange = normalizedRange
|
|
96
|
+
let newRange = normalizedRange;
|
|
86
97
|
|
|
87
98
|
if (normalizedRange.from && normalizedRange.to) {
|
|
88
99
|
if (hasCompleteCurrentRange && !isSelectingRange.current) {
|
|
89
|
-
const sameStart = datesEqual(normalizedRange.from, internalRange.from)
|
|
100
|
+
const sameStart = datesEqual(normalizedRange.from, internalRange.from);
|
|
90
101
|
|
|
91
102
|
if (sameStart) {
|
|
92
103
|
// Adjusting end date on an existing range
|
|
93
104
|
newRange = {
|
|
94
105
|
from: internalRange.from,
|
|
95
106
|
to: normalizedRange.to,
|
|
96
|
-
}
|
|
107
|
+
};
|
|
97
108
|
} else {
|
|
98
109
|
// Starting a new range selection cycle
|
|
99
110
|
newRange = {
|
|
100
111
|
from: normalizedSelectedDay ?? normalizedRange.from,
|
|
101
112
|
to: null,
|
|
102
|
-
}
|
|
113
|
+
};
|
|
103
114
|
}
|
|
104
115
|
} else if (internalRange?.from && !internalRange?.to) {
|
|
105
116
|
// Completing an in-progress range
|
|
106
117
|
newRange = {
|
|
107
118
|
from: internalRange.from,
|
|
108
119
|
to: normalizedRange.to,
|
|
109
|
-
}
|
|
120
|
+
};
|
|
110
121
|
}
|
|
111
122
|
} else if (normalizedRange.from && !normalizedRange.to) {
|
|
112
123
|
if (hasCompleteCurrentRange && !isSelectingRange.current) {
|
|
@@ -114,48 +125,48 @@ export function DateRangePicker({
|
|
|
114
125
|
newRange = {
|
|
115
126
|
from: normalizedSelectedDay ?? normalizedRange.from,
|
|
116
127
|
to: null,
|
|
117
|
-
}
|
|
128
|
+
};
|
|
118
129
|
} else {
|
|
119
130
|
newRange = {
|
|
120
131
|
from: normalizedRange.from,
|
|
121
132
|
to: null,
|
|
122
|
-
}
|
|
133
|
+
};
|
|
123
134
|
}
|
|
124
135
|
} else {
|
|
125
|
-
newRange = null
|
|
136
|
+
newRange = null;
|
|
126
137
|
}
|
|
127
138
|
|
|
128
|
-
setInternalRange(newRange)
|
|
139
|
+
setInternalRange(newRange);
|
|
129
140
|
|
|
130
141
|
if (newRange?.from && !newRange?.to) {
|
|
131
|
-
isSelectingRange.current = true
|
|
142
|
+
isSelectingRange.current = true;
|
|
132
143
|
} else {
|
|
133
|
-
isSelectingRange.current = false
|
|
144
|
+
isSelectingRange.current = false;
|
|
134
145
|
}
|
|
135
146
|
|
|
136
147
|
if (!newRange?.from || newRange?.to) {
|
|
137
|
-
onSelect(newRange)
|
|
148
|
+
onSelect(newRange);
|
|
138
149
|
}
|
|
139
|
-
}
|
|
150
|
+
};
|
|
140
151
|
|
|
141
152
|
return (
|
|
142
|
-
<Popover
|
|
143
|
-
open={isOpen}
|
|
144
|
-
onOpenChange={handleOpenChange}
|
|
153
|
+
<Popover
|
|
154
|
+
open={isOpen}
|
|
155
|
+
onOpenChange={handleOpenChange}
|
|
145
156
|
modal={false}
|
|
146
157
|
onPointerDownOutside={(e) => {
|
|
147
158
|
if (isSelectingRange.current) {
|
|
148
|
-
e.preventDefault()
|
|
159
|
+
e.preventDefault();
|
|
149
160
|
}
|
|
150
161
|
}}
|
|
151
162
|
onEscapeKeyDown={(e) => {
|
|
152
163
|
if (isSelectingRange.current) {
|
|
153
|
-
e.preventDefault()
|
|
164
|
+
e.preventDefault();
|
|
154
165
|
}
|
|
155
166
|
}}
|
|
156
167
|
onInteractOutside={(e) => {
|
|
157
168
|
if (isSelectingRange.current) {
|
|
158
|
-
e.preventDefault()
|
|
169
|
+
e.preventDefault();
|
|
159
170
|
}
|
|
160
171
|
}}
|
|
161
172
|
>
|
|
@@ -163,8 +174,8 @@ export function DateRangePicker({
|
|
|
163
174
|
<Button
|
|
164
175
|
variant="outline"
|
|
165
176
|
className={cn(
|
|
166
|
-
|
|
167
|
-
!internalRange?.from &&
|
|
177
|
+
"w-full justify-start text-left font-normal",
|
|
178
|
+
!internalRange?.from && "text-muted-foreground",
|
|
168
179
|
className
|
|
169
180
|
)}
|
|
170
181
|
disabled={disabled}
|
|
@@ -174,20 +185,21 @@ export function DateRangePicker({
|
|
|
174
185
|
{internalRange?.from ? (
|
|
175
186
|
internalRange.to ? (
|
|
176
187
|
<>
|
|
177
|
-
{format(internalRange.from,
|
|
178
|
-
{format(internalRange.to,
|
|
188
|
+
{format(internalRange.from, "LLL dd, y")} -{" "}
|
|
189
|
+
{format(internalRange.to, "LLL dd, y")}
|
|
179
190
|
</>
|
|
180
191
|
) : (
|
|
181
|
-
<>
|
|
182
|
-
{format(internalRange.from, 'LLL dd, y')} - Select end date
|
|
183
|
-
</>
|
|
192
|
+
<>{format(internalRange.from, "LLL dd, y")} - Select end date</>
|
|
184
193
|
)
|
|
185
194
|
) : (
|
|
186
195
|
<span>{placeholder}</span>
|
|
187
196
|
)}
|
|
188
197
|
</Button>
|
|
189
198
|
</PopoverTrigger>
|
|
190
|
-
<PopoverContent
|
|
199
|
+
<PopoverContent
|
|
200
|
+
className={cn("w-[90vw] p-3 sm:w-auto", contentClassName)}
|
|
201
|
+
align="start"
|
|
202
|
+
>
|
|
191
203
|
<div className="relative w-full sm:w-[520px]">
|
|
192
204
|
<Calendar
|
|
193
205
|
mode="range"
|
|
@@ -195,31 +207,33 @@ export function DateRangePicker({
|
|
|
195
207
|
selected={internalRange}
|
|
196
208
|
onSelect={(range, selectedDay) => {
|
|
197
209
|
if (range && (range.from || range.to)) {
|
|
198
|
-
handleSelect(range, selectedDay)
|
|
210
|
+
handleSelect(range, selectedDay);
|
|
199
211
|
}
|
|
200
212
|
}}
|
|
201
213
|
numberOfMonths={numberOfMonths}
|
|
202
|
-
className={cn(
|
|
214
|
+
className={cn("w-full", calendarClassName)}
|
|
203
215
|
/>
|
|
204
216
|
{presetsEnabled && (
|
|
205
217
|
<div className="mt-2 flex flex-wrap gap-2 p-2">
|
|
206
|
-
{(presets && presets.length > 0 ? presets : defaultPresets()).map(
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
218
|
+
{(presets && presets.length > 0 ? presets : defaultPresets()).map(
|
|
219
|
+
(p) => {
|
|
220
|
+
const r = p.getRange();
|
|
221
|
+
return (
|
|
222
|
+
<Button
|
|
223
|
+
key={p.key}
|
|
224
|
+
variant="outline"
|
|
225
|
+
size="sm"
|
|
226
|
+
onClick={() => {
|
|
227
|
+
setInternalRange(r);
|
|
228
|
+
isSelectingRange.current = false;
|
|
229
|
+
onSelect(r);
|
|
230
|
+
}}
|
|
231
|
+
>
|
|
232
|
+
{p.label}
|
|
233
|
+
</Button>
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
)}
|
|
223
237
|
</div>
|
|
224
238
|
)}
|
|
225
239
|
{/* Always-present footer actions for clarity on mobile/desktop */}
|
|
@@ -228,9 +242,9 @@ export function DateRangePicker({
|
|
|
228
242
|
variant="ghost"
|
|
229
243
|
size="sm"
|
|
230
244
|
onClick={() => {
|
|
231
|
-
setInternalRange(null)
|
|
232
|
-
isSelectingRange.current = false
|
|
233
|
-
onSelect(null)
|
|
245
|
+
setInternalRange(null);
|
|
246
|
+
isSelectingRange.current = false;
|
|
247
|
+
onSelect(null);
|
|
234
248
|
}}
|
|
235
249
|
>
|
|
236
250
|
Clear
|
|
@@ -243,42 +257,48 @@ export function DateRangePicker({
|
|
|
243
257
|
</div>
|
|
244
258
|
</PopoverContent>
|
|
245
259
|
</Popover>
|
|
246
|
-
)
|
|
260
|
+
);
|
|
247
261
|
}
|
|
248
262
|
|
|
249
263
|
// Provide a default preset set similar to app usage, but minimal
|
|
250
264
|
function defaultPresets() {
|
|
251
|
-
const today = new Date()
|
|
252
|
-
const last7Days = { from: subDays(today, 6), to: today }
|
|
253
|
-
const monthToDate = { from: startOfMonth(today), to: today }
|
|
254
|
-
const yearToDate = { from: startOfYear(today), to: today }
|
|
255
|
-
const lastMonth = {
|
|
256
|
-
|
|
265
|
+
const today = new Date();
|
|
266
|
+
const last7Days = { from: subDays(today, 6), to: today };
|
|
267
|
+
const monthToDate = { from: startOfMonth(today), to: today };
|
|
268
|
+
const yearToDate = { from: startOfYear(today), to: today };
|
|
269
|
+
const lastMonth = {
|
|
270
|
+
from: startOfMonth(subMonths(today, 1)),
|
|
271
|
+
to: endOfMonth(subMonths(today, 1)),
|
|
272
|
+
};
|
|
273
|
+
const lastYear = {
|
|
274
|
+
from: startOfYear(subYears(today, 1)),
|
|
275
|
+
to: endOfYear(subYears(today, 1)),
|
|
276
|
+
};
|
|
257
277
|
return [
|
|
258
|
-
{ key:
|
|
259
|
-
{ key:
|
|
260
|
-
{ key:
|
|
261
|
-
{ key:
|
|
262
|
-
{ key:
|
|
263
|
-
]
|
|
278
|
+
{ key: "last7", label: "Last 7 days", getRange: () => last7Days },
|
|
279
|
+
{ key: "mtd", label: "Month to date", getRange: () => monthToDate },
|
|
280
|
+
{ key: "lastMonth", label: "Last month", getRange: () => lastMonth },
|
|
281
|
+
{ key: "ytd", label: "Year to date", getRange: () => yearToDate },
|
|
282
|
+
{ key: "lastYear", label: "Last year", getRange: () => lastYear },
|
|
283
|
+
];
|
|
264
284
|
}
|
|
265
285
|
|
|
266
286
|
// Single date picker variant
|
|
267
|
-
export function DatePicker({
|
|
268
|
-
selectedDate,
|
|
269
|
-
onSelect,
|
|
287
|
+
export function DatePicker({
|
|
288
|
+
selectedDate,
|
|
289
|
+
onSelect,
|
|
270
290
|
className,
|
|
271
291
|
placeholder = "Select a date",
|
|
272
292
|
disabled,
|
|
273
293
|
disableFuture = false,
|
|
274
|
-
...props
|
|
294
|
+
...props
|
|
275
295
|
}) {
|
|
276
|
-
const [isOpen, setIsOpen] = useState(false)
|
|
296
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
277
297
|
|
|
278
298
|
const handleSelect = (date) => {
|
|
279
|
-
onSelect(date)
|
|
280
|
-
setIsOpen(false)
|
|
281
|
-
}
|
|
299
|
+
onSelect(date);
|
|
300
|
+
setIsOpen(false);
|
|
301
|
+
};
|
|
282
302
|
|
|
283
303
|
return (
|
|
284
304
|
<Popover open={isOpen} onOpenChange={setIsOpen} modal={false}>
|
|
@@ -286,8 +306,8 @@ export function DatePicker({
|
|
|
286
306
|
<Button
|
|
287
307
|
variant="outline"
|
|
288
308
|
className={cn(
|
|
289
|
-
|
|
290
|
-
!selectedDate &&
|
|
309
|
+
"w-full justify-start text-left font-normal",
|
|
310
|
+
!selectedDate && "text-muted-foreground",
|
|
291
311
|
className
|
|
292
312
|
)}
|
|
293
313
|
disabled={disabled}
|
|
@@ -295,7 +315,7 @@ export function DatePicker({
|
|
|
295
315
|
>
|
|
296
316
|
<CalendarIcon className="mr-2 h-4 w-4" />
|
|
297
317
|
{selectedDate ? (
|
|
298
|
-
format(selectedDate,
|
|
318
|
+
format(selectedDate, "PPP")
|
|
299
319
|
) : (
|
|
300
320
|
<span>{placeholder}</span>
|
|
301
321
|
)}
|
|
@@ -311,5 +331,5 @@ export function DatePicker({
|
|
|
311
331
|
/>
|
|
312
332
|
</PopoverContent>
|
|
313
333
|
</Popover>
|
|
314
|
-
)
|
|
334
|
+
);
|
|
315
335
|
}
|