Modified to fix search and tag filtering not working together, fixed proper count for selected items to not include not displayed items

This commit is contained in:
2023-02-21 20:32:28 -06:00
parent 83a4fba905
commit 0065613cdd

View File

@ -1,5 +1,5 @@
import React, { useEffect, useReducer, useState } from "react"; import React, { useEffect, useReducer, useState } from "react";
import { Alert, Col, Container, Row, Button, Modal } from "../components/external"; import { Alert, Col, Container, Row, Button, Modal, Dropdown, DropdownButton } from "../components/external";
import { Bookmark } from "../components"; import { Bookmark } from "../components";
import { useAuth0 } from "@auth0/auth0-react"; import { useAuth0 } from "@auth0/auth0-react";
import { deleteBookmarks, getAllBookmarks, hideBookmarks } from "../api"; import { deleteBookmarks, getAllBookmarks, hideBookmarks } from "../api";
@ -10,24 +10,6 @@ export function BookmarksListView(props) {
const { getAccessTokenSilently } = useAuth0(); const { getAccessTokenSilently } = useAuth0();
const [searchString, setSearchString] = useState(""); const [searchString, setSearchString] = useState("");
const handleSearch = (e) => {
e.preventDefault();
if(!searchString) {
dispatchBookmarksState({type: "DISPLAY_ALL"});
} else {
dispatchBookmarksState({type: "SEARCH"});
}
};
const handleSearchStringChange = (e) => {
if(!e.target.value) {
dispatchBookmarksState({type: "DISPLAY_ALL"});
} else {
setSearchString(e.target.value);
}
};
const initialSplashScreenState = { show: false, message: null }; const initialSplashScreenState = { show: false, message: null };
const splashScreenReducer = (state = initialSplashScreenState, action) => { const splashScreenReducer = (state = initialSplashScreenState, action) => {
@ -67,7 +49,30 @@ export function BookmarksListView(props) {
}; };
const [alertMessageState, dispatchAlertMessageState] = useReducer(alertReducer, alertMessageInitialState); const [alertMessageState, dispatchAlertMessageState] = useReducer(alertReducer, alertMessageInitialState);
const bookmarksReducer = (state = [], action) => { const tagsInitialState = [];
const tagsReducer = (state = tagsInitialState, action) => {
switch(action.type) {
case "SET":
return action.payload.tags.map(x => ({...x, isSelected: false, isDisplayed: true }));
case "ADD_SELECTED":
case "REMOVE_SELECTED":
const modifiedTags = [...state];
const selectedTagIndex = modifiedTags.findIndex((x) => x.id === action.payload.selectedTagId);
modifiedTags[selectedTagIndex].isSelected = action.type === "ADD_SELECTED";
return modifiedTags;
default:
return state;
}
}
const [tagsState, dispatchTagsState] = useReducer(tagsReducer, tagsInitialState);
const getSelectedTags = () => tagsState.filter((x) => x.isSelected);
const getNotSelectedTags = () => tagsState.filter((x) => !x.isSelected);
const bookmarksInitialState = [];
const bookmarksReducer = (state = bookmarksInitialState, action) => {
switch(action.type) { switch(action.type) {
case "SET": case "SET":
return action.payload.bookmarks.map(x => ({...x, isSelected: false, isDisplayed: true})); return action.payload.bookmarks.map(x => ({...x, isSelected: false, isDisplayed: true}));
@ -81,17 +86,14 @@ export function BookmarksListView(props) {
case "HIDE_SELECTED": case "HIDE_SELECTED":
return state.filter((x) => !action.payload.selectedBookmarkIds.includes(x.id)); return state.filter((x) => !action.payload.selectedBookmarkIds.includes(x.id));
case "SELECT_ALL": case "SELECT_ALL":
return state.map(x => ({...x, isSelected: true})); return state.map(x => ({...x, isSelected: x.isDisplayed}));
case "UNSELECT_ALL": case "UNSELECT_ALL":
return state.map(x => ({...x, isSelected: false})); return state.map(x => ({...x, isSelected: false}));
case "DISPLAY_ALL": case "DISPLAY_ALL":
return state.map(x => ({...x, isDisplayed: true})); return state.map(x => ({...x, isDisplayed: true}));
case "SEARCH": case "SEARCH":
if(!searchString) { return state.map(x => ({...x, isDisplayed: (getSelectedTags().length <= 0 || isSubset(x.tags.map(x => x.id), getSelectedTags().map(x => x.id)))
dispatchBookmarksState({type: "DISPLAY_ALL"}); && (!searchString || containsSubstring(x, searchString))}));
}
return state.map(x => ({...x, isDisplayed: containsSubstring(x, searchString)}));
default: default:
return state; return state;
} }
@ -99,16 +101,11 @@ export function BookmarksListView(props) {
const [bookmarksState, dispatchBookmarksState] = useReducer(bookmarksReducer, []); const [bookmarksState, dispatchBookmarksState] = useReducer(bookmarksReducer, []);
const onBookmarkSelected = (isBookmarkSelected, bookmark) => dispatchBookmarksState({type: isBookmarkSelected ? "ADD_SELECTED" : "REMOVE_SELECTED", payload: {selectedBookmarkId: bookmark.id}}); const onBookmarkSelected = (isBookmarkSelected, bookmark) => dispatchBookmarksState({type: isBookmarkSelected ? "ADD_SELECTED" : "REMOVE_SELECTED", payload: {selectedBookmarkId: bookmark.id}});
const getSelectedBookmarksCount = () => bookmarksState.filter((x) => x.isSelected).length; const getDisplayedBookmarksCount = () => bookmarksState.filter(x => x.isDisplayed).length;
const getSelectedBookmarks = () => bookmarksState.filter((x) => x.isSelected); const getSelectedBookmarksCount = () => bookmarksState.filter(x => x.isSelected && x.isDisplayed).length;
const getAreAllBookmarksSelected = () => bookmarksState.every(x => x.isSelected); const getSelectedBookmarks = () => bookmarksState.filter((x) => x.isSelected && x.isDisplayed);
const getFilteredBookmarks = () => { const getAreAllBookmarksSelected = () => bookmarksState.filter(x => x.isDisplayed).every(x => x.isSelected);
if (getSelectedTags().length <= 0) { const getFilteredBookmarks = () => bookmarksState.filter(x => x.isDisplayed);
return bookmarksState.filter(x => x.isDisplayed);
} else {
return bookmarksState.filter(x => isSubset(x.tags.map(x => x.id), getSelectedTags().map(x => x.id)));
}
}
const onDeleteSelectedBookmarks = async (ids) => { const onDeleteSelectedBookmarks = async (ids) => {
if(ids.length <= 0) { if(ids.length <= 0) {
@ -180,6 +177,25 @@ export function BookmarksListView(props) {
}; };
const onTagSelected = (isTagSelected, tag) => {
dispatchTagsState({type: isTagSelected ? "ADD_SELECTED" : "REMOVE_SELECTED", payload: {selectedTagId: tag.id}});
dispatchBookmarksState({type: "SEARCH"});
};
const handleSearch = (e) => {
e.preventDefault();
setSearchString(e.target[0].value);
dispatchBookmarksState({type: "SEARCH"});
};
const handleSearchStringChange = (e) => {
setSearchString(e.target.value);
dispatchBookmarksState({type: "SEARCH"});
};
useEffect(() => {
dispatchSplashScreenState({type: "SHOW_SPLASH_SCREEN", payload: {message: "Retrieving Bookmarks..."}});
const fetchBookmarks = async() => { const fetchBookmarks = async() => {
const accessToken = await getAccessTokenSilently(); const accessToken = await getAccessTokenSilently();
const { data, error } = await getAllBookmarks(accessToken, props.showHidden); const { data, error } = await getAllBookmarks(accessToken, props.showHidden);
@ -188,36 +204,13 @@ export function BookmarksListView(props) {
dispatchAlertMessageState({type: "SHOW_ALERT", payload: {show: true, alertType: "danger", "message": `Error fetching bookmarks: ${error.message}`}}); dispatchAlertMessageState({type: "SHOW_ALERT", payload: {show: true, alertType: "danger", "message": `Error fetching bookmarks: ${error.message}`}});
} else { } else {
dispatchBookmarksState({type: "SET", payload: {bookmarks: data}}); dispatchBookmarksState({type: "SET", payload: {bookmarks: data}});
dispatchTagsState({type: "SET"}); dispatchTagsState({type: "SET", payload: {tags: flattenTagArrays(data.map(x => x.tags))}});
} }
}
const tagsReducer = (state = [], action) => {
switch(action.type) {
case "SET":
return flattenTagArrays(bookmarksState.map(x => x.tags)).map(x => Object.assign({}, x, {isSelected : false}));
case "ADD_SELECTED":
case "REMOVE_SELECTED":
const modifiedTags = [...state];
const selectedTagIndex = modifiedTags.findIndex((x) => x.id === action.payload.selectedTagId);
modifiedTags[selectedTagIndex].isSelected = action.type === "ADD_SELECTED";
return modifiedTags;
default:
return state;
}
}
const [tagsState, dispatchTagsState] = useReducer(tagsReducer, []);
const onTagSelected = (isTagSelected, tag) => dispatchTagsState({type: isTagSelected ? "ADD_SELECTED" : "REMOVE_SELECTED", payload: {selectedTagId: tag.id}})
const getSelectedTags = () => tagsState.filter((x) => x.isSelected);
const getNotSelectedTags = () => tagsState.filter((x) => !x.isSelected);
useEffect(() => {
dispatchSplashScreenState({type: "SHOW_SPLASH_SCREEN", payload: {message: "Retrieving Bookmarks..."}});
fetchBookmarks();
dispatchSplashScreenState({type: "HIDE_SPLASH_SCREEN", payload: {message: null}}); dispatchSplashScreenState({type: "HIDE_SPLASH_SCREEN", payload: {message: null}});
}
fetchBookmarks();
}, []); }, []);
return( return(
@ -284,26 +277,31 @@ export function BookmarksListView(props) {
</Col> </Col>
</Row> </Row>
<Row> <Row>
<Col xs="2" className="mb-3"> <Col xs="9" className="mb-3">
<div className="d-flex justify-content-start align-items-center">
{ {
bookmarksState.length > 0 && getDisplayedBookmarksCount() <= 0 &&
<Button variant="primary" onClick={() => dispatchBookmarksState({type: getAreAllBookmarksSelected() ? "UNSELECT_ALL" : "SELECT_ALL"})}>{getAreAllBookmarksSelected() ? "Unselect All" : "Select All" }</Button> <span className="fs-4">No bookmarks to display</span>
} }
</Col>
<Col xs="7" className="mb-3"> {
getDisplayedBookmarksCount() > 0 &&
<Button className="me-2" variant="primary" onClick={() => dispatchBookmarksState({type: getAreAllBookmarksSelected() ? "UNSELECT_ALL" : "SELECT_ALL"})}>{getAreAllBookmarksSelected() ? "Unselect All" : "Select All" }</Button>
}
{ {
getSelectedBookmarksCount() > 0 && getSelectedBookmarksCount() > 0 &&
<div className="d-flex justify-content-end align-items-center"> <DropdownButton variant="secondary" title={`${getSelectedBookmarksCount()} selected`}>
<span className="fs-5 me-2"> {getSelectedBookmarksCount()} selected</span> <Dropdown.Item onClick={() => handleHideBookmarks(getSelectedBookmarks().map(x => x.id))}>
<Button variant="primary" className="me-2" onClick={() => handleHideBookmarks(getSelectedBookmarks().map(x => x.id))}>{props.showHidden ? "Unhide" : "Hide"}</Button> {props.showHidden ? "Unhide" : "Hide"}
<Button </Dropdown.Item>
variant="danger" <Dropdown.Item onClick={handleDeleteMultipleBookmarks}>
onClick={handleDeleteMultipleBookmarks} <span className="text-danger">Delete</span>
> </Dropdown.Item>
Delete </DropdownButton>
</Button>
</div>
} }
</div>
</Col> </Col>
<Col xs="3" className="mb-3"> <Col xs="3" className="mb-3">
{ {