diff --git a/client/src/components/admin_v2/Common/FeaturedMedia.jsx b/client/src/components/admin_v2/Common/FeaturedMedia.jsx index 53c7ef3d..de47b76f 100644 --- a/client/src/components/admin_v2/Common/FeaturedMedia.jsx +++ b/client/src/components/admin_v2/Common/FeaturedMedia.jsx @@ -4,13 +4,12 @@ import React, { useEffect, useState } from 'react'; // libraries import { Card, Typography } from '@mui/material'; -// media imports -import SharedMedia, { thumb, thumbInner } from './sharedMedia'; -import imagekit from '../../../utils/imagekit'; - // graphql import { GraphClient } from '../../../config/ApolloClient'; import addMedia from '../../../graphql/mutations/media/addMedia'; +import imagekit from '../../../utils/imageKit'; +// media imports +import SharedMedia, { thumb, thumbInner } from './sharedMedia'; export default function FeaturedMedia({ height, diff --git a/client/src/components/admin_v2/Common/Tags.jsx b/client/src/components/admin_v2/Common/Tags.jsx index 97504a02..81d49531 100644 --- a/client/src/components/admin_v2/Common/Tags.jsx +++ b/client/src/components/admin_v2/Common/Tags.jsx @@ -1,14 +1,20 @@ -import { Cancel } from '@mui/icons-material'; +import { useEffect, useRef, useState } from 'react'; + +import { Cancel, Sell } from '@mui/icons-material'; import { Stack, TextField, Typography } from '@mui/material'; +import { makeStyles } from '@mui/styles'; import { Box } from '@mui/system'; -import { useEffect } from 'react'; -import { useRef, useState } from 'react'; + +import { GraphClient } from '../../../config/ApolloClient'; +import updateArticleTags from '../../../graphql/mutations/article/updateArticleTags'; +import createTag from '../../../graphql/mutations/tag/createTag'; +import useTagAutoComplete from '../../../hooks/useTagAutoComplete'; const Tags = ({ data, handleDelete }) => { return ( { - {data} + {data.name} { ); }; -export default function ArticleTags({ tag, underlineVisible = false }) { - const [tags, SetTags] = useState([]); +export default function ArticleTags({ + tag, + adminTags, + isAdmin, + id, + setErrorMessageAndError, +}) { + const [tags, setTags] = useState([]); + const [active, setActive] = useState(false); + const [search, setSearch] = useState(''); const tagRef = useRef(); + const classes = useStyles({ active }); + + const autoCompleteData = useTagAutoComplete(search, isAdmin, 5); useEffect(() => { - console.log(tag); - if (tag) { - const tagData = tag.map((data) => data.name); - SetTags(tagData); + if (adminTags) { + const adminTagData = adminTags.map((data) => ({ + ...data, + isAdmin: true, + })); + setTags([...adminTagData, ...tag]); } - }, [tag]); + }, []); - const handleDelete = (value) => { - const newtags = tags.filter((val) => val !== value); - SetTags(newtags); + const handleDelete = async (value) => { + await updateTag(value.reference, false); }; - const handleOnSubmit = (e) => { - e.preventDefault(); - if (tagRef.current.value === '') return; + const handleCreate = async () => { + const { + data: { createTag: createdTag }, + } = await GraphClient.mutate({ + mutation: createTag, + variables: { + name: tagRef.current.value, + isAdmin: isAdmin, + }, + }); + + await updateTag(createdTag.id, true); + }; + + const updateTag = async (tagId, isAdded) => { + if (tags.some((tag) => tag.reference === tagId && isAdded)) { + setErrorMessageAndError + ? setErrorMessageAndError('Tag already exists') + : null; + tagRef.current.value = ''; + setActive(false); + return false; + } + + const { + data: { updateArticleTags: updatedTags }, + } = await GraphClient.mutate({ + mutation: updateArticleTags, + variables: { + id: id, + tag: tagId, + isAdded: isAdded, + isAdmin: isAdmin, + }, + }); - SetTags([...tags, tagRef.current.value]); + const updatedAdminTags = isAdmin + ? updatedTags.adminTags.map((data) => ({ + ...data, + isAdmin: true, + })) + : []; + const updatedTag = isAdmin ? [] : updatedTags.tags; + setTags([...updatedAdminTags, ...updatedTag]); tagRef.current.value = ''; + setActive(false); }; + + const searchQuery = (e) => { + e.preventDefault(); + setSearch(e.target.value); + }; + + const searchActive = () => { + setActive((current) => !current); + }; + return ( -
- - {tags.map((data, index) => { - return ( - - ); - })} - - ), - disableUnderline: underlineVisible, - }} - /> - +
+ + {tags.map((data, index) => { + return ; + })} + + +
+ + {tagRef.current?.value?.length ? ( +
+ {autoCompleteData?.map(({ id, name }) => ( +
updateTag(id, true)} + > + {name} +
+ ))} +
+ Create Tag: {tagRef.current.value} +
+
+ ) : ( + <> + )} +
+
); } + +const useStyles = makeStyles({ + search: { + width: (_) => (_.active ? 150 : 0), + opacity: (_) => (_.active ? 1 : 0), + transition: '1s', + top: '0px', + position: 'relative', + }, + tagForm: { + display: 'flex', + alignItems: 'center', + gap: 10, + }, + tagSuggestions: { + position: 'absolute', + background: '#FEFEFF', + width: '100%', + padding: '10px', + zIndex: '20022', + borderRadius: '0px 0px 5px 5px', + border: '1px #ECEDEC', + borderStyle: 'none solid solid', + boxShadow: '0px 0px 5px grey', + display: (_) => (_.active ? 'block' : 'none'), + }, + tagList: { + position: 'absoulte', + display: 'flex', + alignItems: 'center', + cursor: 'pointer', + fontSize: '14px', + }, +}); diff --git a/client/src/graphql/mutations/article/updateArticleTags.js b/client/src/graphql/mutations/article/updateArticleTags.js new file mode 100644 index 00000000..5cde1634 --- /dev/null +++ b/client/src/graphql/mutations/article/updateArticleTags.js @@ -0,0 +1,24 @@ +import { gql } from '@apollo/client'; + +const updateArticleTags = gql` + mutation ($id: ID!, $tag: ID!, $isAdded: Boolean!, $isAdmin: Boolean!) { + updateArticleTags( + id: $id + tag: $tag + isAdded: $isAdded + isAdmin: $isAdmin + ) { + tags { + name + reference + isAdmin + } + adminTags { + name + reference + } + } + } +`; + +export default updateArticleTags; diff --git a/client/src/graphql/mutations/tag/createTag.js b/client/src/graphql/mutations/tag/createTag.js new file mode 100644 index 00000000..59ec1171 --- /dev/null +++ b/client/src/graphql/mutations/tag/createTag.js @@ -0,0 +1,13 @@ +import { gql } from '@apollo/client'; + +const createTag = gql` + mutation createTag($name: String!, $isAdmin: Boolean, $adminColor: String) { + createTag(name: $name, isAdmin: $isAdmin, adminColor: $adminColor) { + id + name + isAdmin + } + } +`; + +export default createTag; diff --git a/client/src/graphql/queries/article/listAllArticles.js b/client/src/graphql/queries/article/listAllArticles.js index c1e8f089..3065cfef 100644 --- a/client/src/graphql/queries/article/listAllArticles.js +++ b/client/src/graphql/queries/article/listAllArticles.js @@ -15,6 +15,11 @@ const listAllArticles = gql` categories { number } + tags { + name + reference + isAdmin + } adminTags { name reference diff --git a/client/src/graphql/queries/tag/getTagAutocomplete.js b/client/src/graphql/queries/tag/getTagAutocomplete.js new file mode 100644 index 00000000..c721b3e9 --- /dev/null +++ b/client/src/graphql/queries/tag/getTagAutocomplete.js @@ -0,0 +1,16 @@ +import { gql } from '@apollo/client'; + +const getAutocomplete = gql` + query ($searchTerm: String!, $isAdmin: Boolean!, $limit: Int) { + getTagAutocomplete( + searchTerm: $searchTerm + isAdmin: $isAdmin + limit: $limit + ) { + id + name + isAdmin + } + } +`; +export default getAutocomplete; diff --git a/client/src/hooks/useTagAutoComplete.js b/client/src/hooks/useTagAutoComplete.js new file mode 100644 index 00000000..c74b012a --- /dev/null +++ b/client/src/hooks/useTagAutoComplete.js @@ -0,0 +1,40 @@ +import { useEffect, useState } from 'react'; + +import { GraphClient } from '../config/ApolloClient'; +import getTagAutocomplete from '../graphql/queries/tag/getTagAutocomplete'; + +const useTagAutoComplete = (searchTag, isAdmin, limit) => { + const [searchKeyword, setSearchKeyword] = useState(searchTag); + const [result, setResult] = useState([]); + + useEffect(() => { + const timer = setTimeout(() => { + setSearchKeyword(searchTag); + }, 100); + + return () => clearTimeout(timer); + }, [searchTag]); + + useEffect(() => { + if (!searchKeyword) return; + + (async () => { + const { + data: { getTagAutocomplete: autoCompleteResult }, + } = await GraphClient.query({ + query: getTagAutocomplete, + variables: { + searchTerm: searchKeyword, + isAdmin: isAdmin, + limit: limit, + }, + }); + + setResult(autoCompleteResult); + })(); + }, [searchKeyword]); + + return result; +}; + +export default useTagAutoComplete; diff --git a/client/src/pages/admin_v2/browse.jsx b/client/src/pages/admin_v2/browse.jsx index 1dab0d4a..6bbe8295 100644 --- a/client/src/pages/admin_v2/browse.jsx +++ b/client/src/pages/admin_v2/browse.jsx @@ -1,21 +1,17 @@ +import Head from 'next/head'; import { useRouter } from 'next/router'; +import { parseCookies } from 'nookies'; import ActivityIndicator from '../../components/shared/ActivityIndicator'; -import BrowseArticle from '../../screens/admin_v2/Browse'; -import Custom500 from '../500'; - -import { parseCookies } from 'nookies'; import { getApolloLink, GraphClient } from '../../config/ApolloClient'; -import listAllArticle from '../../graphql/queries/article/listAllArticles'; import countTotalArticles from '../../graphql/queries/article/countTotalArticles'; -import Head from 'next/head'; +import listAllArticle from '../../graphql/queries/article/listAllArticles'; +import BrowseArticle from '../../screens/admin_v2/Browse'; +import Custom500 from '../500'; -const browseArticlePage = ({ articles, totalArticles, isError, error }) => { +const BrowseArticlePage = ({ articles, totalArticles, isError, error }) => { const { isFallback } = useRouter(); - console.log(articles, totalArticles); if (isError) return ; - console.log(isFallback); - console.log(!isFallback && articles && totalArticles); return ( <> @@ -32,7 +28,7 @@ const browseArticlePage = ({ articles, totalArticles, isError, error }) => { ); }; -export default browseArticlePage; +export default BrowseArticlePage; export async function getServerSideProps(context) { try { diff --git a/client/src/pages/admin_v2/index.jsx b/client/src/pages/admin_v2/index.jsx index 75b850fd..6c6c1831 100644 --- a/client/src/pages/admin_v2/index.jsx +++ b/client/src/pages/admin_v2/index.jsx @@ -1,9 +1,8 @@ import React from 'react'; import Paperbase from '../../components/admin_v2/Marginals/Marginals'; -import Custom500 from '../500.jsx'; - import getAccess from '../../utils/getAccess'; +import Custom500 from '../500.jsx'; const Admin = ({ isError }) => { if (isError) { diff --git a/client/src/screens/admin_v2/Browse.jsx b/client/src/screens/admin_v2/Browse.jsx index 5a474c0b..c98cee8f 100644 --- a/client/src/screens/admin_v2/Browse.jsx +++ b/client/src/screens/admin_v2/Browse.jsx @@ -1,28 +1,29 @@ -import React, { useState, useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; + import Link from 'next/link'; +import Box from '@mui/material/Box'; +import Paper from '@mui/material/Paper'; // Material UI import { styled } from '@mui/material/styles'; import Table from '@mui/material/Table'; import TableBody from '@mui/material/TableBody'; import TableCell, { tableCellClasses } from '@mui/material/TableCell'; -import TablePagination from '@mui/material/TablePagination'; import TableContainer from '@mui/material/TableContainer'; import TableHead from '@mui/material/TableHead'; +import TablePagination from '@mui/material/TablePagination'; import TableRow from '@mui/material/TableRow'; -import Paper from '@mui/material/Paper'; -import Box from '@mui/material/Box'; import Typography from '@mui/material/Typography'; + import ArticleStatusMenu from '../../components/admin_v2/Browse/ArticleStatusMenu'; import DialogBox from '../../components/admin_v2/Browse/DialogBox'; +import SnackBarAleart from '../../components/admin_v2/Common/SnackBarAleart'; import ArticleTags from '../../components/admin_v2/Common/Tags'; - -import determineCategory from '../../utils/determineCategory'; import Marginals from '../../components/admin_v2/Marginals/Marginals'; import { GraphClient } from '../../config/ApolloClient'; -import listAllArticles from '../../graphql/queries/article/listAllArticles'; import updateArticlePublishStatus from '../../graphql/mutations/article/updateArticlePublishStatus'; -import SnackBarAleart from '../../components/admin_v2/Common/SnackBarAleart'; +import listAllArticles from '../../graphql/queries/article/listAllArticles'; +import determineCategory from '../../utils/determineCategory'; const StyledTableCell = styled(TableCell)(({ theme }) => ({ [`&.${tableCellClasses.head}`]: { @@ -46,6 +47,7 @@ const StyledTableRow = styled(TableRow)(({ theme }) => ({ const BrowseArticle = ({ articles, totalArticles }) => { const [_articles, setArticles] = useState(articles); + const [rowsPerPage, setRowsPerPage] = React.useState(25); const [row, setRow] = useState([]); const [page, setPage] = useState(0); @@ -70,6 +72,10 @@ const BrowseArticle = ({ articles, totalArticles }) => { setPage(newPage); }; + const handleChangeRowsPerPage = (_event) => { + setRowsPerPage(+_event.target.value); + }; + //snack bar aleart const [error, setError] = useState(false); const [errorMessage, setErrorMessage] = useState(''); @@ -107,6 +113,7 @@ const BrowseArticle = ({ articles, totalArticles }) => { publishStatus, createdAt, adminTags, + tags, }) => { const authorNames = authors?.map((author) => author.name).join(', '); const categoryNames = categories @@ -124,6 +131,7 @@ const BrowseArticle = ({ articles, totalArticles }) => { categoryNames, publishStatus, adminTags, + tags, createdAt: new Date(createdAt).toString().substring(4, 21), }; }, @@ -137,11 +145,15 @@ const BrowseArticle = ({ articles, totalArticles }) => { data: { listAllArticles: articles }, } = await GraphClient.query({ query: listAllArticles, - variables: { limit: 25, offset: 25 * page, onlyPublished: false }, + variables: { + limit: rowsPerPage, + offset: rowsPerPage * page, + onlyPublished: false, + }, }); setArticles(articles); })(); - }, [page]); + }, [rowsPerPage, page]); return (
@@ -206,7 +218,12 @@ const BrowseArticle = ({ articles, totalArticles }) => { '&:hover': { display: 'initial' }, }} > - + @@ -234,12 +251,12 @@ const BrowseArticle = ({ articles, totalArticles }) => {
diff --git a/client/src/screens/admin_v2/Edit.jsx b/client/src/screens/admin_v2/Edit.jsx index d801bea4..fd0afc21 100644 --- a/client/src/screens/admin_v2/Edit.jsx +++ b/client/src/screens/admin_v2/Edit.jsx @@ -1,35 +1,33 @@ import React, { useState } from 'react'; -//graphql -import { GraphClient } from '../../config/ApolloClient'; -import updateArticleProps from '../../graphql/mutations/article/updateArticleProps'; -import updateArticleUsers from '../../graphql/mutations/article/updateArticleUsers'; -import updateArticleCategories from '../../graphql/mutations/article/updateArticleCategories'; -import updateArticleRestriction from '../../graphql/mutations/article/updateArticleRestriction'; -import updateArticleApprovalStatus from '../../graphql/mutations/article/updateArticleApprovalStatus'; - -//material ui -import Paper from '@mui/material/Paper'; -import InputBase from '@mui/material/InputBase'; -import Box from '@mui/material/Box'; import SendIcon from '@mui/icons-material/Send'; +import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; -import Radio from '@mui/material/Radio'; -import RadioGroup from '@mui/material/RadioGroup'; -import FormControlLabel from '@mui/material/FormControlLabel'; +import Container from '@mui/material/Container'; import FormControl from '@mui/material/FormControl'; +import FormControlLabel from '@mui/material/FormControlLabel'; import FormLabel from '@mui/material/FormLabel'; -import Container from '@mui/material/Container'; +import InputBase from '@mui/material/InputBase'; +//material ui +import Paper from '@mui/material/Paper'; +import Radio from '@mui/material/Radio'; +import RadioGroup from '@mui/material/RadioGroup'; import useMediaQuery from '@mui/material/useMediaQuery'; -//components -import SnackBarAleart from '../../components/admin_v2/Common/SnackBarAleart'; import AuthorsCard from '../../components/admin_v2/AddNew/AuthorCards'; import CategoryCard from '../../components/admin_v2/AddNew/CategoryCard'; -import Marginals from '../../components/admin_v2/Marginals/Marginals'; import FeaturedMedia from '../../components/admin_v2/Common/FeaturedMedia'; +//components +import SnackBarAleart from '../../components/admin_v2/Common/SnackBarAleart'; import ArticleTags from '../../components/admin_v2/Common/Tags'; - +import Marginals from '../../components/admin_v2/Marginals/Marginals'; +//graphql +import { GraphClient } from '../../config/ApolloClient'; +import updateArticleApprovalStatus from '../../graphql/mutations/article/updateArticleApprovalStatus'; +import updateArticleCategories from '../../graphql/mutations/article/updateArticleCategories'; +import updateArticleProps from '../../graphql/mutations/article/updateArticleProps'; +import updateArticleRestriction from '../../graphql/mutations/article/updateArticleRestriction'; +import updateArticleUsers from '../../graphql/mutations/article/updateArticleUsers'; //utils import explorer from '../../utils/categoryCard'; import STORES from '../../utils/getStores'; @@ -282,7 +280,13 @@ const EditArticle = ({ allUsers, article }) => {
- +