Skip to content

Commit

Permalink
add filter dropdown for categorization keywords
Browse files Browse the repository at this point in the history
  • Loading branch information
CollinBeczak committed Sep 8, 2023
1 parent 67a08ef commit 75425ca
Show file tree
Hide file tree
Showing 8 changed files with 220 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import WithChallengeSearch from '../../HOCs/WithSearch/WithChallengeSearch'
import WithCommandInterpreter from '../../HOCs/WithCommandInterpreter/WithCommandInterpreter'
import FilterByDifficulty from './FilterByDifficulty'
import FilterByKeyword from './FilterByKeyword'
import FilterByCategorizationKeywords from './FilterByCategorizationKeywords'
import ClearFiltersControl from './ClearFiltersControl'
import SortChallengesSelector from './SortChallengesSelector'
import './ChallengeFilterSubnav.scss'
Expand All @@ -20,6 +21,7 @@ const CommandSearchBox = WithCommandInterpreter(SearchBox)
*
* @see See FilterByDifficulty
* @see See FilterByKeyword
* @see See FilterByCategorizationKeywords
* @see See FilterByLocation
* @see See SearchBox
*
Expand All @@ -46,6 +48,8 @@ export class ChallengeFilterSubnav extends Component {
<SortChallengesSelector {...this.props} />
<FilterByKeyword {...this.props} />
<FilterByDifficulty {...this.props} />
<FilterByKeyword {...this.props} />
<FilterByCategorizationKeywords {...this.props} />
<CommandSearchBox
{...this.props}
className="mr-h-12"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import _keys from 'lodash/keys'
import _without from 'lodash/without'
import _isEmpty from 'lodash/isEmpty'
import _first from 'lodash/first'
import { injectIntl, FormattedMessage } from 'react-intl'
import Dropdown from '../../Dropdown/Dropdown'
import ButtonFilter from './ButtonFilter'
import messages from './Messages'

/**
* FilterByCategorizationKeywords displays a nav dropdown containing customizable options
* for filtering challenges by category keyword. The redux
* store is updated to reflect the chosen keywords.
*
* @author [Collin Beczak](https://github.com/collinbeczak)
*/
class FilterByCategorizationKeywords extends Component {
updateFilter = (value, categorizationFilters) => {
const updatedFilters = new Set(categorizationFilters)

if (value === null) {
this.props.setCategorizationFilters([])
return
} else {
if (updatedFilters.has(value)) {
updatedFilters.delete(value)
} else {
updatedFilters.add(value)
}
}

// Update redux store or perform other necessary actions
this.props.setCategorizationFilters(Array.from(updatedFilters))
}

addKeyword = (value, categories) => {
value = value.trim()
const addKeyword = new Set(categories)

if (value === "" || categories.length === 6) {
return
}

addKeyword.add(value)

// Update redux store or perform other necessary actions
this.props.updateUserAppSetting(this.props.user.id, {
'categorizationKeys': Array.from(addKeyword),
})
}

removeKeyword = (value, categories, categorizationFilters) => {
const removeKeyword = new Set(categories)

if (removeKeyword.has(value)) {
removeKeyword.delete(value)
this.props.setCategorizationFilters(Array.from(removeKeyword))
}

removeKeyword.delete(value)

// Update redux store or perform other necessary actions
this.props.updateUserAppSetting(this.props.user.id, {
'categorizationKeys': Array.from(removeKeyword),
})
}

render() {
const categorizationFilters = this.props.searchFilters.categorizationKeywords ? this.props.searchFilters.categorizationKeywords : []
const categories = this.props.user.properties.mr3Frontend.settings.categorizationKeys

return (
<Dropdown
className="mr-dropdown--flush xl:mr-border-l xl:mr-border-white-10 mr-p-6 mr-pl-0 xl:mr-pl-6"
dropdownButton={(dropdown) => (
<ButtonFilter
type={<FormattedMessage {...messages.categorizeLabel} />}
selection={categorizationFilters.length > 0 ? `${categorizationFilters.length} Filters` : "Anything"}
onClick={dropdown.toggleDropdownVisible}
selectionClassName={categorizationFilters.length > 0 ? 'mr-text-yellow' : null}
/>
)}
dropdownContent={(dropdown) => (
<ListFilterItems
categorizationFilters={categorizationFilters}
categories={categories}
updateFilter={this.updateFilter}
addKeyword={this.addKeyword}
removeKeyword={this.removeKeyword}
closeDropdown={dropdown.closeDropdown}
/>
)}
/>
)
}
}

const ListFilterItems = function (props) {
const { categorizationFilters, categories } = props

const menuItems = categories.map((keyword) => (
<li className="mr-flex" key={keyword}>
<a
className={`mr-flex-1 ${
categorizationFilters.includes(keyword) ? 'mr-text-yellow' : ''
}`}
onClick={() => props.updateFilter(keyword, categorizationFilters)}
>
<div className={
categorizationFilters.includes(keyword) ? 'mr-text-yellow' : ''}>{keyword}</div>
</a>
<button onClick={() => props.removeKeyword(keyword, categories, categorizationFilters)}>
<svg viewBox="0 0 40 40" className="mr-fill-current mr-w-5 mr-h-5 mr-my-auto mr-mx-2">
<use href="#close-outline-icon"></use>
</svg>
</button>
</li>
))

// Add 'Anything' option to start of dropdown
menuItems.unshift(
<li key="anything">
<a onClick={() => props.updateFilter(null)}>Anything</a>
</li>
)

// Add box for manually entering other keywords not included in the menu.
menuItems.push(
<li key="add">
{props.categories.length === 0 ? (
<div className="mr-text-grey-light mr-pt-2"> You have not set any categories.</div>
) : null}
<div className="mr-flex mr-items-center mr-py-3">
<label className="mr-text-green-lighter mr-mr-4 mr-cursor-pointer">Add:</label>
<form onSubmit={(e) => {
e.preventDefault() // Prevent the default form submission behavior
const value = e.target.elements.inputName.value // Replace 'inputName' with the actual name attribute of the input
props.addKeyword(value, categories)

// Clear the input field
e.target.elements.inputName.value = ''
}}>
<input
className="mr-flex mr-items-center mr-border-none mr-text-white mr-rounded mr-bg-black-15 mr-shadow-inner mr-px-2"
name="inputName"
/>
</form>
</div>
<div className="mr-text-grey-light">
{props.categories.length === 6
? <div><div>You must delete a category</div>to add a new one.</div>
: "Add a new category"}
</div>
</li>
)

return <ol className="mr-list-dropdown mr-list-dropdown--ruled">{menuItems}</ol>
}

FilterByCategorizationKeywords.propTypes = {
/** Invoked to update the challenge keyword filter */
setCategorizationFilters: PropTypes.func.isRequired,
}

export default injectIntl(FilterByCategorizationKeywords)
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ export default defineMessages({
defaultMessage: 'Work on',
},

categorizeLabel: {
id: 'ChallengeFilterSubnav.filter.categorize.label',
defaultMessage: 'Categorize',
},

locationLabel: {
id: 'ChallengeFilterSubnav.filter.location.label',
defaultMessage: 'Location',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { challengePassesDifficultyFilter }
from '../../../services/Challenge/ChallengeDifficulty/ChallengeDifficulty'
import { challengePassesKeywordFilter }
from '../../../services/Challenge/ChallengeKeywords/ChallengeKeywords'
import { challengePassesCategorizationKeywordsFilter }
from '../../../services/Challenge/ChallengeKeywords/ChallengeKeywords'
import { challengePassesLocationFilter }
from '../../../services/Challenge/ChallengeLocation/ChallengeLocation'
import { challengePassesProjectFilter }
Expand All @@ -15,6 +17,7 @@ import { challengePassesProjectFilter }
const allFilters = [
challengePassesDifficultyFilter,
challengePassesKeywordFilter,
challengePassesCategorizationKeywordsFilter,
challengePassesLocationFilter,
challengePassesProjectFilter,
]
Expand Down
4 changes: 4 additions & 0 deletions src/components/HOCs/WithSearch/WithSearch.js
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,10 @@ export const mapDispatchToProps = (dispatch, ownProps, searchGroup) => ({
dispatch(setFilters(searchGroup, {keywords}))
},

setCategorizationFilters: categorizationKeywords => {
dispatch(setFilters(searchGroup, {categorizationKeywords}))
},

clearSearchFilters: () => {
dispatch(clearFilters(searchGroup))
},
Expand Down
3 changes: 3 additions & 0 deletions src/components/MobileFilterMenu/MobileFilterMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import FilterByLocation
from '../ChallengePane/ChallengeFilterSubnav/FilterByLocation'
import FilterByKeyword
from '../ChallengePane/ChallengeFilterSubnav/FilterByKeyword'
import FilterByCategorizationKeywords
from '../ChallengePane/ChallengeFilterSubnav/FilterByCategorizationKeywords'
import WithDeactivateOnOutsideClick from
'../HOCs/WithDeactivateOnOutsideClick/WithDeactivateOnOutsideClick'
import './MobileFilterMenu.scss'
Expand All @@ -27,6 +29,7 @@ export class MobileFilterMenu extends Component {
<Menu {...this.props}>
<FilterByDifficulty asMenuList {...this.props} />
<LocationFilter asMenuList {...this.props} />
<FilterByCategorizationKeywords asMenuList {...this.props} />
<FilterByKeyword asMenuList {...this.props} />
</Menu>
</SimpleDropdown>
Expand Down
13 changes: 13 additions & 0 deletions src/services/Challenge/ChallengeKeywords/ChallengeKeywords.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,3 +142,16 @@ export const challengePassesKeywordFilter = function(filter, challenge) {

return true
}

/**
* Determines if the given challenge passes the given categorization keywords filter.
*/
export const challengePassesCategorizationKeywordsFilter = function(filter, challenge) {
console.log(filter.CategorizationKeywords, challenge.tags)
if (_isArray(filter.categorizationKeywords)) {
// Any matching keyword is a pass
return _intersection(filter.categorizationKeywords, challenge.tags).length > 0
}

return true
}
41 changes: 21 additions & 20 deletions src/services/FundraisingNotices/FundraisingNotices.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,27 @@ import isFuture from 'date-fns/is_future'
const NOTICES_URL = process.env.REACT_APP_FUNDRAISING_NOTICES_URL

export const fetchActiveFundraisingNotices = async function () {
if (_isEmpty(NOTICES_URL)) {
return []
}
// if (_isEmpty(NOTICES_URL)) {
// return []
// }

const response = await fetch(NOTICES_URL)
if (response.ok) {
const fundraisingNotices = await response.json()
if (!fundraisingNotices || !_isArray(fundraisingNotices.notices)) {
return []
}
// const response = await fetch(NOTICES_URL)
// if (response.ok) {
// const fundraisingNotices = await response.json()
// if (!fundraisingNotices || !_isArray(fundraisingNotices.notices)) {
// return []
// }

return fundraisingNotices.notices
.map((notice) => {
// add Date instance for expiration timestamp
notice.expirationDate = parse(notice.expirationTimestamp)
return notice
})
.filter((notice) => isFuture(notice.expirationDate))
} else {
// Allow server admin to delete file when not in use
return []
}
// return fundraisingNotices.notices
// .map((notice) => {
// // add Date instance for expiration timestamp
// notice.expirationDate = parse(notice.expirationTimestamp)
// return notice
// })
// .filter((notice) => isFuture(notice.expirationDate))
// } else {
// // Allow server admin to delete file when not in use
// return []
// }
return []
}

0 comments on commit 75425ca

Please sign in to comment.