@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@snapdragonsnursery/react-components",
3
- "version": "1.18.4",
3
+ "version": "1.19.2",
4
4
  "description": "",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -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(`Failed to fetch profile picture for ${entraId}: Non-200 response`);
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 defaultDataColumns = [
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
- <div className="text-sm text-gray-500">
331
- ID: {row.original.employee_id}
332
- </div>
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
- <EmployeeSearchFilters
889
- filters={advancedFilters}
890
- onFiltersChange={setAdvancedFilters}
891
- onApplyFilters={setAdvancedFilters}
892
- sites={sites}
893
- roles={roles}
894
- managers={managers}
895
- activeOnly={activeOnly}
896
- isAdvancedFiltersOpen={isAdvancedFiltersOpen}
897
- onToggleAdvancedFilters={() =>
898
- setIsAdvancedFiltersOpen(!isAdvancedFiltersOpen)
899
- }
900
- onClearFilters={clearFilters}
901
- showRoleFilter={showRoleFilter}
902
- showManagerFilter={showManagerFilter}
903
- showTermTimeFilter={showTermTimeFilter}
904
- showMaternityFilter={showMaternityFilter}
905
- showStartDateFilter={showStartDateFilter}
906
- showEndDateFilter={showEndDateFilter}
907
- showDbsFilter={showDbsFilter}
908
- showWorkingHoursFilter={showWorkingHoursFilter}
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 'react'
7
- import { format, addDays, addMonths, endOfMonth, endOfYear, startOfMonth, startOfYear, subDays, subMonths, subYears } from 'date-fns'
8
- import { CalendarIcon } from '@heroicons/react/24/outline'
9
- import { Popover, PopoverContent, PopoverTrigger } from './popover'
10
- import { Calendar } from './calendar'
11
- import { Button } from './button'
12
- import { cn } from '../../lib/utils'
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
- 'w-full justify-start text-left font-normal',
167
- !internalRange?.from && 'text-muted-foreground',
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, 'LLL dd, y')} -{' '}
178
- {format(internalRange.to, 'LLL dd, y')}
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 className={cn('w-[90vw] p-3 sm:w-auto', contentClassName)} align="start">
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('w-full', calendarClassName)}
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((p) => {
207
- const r = p.getRange()
208
- return (
209
- <Button
210
- key={p.key}
211
- variant="outline"
212
- size="sm"
213
- onClick={() => {
214
- setInternalRange(r)
215
- isSelectingRange.current = false
216
- onSelect(r)
217
- }}
218
- >
219
- {p.label}
220
- </Button>
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 = { from: startOfMonth(subMonths(today, 1)), to: endOfMonth(subMonths(today, 1)) }
256
- const lastYear = { from: startOfYear(subYears(today, 1)), to: endOfYear(subYears(today, 1)) }
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: 'last7', label: 'Last 7 days', getRange: () => last7Days },
259
- { key: 'mtd', label: 'Month to date', getRange: () => monthToDate },
260
- { key: 'lastMonth', label: 'Last month', getRange: () => lastMonth },
261
- { key: 'ytd', label: 'Year to date', getRange: () => yearToDate },
262
- { key: 'lastYear', label: 'Last year', getRange: () => lastYear },
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
- 'w-full justify-start text-left font-normal',
290
- !selectedDate && 'text-muted-foreground',
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, 'PPP')
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
  }