11import { useState , useMemo , FC } from 'react' ;
22import {
33 Card ,
4- CheckBox ,
4+ Chip ,
5+ Button ,
6+ useCheckboxSetValues ,
57} from '@openedx/paragon' ;
8+ import {
9+ ArrowDropDown ,
10+ CloseSmall ,
11+ } from '@openedx/paragon/icons' ;
612import { useIntl } from '@edx/frontend-platform/i18n' ;
713import messages from './messages' ;
814import SectionCollapsible from './SectionCollapsible' ;
915import BrokenLinkTable from './BrokenLinkTable' ;
10- import LockedInfoIcon from './LockedInfoIcon' ;
1116import { LinkCheckResult } from '../types' ;
1217import { countBrokenLinks } from '../utils' ;
18+ import FilterModal from './filterModal' ;
1319
1420const InfoCard : FC < { text : string } > = ( { text } ) => (
1521 < Card className = "mt-4" >
@@ -28,63 +34,130 @@ interface Props {
2834
2935const ScanResults : FC < Props > = ( { data } ) => {
3036 const intl = useIntl ( ) ;
31- const [ showLockedLinks , setShowLockedLinks ] = useState ( true ) ;
37+ const [ isModalOpen , setModalOpen ] = useState ( false ) ;
38+ const initialFilters = {
39+ brokenLinks : false ,
40+ lockedLinks : false ,
41+ externalForbiddenLinks : false ,
42+ } ;
43+ const [ filters , setFilters ] = useState ( initialFilters ) ;
44+ const [ buttonRef , setButtonRef ] = useState < HTMLButtonElement | null > ( null ) ;
3245
3346 const {
3447 brokenLinksCounts,
3548 lockedLinksCounts,
3649 externalForbiddenLinksCounts,
3750 } = useMemo ( ( ) => countBrokenLinks ( data ) , [ data ?. sections ] ) ;
3851
52+ const activeFilters = Object . keys ( filters ) . filter ( key => filters [ key ] ) ;
53+ const [ filterBy , {
54+ add, remove, set, clear,
55+ } ] = useCheckboxSetValues ( activeFilters ) ;
56+
3957 if ( ! data ?. sections ) {
4058 return < InfoCard text = { intl . formatMessage ( messages . noBrokenLinksCard ) } /> ;
4159 }
4260
4361 const { sections } = data ;
62+ const filterOptions = [
63+ { name : intl . formatMessage ( messages . brokenLabel ) , value : 'brokenLinks' } ,
64+ { name : intl . formatMessage ( messages . manualLabel ) , value : 'externalForbiddenLinks' } ,
65+ { name : intl . formatMessage ( messages . lockedLabel ) , value : 'lockedLinks' } ,
66+ ] ;
4467
4568 return (
4669 < div className = "scan-results" >
47- < div className = "border-bottom border-light-400 mb-3" >
70+ < div className = "scan-header-title-container" >
71+ < h2 className = "scan-header-title" > { intl . formatMessage ( messages . scanHeader ) } </ h2 >
72+ </ div >
73+ < div className = "scan-header-second-title-container" >
4874 < header className = "sub-header-content" >
49- < h2 className = "sub-header-content-title" > { intl . formatMessage ( messages . scanHeader ) } </ h2 >
50- < span className = "locked-links-checkbox-wrapper" >
51- < CheckBox
52- className = "locked-links-checkbox"
53- type = "checkbox"
54- checked = { showLockedLinks }
55- onClick = { ( ) => {
56- setShowLockedLinks ( ! showLockedLinks ) ;
57- } }
58- label = { intl . formatMessage ( messages . lockedCheckboxLabel ) }
59- />
60- < LockedInfoIcon />
61- </ span >
75+ < h2 className = "broken-links-header-title pt-2" > { intl . formatMessage ( messages . brokenLinksHeader ) } </ h2 >
76+ < Button
77+ ref = { setButtonRef }
78+ variant = "outline-primary"
79+ onClick = { ( ) => setModalOpen ( true ) }
80+ disabled = { false }
81+ iconAfter = { ArrowDropDown }
82+ className = "rounded-sm justify-content-between cadence-button"
83+ >
84+ { intl . formatMessage ( messages . filterButtonLabel ) }
85+ </ Button >
6286 </ header >
6387 </ div >
88+ < FilterModal
89+ isOpen = { isModalOpen }
90+ onClose = { ( ) => setModalOpen ( false ) }
91+ onApply = { setFilters }
92+ positionRef = { buttonRef }
93+ filterOptions = { filterOptions }
94+ initialFilters = { filters }
95+ activeFilters = { activeFilters }
96+ filterBy = { filterBy }
97+ add = { add }
98+ remove = { remove }
99+ set = { set }
100+ />
101+ { activeFilters . length > 0 && < div className = "border-bottom border-light-400" /> }
102+ { activeFilters . length > 0 && (
103+ < div className = "scan-results-active-filters-container" >
104+ < span className = "scan-results-active-filters-chips" >
105+ { activeFilters . map ( filter => (
106+ < Chip
107+ key = { filter }
108+ iconAfter = { CloseSmall }
109+ iconAfterAlt = "icon-after"
110+ className = "scan-results-active-filters-chip"
111+ onClick = { ( ) => {
112+ remove ( filter ) ;
113+ const updatedFilters = { ...filters , [ filter ] : false } ;
114+ setFilters ( updatedFilters ) ;
115+ } }
116+ >
117+ { filterOptions . find ( option => option . value === filter ) ?. name }
118+ </ Chip >
119+ ) ) }
120+ </ span >
121+ < Button
122+ variant = "link"
123+ className = "clear-all-btn"
124+ onClick = { ( ) => {
125+ clear ( ) ;
126+ setFilters ( initialFilters ) ;
127+ } }
128+ >
129+ { intl . formatMessage ( messages . clearFilters ) }
130+ </ Button >
131+ </ div >
132+ ) }
64133
65134 { sections ?. map ( ( section , index ) => (
66135 < SectionCollapsible
67136 key = { section . id }
68137 title = { section . displayName }
69- redItalics = { intl . formatMessage ( messages . brokenLinksNumber , { count : brokenLinksCounts [ index ] } ) }
70- yellowItalics = { ! showLockedLinks ? '' : intl . formatMessage ( messages . lockedLinksNumber , { count : lockedLinksCounts [ index ] } ) }
71- greenItalics = {
72- intl . formatMessage ( messages . externalForbiddenLinksNumber , { count : externalForbiddenLinksCounts [ index ] } )
73- }
138+ brokenNumber = { brokenLinksCounts [ index ] }
139+ manualNumber = { externalForbiddenLinksCounts [ index ] }
140+ lockedNumber = { lockedLinksCounts [ index ] }
141+ className = "section-collapsible-header"
74142 >
75143 { section . subsections . map ( ( subsection ) => (
76144 < >
77- < h2
78- className = "subsection-header"
79- style = { { marginBottom : '2rem' } }
80- >
81- { subsection . displayName }
82- </ h2 >
83- { subsection . units . map ( ( unit ) => (
84- < div className = "unit" >
85- < BrokenLinkTable unit = { unit } showLockedLinks = { showLockedLinks } />
86- </ div >
87- ) ) }
145+ { subsection . units . map ( ( unit ) => {
146+ if (
147+ ( ! filters . brokenLinks && ! filters . externalForbiddenLinks && ! filters . lockedLinks )
148+ || ( filters . brokenLinks && unit . blocks . some ( block => block . brokenLinks . length > 0 ) )
149+ || ( filters . externalForbiddenLinks
150+ && unit . blocks . some ( block => block . externalForbiddenLinks . length > 0 ) )
151+ || ( filters . lockedLinks && unit . blocks . some ( block => block . lockedLinks . length > 0 ) )
152+ ) {
153+ return (
154+ < div className = "unit" >
155+ < BrokenLinkTable unit = { unit } filters = { filters } />
156+ </ div >
157+ ) ;
158+ }
159+ return null ;
160+ } ) }
88161 </ >
89162 ) ) }
90163 </ SectionCollapsible >
0 commit comments