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:
@ -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">
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user