From 75425cad005a7eb7cd13a8de91912d22627c0de3 Mon Sep 17 00:00:00 2001 From: CollinBeczak Date: Fri, 8 Sep 2023 10:46:24 -0500 Subject: [PATCH] add filter dropdown for categorization keywords --- .../ChallengeFilterSubnav.js | 4 + .../FilterByCategorizationKeywords.js | 167 ++++++++++++++++++ .../ChallengeFilterSubnav/Messages.js | 5 + .../WithFilteredChallenges.js | 3 + src/components/HOCs/WithSearch/WithSearch.js | 4 + .../MobileFilterMenu/MobileFilterMenu.js | 3 + .../ChallengeKeywords/ChallengeKeywords.js | 13 ++ .../FundraisingNotices/FundraisingNotices.js | 41 ++--- 8 files changed, 220 insertions(+), 20 deletions(-) create mode 100644 src/components/ChallengePane/ChallengeFilterSubnav/FilterByCategorizationKeywords.js diff --git a/src/components/ChallengePane/ChallengeFilterSubnav/ChallengeFilterSubnav.js b/src/components/ChallengePane/ChallengeFilterSubnav/ChallengeFilterSubnav.js index 9a00a31ff..5be04ed50 100644 --- a/src/components/ChallengePane/ChallengeFilterSubnav/ChallengeFilterSubnav.js +++ b/src/components/ChallengePane/ChallengeFilterSubnav/ChallengeFilterSubnav.js @@ -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' @@ -20,6 +21,7 @@ const CommandSearchBox = WithCommandInterpreter(SearchBox) * * @see See FilterByDifficulty * @see See FilterByKeyword + * @see See FilterByCategorizationKeywords * @see See FilterByLocation * @see See SearchBox * @@ -46,6 +48,8 @@ export class ChallengeFilterSubnav extends Component { + + { + 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 ( + ( + } + selection={categorizationFilters.length > 0 ? `${categorizationFilters.length} Filters` : "Anything"} + onClick={dropdown.toggleDropdownVisible} + selectionClassName={categorizationFilters.length > 0 ? 'mr-text-yellow' : null} + /> + )} + dropdownContent={(dropdown) => ( + + )} + /> + ) + } +} + +const ListFilterItems = function (props) { + const { categorizationFilters, categories } = props + + const menuItems = categories.map((keyword) => ( +
  • + props.updateFilter(keyword, categorizationFilters)} + > +
    {keyword}
    +
    + +
  • + )) + + // Add 'Anything' option to start of dropdown + menuItems.unshift( +
  • + props.updateFilter(null)}>Anything +
  • + ) + + // Add box for manually entering other keywords not included in the menu. + menuItems.push( +
  • + {props.categories.length === 0 ? ( +
    You have not set any categories.
    + ) : null} +
    + +
    { + 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 = '' + }}> + +
    +
    +
    + {props.categories.length === 6 + ?
    You must delete a category
    to add a new one.
    + : "Add a new category"} +
    +
  • + ) + + return
      {menuItems}
    +} + +FilterByCategorizationKeywords.propTypes = { + /** Invoked to update the challenge keyword filter */ + setCategorizationFilters: PropTypes.func.isRequired, +} + +export default injectIntl(FilterByCategorizationKeywords) diff --git a/src/components/ChallengePane/ChallengeFilterSubnav/Messages.js b/src/components/ChallengePane/ChallengeFilterSubnav/Messages.js index 64160a08a..b76f47491 100644 --- a/src/components/ChallengePane/ChallengeFilterSubnav/Messages.js +++ b/src/components/ChallengePane/ChallengeFilterSubnav/Messages.js @@ -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', diff --git a/src/components/HOCs/WithFilteredChallenges/WithFilteredChallenges.js b/src/components/HOCs/WithFilteredChallenges/WithFilteredChallenges.js index 16a2fad9e..583a9c314 100644 --- a/src/components/HOCs/WithFilteredChallenges/WithFilteredChallenges.js +++ b/src/components/HOCs/WithFilteredChallenges/WithFilteredChallenges.js @@ -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 } @@ -15,6 +17,7 @@ import { challengePassesProjectFilter } const allFilters = [ challengePassesDifficultyFilter, challengePassesKeywordFilter, + challengePassesCategorizationKeywordsFilter, challengePassesLocationFilter, challengePassesProjectFilter, ] diff --git a/src/components/HOCs/WithSearch/WithSearch.js b/src/components/HOCs/WithSearch/WithSearch.js index 415b23c33..90c6ac0fa 100644 --- a/src/components/HOCs/WithSearch/WithSearch.js +++ b/src/components/HOCs/WithSearch/WithSearch.js @@ -216,6 +216,10 @@ export const mapDispatchToProps = (dispatch, ownProps, searchGroup) => ({ dispatch(setFilters(searchGroup, {keywords})) }, + setCategorizationFilters: categorizationKeywords => { + dispatch(setFilters(searchGroup, {categorizationKeywords})) + }, + clearSearchFilters: () => { dispatch(clearFilters(searchGroup)) }, diff --git a/src/components/MobileFilterMenu/MobileFilterMenu.js b/src/components/MobileFilterMenu/MobileFilterMenu.js index 949f292d4..ac588ca8f 100644 --- a/src/components/MobileFilterMenu/MobileFilterMenu.js +++ b/src/components/MobileFilterMenu/MobileFilterMenu.js @@ -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' @@ -27,6 +29,7 @@ export class MobileFilterMenu extends Component { + diff --git a/src/services/Challenge/ChallengeKeywords/ChallengeKeywords.js b/src/services/Challenge/ChallengeKeywords/ChallengeKeywords.js index a049bb669..8bf4dfb79 100644 --- a/src/services/Challenge/ChallengeKeywords/ChallengeKeywords.js +++ b/src/services/Challenge/ChallengeKeywords/ChallengeKeywords.js @@ -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 +} diff --git a/src/services/FundraisingNotices/FundraisingNotices.js b/src/services/FundraisingNotices/FundraisingNotices.js index 7761ae6e0..1521ccdb7 100644 --- a/src/services/FundraisingNotices/FundraisingNotices.js +++ b/src/services/FundraisingNotices/FundraisingNotices.js @@ -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 [] }