/*eslint max-lines: ["warn", {"max": 300, "skipBlankLines": true, "skipComments": true}]*/ import './listPage.less'; import React, { useEffect, useState, useRef, useMemo } from 'react'; import moment from 'moment'; import _ from 'lodash'; import BrewItem from './brewItem/brewItem.jsx'; const USERPAGE_SORT_DIR = 'HB_listPage_sortDir'; const USERPAGE_SORT_TYPE = 'HB_listPage_sortType'; const USERPAGE_GROUP_VISIBILITY_PREFIX = 'HB_listPage_visibility_group'; const DEFAULT_SORT_TYPE = 'alpha'; const DEFAULT_SORT_DIR = 'asc'; const ListPage = ({ brewCollection = [{ title: '', class: '', brews: [] }], navItems = <>, reportError = null, query })=>{ const [filterString, setFilterString] = useState(query?.filter || ''); const [filterTags, setFilterTags] = useState([]); const [sortType, setSortType] = useState(query?.sort || null); const [sortDir, setSortDir] = useState(query?.dir || null); const [groupVisibility, setGroupVisibility] = useState({}); const groupVisibilityRef = useRef(groupVisibility); const sortTypeRef = useRef(sortType); const sortDirRef = useRef(sortDir); useEffect(()=>{ groupVisibilityRef.current = groupVisibility; }, [groupVisibility]); useEffect(()=>{ sortTypeRef.current = sortType; }, [sortType]); useEffect(()=>{ sortDirRef.current = sortDir; }, [sortDir]); useEffect(()=>{ window.onbeforeunload = saveToLocalStorage; if(typeof window === 'undefined') return; const newSortType = sortType ?? (localStorage.getItem(USERPAGE_SORT_TYPE) || DEFAULT_SORT_TYPE); const newSortDir = sortDir ?? (localStorage.getItem(USERPAGE_SORT_DIR) || DEFAULT_SORT_DIR); updateUrl(filterString, newSortType, newSortDir); const namedBrewCollection = brewCollection.reduce((visibility, brewGroup)=>{ visibility[brewGroup.class] = (localStorage.getItem(`${USERPAGE_GROUP_VISIBILITY_PREFIX}_${brewGroup.class}`) ?? 'true') == 'true'; return visibility; }, {}); setGroupVisibility(namedBrewCollection); setSortType(newSortType); setSortDir(newSortDir); return ()=>{ window.onbeforeunload = null; }; }, []); const saveToLocalStorage = ()=>{ brewCollection.forEach((brewGroup)=>{ localStorage.setItem(`${USERPAGE_GROUP_VISIBILITY_PREFIX}_${brewGroup.class}`, `${groupVisibilityRef.current[brewGroup.class]}`); }); localStorage.setItem(USERPAGE_SORT_TYPE, sortTypeRef.current); localStorage.setItem(USERPAGE_SORT_DIR, sortDirRef.current); }; const renderBrews = (brews)=>{ if(!brews || !brews.length) return
No Brews.
; return _.map(brews, (brew, idx)=>( { updateUrl(filterString, sortType, sortDir, tag); }} /> )); }; const sortBrewOrder = (brew)=>{ const title = brew.title || 'No Title'; const mapping = { 'alpha' : _.deburr(title.trim().toLowerCase()), 'created' : moment(brew.createdAt).format(), 'updated' : moment(brew.updatedAt).format(), 'views' : brew.views, 'latest' : moment(brew.lastViewed).format(), }; return mapping[sortType]; }; const handleSortOptionChange = (event)=>{ updateUrl(filterString, event.target.value, sortDir); setSortType(event.target.value); }; const handleSortDirChange = (event)=>{ const newDir = sortDir == 'asc' ? 'desc' : 'asc'; updateUrl(filterString, sortType, newDir); setSortDir(newDir); }; const renderSortOption = (sortTitle, sortValue)=>{ return (
{sortType == sortValue && }
); }; const handleFilterTextChange = (e)=>{ setFilterString(e.target.value); updateUrl(e.target.value, sortType, sortDir); return; }; const updateUrl = (filterTerm, sortType, sortDir, filterTag = '')=>{ const url = new URL(window.location.href); const urlParams = new URLSearchParams(url.search); urlParams.set('sort', sortType); urlParams.set('dir', sortDir); let filterTags = urlParams.getAll('tag'); if(filterTag != '') { if( filterTags.findIndex((tag)=>{ return tag.toLowerCase() == filterTag.toLowerCase(); }) == -1 ) { filterTags.push(filterTag); } else { filterTags = filterTags.filter((tag)=>{ return tag.toLowerCase() != filterTag.toLowerCase(); }); } } urlParams.delete('tag'); // Add tags to URL in the order they were clicked filterTags.forEach((tag)=>urlParams.append('tag', tag)); // Sort tags before updating state filterTags.sort((a, b)=>{ return a.indexOf(':') - b.indexOf(':') != 0 ? a.indexOf(':') - b.indexOf(':') : a.toLowerCase().localeCompare(b.toLowerCase()); }); setFilterTags(filterTags); if(!filterTerm) urlParams.delete('filter'); else urlParams.set('filter', filterTerm); url.search = urlParams; window.history.replaceState(null, null, url); }; const renderFilterOption = ()=>{ return (
); }; const renderTagsOptions = ()=>{ if(filterTags?.length == 0) return; return (
{_.map(filterTags, (tag, idx)=>{ const matches = tag.match(/^(?:([^:]+):)?([^:]+)$/); return ( { updateUrl(filterString, sortType, sortDir, tag); }}> {matches[2]} ); })}
); }; const renderSortOptions = ()=>{ return (
Sort by :
{renderSortOption('Title', 'alpha')} {renderSortOption('Created Date', 'created')} {renderSortOption('Updated Date', 'updated')} {renderSortOption('Views', 'views')} {/* {renderSortOption('Latest', 'latest')} */} {renderFilterOption()}
); }; const getSortedBrews = (brews)=>{ const testString = _.deburr(filterString).toLowerCase(); brews = _.filter(brews, (brew)=>{ // Filter by user entered text const brewStrings = _.deburr([brew.title, brew.description, brew.tags].join('\n').toLowerCase()); const filterTextTest = brewStrings.includes(testString); // Filter by user selected tags let filterTagTest = true; if(filterTags.length > 0) { filterTagTest = Array.isArray(brew.tags) && filterTags?.every((tag)=>{ return ( brew.tags.findIndex((brewTag)=>{ return brewTag.toLowerCase() == tag.toLowerCase(); }) >= 0 ); }); } return filterTextTest && filterTagTest; }); return _.orderBy( brews, (brew)=>{ return sortBrewOrder(brew); }, sortDir, ); }; const sortedBrewCollection = useMemo(()=>{ return brewCollection.map((brewGroup)=>({ ...brewGroup, brews: getSortedBrews(brewGroup.brews) })); }, [brewCollection, filterString, filterTags, sortType, sortDir]); const toggleBrewCollectionState = (brewGroupClass)=>{ setGroupVisibility((prevVisibility)=>({ ...prevVisibility, [brewGroupClass]: !prevVisibility[brewGroupClass] })); }; const renderBrewCollection = (brewCollection)=>{ if(brewCollection.length === 0) return (

No Brews

); return _.map(brewCollection, (brewGroup, idx)=>{ const sortedBrewGroup = sortedBrewCollection[idx]; const visible = groupVisibility[brewGroup.class]; return (

{ toggleBrewCollectionState(brewGroup.class); }}> {brewGroup.title || 'No Title'}

{visible ? renderBrews(sortedBrewGroup.brews) : <>}
); }); }; return (
{navItems} {renderSortOptions()} {renderTagsOptions()}
{renderBrewCollection(brewCollection)}
); }; export default ListPage;