@snapdragonsnursery/react-components 1.13.0 → 1.16.0
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 +4 -4
- package/package.json +1 -1
- package/src/ChildSearchModal.jsx +41 -11
- package/src/ChildSearchPage.jsx +33 -6
- package/src/EmployeeSearchDemo.jsx +37 -1
- package/src/EmployeeSearchModal.jsx +425 -122
- package/src/EmployeeSearchPage.jsx +193 -138
- package/src/__mocks__/importMetaEnv.js +2 -3
- package/src/components/ui/date-range-picker.jsx +68 -76
- package/src/components/ui/popover.jsx +1 -1
package/README.md
CHANGED
|
@@ -188,16 +188,16 @@ The `ChildSearchFilters` component now includes an "Apply Filters" button that p
|
|
|
188
188
|
|
|
189
189
|
## Environment Variables
|
|
190
190
|
|
|
191
|
-
Set
|
|
191
|
+
Set this environment variable in your application:
|
|
192
192
|
|
|
193
193
|
```env
|
|
194
|
-
|
|
194
|
+
VITE_APIM_SCOPE=api://your-apim-app-id/.default
|
|
195
195
|
```
|
|
196
196
|
|
|
197
197
|
## Documentation
|
|
198
198
|
|
|
199
|
-
- [ChildSearchModal Documentation](
|
|
200
|
-
- [ChildSearchModal README](
|
|
199
|
+
- [ChildSearchModal Documentation](docs/CHILD_SEARCH_MODAL_DOCUMENTATION.md)
|
|
200
|
+
- [ChildSearchModal README](docs/CHILD_SEARCH_README.md)
|
|
201
201
|
- [Release Guide](./RELEASE.md)
|
|
202
202
|
- [SoftWarningAlert](./SOFT_WARNING_ALERT.md)
|
|
203
203
|
|
package/package.json
CHANGED
package/src/ChildSearchModal.jsx
CHANGED
|
@@ -26,6 +26,12 @@ const ChildSearchModal = ({
|
|
|
26
26
|
multiSelect = false, // Enable multiple child selection
|
|
27
27
|
maxSelections = null, // Maximum number of children that can be selected (null = unlimited)
|
|
28
28
|
selectedChildren = [], // Array of already selected children (for multi-select mode)
|
|
29
|
+
// Auth options: provide token directly or a function to fetch it
|
|
30
|
+
authToken = null,
|
|
31
|
+
getAccessToken = null,
|
|
32
|
+
// Layout controls
|
|
33
|
+
zIndex = 60,
|
|
34
|
+
maxHeightVh = 90,
|
|
29
35
|
}) => {
|
|
30
36
|
const [searchTerm, setSearchTerm] = useState("");
|
|
31
37
|
const [children, setChildren] = useState([]);
|
|
@@ -137,8 +143,35 @@ const ChildSearchModal = ({
|
|
|
137
143
|
setError(null);
|
|
138
144
|
|
|
139
145
|
try {
|
|
140
|
-
//
|
|
141
|
-
const
|
|
146
|
+
// Resolve access token: prefer prop function, then prop token, then MSAL using APIM scope
|
|
147
|
+
const apimScope = import.meta.env.VITE_APIM_SCOPE;
|
|
148
|
+
|
|
149
|
+
let accessToken = null;
|
|
150
|
+
if (typeof getAccessToken === "function") {
|
|
151
|
+
accessToken = await getAccessToken();
|
|
152
|
+
} else if (authToken) {
|
|
153
|
+
accessToken = authToken;
|
|
154
|
+
} else if (apimScope) {
|
|
155
|
+
try {
|
|
156
|
+
const response = await instance.acquireTokenSilent({
|
|
157
|
+
account: accounts[0],
|
|
158
|
+
scopes: [apimScope],
|
|
159
|
+
});
|
|
160
|
+
accessToken = response.accessToken;
|
|
161
|
+
} catch (silentErr) {
|
|
162
|
+
// Fallback to popup if silent acquisition fails
|
|
163
|
+
const response = await instance.acquireTokenPopup({
|
|
164
|
+
scopes: [apimScope],
|
|
165
|
+
});
|
|
166
|
+
accessToken = response.accessToken;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (!accessToken) {
|
|
171
|
+
throw new Error(
|
|
172
|
+
"Missing access token. Provide authToken/getAccessToken or set VITE_APIM_SCOPE."
|
|
173
|
+
);
|
|
174
|
+
}
|
|
142
175
|
|
|
143
176
|
// Build query parameters
|
|
144
177
|
const params = new URLSearchParams({
|
|
@@ -186,16 +219,13 @@ const ChildSearchModal = ({
|
|
|
186
219
|
params.append("sort_by", advancedFilters.sortBy);
|
|
187
220
|
params.append("sort_order", advancedFilters.sortOrder);
|
|
188
221
|
|
|
189
|
-
// Make API call
|
|
222
|
+
// Make API call via APIM with Bearer token
|
|
190
223
|
const apiResponse = await fetch(
|
|
191
|
-
|
|
192
|
-
import.meta.env.VITE_COMMON_API_BASE_URL ||
|
|
193
|
-
"https://snaps-common-api.azurewebsites.net"
|
|
194
|
-
}/api/search-children?${params}`,
|
|
224
|
+
`https://snapdragons.azure-api.net/api/children/search-children?${params}`,
|
|
195
225
|
{
|
|
196
226
|
headers: {
|
|
197
227
|
"Content-Type": "application/json",
|
|
198
|
-
|
|
228
|
+
Authorization: `Bearer ${accessToken}`,
|
|
199
229
|
},
|
|
200
230
|
}
|
|
201
231
|
);
|
|
@@ -351,13 +381,13 @@ const ChildSearchModal = ({
|
|
|
351
381
|
if (!isOpen) return null;
|
|
352
382
|
|
|
353
383
|
return (
|
|
354
|
-
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center
|
|
384
|
+
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 overflow-hidden" style={{ zIndex }}>
|
|
355
385
|
<div
|
|
356
386
|
ref={modalRef}
|
|
357
387
|
className={`bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-4xl w-full flex flex-col overflow-hidden ${className}`}
|
|
358
388
|
style={{
|
|
359
|
-
maxHeight:
|
|
360
|
-
height:
|
|
389
|
+
maxHeight: `${maxHeightVh}vh`,
|
|
390
|
+
height: `${maxHeightVh}vh`,
|
|
361
391
|
display: 'flex',
|
|
362
392
|
flexDirection: 'column',
|
|
363
393
|
position: 'relative'
|
package/src/ChildSearchPage.jsx
CHANGED
|
@@ -61,6 +61,9 @@ const ChildSearchPage = ({
|
|
|
61
61
|
multiSelect = false,
|
|
62
62
|
maxSelections = null,
|
|
63
63
|
selectedChildren = [],
|
|
64
|
+
// Auth options: provide token directly or a function to fetch it
|
|
65
|
+
authToken = null,
|
|
66
|
+
getAccessToken = null,
|
|
64
67
|
}) => {
|
|
65
68
|
const [searchTerm, setSearchTerm] = useState("");
|
|
66
69
|
const [children, setChildren] = useState([]);
|
|
@@ -286,7 +289,34 @@ const ChildSearchPage = ({
|
|
|
286
289
|
setError(null);
|
|
287
290
|
|
|
288
291
|
try {
|
|
289
|
-
|
|
292
|
+
// Resolve access token for APIM
|
|
293
|
+
const apimScope = import.meta.env.VITE_APIM_SCOPE;
|
|
294
|
+
|
|
295
|
+
let accessToken = null;
|
|
296
|
+
if (typeof getAccessToken === "function") {
|
|
297
|
+
accessToken = await getAccessToken();
|
|
298
|
+
} else if (authToken) {
|
|
299
|
+
accessToken = authToken;
|
|
300
|
+
} else if (apimScope) {
|
|
301
|
+
try {
|
|
302
|
+
const response = await instance.acquireTokenSilent({
|
|
303
|
+
account: accounts[0],
|
|
304
|
+
scopes: [apimScope],
|
|
305
|
+
});
|
|
306
|
+
accessToken = response.accessToken;
|
|
307
|
+
} catch (silentErr) {
|
|
308
|
+
const response = await instance.acquireTokenPopup({
|
|
309
|
+
scopes: [apimScope],
|
|
310
|
+
});
|
|
311
|
+
accessToken = response.accessToken;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (!accessToken) {
|
|
316
|
+
throw new Error(
|
|
317
|
+
"Missing access token. Provide authToken/getAccessToken or set VITE_APIM_SCOPE."
|
|
318
|
+
);
|
|
319
|
+
}
|
|
290
320
|
|
|
291
321
|
const params = new URLSearchParams({
|
|
292
322
|
entra_id: accounts[0].localAccountId,
|
|
@@ -334,14 +364,11 @@ const ChildSearchPage = ({
|
|
|
334
364
|
params.append("sort_order", debouncedAdvancedFilters.sortOrder);
|
|
335
365
|
|
|
336
366
|
const apiResponse = await fetch(
|
|
337
|
-
|
|
338
|
-
import.meta.env.VITE_COMMON_API_BASE_URL ||
|
|
339
|
-
"https://snaps-common-api.azurewebsites.net"
|
|
340
|
-
}/api/search-children?${params}`,
|
|
367
|
+
`https://snapdragons.azure-api.net/api/children/search-children?${params}`,
|
|
341
368
|
{
|
|
342
369
|
headers: {
|
|
343
370
|
"Content-Type": "application/json",
|
|
344
|
-
|
|
371
|
+
Authorization: `Bearer ${accessToken}`,
|
|
345
372
|
},
|
|
346
373
|
}
|
|
347
374
|
);
|
|
@@ -174,6 +174,42 @@ const EmployeeSearchDemo = () => {
|
|
|
174
174
|
showWorkingHoursFilter={true}
|
|
175
175
|
/>
|
|
176
176
|
</div>
|
|
177
|
+
|
|
178
|
+
{/* Custom Columns (Page) */}
|
|
179
|
+
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-6">
|
|
180
|
+
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-4">
|
|
181
|
+
Custom Columns (Page)
|
|
182
|
+
</h3>
|
|
183
|
+
<p className="text-sm text-gray-600 dark:text-gray-400 mb-4">
|
|
184
|
+
Demonstrates visibleColumns, columnLabels, and columnRenderers.
|
|
185
|
+
</p>
|
|
186
|
+
<EmployeeSearchPage
|
|
187
|
+
title="Custom Column Set"
|
|
188
|
+
onSelect={handleEmployeeSelect}
|
|
189
|
+
sites={mockSites}
|
|
190
|
+
roles={mockRoles}
|
|
191
|
+
managers={mockManagers}
|
|
192
|
+
visibleColumns={[
|
|
193
|
+
'full_name',
|
|
194
|
+
'site_name',
|
|
195
|
+
'role_name',
|
|
196
|
+
'email',
|
|
197
|
+
'employee_status',
|
|
198
|
+
]}
|
|
199
|
+
columnLabels={{
|
|
200
|
+
full_name: 'Employee',
|
|
201
|
+
site_name: 'Location',
|
|
202
|
+
role_name: 'Position',
|
|
203
|
+
}}
|
|
204
|
+
columnRenderers={{
|
|
205
|
+
email: (row) => (
|
|
206
|
+
<a href={`mailto:${row.email}`} className="text-blue-600">
|
|
207
|
+
{row.email}
|
|
208
|
+
</a>
|
|
209
|
+
),
|
|
210
|
+
}}
|
|
211
|
+
/>
|
|
212
|
+
</div>
|
|
177
213
|
</div>
|
|
178
214
|
|
|
179
215
|
{/* Site-Specific Search */}
|
|
@@ -272,4 +308,4 @@ const EmployeeSearchDemo = () => {
|
|
|
272
308
|
);
|
|
273
309
|
};
|
|
274
310
|
|
|
275
|
-
export default EmployeeSearchDemo;
|
|
311
|
+
export default EmployeeSearchDemo;
|