From 2f11ff015ad63eba767c19cb171c151741d974b3 Mon Sep 17 00:00:00 2001 From: Anthony <18110175@student.hcmute.edu.vn> Date: Thu, 22 Dec 2022 01:22:16 +0700 Subject: [PATCH 1/8] Fix: Not navigate to /articles when chose other category but is in content --- .../src/components/Category/NavMenu.jsx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/front-end/knowledgesharing/src/components/Category/NavMenu.jsx b/front-end/knowledgesharing/src/components/Category/NavMenu.jsx index 3eb42c5..f3f0bbe 100644 --- a/front-end/knowledgesharing/src/components/Category/NavMenu.jsx +++ b/front-end/knowledgesharing/src/components/Category/NavMenu.jsx @@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react'; import { DropdownItem, Nav, NavItem, NavLink } from 'reactstrap'; import categoryApi from '../../apis/categoryApi'; import './NavMenu.css'; +import { useLocation, useNavigate } from 'react-router-dom'; // import PropTypes from 'prop-types'; // CategoryNavMenu.propTypes = { @@ -14,6 +15,18 @@ function CategoryNavMenu(props) { const [loaded, setLoaded] = useState(false); const [category, setCategory] = useState({id: undefined, name: "Newest"}); + let navigate = useNavigate(); + const location = useLocation(); + // console.log("ZZ location: ", location.pathname); + + const checkIfIsInArticleContent = () => { + if(location.pathname.substring(0, 10) === "/articles/" && location.pathname.length > 10) { + return true; + } else { + return false; + } + } + useEffect(() => { const fetchCategory = async () => { try { @@ -51,6 +64,10 @@ function CategoryNavMenu(props) { setCategory(item); props.onHandleChangeCat(item); } + + if(checkIfIsInArticleContent()) { + navigate("/articles"); + } }} > {item.name} From 867144cb7a6e91bb1f895de644ec9ca82053ff04 Mon Sep 17 00:00:00 2001 From: Anthony <18110175@student.hcmute.edu.vn> Date: Fri, 23 Dec 2022 01:24:26 +0700 Subject: [PATCH 2/8] Home: Newly Articles & ReadingList --- .../Controllers/HomeNewArticleController.java | 54 ++++++++++++ .../NewlyArticleForHomeReturnModel.java | 86 +++++++++++++++++++ .../knowsharing/Repositories/ArticleRepo.java | 2 + .../knowsharing/Services/ArticleService.java | 4 + 4 files changed, 146 insertions(+) create mode 100644 back-end/knowsharing/src/main/java/com/ndp/knowsharing/Controllers/HomeNewArticleController.java create mode 100644 back-end/knowsharing/src/main/java/com/ndp/knowsharing/Models/ArticleForHome/NewlyArticleForHomeReturnModel.java diff --git a/back-end/knowsharing/src/main/java/com/ndp/knowsharing/Controllers/HomeNewArticleController.java b/back-end/knowsharing/src/main/java/com/ndp/knowsharing/Controllers/HomeNewArticleController.java new file mode 100644 index 0000000..8ae1a29 --- /dev/null +++ b/back-end/knowsharing/src/main/java/com/ndp/knowsharing/Controllers/HomeNewArticleController.java @@ -0,0 +1,54 @@ +package com.ndp.knowsharing.Controllers; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.ndp.knowsharing.Entities.Article; +import com.ndp.knowsharing.Entities.Category; +import com.ndp.knowsharing.Models.ArticleForHome.NewlyArticleForHomeReturnModel; +import com.ndp.knowsharing.Services.ArticleService; +import com.ndp.knowsharing.Services.CategoryService; + +@RestController +@CrossOrigin("*") +@RequestMapping("/api/v1/new-articles") +public class HomeNewArticleController { + + @Autowired + private ArticleService articleService; + + @Autowired + private CategoryService categoryService; + + @GetMapping( + produces = MediaType.APPLICATION_JSON_VALUE + ) + public ResponseEntity retrieveAll() { + ResponseEntity entity; + + List newlyArticleForHomeReturnModels = new ArrayList(); + + List
newestArticles = articleService.retrieveTop5ByHiddenAndNewest(0); + + for (Article article : newestArticles) { + Category category = categoryService.retrieveById(article.getCategory()); + + NewlyArticleForHomeReturnModel tmp = new NewlyArticleForHomeReturnModel(article, category.getName()); + + newlyArticleForHomeReturnModels.add(tmp); + } + + entity = new ResponseEntity<>(newlyArticleForHomeReturnModels, HttpStatus.OK); + + return entity; + } +} diff --git a/back-end/knowsharing/src/main/java/com/ndp/knowsharing/Models/ArticleForHome/NewlyArticleForHomeReturnModel.java b/back-end/knowsharing/src/main/java/com/ndp/knowsharing/Models/ArticleForHome/NewlyArticleForHomeReturnModel.java new file mode 100644 index 0000000..b552c21 --- /dev/null +++ b/back-end/knowsharing/src/main/java/com/ndp/knowsharing/Models/ArticleForHome/NewlyArticleForHomeReturnModel.java @@ -0,0 +1,86 @@ +package com.ndp.knowsharing.Models.ArticleForHome; + +import java.time.LocalDateTime; + +import com.ndp.knowsharing.Entities.Article; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class NewlyArticleForHomeReturnModel { + private String id; + + private LocalDateTime dateCreated; + + private LocalDateTime dateModified; + + private String createdBy; + + private String createdByName; + + private String modifiedBy; + + private String modifiedByName; + + private String title; + + private String description; + + private String content; + + private String audioContent; + + private String author; + + private String url; + + private String category; + + private String thumbnailUrl; + + private Integer hidden; + + private String categoryName; + + public NewlyArticleForHomeReturnModel(Article article, String categoryName) { + this.id = article.getId(); + + this.dateCreated = article.getDateCreated(); + + this.dateModified = article.getDateModified(); + + this.createdBy = article.getCreatedBy(); + + this.createdByName = article.getCreatedByName(); + + this.modifiedBy = article.getModifiedBy(); + + this.modifiedByName = article.getModifiedByName(); + + this.title = article.getTitle(); + + this.description = article.getDescription(); + + this.content = article.getContent(); + + this.audioContent = article.getAudioContent(); + + this.author = article.getAuthor(); + + this.url = article.getUrl(); + + this.category = article.getCategory(); + + this.thumbnailUrl = article.getThumbnailUrl(); + + this.hidden = article.getHidden(); + + this.categoryName = categoryName; + } +} diff --git a/back-end/knowsharing/src/main/java/com/ndp/knowsharing/Repositories/ArticleRepo.java b/back-end/knowsharing/src/main/java/com/ndp/knowsharing/Repositories/ArticleRepo.java index 1b9bf0c..66cb86c 100644 --- a/back-end/knowsharing/src/main/java/com/ndp/knowsharing/Repositories/ArticleRepo.java +++ b/back-end/knowsharing/src/main/java/com/ndp/knowsharing/Repositories/ArticleRepo.java @@ -14,6 +14,8 @@ @Repository public interface ArticleRepo extends JpaRepository { + List
findTop5ByHiddenOrderByDateCreatedDesc(Integer hidden); + List
findByUrl(String url); List
findByCategory(String category, Pageable pageable); diff --git a/back-end/knowsharing/src/main/java/com/ndp/knowsharing/Services/ArticleService.java b/back-end/knowsharing/src/main/java/com/ndp/knowsharing/Services/ArticleService.java index b157915..5fca8ff 100644 --- a/back-end/knowsharing/src/main/java/com/ndp/knowsharing/Services/ArticleService.java +++ b/back-end/knowsharing/src/main/java/com/ndp/knowsharing/Services/ArticleService.java @@ -25,6 +25,10 @@ public List
retrieveByUrl(String url) { return repo.findByUrl(url); } + public List
retrieveTop5ByHiddenAndNewest(Integer hidden) { + return repo.findTop5ByHiddenOrderByDateCreatedDesc(hidden); + } + public List
retrieveOneCommonPage(Integer pageNumber) { Page
page = repo.findAll(PageRequest.of(pageNumber, 10, Sort.by("dateCreated").descending())); From 60a03c811be2bdf6506adabcaf6daf7c730bcb45 Mon Sep 17 00:00:00 2001 From: Anthony <18110175@student.hcmute.edu.vn> Date: Fri, 23 Dec 2022 01:24:43 +0700 Subject: [PATCH 3/8] Home: Newly Articles & ReadingList --- .../src/apis/newlyArticleApi.js | 11 ++++ .../components/ReadingList/ReadingList.jsx | 33 ++++++++++- .../src/screens/Home/LeaderBoard/List.jsx | 56 +++++++++++++++---- .../src/screens/Home/NewlyUpdated/List.jsx | 48 +++++++++++++++- .../src/screens/Home/Nomination/List.jsx | 4 +- .../MainPage/Articles/Form/Content.jsx | 38 ++++++++++++- 6 files changed, 172 insertions(+), 18 deletions(-) create mode 100644 front-end/knowledgesharing/src/apis/newlyArticleApi.js diff --git a/front-end/knowledgesharing/src/apis/newlyArticleApi.js b/front-end/knowledgesharing/src/apis/newlyArticleApi.js new file mode 100644 index 0000000..ec1c423 --- /dev/null +++ b/front-end/knowledgesharing/src/apis/newlyArticleApi.js @@ -0,0 +1,11 @@ +import axiosClient from "./axiosClient"; + +const newlyArticleApi = { + getAll: () => { + const url = "/new-articles"; + + return axiosClient.get(url); + } +}; + +export default newlyArticleApi; \ No newline at end of file diff --git a/front-end/knowledgesharing/src/components/ReadingList/ReadingList.jsx b/front-end/knowledgesharing/src/components/ReadingList/ReadingList.jsx index 23cddf2..90c669f 100644 --- a/front-end/knowledgesharing/src/components/ReadingList/ReadingList.jsx +++ b/front-end/knowledgesharing/src/components/ReadingList/ReadingList.jsx @@ -1,4 +1,5 @@ import React from 'react'; +import { Link } from 'react-router-dom'; import { Card, CardBody, CardColumns, CardSubtitle, CardTitle } from 'reactstrap'; // import PropTypes from 'prop-types'; @@ -7,11 +8,38 @@ import { Card, CardBody, CardColumns, CardSubtitle, CardTitle } from 'reactstrap // }; function ReadingList(props) { + + const loadReadingListArr = () => { + const readingListStr = localStorage.getItem("readingList"); + + if(readingListStr !== null) { + const arrReadingList = JSON.parse(readingListStr); + + return arrReadingList.map((item) => + + + + {item.title} + + + {item.author} + + + + ); + } + } + return (
Reading
- - + */} + {loadReadingListArr()}
); diff --git a/front-end/knowledgesharing/src/screens/Home/LeaderBoard/List.jsx b/front-end/knowledgesharing/src/screens/Home/LeaderBoard/List.jsx index ae3e7d6..8f2f0a8 100644 --- a/front-end/knowledgesharing/src/screens/Home/LeaderBoard/List.jsx +++ b/front-end/knowledgesharing/src/screens/Home/LeaderBoard/List.jsx @@ -1,12 +1,43 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import { Card, CardBody, CardGroup, CardImg, CardSubtitle, CardText, CardTitle } from 'reactstrap'; // import PropTypes from 'prop-types'; +import leaderBoardArticleApi from '../../../apis/leaderBoardArticleApi'; +import { BASE_URL_API_BE } from '../../../constants/global'; +import { Link } from 'react-router-dom'; // LeaderBoardList.propTypes = { // }; function LeaderBoardList(props) { + + const [listLeaderBoardArticles, setListLeaderBoardArticles] = useState([]); + + const [articleItem1, setArticleItem1] = useState(undefined); + const [articleItem2, setArticleItem2] = useState(undefined); + const [articleItem3, setArticleItem3] = useState(undefined); + + useEffect(() => { + const fetchListLeaderBoardArticles = async () => { + try { + const response = await leaderBoardArticleApi.getAll(); + + setListLeaderBoardArticles(response); + + setArticleItem1(response.filter(item => item.id === "leader-board-1")[0]); + setArticleItem2(response.filter(item => item.id === "leader-board-2")[0]); + setArticleItem3(response.filter(item => item.id === "leader-board-3")[0]); + + console.log("Fetch list leader board articles successfully: ", response); + console.log("Fetch list leader board articles successfully --- ITEM: ", response.filter(item => item.id === "leader-board-2")[0]); + } catch (error) { + console.log("Failed to fetch leader board articles: ", error); + } + } + + fetchListLeaderBoardArticles(); + }, []); + return (
Leader board
@@ -14,23 +45,24 @@ function LeaderBoardList(props) { - Card title + {articleItem1 !== undefined ? {articleItem1.title} : "Blank"} - Card subtitle + {articleItem1 !== undefined ? articleItem1.author : "Author"} - This is a wider card with supporting text below as a natural lead-in to additional content. This content is a little bit longer. + {articleItem1 !== undefined ? articleItem1.description : "Author"} @@ -38,20 +70,21 @@ function LeaderBoardList(props) { - Card title + {articleItem2 !== undefined ? {articleItem2.title} : "Blank"} - Card subtitle + {articleItem2 !== undefined ? articleItem2.author : "Author"} This is a wider card with supporting text below as a natural lead-in to additional content. This content is a little bit longer. @@ -62,20 +95,21 @@ function LeaderBoardList(props) { - Card title + {articleItem3 !== undefined ? {articleItem3.title} : "Blank"} - Card subtitle + {articleItem3 !== undefined ? articleItem3.author : "Author"} This is a wider card with supporting text below as a natural lead-in to additional content. This content is a little bit longer. diff --git a/front-end/knowledgesharing/src/screens/Home/NewlyUpdated/List.jsx b/front-end/knowledgesharing/src/screens/Home/NewlyUpdated/List.jsx index ecca9ca..339a57a 100644 --- a/front-end/knowledgesharing/src/screens/Home/NewlyUpdated/List.jsx +++ b/front-end/knowledgesharing/src/screens/Home/NewlyUpdated/List.jsx @@ -1,12 +1,53 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import { Table } from 'reactstrap'; // import PropTypes from 'prop-types'; +import newlyArticleApi from '../../../apis/newlyArticleApi'; +import { Link } from 'react-router-dom'; // NewlyUpdatedArticleList.propTypes = { // }; function NewlyUpdatedArticleList(props) { + + const [listNewlyArticles, setListNewlyArticles] = useState([]); + + useEffect(() => { + const fetchListNewlyArticles = async () => { + try { + const response = await newlyArticleApi.getAll(); + + setListNewlyArticles(response); + + console.log("Fetch list newly articles successfully: ", response); + } catch (error) { + console.log("Failed to fetch list newly articles: ", error); + } + } + + fetchListNewlyArticles(); + }, []); + + const listJsxNewArticleItems = listNewlyArticles.map((item) => + + + {item.title} + + + {item.description} + + + {item.author} + + + {item.categoryName} + + + {item.dateCreated} + + + ); + return (
@@ -36,7 +77,7 @@ function NewlyUpdatedArticleList(props) { - + {/* ReactJS là gì? @@ -86,7 +127,8 @@ function NewlyUpdatedArticleList(props) { 01/12/2022 15:30 - + */} + {listJsxNewArticleItems}
diff --git a/front-end/knowledgesharing/src/screens/Home/Nomination/List.jsx b/front-end/knowledgesharing/src/screens/Home/Nomination/List.jsx index 1b715c6..c8f53dc 100644 --- a/front-end/knowledgesharing/src/screens/Home/Nomination/List.jsx +++ b/front-end/knowledgesharing/src/screens/Home/Nomination/List.jsx @@ -6,6 +6,7 @@ import ReadingList from '../../../components/ReadingList/ReadingList'; import { useEffect } from 'react'; import nominatedArticleApi from '../../../apis/nominatedArticleApi'; import { BASE_URL_API_BE } from '../../../constants/global'; +import { Link } from 'react-router-dom'; // NominatedArticleList.propTypes = { @@ -51,7 +52,8 @@ function NominatedArticleList(props) { /> - {listNominatedArticles[ii].title} + {/* {listNominatedArticles[ii].title} */} + {listNominatedArticles[ii].title} { + const readingListStr = localStorage.getItem("readingList"); + + if(readingListStr === null) { + const arrReadingList = new Array(item); + + localStorage.setItem("readingList", JSON.stringify(arrReadingList)); + } else { + const arrReadingList = JSON.parse(readingListStr); + + const newArrReadingList = [item].concat(arrReadingList.filter(itemTmp => itemTmp.id !== item.id)); + + if(newArrReadingList.length > 5) { + localStorage.setItem("readingList", JSON.stringify(newArrReadingList.slice(0, 5))); + } else { + localStorage.setItem("readingList", JSON.stringify(newArrReadingList)); + } + } + } + + appendToReadingList(currentReadingArticle); + useEffect(() => { // const fetchArticleByUrl = async () => { @@ -144,7 +175,12 @@ function ScreenArticleFormContent(props) { localStorage.getItem("role") === "admin") { try { - await articleApi.delete(articleId); + // await articleApi.delete(articleId); + const data = { + hidden: 1 + }; + + await articleApi.hideShow(articleId, data); alert("Article is deleted"); From fe8805ec90fcbc93d6cebb59dd31cdcd82c3b963 Mon Sep 17 00:00:00 2001 From: Anthony <18110175@student.hcmute.edu.vn> Date: Sat, 24 Dec 2022 07:26:38 +0700 Subject: [PATCH 4/8] Add search page (not yet data) --- front-end/knowledgesharing/src/App.jsx | 13 +++- .../knowledgesharing/src/apis/articleApi.js | 6 ++ .../src/components/Header/index.jsx | 8 ++- .../src/components/Header/navbar.jsx | 25 ++++++- .../src/screens/ArticleSearchResult/List.jsx | 70 +++++++++++++++++++ .../src/screens/Home/HomePage.jsx | 4 +- .../src/screens/MainPage/MainPage.jsx | 5 +- 7 files changed, 123 insertions(+), 8 deletions(-) create mode 100644 front-end/knowledgesharing/src/screens/ArticleSearchResult/List.jsx diff --git a/front-end/knowledgesharing/src/App.jsx b/front-end/knowledgesharing/src/App.jsx index 1f87382..0a4dba9 100644 --- a/front-end/knowledgesharing/src/App.jsx +++ b/front-end/knowledgesharing/src/App.jsx @@ -18,16 +18,25 @@ import ScreenReportCommentList from './screens/Reports/Comment/List'; import AdminPage from './screens/Admin/AdminPage'; import ScreenHomePage from './screens/Home/HomePage'; import { Nav, NavItem, NavLink } from 'reactstrap'; +import ScreenArticleSearchResult from './screens/ArticleSearchResult/List'; function App() { const [reloadToggle, setReloadToggle] = useState(false); + const [searchStrValue, setSearchStrValue] = useState(""); + const receiveReloadToggle = () => { setReloadToggle(!reloadToggle); }; + const receiveSearchStr = (searchStr) => { + // console.log("Received Search String: ", searchStr); + + setSearchStrValue(searchStr); + } + // console.log("Location: ", window.location.pathname); return ( @@ -48,7 +57,7 @@ function App() { */} Loading...
}> -
+
} /> @@ -56,6 +65,8 @@ function App() { } /> } /> + } /> + } /> } /> diff --git a/front-end/knowledgesharing/src/apis/articleApi.js b/front-end/knowledgesharing/src/apis/articleApi.js index 739f1b4..f56f6ec 100644 --- a/front-end/knowledgesharing/src/apis/articleApi.js +++ b/front-end/knowledgesharing/src/apis/articleApi.js @@ -58,6 +58,12 @@ const articleApi = { const url = `/articles/by-url/${articleUrl}`; return axiosClient.get(url); + }, + + getWithSearch: (params) => { + const url = '/articles/search'; + + return axiosClient.get(url, {params}); } }; diff --git a/front-end/knowledgesharing/src/components/Header/index.jsx b/front-end/knowledgesharing/src/components/Header/index.jsx index 4ef321c..26a105d 100644 --- a/front-end/knowledgesharing/src/components/Header/index.jsx +++ b/front-end/knowledgesharing/src/components/Header/index.jsx @@ -7,10 +7,16 @@ import NavBar from './navbar'; // }; function Header(props) { + + const receiveSearchStr = (searchStr) => { + // console.log("Received Search String: ", searchStr); + props.onHandleChangeSearchStr(searchStr); + } + return (
- +
); diff --git a/front-end/knowledgesharing/src/components/Header/navbar.jsx b/front-end/knowledgesharing/src/components/Header/navbar.jsx index acd9eb2..eb154ac 100644 --- a/front-end/knowledgesharing/src/components/Header/navbar.jsx +++ b/front-end/knowledgesharing/src/components/Header/navbar.jsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; -import { Collapse, DropdownItem, DropdownMenu, DropdownToggle, Nav, Navbar, NavbarBrand, NavbarText, NavbarToggler, NavItem, NavLink, UncontrolledDropdown } from 'reactstrap'; +import { Button, Collapse, DropdownItem, DropdownMenu, DropdownToggle, Input, InputGroup, Nav, Navbar, NavbarBrand, NavbarText, NavbarToggler, NavItem, NavLink, UncontrolledDropdown } from 'reactstrap'; import AddArticleBtn from './addArticleBtn'; import "./navbarStyles.css" import ReportManBtn from './reportManBtn'; @@ -109,11 +109,19 @@ function UserAvtNav(props) { function NavBar(props) { const [reloadToggle, setReloadToggle] = useState(false); + + const [searchStrValue, setSearchStrValue] = useState(""); + + let navigate = useNavigate(); const receiveData = () => { setReloadToggle(!reloadToggle); }; + const changeSearchInputValue = (e) => { + setSearchStrValue(e.target.value); + } + return (
+ + changeSearchInputValue(e)} + /> + + + {/* */} {(localStorage.getItem("role") === "norm" || localStorage.getItem("role") === "mod" || localStorage.getItem("role") === "admin") ? : ""} diff --git a/front-end/knowledgesharing/src/screens/ArticleSearchResult/List.jsx b/front-end/knowledgesharing/src/screens/ArticleSearchResult/List.jsx new file mode 100644 index 0000000..03fdf76 --- /dev/null +++ b/front-end/knowledgesharing/src/screens/ArticleSearchResult/List.jsx @@ -0,0 +1,70 @@ +import React, { useEffect, useState } from 'react'; +// import PropTypes from 'prop-types'; +import articleApi from '../../apis/articleApi'; +import ArticleCard from '../MainPage/Articles/List/Item/ArticleCard'; +import PaginationBar from '../MainPage/Articles/List/Pagination/PaginationBar'; + +// ScreenArticleSearchResult.propTypes = { + +// }; + +function ScreenArticleSearchResult(props) { + + const searchStrVal = props.searchString; + + const [articlesCrude, setArticlesCrude] = useState({}); + const [loaded, setLoaded] = useState(false); + const [page, setPage] = useState(0); + + useEffect(() => { + const fetchListArticlesSearchResult = async () => { + try { + const params = { + q: searchStrVal + } + + console.log("DT search: ", params); + + const response = await articleApi.getWithSearch(params); + + console.log("Fetch list articles with search successfully: ", response); + + setArticlesCrude(response); + + setLoaded(true); + + } catch (error) { + console.log("Failed to fetch list articles with search: ", error); + } + } + + fetchListArticlesSearchResult(); + }, [searchStrVal]); + + const receivePage = (indexPage) => { + setPage(indexPage); + // props.onHandleChange(indexPage); + } + + const listItems = loaded ? articlesCrude.articles.map((item) => + + ) : ""; + + return ( +
+
+ Showing results for: {searchStrVal} +
+ + {listItems} + + +
+ ); +} + +export default ScreenArticleSearchResult; \ No newline at end of file diff --git a/front-end/knowledgesharing/src/screens/Home/HomePage.jsx b/front-end/knowledgesharing/src/screens/Home/HomePage.jsx index 41c51a0..5aa226a 100644 --- a/front-end/knowledgesharing/src/screens/Home/HomePage.jsx +++ b/front-end/knowledgesharing/src/screens/Home/HomePage.jsx @@ -27,11 +27,11 @@ function ScreenHomePage(props) { -
+ {/*


- + */}
); } diff --git a/front-end/knowledgesharing/src/screens/MainPage/MainPage.jsx b/front-end/knowledgesharing/src/screens/MainPage/MainPage.jsx index aea2c8b..165058a 100644 --- a/front-end/knowledgesharing/src/screens/MainPage/MainPage.jsx +++ b/front-end/knowledgesharing/src/screens/MainPage/MainPage.jsx @@ -1,6 +1,5 @@ import React, { useEffect, useState } from 'react'; import { Route, Routes, useLocation } from 'react-router-dom'; -import { Button, Input, InputGroup } from 'reactstrap'; import articleApi from '../../apis/articleApi'; import CategoryNavMenu from '../../components/Category/NavMenu'; import ScreenArticleFormContent from './Articles/Form/Content'; @@ -151,14 +150,14 @@ function ScreenMainPage(props) {
-
+ {/*
-
+
*/} {/* } /> */} From 12c7aae59f2f5867d3e819da49766c2bd630dac5 Mon Sep 17 00:00:00 2001 From: Anthony <18110175@student.hcmute.edu.vn> Date: Sat, 24 Dec 2022 07:27:16 +0700 Subject: [PATCH 5/8] API: Full text search on title, description, content --- .../Controllers/ArticleController.java | 68 +++++++++++++++++++ .../Controllers/ArticleReportController.java | 2 +- .../Models/Article/ArticleHideShowModel.java | 14 ++++ .../knowsharing/Repositories/ArticleRepo.java | 10 +++ .../knowsharing/Services/ArticleService.java | 28 ++++++++ .../src/main/resources/application.properties | 6 ++ 6 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 back-end/knowsharing/src/main/java/com/ndp/knowsharing/Models/Article/ArticleHideShowModel.java diff --git a/back-end/knowsharing/src/main/java/com/ndp/knowsharing/Controllers/ArticleController.java b/back-end/knowsharing/src/main/java/com/ndp/knowsharing/Controllers/ArticleController.java index e1f4ca5..d5004ff 100644 --- a/back-end/knowsharing/src/main/java/com/ndp/knowsharing/Controllers/ArticleController.java +++ b/back-end/knowsharing/src/main/java/com/ndp/knowsharing/Controllers/ArticleController.java @@ -24,6 +24,7 @@ import com.ndp.knowsharing.Entities.Comment; import com.ndp.knowsharing.Entities.UserVoteState; import com.ndp.knowsharing.Models.Article.ArticleCreateModel; +import com.ndp.knowsharing.Models.Article.ArticleHideShowModel; import com.ndp.knowsharing.Models.Article.ArticleItemReturnModel; import com.ndp.knowsharing.Models.Article.ArticleUpdateModel; import com.ndp.knowsharing.Models.Article.PageOfArticleModel; @@ -333,6 +334,42 @@ public ResponseEntity retrieveByUrl(@PathVariable("url") String url) { return entity; } + @GetMapping( + value = "/search", + produces = MediaType.APPLICATION_JSON_VALUE + ) + public ResponseEntity searchArticles(@RequestParam(value = "q", required = true) String q, @RequestParam(value = "page", required = true) Integer pageNum) { + ResponseEntity entity; + + PageOfArticleModel pageOfArticleModel = new PageOfArticleModel(); + + List articleItemReturnModels = new ArrayList(); + + List
articles = articleService.retrieveWithFullTextSearchByTitleAndDescriptionAndContent(q, pageNum); + + for (Article article : articles) { + List userVoteStates = userVoteStateService.retrieveByArticleId(article.getId()); + + Integer voteScore = 0; + + for(UserVoteState uvsItem : userVoteStates) { + voteScore = voteScore + uvsItem.getVoteState(); + } + + ArticleItemReturnModel articleItemReturnModel = new ArticleItemReturnModel(article, voteScore); + + articleItemReturnModels.add(articleItemReturnModel); + } + + pageOfArticleModel.setNumberOfPages(articleService.retrieveNumOfPagesWithFullTextSearchByTitleAndDescriptionAndContent(q).intValue()); + pageOfArticleModel.setCurrentPage(0); + pageOfArticleModel.setArticles(articleItemReturnModels); + + entity = new ResponseEntity<>(pageOfArticleModel, HttpStatus.OK); + + return entity; + } + @PostMapping( produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE @@ -439,6 +476,37 @@ public ResponseEntity updateOneArticle(@PathVariable("id") String id, @R return entity; } + @PutMapping( + value = "/{articleId}/hide", + produces = MediaType.APPLICATION_JSON_VALUE, + consumes = MediaType.APPLICATION_JSON_VALUE + ) + public ResponseEntity hideShowArticle(@PathVariable("articleId") String articleId, @RequestBody ArticleHideShowModel articleHideShowModel) { + ResponseEntity entity; + + if(articleHideShowModel.getHidden() == null) { + entity = new ResponseEntity<>("{ \"Notice\": \"Not allow null\" }", HttpStatus.BAD_REQUEST); + } else { + Article tmpArticle = articleService.retrieveOne(articleId); + + if(tmpArticle == null) { + entity = new ResponseEntity<>("{ \"Notice\": \"Not found\" }", HttpStatus.NOT_FOUND); + } else { + tmpArticle.setHidden(articleHideShowModel.getHidden() ? 1 : 0); + + Article tmpSaved = articleService.updateOne(tmpArticle); + + if(tmpSaved == null) { + entity = new ResponseEntity<>("{ \"Notice\": \"Failed\" }", HttpStatus.INTERNAL_SERVER_ERROR); + } else { + entity = new ResponseEntity<>(tmpSaved, HttpStatus.OK); + } + } + } + + return entity; + } + // For comments @GetMapping( diff --git a/back-end/knowsharing/src/main/java/com/ndp/knowsharing/Controllers/ArticleReportController.java b/back-end/knowsharing/src/main/java/com/ndp/knowsharing/Controllers/ArticleReportController.java index bf15874..44c99f1 100644 --- a/back-end/knowsharing/src/main/java/com/ndp/knowsharing/Controllers/ArticleReportController.java +++ b/back-end/knowsharing/src/main/java/com/ndp/knowsharing/Controllers/ArticleReportController.java @@ -50,7 +50,7 @@ public ResponseEntity retrieveAll(@RequestParam(value = "solved", requir Article article = articleService.retrieveOne(articleReport.getArticleId()); - ArticleReportItemModel tmpARIModel = new ArticleReportItemModel(articleReport.getId(), articleReport.getDateCreated(), articleReport.getArticleId(), article.getAuthor(), articleReport.getContent(), article.getTitle(), article.getUrl(), articleReport.getIsSolved()); + ArticleReportItemModel tmpARIModel = new ArticleReportItemModel(articleReport.getId(), articleReport.getDateCreated(), articleReport.getArticleId(), articleReport.getAuthor(), articleReport.getContent(), article.getTitle(), article.getUrl(), articleReport.getIsSolved()); articleReportItemModels.add(tmpARIModel); } diff --git a/back-end/knowsharing/src/main/java/com/ndp/knowsharing/Models/Article/ArticleHideShowModel.java b/back-end/knowsharing/src/main/java/com/ndp/knowsharing/Models/Article/ArticleHideShowModel.java new file mode 100644 index 0000000..8d6b289 --- /dev/null +++ b/back-end/knowsharing/src/main/java/com/ndp/knowsharing/Models/Article/ArticleHideShowModel.java @@ -0,0 +1,14 @@ +package com.ndp.knowsharing.Models.Article; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class ArticleHideShowModel { + private Boolean hidden; +} \ No newline at end of file diff --git a/back-end/knowsharing/src/main/java/com/ndp/knowsharing/Repositories/ArticleRepo.java b/back-end/knowsharing/src/main/java/com/ndp/knowsharing/Repositories/ArticleRepo.java index 66cb86c..d9fce00 100644 --- a/back-end/knowsharing/src/main/java/com/ndp/knowsharing/Repositories/ArticleRepo.java +++ b/back-end/knowsharing/src/main/java/com/ndp/knowsharing/Repositories/ArticleRepo.java @@ -58,4 +58,14 @@ public interface ArticleRepo extends JpaRepository { nativeQuery = true ) Page
findByCategoryAndHiddenWithTagIds(@Param("tagids") List tagIds, @Param("category") String category, @Param("hidden") Integer hidden, Pageable pageable); + + String query5 = "select a.id, a.dateCreated, a.dateModified, a.createdBy, a.createdByName, a.modifiedBy, a.modifiedByName, a.c_title, a.c_description, a.c_content, a.c_audio_content, a.c_author, a.c_url, a.c_category, a.c_thumbnail_url, a.c_hidden from app_fd_article as a where match(a.c_title, a.c_description, a.c_content) against ( :searchstr )"; + String countQuery5 = "select count(a.id) from app_fd_article as a where match(a.c_title, a.c_description, a.c_content) against ( :searchstr )"; + @Query( + value = query5, + countQuery = countQuery5, + nativeQuery = true + ) + Page
findWithFullTextSearchByTitleAndDescriptionAndContent(@Param("searchstr") String searchStr, Pageable pageable); + // List
findWithFullTextSearchByTitleAndDescriptionAndContent(@Param("searchstr") String searchStr); } diff --git a/back-end/knowsharing/src/main/java/com/ndp/knowsharing/Services/ArticleService.java b/back-end/knowsharing/src/main/java/com/ndp/knowsharing/Services/ArticleService.java index 5fca8ff..e3f18ef 100644 --- a/back-end/knowsharing/src/main/java/com/ndp/knowsharing/Services/ArticleService.java +++ b/back-end/knowsharing/src/main/java/com/ndp/knowsharing/Services/ArticleService.java @@ -29,6 +29,20 @@ public List
retrieveTop5ByHiddenAndNewest(Integer hidden) { return repo.findTop5ByHiddenOrderByDateCreatedDesc(hidden); } + public List
retrieveWithFullTextSearchByTitleAndDescriptionAndContent(String searchStr, Integer pageNumber) { + List
articles = new ArrayList
(); + + try { + Page
page = repo.findWithFullTextSearchByTitleAndDescriptionAndContent(searchStr, PageRequest.of(pageNumber, 10)); + + articles = page.getContent(); + } catch (Exception e) { + // TODO: handle exception + } + + return articles; + } + public List
retrieveOneCommonPage(Integer pageNumber) { Page
page = repo.findAll(PageRequest.of(pageNumber, 10, Sort.by("dateCreated").descending())); @@ -184,6 +198,20 @@ public Long retrieveNumOfPagesAndHidden(String categoryId, Integer hidden) { } } + public Long retrieveNumOfPagesWithFullTextSearchByTitleAndDescriptionAndContent(String searchStr) { + Long numOfPage = Long.valueOf(0); + + try { + Page
page = repo.findWithFullTextSearchByTitleAndDescriptionAndContent(searchStr, PageRequest.of(0, 10)); + + numOfPage = Long.valueOf(page.getTotalPages()); + } catch (Exception e) { + // TODO: handle exception + } + + return numOfPage; + } + public Article retrieveOne(String id) { Article sth = null; diff --git a/back-end/knowsharing/src/main/resources/application.properties b/back-end/knowsharing/src/main/resources/application.properties index 7402752..59c5c27 100644 --- a/back-end/knowsharing/src/main/resources/application.properties +++ b/back-end/knowsharing/src/main/resources/application.properties @@ -14,6 +14,12 @@ spring.datasource.password=123456 logging.level.root=WARN +spring.jpa.show-sql=true +spring.jpa.properties.hibernate.format_sql=true + +logging.level.org.hibernate.SQL=DEBUG +logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE + spring.jpa.properties.hibernate.jdbc.batch_size=30 spring.jpa.properties.hibernate.order_inserts=true spring.jpa.properties.hibernate.order_updates=true From 7076423c159429555f80a6d706ca069ea7aa4ebd Mon Sep 17 00:00:00 2001 From: Anthony <18110175@student.hcmute.edu.vn> Date: Sat, 24 Dec 2022 07:31:30 +0700 Subject: [PATCH 6/8] SQL - Config index: full text search on app_fd_article --- sql/init-knowsharing_v2.3.0.sql | 285 ++++++++++++++++++++++++++++++++ 1 file changed, 285 insertions(+) create mode 100644 sql/init-knowsharing_v2.3.0.sql diff --git a/sql/init-knowsharing_v2.3.0.sql b/sql/init-knowsharing_v2.3.0.sql new file mode 100644 index 0000000..f793a69 --- /dev/null +++ b/sql/init-knowsharing_v2.3.0.sql @@ -0,0 +1,285 @@ +create database knowsharing; + +use knowsharing; + +create table app_fd_files ( + id varchar(255) not null primary key, + c_file_name_extension varchar(20), + c_file_name varchar(61) +); + +create table dir_user ( + id varchar(255) not null primary key, + username varchar(255), + password varchar(255), + firstName varchar(255), + lastName varchar(255), + email varchar(255), + avatar varchar(255), + verified int, + active int +); + +create table dir_role ( + id varchar(255) not null primary key, + name varchar(255), + description varchar(255) +); + +create table dir_user_role ( + roleId varchar(255) not null, + userId varchar(255) not null, + primary key (userId, roleId), + + constraint FK4E2710B9B7154794BE1CD801E3B33CF0 + foreign key (roleId) references dir_role(id), + constraint FKD9D4CFF4C50C41218FA0906B0F50A507 + foreign key (userId) references dir_user(id) +); + +-- --- + +create table app_fd_category ( + id varchar(255) not null primary key, + dateCreated datetime, + dateModified datetime, + createdBy varchar(255), + createdByName varchar(255), + modifiedBy varchar(255), + modifiedByName varchar(255), + c_name varchar(255) +); + +create table app_fd_article_tag ( + id varchar(255) not null primary key, + c_category varchar(255) not null, + c_tag_name varchar(255) not null, + + constraint FK722918EBDE9E4AE1AAF72B53F5EA4644 + foreign key (c_category) references app_fd_category(id) +); + +create table app_fd_article ( + id varchar(255) not null primary key, + dateCreated datetime, + dateModified datetime, + createdBy varchar(255), + createdByName varchar(255), + modifiedBy varchar(255), + modifiedByName varchar(255), + c_title varchar(255), + c_description longtext, + c_content longtext, + c_audio_content longtext, + c_author varchar(255) not null, + c_url varchar(255) not null, + c_category varchar(255) not null, + c_thumbnail_url longtext not null, + c_hidden int default 0 not null, + + constraint FKE0D32E80FAA44DA393F0447F25798257 + foreign key (c_author) references dir_user(id), + constraint FK95DD882CC0BF4EDDA2CE04A26A5D220E + foreign key (c_category) references app_fd_category(id) +); + +alter table app_fd_article add fulltext(c_title, c_description, c_content); +show index from app_fd_article; + +create table app_fd_article_tag_rel ( + id varchar(255) not null primary key, + c_tag_id varchar(255) not null, + c_article_id varchar(255) not null, + + constraint FK77376AC6FDF04BAFB24036B41F8C5CCB + foreign key (c_article_id) references app_fd_article(id), + constraint FK3F984925FC614A029BC695F5780C70C0 + foreign key (c_tag_id) references app_fd_article_tag(id) +); + +create table app_fd_comment ( + id varchar(255) not null primary key, + dateCreated datetime, + dateModified datetime, + createdBy varchar(255), + createdByName varchar(255), + modifiedBy varchar(255), + modifiedByName varchar(255), + c_author varchar(255) not null, + c_article_id varchar(255) not null, + c_content longtext not null, + c_hidden int default 0 not null, + + constraint FK5B6688F29DF84B6AB5B6E22F360E8276 + foreign key (c_author) references dir_user(id), + constraint FKF156FDE5ADC647B68D97D2C126190B7B + foreign key (c_article_id) references app_fd_article(id) +); + +create table app_fd_user_vote_state ( + id varchar(255) not null primary key, + dateCreated datetime, + dateModified datetime, + createdBy varchar(255), + createdByName varchar(255), + modifiedBy varchar(255), + modifiedByName varchar(255), + c_article_id varchar(255) not null, + c_author varchar(255) not null, + c_vote_state int default 0, + + unique (c_article_id, c_author), + + constraint FKDEEBFD1F79F340D9812AAC109E992636 + foreign key (c_article_id) references app_fd_article(id), + constraint FKB82D06AE979D440C8D482CA15FF8145C + foreign key (c_author) references dir_user(id) +); + +create table app_fd_interaction ( + c_article_id varchar(255) not null, + c_comment_score int not null, + c_vote_score int not null, + + primary key (c_article_id), + + constraint FK349797B931CC4FF492088A1B8F943C68 + foreign key (c_article_id) references app_fd_article(id) +); + +create table app_fd_report_category ( + id varchar(255) not null primary key, + dateCreated datetime, + dateModified datetime, + createdBy varchar(255), + createdByName varchar(255), + modifiedBy varchar(255), + modifiedByName varchar(255), + c_name varchar(255), + c_type varchar(255) +); + +create table app_fd_article_report ( + id varchar(255) not null primary key, + dateCreated datetime, + dateModified datetime, + createdBy varchar(255), + createdByName varchar(255), + modifiedBy varchar(255), + modifiedByName varchar(255), + c_article_id varchar(255) not null, + c_author varchar(255) not null, + c_content longtext not null, + c_is_solved int default 0 not null, + + constraint FK539CA7D17EDF43EA94D4BC8AE2E69562 + foreign key (c_article_id) references app_fd_article(id), + constraint FKC704090104394E7295A87670CAA0396B + foreign key (c_author) references dir_user(id) +); + +create table app_fd_article_report_category_rel ( + id varchar(255) not null primary key, + c_article_report_id varchar(255) not null, + c_report_category_id varchar(255) not null, + + unique (c_article_report_id, c_report_category_id), + + constraint FK73B8F2BBF4EC4EC09C1D9591C14BA006 + foreign key (c_article_report_id) references app_fd_article_report(id), + constraint FK7736332C414F444894D9C65EEAFE7A9D + foreign key (c_report_category_id) references app_fd_report_category(id) +); + +create table app_fd_comment_report ( + id varchar(255) not null primary key, + dateCreated datetime, + dateModified datetime, + createdBy varchar(255), + createdByName varchar(255), + modifiedBy varchar(255), + modifiedByName varchar(255), + c_comment_id varchar(255) not null, + c_author varchar(255) not null, + c_content longtext not null, + c_is_solved int default 0 not null, + + constraint FKE2397A934C85476780152529E2027134 + foreign key (c_comment_id) references app_fd_comment(id), + constraint FK65AD022C83AD4B0490CB9159DCDB5211 + foreign key (c_author) references dir_user(id) +); + +create table app_fd_comment_report_category_rel ( + id varchar(255) not null primary key, + c_comment_report_id varchar(255) not null, + c_report_category_id varchar(255) not null, + + unique (c_comment_report_id, c_report_category_id), + + constraint FKE15888246653419FA2987FE4B0971FAC + foreign key (c_comment_report_id) references app_fd_comment_report(id), + constraint FK7424DB5A34214FF5817C185D7C512BB1 + foreign key (c_report_category_id) references app_fd_report_category(id) +); + +create table app_fd_user_report ( + id varchar(255) not null primary key, + dateCreated datetime, + dateModified datetime, + createdBy varchar(255), + createdByName varchar(255), + modifiedBy varchar(255), + modifiedByName varchar(255), + c_user_id varchar(255) not null, + c_author varchar(255) not null, + c_content longtext not null, + c_is_solved int default 0 not null, + + constraint FK1996E205C2C34F7196B27B82F527ECA8 + foreign key (c_user_id) references dir_user(id), + constraint FK04D01B8B643F42BA8412E312B6A6EB75 + foreign key (c_author) references dir_user(id) +); + +create table app_fd_user_report_category_rel ( + id varchar(255) not null primary key, + c_user_report_id varchar(255) not null, + c_report_category_id varchar(255) not null, + + unique (c_user_report_id, c_report_category_id), + + constraint FK7A3728A2C0CF41ED97311F9BCC205AAD + foreign key (c_user_report_id) references app_fd_user_report(id), + constraint FKF378EAF0A5AF4D4789AE8272934F0754 + foreign key (c_report_category_id) references app_fd_report_category(id) +); + +create table app_fd_articles_for_home ( + id varchar(255) not null primary key, + c_article_id varchar(255) not null, + dateCreated datetime, + dateModified datetime, + c_type varchar(255), + + constraint 55A5B27F80824BB780FDD02275A4B3D8 + foreign key (c_article_id) references app_fd_article(id) +); + +insert into dir_user +(id, username, password, firstName, lastName, email, avatar, verified, active) VALUES +('admin', 'admin', '$2a$10$jLTZ9.FMCcnwVqXQDQhTou3GBN3LZo47Wef1pjiWCNyU18vvN//R2', 'Admin', 'Admin', null, null, 0, 1); + +insert into dir_role +(id, name, description) VALUES +('admin', 'admin', 'admin'); +insert into dir_role +(id, name, description) VALUES +('mod', 'mod', 'mod'); +insert into dir_role +(id, name, description) VALUES +('norm', 'norm', 'norm'); + +insert into dir_user_role +(roleId, userId) VALUES +('admin', 'admin'); \ No newline at end of file From 3067a0f66884cc1dc517022d067f2e8c609349f9 Mon Sep 17 00:00:00 2001 From: Anthony <18110175@student.hcmute.edu.vn> Date: Sat, 24 Dec 2022 17:11:45 +0700 Subject: [PATCH 7/8] Search page: complete search article with FTS --- .../src/screens/ArticleSearchResult/List.jsx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/front-end/knowledgesharing/src/screens/ArticleSearchResult/List.jsx b/front-end/knowledgesharing/src/screens/ArticleSearchResult/List.jsx index 03fdf76..5d64fab 100644 --- a/front-end/knowledgesharing/src/screens/ArticleSearchResult/List.jsx +++ b/front-end/knowledgesharing/src/screens/ArticleSearchResult/List.jsx @@ -20,7 +20,8 @@ function ScreenArticleSearchResult(props) { const fetchListArticlesSearchResult = async () => { try { const params = { - q: searchStrVal + q: searchStrVal, + page: page } console.log("DT search: ", params); @@ -39,7 +40,7 @@ function ScreenArticleSearchResult(props) { } fetchListArticlesSearchResult(); - }, [searchStrVal]); + }, [page, searchStrVal]); const receivePage = (indexPage) => { setPage(indexPage); @@ -53,10 +54,12 @@ function ScreenArticleSearchResult(props) { return (
- Showing results for: {searchStrVal} +
Showing results for: {searchStrVal}
- {listItems} +
+ {listItems} +
Date: Sun, 25 Dec 2022 02:54:23 +0700 Subject: [PATCH 8/8] Update Nomination, Tags when create/edit articles --- .../src/main/resources/application.properties | 8 +- .../src/screens/ArticleSearchResult/List.jsx | 6 +- .../src/screens/Home/Nomination/List.jsx | 151 ++++++++++++++--- .../MainPage/Articles/Form/AddArticle.jsx | 160 +++++++++++++----- .../MainPage/Articles/Form/Content.css | 7 + .../MainPage/Articles/Form/Content.jsx | 144 +++++++++++++++- .../EditArticlePopup/EditArticlePopup.jsx | 85 ++++++++-- 7 files changed, 467 insertions(+), 94 deletions(-) diff --git a/back-end/knowsharing/src/main/resources/application.properties b/back-end/knowsharing/src/main/resources/application.properties index 59c5c27..c3e78f6 100644 --- a/back-end/knowsharing/src/main/resources/application.properties +++ b/back-end/knowsharing/src/main/resources/application.properties @@ -14,11 +14,11 @@ spring.datasource.password=123456 logging.level.root=WARN -spring.jpa.show-sql=true -spring.jpa.properties.hibernate.format_sql=true +# spring.jpa.show-sql=true +# spring.jpa.properties.hibernate.format_sql=true -logging.level.org.hibernate.SQL=DEBUG -logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE +# logging.level.org.hibernate.SQL=DEBUG +# logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE spring.jpa.properties.hibernate.jdbc.batch_size=30 spring.jpa.properties.hibernate.order_inserts=true diff --git a/front-end/knowledgesharing/src/screens/ArticleSearchResult/List.jsx b/front-end/knowledgesharing/src/screens/ArticleSearchResult/List.jsx index 5d64fab..ad218e5 100644 --- a/front-end/knowledgesharing/src/screens/ArticleSearchResult/List.jsx +++ b/front-end/knowledgesharing/src/screens/ArticleSearchResult/List.jsx @@ -54,18 +54,18 @@ function ScreenArticleSearchResult(props) { return (
-
Showing results for: {searchStrVal}
+
{articlesCrude.numberOfPages > 0 ? "Showing results for: " : "No result for:"} {searchStrVal}
{listItems}
- 0 ? + /> : ""}
); } diff --git a/front-end/knowledgesharing/src/screens/Home/Nomination/List.jsx b/front-end/knowledgesharing/src/screens/Home/Nomination/List.jsx index c8f53dc..a2f63b4 100644 --- a/front-end/knowledgesharing/src/screens/Home/Nomination/List.jsx +++ b/front-end/knowledgesharing/src/screens/Home/Nomination/List.jsx @@ -15,6 +15,7 @@ import { Link } from 'react-router-dom'; function NominatedArticleList(props) { const [listNominatedArticles, setListNominatedArticles] = useState([]); + const [loaded, setLoaded] = useState(false); useEffect(() => { const fetchListNominatedArticles = async () => { @@ -24,6 +25,8 @@ function NominatedArticleList(props) { setListNominatedArticles(response); console.log("Fetch list nominated articles successfully: ", response); + + setLoaded(true); } catch (error) { console.log("Failed to fetch nominated articles: ", error); } @@ -37,36 +40,128 @@ function NominatedArticleList(props) { const outerListJsx = []; let innerListJsx = []; + let countRealNomination = 0; + for(let ii = 0; ii < 9; ii++) { - if(ii < listNominatedArticles.length) { - innerListJsx.push( - - - - - {/* {listNominatedArticles[ii].title} */} - {listNominatedArticles[ii].title} - - - {listNominatedArticles[ii].author} - - - {listNominatedArticles[ii].description.length > 15 ? listNominatedArticles[ii].description.substring(0, 75) + "..." : listNominatedArticles[ii].description} - - - - ); + // if(ii < listNominatedArticles.length) { + // innerListJsx.push( + // + // + // + // + // {/* {listNominatedArticles[ii].title} */} + // {listNominatedArticles[ii].title} + // + // + // {listNominatedArticles[ii].author} + // + // + // {listNominatedArticles[ii].description.length > 15 ? listNominatedArticles[ii].description.substring(0, 75) + "..." : listNominatedArticles[ii].description} + // + // + // + // ); + // } else { + // innerListJsx.push( + // + // + // + // + // Blank - Position {ii} + // + // + // Author + // + // + // {/* This is a wider card with supporting text below as a natural lead-in to additional content. This content is a little bit longer. */} + // Blank content + // + // + // + // ); + // } + + if(countRealNomination < listNominatedArticles.length) { + if(listNominatedArticles[countRealNomination].id === ("nomination-" + (ii + 1))) { + innerListJsx.push( + + + + + {/* {listNominatedArticles[ii].title} */} + {listNominatedArticles[countRealNomination].title} + + + {listNominatedArticles[countRealNomination].author} + + + {listNominatedArticles[countRealNomination].description.length > 15 ? listNominatedArticles[countRealNomination].description.substring(0, 75) + "..." : listNominatedArticles[countRealNomination].description} + + + + ); + + countRealNomination++; + } else { + innerListJsx.push( + + + + + Blank - Position {ii} + + + Author + + + {/* This is a wider card with supporting text below as a natural lead-in to additional content. This content is a little bit longer. */} + Blank content + + + + ); + } } else { innerListJsx.push( diff --git a/front-end/knowledgesharing/src/screens/MainPage/Articles/Form/AddArticle.jsx b/front-end/knowledgesharing/src/screens/MainPage/Articles/Form/AddArticle.jsx index b128ab9..4c414cc 100644 --- a/front-end/knowledgesharing/src/screens/MainPage/Articles/Form/AddArticle.jsx +++ b/front-end/knowledgesharing/src/screens/MainPage/Articles/Form/AddArticle.jsx @@ -4,11 +4,13 @@ import "react-draft-wysiwyg/dist/react-draft-wysiwyg.css"; import { EditorState, convertToRaw, convertFromRaw, convertFromHTML, ContentState} from 'draft-js'; import draftToHtml from 'draftjs-to-html'; import htmlToDraft from 'html-to-draftjs'; -import { Button, Input, Label } from 'reactstrap'; +import { Button, ButtonGroup, Input, Label } from 'reactstrap'; import articleApi from '../../../../apis/articleApi'; import categoryApi from '../../../../apis/categoryApi'; import UploadFiles from '../../../../components/FileUpload/FileUpload'; +import { useNavigate } from 'react-router-dom'; // import PropTypes from 'prop-types'; +import articleTagApi from '../../../../apis/articleTagApi'; // AddArticle.propTypes = { @@ -19,11 +21,16 @@ function AddArticle(props) { const [description, setDescription] = useState(""); const [content, setContent] = useState(""); const [thumbnailUrl, setThumbnailUrl] = useState(""); - const [audioFileName, setAudioFileName] = useState(""); - const [category, setCategory] = useState("05f15fb9-737b-4a44-a424-168f04d474c8"); // Category: Linux + // const [audioFileName, setAudioFileName] = useState(""); + const [category, setCategory] = useState(null); // Category: Linux const [categoryList, setCategoryList] = useState([]); const [loaded, setLoaded] = useState(false); + const [listTagsLoaded, setListTagsLoaded] = useState([]); + const [tagsSelected, setTagsSelected] = useState([]); + + let navigate = useNavigate(); + useEffect(() => { const fetchCategory = async () => { try { @@ -33,6 +40,10 @@ function AddArticle(props) { setCategoryList(response); + if(category === null) { + setCategory(response[0].id); + } + setLoaded(true); } catch (error) { console.log("Failed to fetch category: ", error); @@ -40,7 +51,27 @@ function AddArticle(props) { } fetchCategory(); - }, []); + + const fetchListArticleTags = async (categoryId) => { + try { + const params = { + category: categoryId + } + + const response = await articleTagApi.getAll(params); + + setListTagsLoaded(response); + + console.log("Fetch article tags by category successfully: ", response); + } catch (error) { + console.log("Failed to fetch article tags by category: ", error); + } + } + + if(category !== null) { + fetchListArticleTags(category); + } + }, [category]); const isInDefaultCategory = (catNameParam) => { if(catNameParam === "front-end" || @@ -117,6 +148,31 @@ function AddArticle(props) { } }; + const onCheckboxBtnClick = (selected) => { + const index = tagsSelected.indexOf(selected); + if (index < 0) { + tagsSelected.push(selected); + } else { + tagsSelected.splice(index, 1); + } + + setTagsSelected([...tagsSelected]); + }; + + const listJsxTagsLoadedItems = listTagsLoaded.map((item) => + + ); + const changeInputValueTitle = (e) => { setTitle(e.target.value.trim()); }; @@ -132,8 +188,8 @@ function AddArticle(props) { const changeInputValueCategory = (e) => { if(!isInDefaultCategoryLabel(e.target.value)) { setCategory(e.target.value); - console.log("Value in event: ", e.target.value); - console.log("Value in state: ", category); + // console.log("Value in event: ", e.target.value); + // console.log("Value in state: ", category); } else { // setCategory(getQueryValueFromLabel(e.target.value)); } @@ -175,12 +231,12 @@ function AddArticle(props) { } } - if(audioFileName.length < 10) { - returnData = { - error: true, - msg: "Wrong url length" - } - } + // if(audioFileName.length < 10) { + // returnData = { + // error: true, + // msg: "Wrong url length" + // } + // } return returnData; }; @@ -193,37 +249,37 @@ function AddArticle(props) { if(validation.error) { alert(validation.msg); } else { - alert("Submit success") + // alert("Submit success") // handle submit ok here createArticleToBE(); - const data = { - // author: localStorage.getItem("username"), - // title: title, - // description: description, - // content: content, - // audioContent: audioFileName, - // thumbnailUrl: thumbnailUrl, - // category: category, - - createdBy: localStorage.getItem("username"), - createdByName: "AA", - title: title, - description: description, - content: content, - audioContent: audioFileName, - author: localStorage.getItem("username"), - category: category, - thumbnailUrl: thumbnailUrl, - tags: [] - }; - - console.log("VZVZ: ", data); + // const data = { + // // author: localStorage.getItem("username"), + // // title: title, + // // description: description, + // // content: content, + // // audioContent: audioFileName, + // // thumbnailUrl: thumbnailUrl, + // // category: category, + + // createdBy: localStorage.getItem("username"), + // createdByName: "AA", + // title: title, + // description: description, + // content: content, + // audioContent: "", + // author: localStorage.getItem("username"), + // category: category, + // thumbnailUrl: thumbnailUrl, + // tags: [] + // }; + + // console.log("VZVZ: ", data); } } const createArticleToBE = async () => { - if(audioFileName !== "") { + // if(audioFileName !== "") { try { const data = { // author: localStorage.getItem("username"), @@ -239,21 +295,27 @@ function AddArticle(props) { title: title, description: description, content: content, - audioContent: audioFileName, + audioContent: "", author: localStorage.getItem("username"), category: category, thumbnailUrl: thumbnailUrl, - tags: [] + tags: tagsSelected }; const response = await articleApi.post(data); console.log("Post article successfully: ", response); + + alert("Submit success") + + navigate("/articles"); } catch(error) { console.log("Failed to post article to BE: ", error.request); + + alert("Submit failed") } - } + // } } // const getQueryValueFromLabel = (label) => { @@ -279,9 +341,9 @@ function AddArticle(props) { // } // }; - const receiveAudioUrl = (auFileName) => { - setAudioFileName(auFileName); - } + // const receiveAudioUrl = (auFileName) => { + // setAudioFileName(auFileName); + // } const receiveImageUrl = (imgFileName) => { setThumbnailUrl(imgFileName); @@ -348,13 +410,13 @@ function AddArticle(props) {
-
+ {/*

-
+
*/}
+
+
+ + + {listJsxTagsLoadedItems} + +
+
: ""} - {((article.author === localStorage.getItem("username"))) ? + {((article.author === localStorage.getItem("username")) && + (localStorage.getItem("role") !== "mod" && localStorage.getItem("role") !== "admin") + ) ? // (localStorage.getItem("role") === "mod") || // localStorage.getItem("role") === "admin") ? <> @@ -277,6 +311,16 @@ function ScreenArticleFormContent(props) { Hide } + + : ""}
@@ -361,6 +405,102 @@ function ScreenArticleFormContent(props) { {editPopupOpen ? : ""} {reportPopupOpen ? : ""} + + + Show article in nominated articles + + + + { + fetchUpdateNominateArticleWithPosition(1); + }} + > + Position 1 + + { + fetchUpdateNominateArticleWithPosition(2); + }} + > + Position 2 + + { + fetchUpdateNominateArticleWithPosition(3); + }} + > + Position 3 + + + + { + fetchUpdateNominateArticleWithPosition(4); + }} + > + Position 4 + + { + fetchUpdateNominateArticleWithPosition(5); + }} + > + Position 5 + + { + fetchUpdateNominateArticleWithPosition(6); + }} + > + Position 6 + + + + { + fetchUpdateNominateArticleWithPosition(7); + }} + > + Position 7 + + { + fetchUpdateNominateArticleWithPosition(8); + }} + > + Position 8 + + { + fetchUpdateNominateArticleWithPosition(9); + }} + > + Position 9 + + + + + {/* {' '} */} + + + ) } diff --git a/front-end/knowledgesharing/src/screens/MainPage/Articles/Form/EditArticlePopup/EditArticlePopup.jsx b/front-end/knowledgesharing/src/screens/MainPage/Articles/Form/EditArticlePopup/EditArticlePopup.jsx index b2d22eb..58dad16 100644 --- a/front-end/knowledgesharing/src/screens/MainPage/Articles/Form/EditArticlePopup/EditArticlePopup.jsx +++ b/front-end/knowledgesharing/src/screens/MainPage/Articles/Form/EditArticlePopup/EditArticlePopup.jsx @@ -4,13 +4,14 @@ import "react-draft-wysiwyg/dist/react-draft-wysiwyg.css"; import { EditorState, convertToRaw, convertFromRaw, convertFromHTML, ContentState} from 'draft-js'; import draftToHtml from 'draftjs-to-html'; import htmlToDraft from 'html-to-draftjs'; -import { Button, Input, Label } from 'reactstrap'; +import { Button, ButtonGroup, Input, Label } from 'reactstrap'; import { BASE_URL_API_BE } from '../../../../../constants/global'; import "./EditArticlePopup.css"; // import PropTypes from 'prop-types'; import UploadFiles from '../../../../../components/FileUpload/FileUpload'; import articleApi from '../../../../../apis/articleApi'; import categoryApi from '../../../../../apis/categoryApi'; +import articleTagApi from '../../../../../apis/articleTagApi'; // EditArticlePopup.propTypes = { @@ -23,12 +24,16 @@ function EditArticlePopup(props) { const [description, setDescription] = useState(""); const [content, setContent] = useState(""); const [thumbnailUrl, setThumbnailUrl] = useState(""); - const [audioFileName, setAudioFileName] = useState(""); + // const [audioFileName, setAudioFileName] = useState(""); const [category, setCategory] = useState(article.category); const [newArticle, setNewArticle] = useState({}); const [categoryList, setCategoryList] = useState([]); const [loaded, setLoaded] = useState(false); + const [listTagsLoaded, setListTagsLoaded] = useState([]); + const [tagsSelected, setTagsSelected] = useState([]); + + const generateFirstValueForEditorContent = (inpHtmlStr) => { const blocksFromHTML = convertFromHTML(inpHtmlStr); @@ -50,7 +55,7 @@ function EditArticlePopup(props) { setDescription(article.description); setContent(article.content); setThumbnailUrl(article.thumbnailUrl); - setAudioFileName(article.audioContent); + // setAudioFileName(article.audioContent); setEditorState(EditorState.createWithContent(generateFirstValueForEditorContent(article.content))); // setCategory(article.category); @@ -71,8 +76,28 @@ function EditArticlePopup(props) { fetchCategory(); + const fetchListArticleTags = async (categoryId) => { + try { + const params = { + category: categoryId + } + + const response = await articleTagApi.getAll(params); + + setListTagsLoaded(response); + + console.log("Fetch article tags by category successfully: ", response); + } catch (error) { + console.log("Failed to fetch article tags by category: ", error); + } + } + + if(category !== null) { + fetchListArticleTags(category); + } + // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + }, [category]); const isInDefaultCategory = (catNameParam) => { if(catNameParam === "front-end" || @@ -122,6 +147,31 @@ function EditArticlePopup(props) { } }; + const onCheckboxBtnClick = (selected) => { + const index = tagsSelected.indexOf(selected); + if (index < 0) { + tagsSelected.push(selected); + } else { + tagsSelected.splice(index, 1); + } + + setTagsSelected([...tagsSelected]); + }; + + const listJsxTagsLoadedItems = listTagsLoaded.map((item) => + + ); + const changeInputValueTitle = (e) => { setTitle(e.target.value.trim()); }; @@ -204,7 +254,7 @@ function EditArticlePopup(props) { } const updateArticleToBE = async () => { - if(audioFileName !== "") { + // if(audioFileName !== "") { try { const data = { modifiedBy: localStorage.getItem("username"), @@ -212,10 +262,10 @@ function EditArticlePopup(props) { title: title, description: description, content: content, - audioContent: audioFileName, + audioContent: "", category: category, thumbnailUrl: thumbnailUrl, - tags: [] + tags: tagsSelected }; console.log("Data to update: ", data); @@ -227,7 +277,7 @@ function EditArticlePopup(props) { } catch(error) { console.log("Failed to post article to BE: ", error); } - } + // } } // const getQueryValueFromLabel = (label) => { @@ -276,9 +326,9 @@ function EditArticlePopup(props) { // } // }; - const receiveAudioUrl = (auFileName) => { - setAudioFileName(auFileName); - } + // const receiveAudioUrl = (auFileName) => { + // setAudioFileName(auFileName); + // } const receiveImageUrl = (imgFileName) => { setThumbnailUrl(imgFileName); @@ -351,7 +401,7 @@ function EditArticlePopup(props) {
-
+ {/*
@@ -362,7 +412,7 @@ function EditArticlePopup(props) {
-
+
*/}
+
+
+ + + {listJsxTagsLoadedItems} + +