From 7b931bf046530f063ada4f2d56509714ef0f1c33 Mon Sep 17 00:00:00 2001 From: maxidragon Date: Thu, 21 Sep 2023 20:38:12 +0200 Subject: [PATCH] Add edit and delete answers --- backend/app/controllers/answers_controller.rb | 27 ++++-- .../ModalComponents/Edit/EditAnswerModal.tsx | 83 +++++++++++++++++ .../src/Components/Questions/AnswerRow.tsx | 93 +++++++++++++++++++ .../src/Components/Questions/QuestionRow.tsx | 24 ++--- frontend/src/logic/answers.ts | 26 ++++-- frontend/src/logic/interfaces.ts | 1 + 6 files changed, 224 insertions(+), 30 deletions(-) create mode 100644 frontend/src/Components/ModalComponents/Edit/EditAnswerModal.tsx create mode 100644 frontend/src/Components/Questions/AnswerRow.tsx diff --git a/backend/app/controllers/answers_controller.rb b/backend/app/controllers/answers_controller.rb index a4fce06..8abd82d 100644 --- a/backend/app/controllers/answers_controller.rb +++ b/backend/app/controllers/answers_controller.rb @@ -34,19 +34,32 @@ def create def update answer = Answer.find(params[:id]) - authorize_answer - if answer.update(answer_params) - render json: answer + if answer.present? && answer.question.quiz.user == current_user + answer_params = params.require(:answer).permit(:text, :is_correct) + if answer.update(answer_params) + render json: { + answer: answer, + status: :updated + } + else + render json: {error: answer.errors.full_messages}, status: :unprocessable_entity + end else - render json: {error: answer.errors.full_messages}, status: :unprocessable_entity + render json: {error: "Answer not found"}, status: :not_found end end def destroy answer = Answer.find(params[:id]) - authorize_answer - answer.destroy - render json: answer + if answer.present? && answer.question.quiz.user == current_user + answer.destroy + render json: { + answer: answer, + status: :deleted + } + else + render json: {error: "Answer not found"}, status: :not_found + end end end diff --git a/frontend/src/Components/ModalComponents/Edit/EditAnswerModal.tsx b/frontend/src/Components/ModalComponents/Edit/EditAnswerModal.tsx new file mode 100644 index 0000000..2c5b8e9 --- /dev/null +++ b/frontend/src/Components/ModalComponents/Edit/EditAnswerModal.tsx @@ -0,0 +1,83 @@ +import { + Box, + Typography, + Modal, + TextField, + Grid, + Checkbox, + FormControlLabel, +} from "@mui/material"; +import { formStyle, style } from "../modalStyles"; +import { enqueueSnackbar } from "notistack"; +import ActionsButtons from "../ActionsButtons"; +import EditIcon from "@mui/icons-material/Edit"; +import { editAnswer } from "../../../logic/answers"; +import { Answer } from "../../../logic/interfaces"; + +const EditAnswerModal = (props: { + open: boolean; + handleClose: () => void; + answer: Answer; + quizId: number; + updateAnswer: (answer: Answer) => void; +}) => { + const handleEdit = async () => { + const response = await editAnswer(props.answer, props.quizId); + if (response.status === "updated") { + enqueueSnackbar("Answer updated!", { variant: "success" }); + props.handleClose(); + } else { + enqueueSnackbar("Something went wrong!", { variant: "error" }); + } + }; + return ( + + + + + + Edit answer + + + + + props.updateAnswer({ + ...props.answer, + text: event.target.value, + }) + } + /> + + + + props.updateAnswer({ + ...props.answer, + is_correct: event.target.checked, + }) + } + /> + } + label="Correct" + /> + + + } + /> + + + ); +}; + +export default EditAnswerModal; diff --git a/frontend/src/Components/Questions/AnswerRow.tsx b/frontend/src/Components/Questions/AnswerRow.tsx new file mode 100644 index 0000000..b1a3572 --- /dev/null +++ b/frontend/src/Components/Questions/AnswerRow.tsx @@ -0,0 +1,93 @@ +import { useState } from "react"; +import { TableRow, TableCell, IconButton } from "@mui/material"; +import { Answer } from "../../logic/interfaces"; +import { useConfirm } from "material-ui-confirm"; +import EditIcon from "@mui/icons-material/Edit"; +import DeleteIcon from "@mui/icons-material/Delete"; +import EditAnswerModal from "../ModalComponents/Edit/EditAnswerModal"; +import { enqueueSnackbar } from "notistack"; +import { deleteAnswer } from "../../logic/answers"; + +const AnswerRow = (props: { + answerRow: Answer; + answerNumber: number; + quizId: number; +}) => { + const confirm = useConfirm(); + const { answerRow } = props; + const [hide, setHide] = useState(false); + const [editedAnswer, setEditedAnswer] = useState(answerRow); + const [openEditAnswerModal, setOpenEditAnswerModal] = + useState(false); + + const handleDelete = async () => { + if (answerRow === null) return; + confirm({ + description: "Are you sure you want to delete this answer?", + }) + .then(async () => { + const response = await deleteAnswer( + props.quizId, + answerRow.question_id, + answerRow.id, + ); + if (response.status === "deleted") { + enqueueSnackbar("Answer deleted!", { variant: "success" }); + setHide(true); + } else { + enqueueSnackbar("Something went wrong!", { variant: "error" }); + } + }) + .catch(() => { + enqueueSnackbar("Answer not deleted!", { variant: "info" }); + }); + }; + + const updateAnswer = (answer: Answer) => { + setEditedAnswer(answer); + }; + + return ( + <> + {!hide && ( + <> + + + {props.answerNumber + 1} + + + {answerRow.text} + + {answerRow.is_correct ? "Yes" : "No"} + + { + setOpenEditAnswerModal(true); + }} + > + + + + + + + + + setOpenEditAnswerModal(false)} + answer={editedAnswer} + quizId={props.quizId} + updateAnswer={updateAnswer} + /> + + )} + + ); +}; + +export default AnswerRow; diff --git a/frontend/src/Components/Questions/QuestionRow.tsx b/frontend/src/Components/Questions/QuestionRow.tsx index 9db9131..dd364e4 100644 --- a/frontend/src/Components/Questions/QuestionRow.tsx +++ b/frontend/src/Components/Questions/QuestionRow.tsx @@ -22,6 +22,7 @@ import AddIcon from "@mui/icons-material/Add"; import EditQuestionModal from "../ModalComponents/Edit/EditQuestionModal"; import EditIcon from "@mui/icons-material/Edit"; import DeleteIcon from "@mui/icons-material/Delete"; +import AnswerRow from "./AnswerRow"; const QuestionRow = (props: { row: Question; @@ -31,7 +32,7 @@ const QuestionRow = (props: { const confirm = useConfirm(); const { row } = props; const [hide, setHide] = useState(false); - const [editedQuestion, setEditedQuestion] = useState(props.row); + const [editedQuestion, setEditedQuestion] = useState(row); const [open, setOpen] = useState(false); const [openCreateModal, setOpenCreateModal] = useState(false); const [openEditQuestionModal, setOpenEditQuestionModal] = @@ -137,21 +138,12 @@ const QuestionRow = (props: { {row.answers.map((answerRow, answerNumber: number) => ( - - - {answerNumber + 1} - - - {answerRow.text} - - - {answerRow.is_correct ? "Yes" : "No"} - - + ))} diff --git a/frontend/src/logic/answers.ts b/frontend/src/logic/answers.ts index d92715f..ded4854 100644 --- a/frontend/src/logic/answers.ts +++ b/frontend/src/logic/answers.ts @@ -1,3 +1,4 @@ +import { Answer } from "./interfaces"; import { backendRequest } from "./request"; export const createAnswer = async ( @@ -19,19 +20,30 @@ export const createAnswer = async ( } }; -export const editAnswer = async ( - quizId: number, +export const editAnswer = async (answer: Answer, quizId: number) => { + try { + const response = await backendRequest( + `/quizzes/${quizId}/questions/${answer.question_id}/answers/${answer.id}`, + "PUT", + true, + answer, + ); + return await response.json(); + } catch (error) { + console.log(error); + } +}; + +export const deleteAnswer = async ( + quiz_id: number, question_id: number, answer_id: number, - text: string, - is_correct: boolean, ) => { try { const response = await backendRequest( - `/quizzes/${quizId}/questions/${question_id}/answers/${answer_id}`, - "PUT", + `/quizzes/${quiz_id}/questions/${question_id}/answers/${answer_id}`, + "DELETE", true, - { text, is_correct, question_id }, ); return await response.json(); } catch (error) { diff --git a/frontend/src/logic/interfaces.ts b/frontend/src/logic/interfaces.ts index 67e8d82..92895d3 100644 --- a/frontend/src/logic/interfaces.ts +++ b/frontend/src/logic/interfaces.ts @@ -18,6 +18,7 @@ export interface Answer { id: number; text: string; is_correct: boolean; + question_id: number; } export interface User {