diff --git a/YABA.API/Controllers/BookmarksController.cs b/YABA.API/Controllers/BookmarksController.cs index 703f016..8792c4e 100644 --- a/YABA.API/Controllers/BookmarksController.cs +++ b/YABA.API/Controllers/BookmarksController.cs @@ -74,9 +74,9 @@ namespace YABA.API.Controllers [HttpGet] [ProducesResponseType(typeof(IEnumerable), (int)HttpStatusCode.OK)] - public IActionResult GetAll(bool isHidden = false) + public IActionResult GetAll(bool showHidden = false) { - var result = _bookmarkService.GetAll(isHidden); + var result = _bookmarkService.GetAll(showHidden); return Ok(_mapper.Map>(result)); } diff --git a/YABA.API/WeatherForecast.cs b/YABA.API/WeatherForecast.cs deleted file mode 100644 index 59b87e7..0000000 --- a/YABA.API/WeatherForecast.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace YABA.API -{ - public class WeatherForecast - { - public DateTime Date { get; set; } - - public int TemperatureC { get; set; } - - public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); - - public string? Summary { get; set; } - } -} \ No newline at end of file diff --git a/YABA.Service/BookmarkService.cs b/YABA.Service/BookmarkService.cs index 7c4d098..8fc333a 100644 --- a/YABA.Service/BookmarkService.cs +++ b/YABA.Service/BookmarkService.cs @@ -37,35 +37,29 @@ namespace YABA.Service _mapper = mapper; } - public IEnumerable GetAll(bool isHidden = false) + public IEnumerable GetAll(bool showHidden = false) { var currentUserId = GetCurrentUserId(); - var userBookmarks = _roContext.Bookmarks.Where(x => x.UserId == currentUserId && x.IsHidden == isHidden).ToDictionary(k => k.Id, v => v); var bookmarkTagsLookup = _roContext.BookmarkTags - .Include(x => x.Tag) - .Where(x => userBookmarks.Keys.Contains(x.BookmarkId)) - .ToList() - .GroupBy(x => x.BookmarkId) - .ToDictionary(k => k.Key, v => v.ToList()); - + .Include(x => x.Bookmark) + .Include(x => x.Tag) + .Where(x => x.Bookmark.UserId == currentUserId && x.Tag.UserId == currentUserId) + .ToList() + .GroupBy(x => x.BookmarkId) + .ToDictionary(k => k.Key, v => new KeyValuePair>(v.FirstOrDefault()?.Bookmark, v.Select(x => x.Tag).ToList())); var userBookmarkDTOs = new List(); - foreach(var bookmark in userBookmarks) + foreach (var bookmarkTag in bookmarkTagsLookup) { - var bookmarkDTO = _mapper.Map(bookmark.Value); - bookmarkTagsLookup.TryGetValue(bookmark.Key, out var bookmarkTags); - - if(bookmarkTags != null) + if ((showHidden && (bookmarkTag.Value.Key.IsHidden || bookmarkTag.Value.Value.Any(x => x.IsHidden))) // If showHidden = true, only add Bookmarks that are marked Hidden OR when any of its Tags are marked Hidden + || (!showHidden && !bookmarkTag.Value.Key.IsHidden && bookmarkTag.Value.Value.All(x => !x.IsHidden))) // If showHidden = false, only add when Bookmark is not marked Hidden AND when any of its Tags are not marked as Hidden { - foreach (var bookmarkTag in bookmarkTags) - { - var tagDTO = _mapper.Map(bookmarkTag.Tag); - bookmarkDTO.Tags.Add(tagDTO); - } + var bookmarkDTO = _mapper.Map(bookmarkTag.Value.Key); + var bookmarkTagDTOs = _mapper.Map>(bookmarkTag.Value.Value); + bookmarkDTO.Tags.AddRange(bookmarkTagDTOs); + userBookmarkDTOs.Add(bookmarkDTO); } - - userBookmarkDTOs.Add(bookmarkDTO); } return userBookmarkDTOs; diff --git a/yaba-web/src/api/v1/bookmarks.js b/yaba-web/src/api/v1/bookmarks.js index dce5b21..928c367 100644 --- a/yaba-web/src/api/v1/bookmarks.js +++ b/yaba-web/src/api/v1/bookmarks.js @@ -2,9 +2,9 @@ import { callExternalApi } from "../apiHelper"; const apiServerUrl = `${process.env.REACT_APP_API_BASE_URL}/v1/Bookmarks`; -export const getAllBookmarks = async(accessToken, isHidden = false) => { +export const getAllBookmarks = async(accessToken, showHidden = false) => { const config = { - url: `${apiServerUrl}?isHidden=${isHidden}`, + url: `${apiServerUrl}?showHidden=${showHidden}`, method: "GET", headers: { "content-type": "application/json", diff --git a/yaba-web/src/utils/index.js b/yaba-web/src/utils/index.js index ca6cfa9..523bb57 100644 --- a/yaba-web/src/utils/index.js +++ b/yaba-web/src/utils/index.js @@ -1,5 +1,5 @@ import { isDev } from "./isDevHelper"; -import { getTagGroups } from "./tagsHelper"; +import { getTagGroups, flattenTagArrays } from "./tagsHelper"; import { isSubset } from "./arrayHelper"; import { containsSubstring } from "./bookmarkHelper"; @@ -7,5 +7,6 @@ export { isDev, getTagGroups, isSubset, - containsSubstring + containsSubstring, + flattenTagArrays } \ No newline at end of file diff --git a/yaba-web/src/utils/tagsHelper.js b/yaba-web/src/utils/tagsHelper.js index b0da23a..dd50ed0 100644 --- a/yaba-web/src/utils/tagsHelper.js +++ b/yaba-web/src/utils/tagsHelper.js @@ -14,4 +14,12 @@ export const getTagGroups = (allBookmarkTags) => { return accumulator }, []).sort((a, b) => (a.name < b.name) ? -1 : (a.name > b.name) ? 1 : 0); -} \ No newline at end of file +}; + +export const flattenTagArrays = (allBookmarkTags) => allBookmarkTags.flat().reduce((accumulator, current) => { + if(!accumulator.find((item) => item.id === current.id)) { + accumulator.push(current); + } + + return accumulator +}, []); \ No newline at end of file diff --git a/yaba-web/src/views/bookmarksListView.jsx b/yaba-web/src/views/bookmarksListView.jsx index b87367c..acfa1ce 100644 --- a/yaba-web/src/views/bookmarksListView.jsx +++ b/yaba-web/src/views/bookmarksListView.jsx @@ -2,9 +2,9 @@ import React, { useEffect, useReducer, useState } from "react"; import { Alert, Col, Container, Row, Button, Modal } from "../components/external"; import { Bookmark } from "../components"; import { useAuth0 } from "@auth0/auth0-react"; -import { deleteBookmarks, getAllBookmarks, getAllTags, hideBookmarks } from "../api"; +import { deleteBookmarks, getAllBookmarks, hideBookmarks } from "../api"; import { SplashScreen, SearchForm } from "../components"; -import { getTagGroups, isSubset, containsSubstring } from "../utils"; +import { getTagGroups, isSubset, containsSubstring, flattenTagArrays } from "../utils"; export function BookmarksListView(props) { const { getAccessTokenSilently } = useAuth0(); @@ -65,34 +65,9 @@ export function BookmarksListView(props) { }); } }; - const [alertMessageState, dispatchAlertMessageState] = useReducer(alertReducer, alertMessageInitialState); - const tagsInitialState = []; - - const tagsReducer = (state = tagsInitialState, action) => { - switch(action.type) { - case "SET": - return action.payload.tags.map(x => ({...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, tagsInitialState); - 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); - - const bookmarksInitialState = []; - - const bookmarksReducer = (state = bookmarksInitialState, action) => { + const bookmarksReducer = (state = [], action) => { switch(action.type) { case "SET": return action.payload.bookmarks.map(x => ({...x, isSelected: false, isDisplayed: true})); @@ -122,7 +97,7 @@ export function BookmarksListView(props) { } }; - const [bookmarksState, dispatchBookmarksState] = useReducer(bookmarksReducer, bookmarksInitialState); + const [bookmarksState, dispatchBookmarksState] = useReducer(bookmarksReducer, []); const onBookmarkSelected = (isBookmarkSelected, bookmark) => dispatchBookmarksState({type: isBookmarkSelected ? "ADD_SELECTED" : "REMOVE_SELECTED", payload: {selectedBookmarkId: bookmark.id}}); const getSelectedBookmarksCount = () => bookmarksState.filter((x) => x.isSelected).length; const getSelectedBookmarks = () => bookmarksState.filter((x) => x.isSelected); @@ -205,17 +180,6 @@ export function BookmarksListView(props) { }; - const fetchTags = async() => { - const accessToken = await getAccessTokenSilently(); - const { data, error } = await getAllTags(accessToken); - - if(error) { - dispatchAlertMessageState({type: "SHOW_ALERT", payload: {show: true, alertType: "danger", "message": `Error fetching tags: ${error.message}`}}); - } else { - dispatchTagsState({type: "SET", payload: {tags: data}}); - } - }; - const fetchBookmarks = async() => { const accessToken = await getAccessTokenSilently(); const { data, error } = await getAllBookmarks(accessToken, props.showHidden); @@ -224,13 +188,32 @@ export function BookmarksListView(props) { dispatchAlertMessageState({type: "SHOW_ALERT", payload: {show: true, alertType: "danger", "message": `Error fetching bookmarks: ${error.message}`}}); } else { dispatchBookmarksState({type: "SET", payload: {bookmarks: data}}); + dispatchTagsState({type: "SET"}); + } + } + + + 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; } } - useEffect(() => { - dispatchSplashScreenState({type: "SHOW_SPLASH_SCREEN", payload: {message: "Retrieving Tags..."}}); - fetchTags(); + 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(); @@ -354,7 +337,7 @@ export function BookmarksListView(props) {
{ group.tags.map((tag) => { - return })