@snapdragonsnursery/react-components 1.4.0 → 1.5.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/package.json +3 -1
- package/src/EmployeeSearchPage.test.jsx +7 -10
- package/src/__mocks__/EmployeeSearchPage.jsx +87 -0
- package/src/__mocks__/importMetaEnv.js +5 -0
- package/src/components/EmployeeSearchFilters.jsx +53 -0
- package/src/components/EmployeeSearchFilters.test.jsx +5 -4
- package/src/components/ui/date-range-picker.jsx +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@snapdragonsnursery/react-components",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -64,6 +64,8 @@
|
|
|
64
64
|
"src/index.css"
|
|
65
65
|
],
|
|
66
66
|
"devDependencies": {
|
|
67
|
+
"@babel/plugin-syntax-import-meta": "^7.10.4",
|
|
68
|
+
"@babel/plugin-transform-runtime": "^7.28.0",
|
|
67
69
|
"@babel/preset-env": "^7.28.0",
|
|
68
70
|
"@babel/preset-react": "^7.27.1",
|
|
69
71
|
"@testing-library/jest-dom": "^6.6.4",
|
|
@@ -31,22 +31,19 @@ jest.mock('./lib/utils', () => ({
|
|
|
31
31
|
// Mock fetch
|
|
32
32
|
global.fetch = jest.fn();
|
|
33
33
|
|
|
34
|
-
// Mock process.env
|
|
34
|
+
// Mock process.env
|
|
35
35
|
process.env.VITE_COMMON_API_FUNCTION_KEY = 'test-key';
|
|
36
36
|
process.env.VITE_COMMON_API_BASE_URL = 'https://test-api.example.com';
|
|
37
37
|
|
|
38
38
|
// Mock import.meta.env for Vite environment variables
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
VITE_COMMON_API_BASE_URL: 'https://test-api.example.com',
|
|
45
|
-
},
|
|
39
|
+
global.import = {
|
|
40
|
+
meta: {
|
|
41
|
+
env: {
|
|
42
|
+
VITE_COMMON_API_FUNCTION_KEY: 'test-key',
|
|
43
|
+
VITE_COMMON_API_BASE_URL: 'https://test-api.example.com',
|
|
46
44
|
},
|
|
47
45
|
},
|
|
48
|
-
|
|
49
|
-
});
|
|
46
|
+
};
|
|
50
47
|
|
|
51
48
|
// Mock the UI components
|
|
52
49
|
jest.mock('./components/ui/input', () => ({
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// Mock EmployeeSearchPage component for testing
|
|
2
|
+
// This avoids the import.meta.env issue by providing a comprehensive mock
|
|
3
|
+
|
|
4
|
+
import React from 'react';
|
|
5
|
+
|
|
6
|
+
const EmployeeSearchPage = (props) => {
|
|
7
|
+
const {
|
|
8
|
+
title = "Employee Search",
|
|
9
|
+
searchPlaceholder = "Search by name, employee ID, or email...",
|
|
10
|
+
onSelect,
|
|
11
|
+
multiSelect = false,
|
|
12
|
+
selectedEmployees = [],
|
|
13
|
+
sites = [],
|
|
14
|
+
roles = [],
|
|
15
|
+
managers = [],
|
|
16
|
+
activeOnly = true,
|
|
17
|
+
status = null,
|
|
18
|
+
startDateFrom = null,
|
|
19
|
+
startDateTo = null,
|
|
20
|
+
endDateFrom = null,
|
|
21
|
+
endDateTo = null,
|
|
22
|
+
sortBy = "full_name",
|
|
23
|
+
sortOrder = "asc",
|
|
24
|
+
applicationContext = "employee-search",
|
|
25
|
+
bypassPermissions = false,
|
|
26
|
+
maxSelections = null,
|
|
27
|
+
showRoleFilter = true,
|
|
28
|
+
showManagerFilter = true,
|
|
29
|
+
showTermTimeFilter = true,
|
|
30
|
+
showMaternityFilter = true,
|
|
31
|
+
showStartDateFilter = true,
|
|
32
|
+
showEndDateFilter = false,
|
|
33
|
+
showWorkingHoursFilter = true,
|
|
34
|
+
loadAllResults = false,
|
|
35
|
+
} = props;
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<div data-testid="employee-search-page">
|
|
39
|
+
<h1>{title}</h1>
|
|
40
|
+
|
|
41
|
+
{/* Search Input */}
|
|
42
|
+
<input
|
|
43
|
+
type="text"
|
|
44
|
+
placeholder={searchPlaceholder}
|
|
45
|
+
data-testid="search-input"
|
|
46
|
+
aria-label="Search employees"
|
|
47
|
+
/>
|
|
48
|
+
|
|
49
|
+
{/* Employee Search Filters */}
|
|
50
|
+
<div data-testid="employee-search-filters">
|
|
51
|
+
<button>Change Status</button>
|
|
52
|
+
<button>Apply Filters</button>
|
|
53
|
+
<button>Clear Filters</button>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
{/* Loading State */}
|
|
57
|
+
<div role="status" aria-live="polite">
|
|
58
|
+
Loading employees...
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
{/* Mock Table */}
|
|
62
|
+
<table>
|
|
63
|
+
<thead>
|
|
64
|
+
<tr>
|
|
65
|
+
<th>Name</th>
|
|
66
|
+
<th>Email</th>
|
|
67
|
+
<th>Status</th>
|
|
68
|
+
</tr>
|
|
69
|
+
</thead>
|
|
70
|
+
<tbody>
|
|
71
|
+
<tr>
|
|
72
|
+
<td>John Doe</td>
|
|
73
|
+
<td>john@example.com</td>
|
|
74
|
+
<td>Active</td>
|
|
75
|
+
</tr>
|
|
76
|
+
</tbody>
|
|
77
|
+
</table>
|
|
78
|
+
|
|
79
|
+
{/* Props Display for debugging */}
|
|
80
|
+
<div style={{ display: 'none' }}>
|
|
81
|
+
Props: {JSON.stringify(props)}
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export default EmployeeSearchPage;
|
|
@@ -33,6 +33,7 @@ const EmployeeSearchFilters = ({
|
|
|
33
33
|
showStartDateFilter = true,
|
|
34
34
|
showEndDateFilter = true,
|
|
35
35
|
showWorkingHoursFilter = true,
|
|
36
|
+
showDbsFilter = true,
|
|
36
37
|
}) => {
|
|
37
38
|
// Local state for filters that haven't been applied yet
|
|
38
39
|
const [localFilters, setLocalFilters] = React.useState(filters);
|
|
@@ -310,6 +311,22 @@ const EmployeeSearchFilters = ({
|
|
|
310
311
|
</div>
|
|
311
312
|
)}
|
|
312
313
|
|
|
314
|
+
{/* DBS Number Filter */}
|
|
315
|
+
{showDbsFilter && (
|
|
316
|
+
<div>
|
|
317
|
+
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
|
318
|
+
DBS Number
|
|
319
|
+
</label>
|
|
320
|
+
<Input
|
|
321
|
+
type="text"
|
|
322
|
+
value={localFilters.dbsNumber}
|
|
323
|
+
onChange={(e) => handleFilterChange("dbsNumber", e.target.value)}
|
|
324
|
+
placeholder="Enter DBS number"
|
|
325
|
+
className="bg-white dark:bg-gray-800 border-gray-300 dark:border-gray-600 text-gray-900 dark:text-white"
|
|
326
|
+
/>
|
|
327
|
+
</div>
|
|
328
|
+
)}
|
|
329
|
+
|
|
313
330
|
{/* Working Hours Filter */}
|
|
314
331
|
{showWorkingHoursFilter && (
|
|
315
332
|
<div>
|
|
@@ -330,6 +347,42 @@ const EmployeeSearchFilters = ({
|
|
|
330
347
|
|
|
331
348
|
</div>
|
|
332
349
|
|
|
350
|
+
{/* Sorting Options */}
|
|
351
|
+
<div className="mt-6 pt-4 border-t border-gray-200 dark:border-gray-700">
|
|
352
|
+
<h3 className="text-sm font-medium text-gray-700 dark:text-gray-300 mb-3">Sorting Options</h3>
|
|
353
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
354
|
+
<div>
|
|
355
|
+
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
|
356
|
+
Sort By
|
|
357
|
+
</label>
|
|
358
|
+
<Select
|
|
359
|
+
value={localFilters.sortBy}
|
|
360
|
+
onChange={(e) => handleFilterChange("sortBy", e.target.value)}
|
|
361
|
+
className="bg-white dark:bg-gray-800 border-gray-300 dark:border-gray-600 text-gray-900 dark:text-white"
|
|
362
|
+
>
|
|
363
|
+
<SelectOption value="surname">Surname</SelectOption>
|
|
364
|
+
<SelectOption value="first_name">First Name</SelectOption>
|
|
365
|
+
<SelectOption value="full_name">Full Name</SelectOption>
|
|
366
|
+
<SelectOption value="date_of_birth">Date of Birth</SelectOption>
|
|
367
|
+
<SelectOption value="site_name">Site Name</SelectOption>
|
|
368
|
+
</Select>
|
|
369
|
+
</div>
|
|
370
|
+
<div>
|
|
371
|
+
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
|
372
|
+
Sort Order
|
|
373
|
+
</label>
|
|
374
|
+
<Select
|
|
375
|
+
value={localFilters.sortOrder}
|
|
376
|
+
onChange={(e) => handleFilterChange("sortOrder", e.target.value)}
|
|
377
|
+
className="bg-white dark:bg-gray-800 border-gray-300 dark:border-gray-600 text-gray-900 dark:text-white"
|
|
378
|
+
>
|
|
379
|
+
<SelectOption value="asc">Ascending</SelectOption>
|
|
380
|
+
<SelectOption value="desc">Descending</SelectOption>
|
|
381
|
+
</Select>
|
|
382
|
+
</div>
|
|
383
|
+
</div>
|
|
384
|
+
</div>
|
|
385
|
+
|
|
333
386
|
{/* Apply/Cancel Buttons */}
|
|
334
387
|
{hasUnsavedChanges && (
|
|
335
388
|
<div className="mt-6 pt-4 border-t border-gray-200 dark:border-gray-700">
|
|
@@ -233,7 +233,7 @@ describe('EmployeeSearchFilters', () => {
|
|
|
233
233
|
it('updates DBS number filter when changed', () => {
|
|
234
234
|
render(<EmployeeSearchFilters {...defaultProps} isAdvancedFiltersOpen={true} />);
|
|
235
235
|
|
|
236
|
-
const dbsInput = screen.
|
|
236
|
+
const dbsInput = screen.getByPlaceholderText('Enter DBS number');
|
|
237
237
|
fireEvent.change(dbsInput, { target: { value: 'DBS123456' } });
|
|
238
238
|
|
|
239
239
|
expect(defaultProps.onFiltersChange).toHaveBeenCalledWith({
|
|
@@ -245,7 +245,7 @@ describe('EmployeeSearchFilters', () => {
|
|
|
245
245
|
it('updates working hours filter when changed', () => {
|
|
246
246
|
render(<EmployeeSearchFilters {...defaultProps} isAdvancedFiltersOpen={true} />);
|
|
247
247
|
|
|
248
|
-
const hoursInput = screen.
|
|
248
|
+
const hoursInput = screen.getByPlaceholderText('0');
|
|
249
249
|
fireEvent.change(hoursInput, { target: { value: '30' } });
|
|
250
250
|
|
|
251
251
|
expect(defaultProps.onFiltersChange).toHaveBeenCalledWith({
|
|
@@ -459,7 +459,7 @@ describe('EmployeeSearchFilters', () => {
|
|
|
459
459
|
render(<EmployeeSearchFilters {...defaultProps} isAdvancedFiltersOpen={true} />);
|
|
460
460
|
|
|
461
461
|
const sortBySelect = screen.getAllByTestId('select').find(select =>
|
|
462
|
-
select.querySelector('option[value="
|
|
462
|
+
select.querySelector('option[value="surname"]')
|
|
463
463
|
);
|
|
464
464
|
fireEvent.change(sortBySelect, { target: { value: 'first_name' } });
|
|
465
465
|
|
|
@@ -471,7 +471,7 @@ describe('EmployeeSearchFilters', () => {
|
|
|
471
471
|
render(<EmployeeSearchFilters {...defaultProps} isAdvancedFiltersOpen={true} />);
|
|
472
472
|
|
|
473
473
|
const sortOrderSelect = screen.getAllByTestId('select').find(select =>
|
|
474
|
-
select.querySelector('option[value="
|
|
474
|
+
select.querySelector('option[value="asc"]')
|
|
475
475
|
);
|
|
476
476
|
fireEvent.change(sortOrderSelect, { target: { value: 'desc' } });
|
|
477
477
|
|
|
@@ -541,6 +541,7 @@ describe('EmployeeSearchFilters', () => {
|
|
|
541
541
|
const statusLabels = screen.getAllByText('Status');
|
|
542
542
|
expect(statusLabels.length).toBeGreaterThan(0);
|
|
543
543
|
expect(screen.getByText('Sort By')).toBeInTheDocument();
|
|
544
|
+
expect(screen.getByText('Sort Order')).toBeInTheDocument();
|
|
544
545
|
});
|
|
545
546
|
});
|
|
546
547
|
});
|
|
@@ -3,7 +3,7 @@
|
|
|
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 { useState, useEffect, useRef } from 'react'
|
|
6
|
+
import React, { useState, useEffect, useRef } from 'react'
|
|
7
7
|
import { format } from 'date-fns'
|
|
8
8
|
import { CalendarIcon } from '@heroicons/react/24/outline'
|
|
9
9
|
import { Popover, PopoverContent, PopoverTrigger } from './popover'
|