@snapdragonsnursery/react-components 1.17.3 → 1.17.5

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.17.3",
3
+ "version": "1.17.5",
4
4
  "description": "",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -47,7 +47,7 @@ const EmployeeSearchModal = ({
47
47
  onClose,
48
48
  onSelect,
49
49
  title = "Select Employee",
50
- searchPlaceholder = "Search by name, employee ID, or email...",
50
+ searchPlaceholder = "Search by name, ID, or email address...",
51
51
  siteId = null,
52
52
  siteIds = null,
53
53
  sites = null,
@@ -984,7 +984,7 @@ const EmployeeSearchModal = ({
984
984
  >
985
985
  {debouncedSearchTerm
986
986
  ? "No employees found matching your search."
987
- : "Start typing to search for employees."}
987
+ : "Type a name, ID, or email to find an employee"}
988
988
  </TableCell>
989
989
  </TableRow>
990
990
  )}
@@ -995,7 +995,7 @@ const EmployeeSearchModal = ({
995
995
  <div className="p-6 text-center text-gray-500 dark:text-gray-400">
996
996
  {debouncedSearchTerm
997
997
  ? "No employees found matching your search."
998
- : "Start typing to search for employees."}
998
+ : "Type a name, ID, or email to find an employee"}
999
999
  </div>
1000
1000
  )}
1001
1001
 
@@ -61,9 +61,28 @@ export const EmployeeSelect = ({
61
61
  allowAll = false,
62
62
  allLabel = "All employees",
63
63
  maxHeight = "160px",
64
+ // New props for server-side search
65
+ enableServerSearch = false,
66
+ onSearchChange = null,
67
+ searchResults = [],
68
+ isSearching = false,
64
69
  }) => {
65
70
  const [searchTerm, setSearchTerm] = React.useState("");
66
71
  const [isOpen, setIsOpen] = React.useState(false);
72
+ const [debouncedSearchTerm, setDebouncedSearchTerm] = React.useState("");
73
+
74
+ // Debounce search term for server-side search
75
+ React.useEffect(() => {
76
+ const timer = setTimeout(() => {
77
+ setDebouncedSearchTerm(searchTerm);
78
+ if (enableServerSearch && onSearchChange) {
79
+ onSearchChange(searchTerm);
80
+ }
81
+ }, 300); // 300ms debounce
82
+
83
+ return () => clearTimeout(timer);
84
+ }, [searchTerm, enableServerSearch, onSearchChange]);
85
+
67
86
  // Helper function to generate initials from full name
68
87
  const getInitials = (name) => {
69
88
  if (!name) return "??";
@@ -88,7 +107,9 @@ export const EmployeeSelect = ({
88
107
  };
89
108
 
90
109
  const processedItems = React.useMemo(() => {
91
- const list = Array.isArray(items) ? [...items] : [];
110
+ // Use server search results if enabled, otherwise use items
111
+ const sourceList = enableServerSearch ? searchResults : items;
112
+ const list = Array.isArray(sourceList) ? [...sourceList] : [];
92
113
 
93
114
  // Filter by employee status
94
115
  const filtered = list.filter((e) => {
@@ -110,18 +131,20 @@ export const EmployeeSelect = ({
110
131
  }))
111
132
  .filter((e) => e.entraId); // Remove items without entra_id
112
133
 
113
- // Apply search filter
114
- const searchFiltered = mapped.filter((e) => {
115
- if (!searchTerm) return true;
116
- const searchLower = searchTerm.toLowerCase();
117
- return (
118
- e.fullName.toLowerCase().includes(searchLower) ||
119
- e.siteName.toLowerCase().includes(searchLower) ||
120
- e.roleName.toLowerCase().includes(searchLower) ||
121
- e.employeeId.toLowerCase().includes(searchLower) ||
122
- e.email.toLowerCase().includes(searchLower)
123
- );
124
- });
134
+ // Apply client-side search filter only if not using server search
135
+ const searchFiltered = enableServerSearch
136
+ ? mapped // Server search already filtered
137
+ : mapped.filter((e) => {
138
+ if (!searchTerm) return true;
139
+ const searchLower = searchTerm.toLowerCase();
140
+ return (
141
+ e.fullName.toLowerCase().includes(searchLower) ||
142
+ e.siteName.toLowerCase().includes(searchLower) ||
143
+ e.roleName.toLowerCase().includes(searchLower) ||
144
+ e.employeeId.toLowerCase().includes(searchLower) ||
145
+ e.email.toLowerCase().includes(searchLower)
146
+ );
147
+ });
125
148
 
126
149
  // Sort based on grouping
127
150
  searchFiltered.sort((a, b) => {
@@ -139,7 +162,7 @@ export const EmployeeSelect = ({
139
162
  });
140
163
 
141
164
  return searchFiltered;
142
- }, [items, filter, groupBy, searchTerm]);
165
+ }, [items, filter, groupBy, searchTerm, enableServerSearch, searchResults]);
143
166
 
144
167
  const selectedEmployee = processedItems.find((e) => e.entraId === value);
145
168
 
@@ -217,12 +240,17 @@ export const EmployeeSelect = ({
217
240
  <Search className="absolute left-2 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
218
241
  <input
219
242
  type="text"
220
- placeholder="Search employees..."
243
+ placeholder={enableServerSearch ? "Search employees (server-side)..." : "Search employees..."}
221
244
  value={searchTerm}
222
245
  onChange={(e) => setSearchTerm(e.target.value)}
223
246
  className="w-full pl-8 pr-3 py-2 text-sm border rounded-md focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 bg-background"
224
247
  onClick={(e) => e.stopPropagation()}
225
248
  />
249
+ {enableServerSearch && isSearching && (
250
+ <div className="absolute right-2 top-1/2 transform -translate-y-1/2">
251
+ <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-primary"></div>
252
+ </div>
253
+ )}
226
254
  </div>
227
255
  </div>
228
256
  {allowAll && <SelectItem value="__all__">{allLabel}</SelectItem>}