From 716bbdd279f6136ffe6245d71c63a8e43b461670 Mon Sep 17 00:00:00 2001 From: Shivam Gaur <shivamgaur8527@gmail.com> Date: Sun, 4 Aug 2024 02:50:28 +0530 Subject: [PATCH 1/2] [Frontend] Improve the Manage Q&A UI --- .../pages/Admin/Components/Faq/Q&A/QandA.jsx | 216 ++++++++++++++++-- .../Components/Faq/Q&A/qanda.module.scss | 146 ++++++++++-- 2 files changed, 317 insertions(+), 45 deletions(-) diff --git a/frontend/src/pages/Admin/Components/Faq/Q&A/QandA.jsx b/frontend/src/pages/Admin/Components/Faq/Q&A/QandA.jsx index 8df877bb..8d4eb61d 100644 --- a/frontend/src/pages/Admin/Components/Faq/Q&A/QandA.jsx +++ b/frontend/src/pages/Admin/Components/Faq/Q&A/QandA.jsx @@ -1,26 +1,140 @@ import React, { useEffect, useState } from "react"; import style from "./qanda.module.scss"; -import { getAllQuestions } from "../../../../../service/Faq"; +import { + getAllQuestions, + deleteAnswer, + deleteQuestion, + updateQuestionStatus, + updateAnswerStatus, + getAnswers, +} from "../../../../../service/Faq"; import { SimpleToast } from "../../../../../components/util/Toast/Toast"; import Loader from "../../../../../components/util/Loader"; -import { hideToast } from "../../../../../service/toastService"; +import { hideToast, showToast } from "../../../../../service/toastService"; +import Modal from "@material-ui/core/Modal"; +import Button from "@material-ui/core/Button"; +import Typography from "@material-ui/core/Typography"; +import { makeStyles } from "@material-ui/core/styles"; +import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; +import ExpandLessIcon from '@material-ui/icons/ExpandLess'; -export function QandA({ setTab, setQId, tab }) { +const useStyles = makeStyles((theme) => ({ + modal: { + display: "flex", + alignItems: "center", + justifyContent: "center", + }, + paper: { + backgroundColor: theme.palette.background.paper, + border: "2px solid #000", + boxShadow: theme.shadows[5], + padding: theme.spacing(2, 4, 3), + }, + buttons: { + display: "flex", + marginTop: "10px", + justifyContent: "center", + gap: "10px", + }, +})); + +export function QandA() { const [cards, setCards] = useState([]); + const [expandedCards, setExpandedCards] = useState({}); const [isLoaded, setIsLoaded] = useState(false); const [toast, setToast] = useState({ toastStatus: false, toastType: "", toastMessage: "", }); + const [confirmDelete, setConfirmDelete] = useState(false); + const [questionToDelete, setQuestionToDelete] = useState(null); + const classes = useStyles(); - const getdata = async () => { + const getQuestions = async () => { setIsLoaded(true); const data = await getAllQuestions(setToast, toast); setCards(data); setIsLoaded(false); }; + const handleOpenConfirmModal = (id) => { + setConfirmDelete(true); + setQuestionToDelete(id); + }; + + const handleCloseConfirmModal = () => { + setConfirmDelete(false); + setQuestionToDelete(null); + }; + + const handleDeleteQuestion = async () => { + await deleteQuestion(questionToDelete, setToast); + setCards(cards.filter(card => card._id !== questionToDelete)); + setConfirmDelete(false); + }; + + const updateQuestion = async (id, status) => { + setCards(cards.map(card => + card._id === id ? { ...card, isApproved: status } : card + )); + + await updateQuestionStatus(id, status, setToast); + }; + + const handleDeleteAnswer = async (answerId) => { + const questionId = Object.keys(expandedCards)[0]; + const prevAnswers = expandedCards[questionId]?.answers || []; + + setExpandedCards((prev) => ({ + ...prev, + [questionId]: { + ...prev[questionId], + answers: prevAnswers.filter(answer => answer._id !== answerId), + }, + })); + + await deleteAnswer(answerId, setToast); + }; + + const updateAnswer = async (id, status) => { + const questionId = Object.keys(expandedCards)[0]; + const prevAnswers = expandedCards[questionId]?.answers || []; + + setExpandedCards((prev) => ({ + ...prev, + [questionId]: { + ...prev[questionId], + answers: prevAnswers.map(answer => + answer._id === id ? { ...answer, isApproved: status } : answer + ), + }, + })); + + await updateAnswerStatus(id, status, setToast); + }; + + const handleToggleExpand = async (id) => { + if (expandedCards[id]) { + setExpandedCards((prev) => { + const newExpanded = { ...prev }; + delete newExpanded[id]; + return newExpanded; + }); + } else { + setIsLoaded(true); + const aRes = await getAnswers(id, setToast); + setExpandedCards((prev) => ({ + ...prev, + [id]: { + answers: aRes, + }, + })); + setIsLoaded(false); + } + }; + + const handleCloseToast = (event, reason) => { if (reason === "clickaway") { return; @@ -29,43 +143,97 @@ export function QandA({ setTab, setQId, tab }) { }; useEffect(() => { - getdata(); - }, [tab]); + getQuestions(); + }, []); return ( <div> <h1 className={style["head"]}>Manage Q&A</h1> - <div className={style["data-loader"]}>{isLoaded ? <Loader /> : null}</div> + <div className={style["data-loader"]}>{isLoaded && <Loader />}</div> <div className={style["manage-qas"]}> - {cards?.map((d, index) => ( + {cards?.map((qns, index) => ( <div className={style["crd"]} key={index}> - <h2 className="head1"> {d.title} </h2> - <div className={style["content"]}> - <h3 style={{ fontWeight: "bolder" }}>{d.description}</h3> - </div> + <h2 className="head1">{qns.title}</h2> <div className={style["content"]}> - <h5>Status</h5> {`${d.isApproved ? "Approved" : "Not Approved"}`} + <h3 style={{ fontWeight: "bolder" }}>{qns.description}</h3> </div> <div style={{ display: "flex" }}> - {d.tags.map((tag, idx) => ( - <p key={idx} className={style["tags"]}> - {tag.value || tag} - </p> - ))} + <div style={{ display: "flex", padding: "10px 30px 10px 30px" }}> + {qns?.tags?.map((tag) => ( + <p className={style["tags"]} key={tag}>{tag}</p> + ))} + </div> + </div> + <div className={style["button-group"]}> + <button + className={qns?.isApproved ? style["button-delete"] : style["button-approve"]} + id={`${qns?._id}`} + onClick={(e) => updateQuestion(e.currentTarget.id, !qns?.isApproved)} + > + {qns?.isApproved ? "Reject" : "Approve"} + </button> + <button + name={`${qns?._id}`} + onClick={(e) => handleOpenConfirmModal(e.currentTarget.name)} + className={style["button-delete"]} + > + Delete + </button> </div> <button - id={`${d._id}`} - onClick={(e) => { - setQId(e.currentTarget.id); - setTab(19); - }} + id={`${qns._id}`} + onClick={() => handleToggleExpand(qns._id)} className={style["manage"]} > - Answer + {expandedCards[qns._id] ? <ExpandLessIcon /> : <ExpandMoreIcon />} + {expandedCards[qns._id] ? "Hide Answers" : "Show Answers"} </button> + {expandedCards[qns._id] && ( + <div className={style["answers"]}> + {expandedCards[qns._id].answers?.length === 0 ? ( + <span>No answers Found</span> + ) : ( + expandedCards[qns._id].answers.map((a) => ( + <div className={style["card-item"]} key={a?._id}> + <div className={style["card-info"]}> + <div className={style["answerBox"]}> + <h3 className={style["card-answer"]}>{a.answer}</h3> + <div className={style["button-group"]}> + <button + className={a?.isApproved ? style["button-delete"] : style["button-approve"]} + id={`${a?._id}`} + onClick={(e) => updateAnswer(e.currentTarget.id, !a?.isApproved)} + > + {a?.isApproved ? "Reject" : "Approve"} + </button> + <button + name={`${a?._id}`} + onClick={(e) => handleDeleteAnswer(e.currentTarget.name)} + className={style["button-delete"]} + > + Delete + </button> + </div> + </div> + </div> + </div> + )) + )} + </div> + )} </div> ))} </div> + <Modal open={confirmDelete} onClose={handleCloseConfirmModal} className={classes.modal}> + <div className={classes.paper}> + <Typography variant="h6" component="h2">Confirm Delete</Typography> + <Typography sx={{ mt: 2 }}>Are you sure you want to delete this question and all its answers?</Typography> + <div className={classes.buttons}> + <Button onClick={handleDeleteQuestion} variant="contained" color="secondary">Confirm</Button> + <Button onClick={handleCloseConfirmModal} variant="contained" color="primary">Cancel</Button> + </div> + </div> + </Modal> {toast.toastStatus && ( <SimpleToast open={toast.toastStatus} diff --git a/frontend/src/pages/Admin/Components/Faq/Q&A/qanda.module.scss b/frontend/src/pages/Admin/Components/Faq/Q&A/qanda.module.scss index 64fad9cd..0177d7d6 100644 --- a/frontend/src/pages/Admin/Components/Faq/Q&A/qanda.module.scss +++ b/frontend/src/pages/Admin/Components/Faq/Q&A/qanda.module.scss @@ -30,10 +30,12 @@ align-items: center; justify-content: center; background-color: #016795; - box-shadow: rgba(141, 113, 113, 0.24) 0px 3px 8px; + box-shadow: 1em 1em 1em #363535; cursor: pointer; color: white; border-radius: 20px; + overflow: hidden; + transition: background-color 0.3s ease; } .crd:hover { @@ -56,23 +58,23 @@ } @media (max-width: 983px) { - .manage-teams { + .manage-qas { grid-template-columns: auto; margin: auto; justify-content: center; } - .conts { - width: 90%; - } - .crd{ + .crd { max-width: 400px; } + .content { overflow-wrap: break-word; padding: 1rem 1.2rem; + transition: max-height 0.3s ease, opacity 0.3s ease; } - .manage-qas{ + + .manage-qas { display: grid; grid-template-columns: auto; } @@ -85,27 +87,129 @@ } } -.crd > div > .tags { - background-color: gray; +.manage { + width: 120px; + background-color: white; color: black; - padding: 0px 4px; - display: flex; + margin-bottom: 20px; + margin-top: 15px; + padding: 10px; + border: none; + border-radius: 5px; +} + +.card-item { + text-align: center; + font-size: 1.5rem; + border-radius: 1em; + height: auto; + width: 90%; + margin: 10px auto 30px auto; + display: block; + background-position: left center; + transition: all 0.5s ease-in; + background-color: #016795; + box-shadow: 0.5em 0.5em 0.5em rgb(54, 53, 53); + max-width: 400px; +} + +.card-answer { + font-weight: 600; + text-align: left; + font-size: 1.2rem; + width: 100%; + margin: 2px; +} + +.questionBox { + display: grid; + justify-content: center; + margin: 5px; + gap: 10px; +} + +.answerBox { + display: grid; justify-content: center; + margin: 5px; + gap: 10px; +} + +.card-info { + color: white; + margin-top: 10px; + margin-bottom: 20px; + display: flex; + flex-direction: column; + padding: 14px; +} + +.button-group { + display: flex; + width: 100%; align-items: center; - margin: 4px; - height: 18px; - margin-top: 0; - border-radius: 10px; - font-size: x-small; + justify-content: center; + gap: 10px; + margin: 5px 2px 2px 2px; } -.manage{ +.button-approve { + padding: 10px; + border: none; + outline: none; + border-radius: 5px; + background-color: rgb(6, 158, 41); + margin: 5px; + color: #fff; width: 120px; - background-color: white; - color: black; - margin-bottom: 20px; - margin-top: 15px; + font-size: medium; + font-weight: bold; + transition: background-color 200ms; +} + +.button-delete { padding: 10px; border: none; + outline: none; border-radius: 5px; + background-color: #fc0254; + margin: 5px; + color: #fff; + width: 120px; + font-size: medium; + font-weight: bold; + transition: background-color 200ms; + text-align: center; +} + +.button-delete:hover { + background-color: #fc3779; +} + +.tags { + background-color: gainsboro; + color: black; + padding: 0px 4px; + display: flex; + justify-content: center; + align-items: center; + margin: 4px; + height: 25px; + margin-top: 0; + border-radius: 4px; + font-size: small; +} + +@media (max-width: 900px) { + .answerBox { + display: block; + } + .questionBox { + display: block; + } + .answer { + display: flex; + justify-content: center; + flex-direction: column; + } } From 3b7d9f57c022e415e6db99b68014b91c1ae241ec Mon Sep 17 00:00:00 2001 From: Shivam Gaur <shivamgaur8527@gmail.com> Date: Mon, 5 Aug 2024 01:36:03 +0530 Subject: [PATCH 2/2] removing redundant code --- frontend/src/pages/Admin/Admin.jsx | 3 - .../Faq/Q&A/ManageQ&A/ManageQ&A.jsx | 199 ------------------ .../Components/Faq/Q&A/ManageQ&A/index.js | 1 - .../Faq/Q&A/ManageQ&A/manage.module.scss | 146 ------------- 4 files changed, 349 deletions(-) delete mode 100644 frontend/src/pages/Admin/Components/Faq/Q&A/ManageQ&A/ManageQ&A.jsx delete mode 100644 frontend/src/pages/Admin/Components/Faq/Q&A/ManageQ&A/index.js delete mode 100644 frontend/src/pages/Admin/Components/Faq/Q&A/ManageQ&A/manage.module.scss diff --git a/frontend/src/pages/Admin/Admin.jsx b/frontend/src/pages/Admin/Admin.jsx index 312af9f5..1c649dee 100644 --- a/frontend/src/pages/Admin/Admin.jsx +++ b/frontend/src/pages/Admin/Admin.jsx @@ -24,7 +24,6 @@ import { END_POINT } from "../../config/api"; import { useDispatch } from "react-redux"; import { ManageFaq } from "./Components/Faq/ManageFaq"; import { QandA } from "./Components/Faq/Q&A/QandA"; -import { Manageqa } from "./Components/Faq/Q&A/ManageQ&A/ManageQ&A"; import { Testimonial } from "./Components/Testimonial"; import { AddTestimonial } from "./Components/Testimonial/AddTestimonial"; import { ManageTestimonial } from "./Components/Testimonial/ManageTestimonial"; @@ -261,8 +260,6 @@ export const Admin = (props) => { <ManageBroadcasts /> ) : tab === 18 ? ( <QandA setQId={setQId} setTab={setTab} tab={tab} /> - ) : tab === 19 ? ( - <Manageqa qId={qId} setTab={setTab} /> ) : tab === 20 ? ( <Testimonial setTab={setTab} /> ) : tab === 21 ? ( diff --git a/frontend/src/pages/Admin/Components/Faq/Q&A/ManageQ&A/ManageQ&A.jsx b/frontend/src/pages/Admin/Components/Faq/Q&A/ManageQ&A/ManageQ&A.jsx deleted file mode 100644 index 4ba23be3..00000000 --- a/frontend/src/pages/Admin/Components/Faq/Q&A/ManageQ&A/ManageQ&A.jsx +++ /dev/null @@ -1,199 +0,0 @@ -import { useEffect, useState } from "react"; -import style from "./manage.module.scss"; -import { makeStyles } from "@material-ui/core/styles"; -import Modal from "@material-ui/core/Modal"; -import Button from "@material-ui/core/Button"; -import Typography from "@material-ui/core/Typography"; -import { AiOutlineArrowLeft } from "react-icons/ai"; -import { SimpleToast } from "../../../../../../components/util/Toast/Toast"; -import Loader from "../../../../../../components/util/Loader"; -import { getQuestionById, deleteAnswer, deleteQuestion, updateQuestionStatus, updateAnswerStatus, getAnswers } from "../../../../../../service/Faq"; -import { hideToast, showToast } from "../../../../../../service/toastService"; - -const useStyles = makeStyles((theme) => ({ - modal: { - display: "flex", - alignItems: "center", - justifyContent: "center", - }, - paper: { - backgroundColor: theme.palette.background.paper, - border: "2px solid #000", - boxShadow: theme.shadows[5], - padding: theme.spacing(2, 4, 3), - }, - buttons: { - display: "flex", - marginTop: "10px", - justifyContent: "center", - gap: "10px", - }, -})); - -export function Manageqa({ setTab, qId }) { - const [ans, setAns] = useState([]); - const [qns, setQns] = useState(); - const [toggle, setToggle] = useState(false); - const [isLoaded, setIsLoaded] = useState(false); - const [toast, setToast] = useState({ - toastStatus: false, - toastType: "", - toastMessage: "", - }); - const [confirmDelete, setConfirmDelete] = useState(false); - const [questionToDelete, setQuestionToDelete] = useState(null); - const classes = useStyles(); - - const getQuestion = async (id) => { - setIsLoaded(true); - const qRes = await getQuestionById(id, setToast); - setQns(qRes); - setIsLoaded(false); - }; - - const handleOpenConfirmModal = (id) => { - setConfirmDelete(true); - setQuestionToDelete(id); - }; - - const handleCloseConfirmModal = () => { - setConfirmDelete(false); - }; - - const handleDeleteAnswer = async (answerId) => { - await deleteAnswer(answerId, setToast); - setToggle(!toggle); - }; - - const handleDeleteQuestion = async () => { - await deleteQuestion(questionToDelete, setToast); - setConfirmDelete(false); - setTab(18); - setToggle(!toggle); - }; - - const updateQuestion = async (id, status) => { - await updateQuestionStatus(id, status, setToast); - setToggle(!toggle); - }; - - const getAnswer = async (questionId) => { - setIsLoaded(true); - const aRes = await getAnswers(questionId, setToast); - setAns(aRes); - setIsLoaded(false); - }; - - const updateAnswer = async (id, status) => { - await updateAnswerStatus(id, status, setToast); - setToggle(!toggle); - }; - - const handleCloseToast = (event, reason) => { - if (reason === "clickaway") { - return; - } - hideToast(setToast); - }; - - useEffect(() => { - getQuestion(qId); - getAnswer(qId); - }, [toggle]); - - return ( - <div> - <h1 className={style["head"]}>Manage Q&A</h1> - <div className={style["back"]} style={{ cursor: "pointer" }}> - <AiOutlineArrowLeft size={"35px"} onClick={() => setTab(18)} /> - </div> - <div className={style["data-loader"]}>{isLoaded && <Loader />}</div> - {!isLoaded && ( - <> - <h1 className={style["head"]}>Question</h1> - <div className={style["question"]}> - <div className={style["card-item"]}> - <div className={style["card-info"]}> - <h1>{qns?.title}</h1> - <div className={style["questionBox"]}> - <h3 className={style["card-question"]}>{qns?.description}</h3> - <div className={style["button-group"]}> - <button - className={qns?.isApproved ? style["button-delete"] : style["button-approve"]} - id={`${qns?._id}`} - onClick={(e) => updateQuestion(e.currentTarget.id, !qns?.isApproved)} - > - {qns?.isApproved ? "Reject" : "Approve"} - </button> - <button - name={`${qns?._id}`} - onClick={(e) => handleOpenConfirmModal(e.currentTarget.name)} - className={style["button-delete"]} - > - Delete - </button> - </div> - <div style={{ display: "flex", padding: "10px 30px 10px 30px" }}> - {qns?.tags?.map((tag) => ( - <p className={style["tags"]} key={tag}>{tag}</p> - ))} - </div> - </div> - </div> - </div> - </div> - </> - )} - <h1 className={style["head"]}>Answers</h1> - {ans?.length === 0 ? ( - <span>No answers Found</span> - ) : ( - <div className={ans?.length === 1 ? style["question"] : style["answer"]}> - {ans?.map((a) => ( - <div className={style["card-item"]} key={a?._id}> - <div className={style["card-info"]}> - <div className={style["answerBox"]}> - <h3 className={style["card-answer"]}>{a.answer}</h3> - <div className={style["button-group"]}> - <button - className={a?.isApproved ? style["button-delete"] : style["button-approve"]} - id={`${a?._id}`} - onClick={(e) => updateAnswer(e.currentTarget.id, !a?.isApproved)} - > - {a?.isApproved ? "Reject" : "Approve"} - </button> - <button - name={`${a?._id}`} - onClick={(e) => handleDeleteAnswer(e.currentTarget.name)} - className={style["button-delete"]} - > - Delete - </button> - </div> - </div> - </div> - </div> - ))} - </div> - )} - <Modal open={confirmDelete} onClose={handleCloseConfirmModal} className={classes.modal}> - <div className={classes.paper}> - <Typography variant="h6" component="h2">Confirm Delete</Typography> - <Typography sx={{ mt: 2 }}>Are you sure you want to delete this question and all its answers?</Typography> - <div className={classes.buttons}> - <Button onClick={handleDeleteQuestion} variant="contained" color="secondary">Confirm</Button> - <Button onClick={handleCloseConfirmModal} variant="contained" color="primary">Cancel</Button> - </div> - </div> - </Modal> - {toast.toastStatus && ( - <SimpleToast - open={toast.toastStatus} - message={toast.toastMessage} - handleCloseToast={handleCloseToast} - severity={toast.toastType} - /> - )} - </div> - ); -} diff --git a/frontend/src/pages/Admin/Components/Faq/Q&A/ManageQ&A/index.js b/frontend/src/pages/Admin/Components/Faq/Q&A/ManageQ&A/index.js deleted file mode 100644 index 747c2843..00000000 --- a/frontend/src/pages/Admin/Components/Faq/Q&A/ManageQ&A/index.js +++ /dev/null @@ -1 +0,0 @@ -export * from "./ManageQ&A"; diff --git a/frontend/src/pages/Admin/Components/Faq/Q&A/ManageQ&A/manage.module.scss b/frontend/src/pages/Admin/Components/Faq/Q&A/ManageQ&A/manage.module.scss deleted file mode 100644 index 11c431d3..00000000 --- a/frontend/src/pages/Admin/Components/Faq/Q&A/ManageQ&A/manage.module.scss +++ /dev/null @@ -1,146 +0,0 @@ -.card-item { - text-align: center; - font-size: 1.5rem; - border-radius: 1em; - height: auto; - width: 90%; - margin: 10px auto 30px auto; - display: inline-block; - background-position: left center; - transition: all 0.5s ease-in; - background-color: #016795; - box-shadow: 0.5em 0.5em 0.5em rgb(54, 53, 53); - max-width: 400px; -} - -.card-title { - font-size: 1.8rem; - margin-bottom: 1.5rem; - line-height: 1.9rem; - font-weight: bold; - color: white; -} -.question{ - display: flex; - justify-content: center; -} -.card-question { - font-weight: bold; - text-align: left; - font-size: 1.3rem; - width: 100%; - margin: 2px; -} - -.card-answer { - font-weight: 600; - text-align: left; - font-size: 1.2rem; - width: 100%; - margin: 2px; -} - -.questionBox{ - display: grid; - justify-content: center; - margin: 5px; - gap: 10px; -} - -.answerBox{ - display: grid; - justify-content: center; - margin: 5px; - gap: 10px; -} - -.card-info { - color: white; - margin-top: 10px; - margin-bottom: 20px; - display: flex; - flex-direction: column; - padding: 14px; -} - -.head { - text-align: center; -} - -.button-group { - display: flex; - width: 100%; - align-items: center; - justify-content: center; - gap: 10px; - margin: 5px 2px 2px 2px; -} - -.button-approve { - padding: 10px; - border: none; - outline: none; - border-radius: 5px; - background-color: rgb(6, 158, 41); - margin: 5px; - color: #fff; - width: 120px; - font-size: medium; - font-weight: bold; - transition: background-color 200ms; -} - -.answer{ - display: grid; - grid-template-columns: auto auto; -} - -.button-edit:hover { - background-color: rgb(10, 205, 53); -} - -.button-delete { - padding: 10px; - border: none; - outline: none; - border-radius: 5px; - background-color: #fc0254; - margin: 5px; - color: #fff; - width: 120px; - font-size: medium; - font-weight: bold; - transition: background-color 200ms; - text-align: center; -} - -.button-delete:hover { - background-color: #fc3779; -} - -.tags{ - background-color: gray; - color: black; - padding: 0px 4px; - display: flex; - justify-content: center; - align-items: center; - margin: 4px; - height: 25px; - margin-top: 0; - border-radius: 10px; - font-size: small; -} -@media (max-width:"900px") { - .answerBox{ - display: block; - } - .questionBox{ - display: block; - } - .answer{ - display: flex; - justify-content: center; - flex-direction: column; - } -} \ No newline at end of file