Skip to content

Commit 162f926

Browse files
authored
Merge pull request #1353 from cisagov/Refactor-Filter-Reset-Button-CRASM-3428
Refactor Filter Reset Button (CRASM-3428)
2 parents 287f782 + 408bbe6 commit 162f926

File tree

7 files changed

+3160
-1824
lines changed

7 files changed

+3160
-1824
lines changed

.pre-commit-config.yaml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,11 @@ repos:
5050
hooks:
5151
- id: eslint
5252
name: ESLint
53-
entry: bash -c 'cd frontend && npx eslint . --cache'
53+
entry: >
54+
bash -c 'export NVM_DIR="$HOME/.nvm";
55+
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" --no-use
56+
&& nvm use || true;
57+
cd frontend && npx eslint . --cache'
5458
language: system
5559
files: ^frontend/src/.*\.(js|jsx|ts|tsx)$
5660

frontend/package-lock.json

Lines changed: 2927 additions & 1683 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/src/components/FilterDrawer/FilterDrawerV2.tsx

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { ContextType } from 'context';
1313
import { DrawerInterior } from './DrawerInterior';
1414
import { RegionAndOrganizationFilters } from './RegionAndOrganizationFilters';
1515
import { matchPath } from 'utils/matchPath';
16+
import { useAreFiltersDefault } from '@/hooks/useAreFiltersDefault';
1617
import { VSDashRegionAndOrgFilters } from './VSDashRegionAndOrgFilters';
1718
import { ROUTES } from '@/constants/routes';
1819

@@ -55,6 +56,8 @@ export const FilterDrawer: FC<
5556
}
5657
};
5758

59+
const defaultFilters = useAreFiltersDefault(filters, initialFilters);
60+
5861
const clearFiltersAndSearch = () => {
5962
setSearchTerm('', {
6063
shouldClearFilters: true,
@@ -140,27 +143,27 @@ export const FilterDrawer: FC<
140143
</Box>
141144
{matchPath([ROUTES.INVENTORY], pathname) && (
142145
<Box>
143-
{filters.length > 0 && (
144-
<Box
145-
paddingBottom={5}
146-
display="flex"
147-
width="100%"
148-
justifyContent="center"
146+
<Box
147+
paddingBottom={5}
148+
display="flex"
149+
width="100%"
150+
justifyContent="center"
151+
>
152+
<Button
153+
onClick={clearFiltersAndSearch}
154+
disabled={defaultFilters}
155+
sx={{
156+
color: 'primary.dark',
157+
fontSize: '14px',
158+
fontWeight: 'bold',
159+
lineHeight: '20px',
160+
letterSpacing: '0.1em'
161+
}}
162+
aria-label="Reset Filters"
149163
>
150-
<Button
151-
onClick={clearFiltersAndSearch}
152-
sx={{
153-
color: 'primary.dark',
154-
fontSize: '14px',
155-
fontWeight: 'bold',
156-
lineHeight: '20px',
157-
letterSpacing: '0.1em'
158-
}}
159-
>
160-
Reset
161-
</Button>
162-
</Box>
163-
)}
164+
Reset
165+
</Button>
166+
</Box>
164167
</Box>
165168
)}
166169
</Stack>
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { ContextType } from 'context';
2+
import { useMemo } from 'react';
3+
4+
type UseAreFiltersDefault = (
5+
filters: ContextType['filters'],
6+
initialFilters: ContextType['filters']
7+
) => boolean;
8+
9+
export const useAreFiltersDefault: UseAreFiltersDefault = (
10+
filters: ContextType['filters'],
11+
initialFilters: ContextType['filters']
12+
): boolean => {
13+
return useMemo(() => {
14+
return filters.every((filter) => {
15+
const initialFilter = initialFilters.find(
16+
(initFilter) => initFilter.field === filter.field
17+
);
18+
if (!initialFilter) return false;
19+
20+
const current = filter.values || [];
21+
const initial = initialFilter.values || [];
22+
23+
if (current.length !== initial.length) return false;
24+
25+
const currentIds = current.map((val: any) =>
26+
typeof val === 'object' && val !== null ? (val.id ?? val.name) : val
27+
);
28+
const initialIds = initial.map((val: any) =>
29+
typeof val === 'object' && val !== null ? (val.id ?? val.name) : val
30+
);
31+
32+
return (
33+
JSON.stringify(currentIds.sort()) === JSON.stringify(initialIds.sort())
34+
);
35+
});
36+
}, [filters, initialFilters]);
37+
};

frontend/src/hooks/useUserTypeFilters.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import {
55
REGIONAL_ADMIN,
66
STANDARD_USER
77
} from './useUserLevel';
8-
// import { GLOBAL_VIEW } from 'context/userStateUtils';
98
import { OrganizationShallow } from 'components/FilterDrawer/RegionAndOrganizationFilters';
109

1110
export const REGIONAL_USER_CAN_SEARCH_OTHER_REGIONS = true;
@@ -48,7 +47,8 @@ export const useUserTypeFilters: UseUserTypeFilters = (
4847
name: role?.organization?.name ?? '',
4948
id: role?.organization?.id ?? '',
5049
region_id: role?.organization?.region_id ?? '',
51-
root_domains: role?.organization?.root_domains ?? []
50+
root_domains: role?.organization?.root_domains ?? [],
51+
acronym: role?.organization?.acronym ?? ''
5252
};
5353
})
5454
: [];
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { renderHook } from '@testing-library/react';
3+
import { useAreFiltersDefault } from '../../hooks/useAreFiltersDefault';
4+
5+
// -------------------- Test Suite --------------------
6+
7+
describe('useAreFiltersDefault hook determines if current filters match initial filters', () => {
8+
it('should return true when filters match initial filters', () => {
9+
const { result } = renderHook(() =>
10+
useAreFiltersDefault(
11+
[{ field: 'organization.region_id', values: ['1'], type: 'any' }],
12+
[{ field: 'organization.region_id', values: ['1'], type: 'any' }]
13+
)
14+
);
15+
16+
expect(result.current).toBe(true);
17+
});
18+
19+
it('should return false when filters differ', () => {
20+
const { result } = renderHook(() =>
21+
useAreFiltersDefault(
22+
[
23+
{ field: 'organization.region_id', values: ['1'], type: 'any' },
24+
{ field: 'organization_id', values: ['org-123'], type: 'any' }
25+
],
26+
[{ field: 'organization.region_id', values: ['1'], type: 'any' }]
27+
)
28+
);
29+
30+
expect(result.current).toBe(false);
31+
});
32+
33+
it('should return false when values differ', () => {
34+
const { result } = renderHook(() =>
35+
useAreFiltersDefault(
36+
[{ field: 'organization.region_id', values: ['2'], type: 'any' }],
37+
[{ field: 'organization.region_id', values: ['1'], type: 'any' }]
38+
)
39+
);
40+
41+
expect(result.current).toBe(false);
42+
});
43+
44+
it('should return true for empty filters', () => {
45+
const { result } = renderHook(() => useAreFiltersDefault([], []));
46+
expect(result.current).toBe(true);
47+
});
48+
});

0 commit comments

Comments
 (0)