Commit be1ba639 authored by Yoon, Daeki's avatar Yoon, Daeki 😅
Browse files

result, answer 로직 및 불필요한 폴더 파일 삭제

parent e7ae6d3f
export { Accordion } from "./Accordion";
import React from "react";
import { useNavigate } from "react-router-dom";
export const CompleteSurvey = () => {
const navigate = useNavigate();
return (
<div className="flex flex-col place-items-center mt-24">
<div className="flex flex-col container place-items-center place-content-center space-y-5 space-x-2 w-full h-56">
<p className="text-3xl font-bold">설문조사가 제출되었습니다</p>
<p>응답이 완료되었습니다</p>
<button
className="flex place-content-start rounded-lg bg-themeColor w-20 h-10 text-center py-2 px-4 text-white"
type="button"
onClick={() => navigate("/")}
>
홈으로
</button>
</div>
</div>
);
};
import React, { FormEvent, useEffect, useState } from "react";
import { useParams, useLocation, useNavigate } from "react-router-dom";
import { questionApi, surveyApi } from "../apis";
import { SpinnerIcon } from "../icons";
import { Question } from "../questions";
import { IQuestionData, ISurvey } from "../types";
import { catchErrors } from "../helpers";
export const CreateSurvey = () => {
let { surveyId } = useParams<{ surveyId: string }>();
const [isEditing, setIsEditing] =
useState<{ qid: string; isEditing: boolean }[]>();
const [error, setError] = useState("");
const [loading, setLoading] = useState(false);
const [success, setSuccess] = useState(false);
const navigate = useNavigate();
const [survey, setSurvey] = useState<ISurvey>({
_id: surveyId || "",
user: {},
title: "",
comment: "",
questions: [],
});
useEffect(() => {
getSurvey();
}, [surveyId]);
async function getSurvey() {
try {
if (surveyId) {
const thisSurvey: ISurvey = await surveyApi.getSurvey(surveyId);
const initEditing = thisSurvey.questions.map((question) => {
return { qid: question._id, isEditing: false };
});
// console.log("init editing", initEditing);
setIsEditing(initEditing);
setSurvey(thisSurvey);
setSuccess(true);
setError("");
} else {
setLoading(true);
}
} catch (error) {
catchErrors(error, setError);
} finally {
setLoading(false);
}
}
const handleEditing = (qid: string, edited: boolean) => {
console.log("handle editing:", qid, edited);
if (isEditing) {
const index = isEditing.findIndex((q) => q.qid === qid);
isEditing[index].isEditing = edited;
setIsEditing([...isEditing]);
}
};
const handleQuestion = (element: IQuestionData) => {
const index = survey.questions.findIndex(
(question) => question._id === element._id
);
survey.questions[index] = element;
const newList = [...survey.questions];
console.log("new list in handle question", newList);
setSurvey({ ...survey, questions: newList });
};
const handleSurvey = (event: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = event.currentTarget;
setSurvey({ ...survey, [name]: value });
};
async function handleSubmit(event: FormEvent) {
event.preventDefault();
const notEditComplete = isEditing?.find((el) => el.isEditing);
if (notEditComplete) {
alert("아직 수정이 완료되지 않은 질문이 존재합니다.");
} else {
try {
const newSurvey: ISurvey = await surveyApi.editSurvey(survey);
console.log(newSurvey);
setSuccess(true);
alert("저장되었습니다");
navigate("/profile");
setError("");
} catch (error) {
catchErrors(error, setError);
} finally {
setLoading(false);
}
}
}
async function addQuestion() {
try {
if (surveyId) {
// const questions: BasicQuestionType[] = await questionApi.createQuestion(
// surveyId
// );
// console.log(questions);
const question: IQuestionData = await questionApi.createQuestion(
surveyId
);
console.log(question);
isEditing &&
setIsEditing([...isEditing, { qid: question._id, isEditing: true }]);
// setSurvey({ ...survey, questions: questions });
setSurvey({ ...survey, questions: [...questions, question] });
setSuccess(true);
setError("");
} else {
setLoading(true);
}
} catch (error) {
catchErrors(error, setError);
} finally {
setLoading(false);
}
}
async function deleteQuestion(id: string) {
const newList: IQuestionData[] = [...survey.questions];
try {
const newQuestion: IQuestionData = await questionApi.deleteQuestion(id);
setSurvey({ ...survey, questions: newList.filter((a) => a._id !== id) });
setSuccess(true);
setError("");
} catch (error) {
catchErrors(error, setError);
} finally {
setLoading(false);
}
}
const questions = survey.questions;
// console.log(questions);
return (
<>
{error ? alert(error) : <></>}
{loading && (
<SpinnerIcon className="animate-spin h-5 w-5 mr-1 text-slate" />
)}
<form onSubmit={handleSubmit}>
<div className="flex flex-col place-items-center">
<div className="flex flex-col container place-items-center mt-4">
<input
type="text"
name="title"
className="font-bold text-4xl text-center m-2 border-b-2"
placeholder="설문지 제목"
value={survey.title}
onChange={handleSurvey}
></input>
<input
type="text"
name="comment"
className="font-bold text-1xl text-center m-2 border-b-2 resize-none"
placeholder="설문조사에 대한 설명을 입력해주세요"
size={50}
value={survey.comment}
onChange={handleSurvey}
></input>
</div>
{questions.map((question) => (
<Question
key={question._id}
element={question}
isEditing={
isEditing?.filter((q) => q.qid === question._id)[0]
?.isEditing ?? true
}
handleEditing={handleEditing}
handleQuestion={handleQuestion}
deleteQuestion={deleteQuestion}
/>
))}
<div className="flex w-4/5 content-center justify-center border-2 border-black h-8 mt-3">
<button type="button" onClick={addQuestion}>
질문 추가
</button>
</div>
<div>
<button
type="submit"
className="border bg-themeColor my-5 py-2 px-3 font-bold text-white"
>
저장하기
</button>
</div>
</div>
</form>
</>
);
};
import React from "react";
import { NavLink } from "react-router-dom";
import { Outlet, useNavigate, useParams } from "react-router-dom";
export const EditResultButton = () => {
let { surveyId } = useParams<{ surveyId: string }>();
const navigate = useNavigate();
return (
<div>
<div className="flex place-content-center mt-6">
<NavLink
to={`/surveys/${surveyId}/edit`}
style={({ isActive }) =>
isActive
? {
width: "140px",
color: "white",
backgroundColor: "#008080",
borderTopLeftRadius: "25px",
borderBottomLeftRadius: "25px",
textAlign: "center",
fontWeight: "bold",
fontSize: "20px",
}
: {
width: "140px",
borderWidth: "1px",
borderColor: "#008080",
borderTopLeftRadius: "25px",
borderBottomLeftRadius: "25px",
textAlign: "center",
fontSize: "18px",
}
}
>
<div className="m-3 ">설문지 수정</div>
</NavLink>
<NavLink
to={`/surveys/${surveyId}/result`}
style={({ isActive }) =>
isActive
? {
width: "140px",
color: "white",
backgroundColor: "#008080",
borderTopRightRadius: "25px",
borderBottomRightRadius: "25px",
textAlign: "center",
fontWeight: "bold",
fontSize: "20px",
}
: {
width: "140px",
borderWidth: "1px",
borderColor: "#008080",
borderTopRightRadius: "25px",
borderBottomRightRadius: "25px",
textAlign: "center",
fontSize: "18px",
}
}
>
<div className="m-3">응답결과</div>
</NavLink>
</div>
<Outlet />
</div>
);
};
import React, { FormEvent, useEffect, useState } from "react";
import { useParams, useLocation, useNavigate } from "react-router-dom";
import { questionApi, surveyApi } from "../apis";
import { SpinnerIcon } from "../icons";
import { Question } from "../questions";
import { IQuestionData, ISurvey } from "../types";
import { catchErrors } from "../helpers";
export const EditSurvey = () => {
let { surveyId } = useParams<{ surveyId: string }>();
interface CustomizedState {
save: boolean;
}
const [isEditing, setIsEditing] =
useState<{ qid: string; isEditing: boolean }[]>();
const [error, setError] = useState("");
const [loading, setLoading] = useState(false);
const [success, setSuccess] = useState(false);
const navigate = useNavigate();
const [survey, setSurvey] = useState<ISurvey>({
_id: surveyId || "",
user: {},
title: "",
comment: "",
questions: [],
});
useEffect(() => {
getSurvey();
}, [surveyId]);
async function getSurvey() {
try {
if (surveyId) {
const thisSurvey: ISurvey = await surveyApi.getSurvey(surveyId);
const initEditing = thisSurvey.questions.map((question) => {
return { qid: question._id, isEditing: false };
});
// console.log("init editing", initEditing);
setIsEditing(initEditing);
setSurvey(thisSurvey);
setSuccess(true);
setError("");
} else {
setLoading(true);
}
} catch (error) {
catchErrors(error, setError);
} finally {
setLoading(false);
}
}
const handleEditing = (qid: string, edited: boolean) => {
console.log("handle editing:", qid, edited);
if (isEditing) {
const index = isEditing.findIndex((q) => q.qid === qid);
isEditing[index].isEditing = edited;
setIsEditing([...isEditing]);
}
};
const handleQuestion = (element: IQuestionData) => {
const index = survey.questions.findIndex(
(question) => question._id === element._id
);
survey.questions[index] = element;
const newList = [...survey.questions];
console.log("new list in handle question", newList);
setSurvey({ ...survey, questions: newList });
};
const handleSurvey = (event: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = event.currentTarget;
setSurvey({ ...survey, [name]: value });
};
async function handleSubmit(event: FormEvent) {
event.preventDefault();
const notEditComplete = isEditing?.find((el) => el.isEditing);
if (notEditComplete) {
alert("아직 수정이 완료되지 않은 질문이 존재합니다.");
} else {
try {
const newSurvey: ISurvey = await surveyApi.editSurvey(survey);
console.log(newSurvey);
setSuccess(true);
alert("저장되었습니다");
navigate("/profile");
setError("");
} catch (error) {
catchErrors(error, setError);
} finally {
setLoading(false);
}
}
}
async function addQuestion() {
try {
if (surveyId) {
// const questions: BasicQuestionType[] = await questionApi.createQuestion(
// surveyId
// );
// console.log(questions);
const question: IQuestionData = await questionApi.createQuestion(
surveyId
);
console.log(question);
// const addedEditing = questions.map((question) => {
// return { qid: question._id, isEditing: false };
// });
// console.log("added editing", addedEditing);
isEditing &&
setIsEditing([...isEditing, { qid: question._id, isEditing: true }]);
// setSurvey({ ...survey, questions: questions });
setSurvey({ ...survey, questions: [...questions, question] });
setSuccess(true);
setError("");
} else {
setLoading(true);
}
} catch (error) {
catchErrors(error, setError);
} finally {
setLoading(false);
}
}
async function deleteQuestion(id: string) {
const newList: IQuestionData[] = [...survey.questions];
try {
const newQuestion: IQuestionData = await questionApi.deleteQuestion(id);
setSurvey({ ...survey, questions: newList.filter((a) => a._id !== id) });
setSuccess(true);
setError("");
} catch (error) {
catchErrors(error, setError);
} finally {
setLoading(false);
}
}
const questions = survey.questions;
// console.log(questions);
// console.log("isediting", isEditing);
return (
<>
{error ? alert(error) : <></>}
{loading && (
<SpinnerIcon className="animate-spin h-5 w-5 mr-1 text-slate" />
)}
<form onSubmit={handleSubmit}>
<div className="flex flex-col place-items-center">
<div className="flex flex-col container place-items-center mt-4">
<input
type="text"
name="title"
className="font-bold text-4xl text-center m-2 border-b-2"
placeholder="설문지 제목"
value={survey.title}
onChange={handleSurvey}
></input>
<input
type="text"
name="comment"
className="font-bold text-1xl text-center m-2 border-b-2 resize-none"
placeholder="설문조사에 대한 설명을 입력해주세요"
size={50}
value={survey.comment}
onChange={handleSurvey}
></input>
</div>
{questions.map((question) => (
<Question
key={question._id}
element={question}
isEditing={
isEditing?.filter((q) => q.qid === question._id)[0]
?.isEditing ?? false
}
handleEditing={handleEditing}
handleQuestion={handleQuestion}
deleteQuestion={deleteQuestion}
/>
))}
<div className="flex w-4/5 content-center justify-center border-2 border-themeColor2 border-addQuestionColor h-8 mt-3 rounded-lg ">
<button type="button" onClick={addQuestion}>
질문 추가
</button>
</div>
<div>
<button
type="submit"
className="border bg-themeColor my-5 py-2 px-3 font-bold text-white"
>
저장하기
</button>
</div>
</div>
</form>
</>
);
};
import React from "react";
import { useNavigate } from "react-router-dom";
export const SameSurvey = () => {
const navigate = useNavigate();
return (
<div className="flex flex-col place-items-center mt-24">
<div className="flex flex-col container place-items-center place-content-center space-y-5 space-x-2 w-full h-56">
<p className="text-3xl font-bold px-3">이미 제출된 설문조사입니다</p>
<button
className="flex place-content-start rounded-lg bg-themeColor w-20 h-10 text-center py-2 px-4 text-white"
type="button"
onClick={() => navigate("/")}
>
홈으로
</button>
</div>
</div>
);
};
export { EditResultButton } from "./EditResultButton";
import React from "react";
import { IQuestionData, IAnswer } from "../types";
import { getAnswerElementByType } from "../helpers";
type Props = {
question: IQuestionData;
answer: IAnswer;
};
export const AQuestion = ({ question, answer }: Props) => {
return (
<div className="flex flex-col container w-4/5 h-auto border-2 border-themeColor items-center m-3 py-4 rounded-lg">
<div className="flex my-1 w-11/12 place-content-between items-center">
<div className="text-xl font-bold">{question.title}</div>
{question.isRequired ? (
<div className="text-xs text-red-600">* 필수질문</div>
) : (
<></>
)}
</div>
<div className="w-11/12 text-slate-500">{question.comment}</div>
{getAnswerElementByType(question, answer)}
</div>
);
};
import React, { useState, useRef, useEffect } from "react";
import { IQuestionData } from "../types";
import { REssayForm } from "./REssayForm";
import { RCheckboxForm } from "./RCheckboxForm";
import { RRadioForm } from "./RRadioForm";
import { RDropdownForm } from "./RDropdownForm";
import { RFileForm } from "./RFileForm";
import { RRatingForm } from "./RRatingForm";
import { RDateForm } from "./RDateForm";
// import {
// REssay,
// RCheckbox,
// RRadio,
// RDropdown,
// RFile,
// RRating,
// RDate,
// } from "../forms";
import { getResultElementByType } from "../helpers/question.helper";
type AccordionProps = {
question: IQuestionData;
......@@ -21,26 +24,26 @@ export const Accordion = ({ question }: AccordionProps) => {
setOpened(!isOpened);
setHeight(!isOpened ? `${contentElement.current?.scrollHeight}px` : "0px");
};
function getContent(question: IQuestionData) {
switch (question.type) {
case "essay":
return <REssayForm question={question} />;
case "radio":
return <RRadioForm question={question} />;
case "checkbox":
return <RCheckboxForm question={question} />;
case "dropdown":
return <RDropdownForm question={question} />;
case "file":
return <RFileForm question={question} />;
case "rating":
return <RRatingForm question={question} />;
case "date":
return <RDateForm question={question} />;
default:
return <></>;
}
}
// function getContent(question: IQuestionData) {
// switch (question.type) {
// case "singletext":
// return <REssay question={question} />;
// case "radio":
// return <RRadio question={question} />;
// case "checkbox":
// return <RCheckbox question={question} />;
// case "dropdown":
// return <RDropdown question={question} />;
// case "file":
// return <RFile question={question} />;
// case "rating":
// return <RRating question={question} />;
// case "date":
// return <RDate question={question} />;
// default:
// return <></>;
// }
// }
// console.log(question);
......@@ -60,7 +63,7 @@ export const Accordion = ({ question }: AccordionProps) => {
style={{ height: height }}
className="bg-gray-100 overflow-hidden transition-all duration-300"
>
{question.answers && getContent(question)}
{question.answers && getResultElementByType(question)}
</div>
</div>
</div>
......
import React, { useEffect, useState } from "react";
import { answerApi, surveyApi } from "../apis";
import { catchErrors } from "../helpers";
import { Accordion } from "../results";
import { Accordion } from "./Accordion";
import { useParams } from "react-router-dom";
import { ISurvey } from "../types";
......
......@@ -3,3 +3,4 @@ export { CreateSurvey } from "./CreateSurvey";
export { EditSurvey } from "./EditSurvey";
export { Preview } from "./Preview";
export { Profile } from "./Profile";
export { ResultSurvey } from "./ResultSurvey";
import { QUESTION_TYPES } from "../commons";
// 타입 지정
export interface IUser {
email?: string;
isLoggedIn: boolean;
......@@ -11,7 +14,7 @@ export interface SignupUser {
}
export interface ISurvey {
_id: string;
_id?: string;
user: any;
title: string;
comment: string;
......@@ -33,7 +36,7 @@ interface IBasicContent {
export interface IQuestionData {
_id?: string;
order: number;
type: string;
type: IQuestionType;
title: string;
isRequired: boolean;
comment: string;
......@@ -42,13 +45,8 @@ export interface IQuestionData {
[key: string]: string | number | boolean | any;
}
export interface AnswerQuestionType extends IQuestionData {
requiredCheck: boolean;
answer: any;
}
export interface AnswerSurveyType extends ISurvey {
questions: AnswerQuestionType[];
export interface CreateQuestionData extends IQuestionData {
isEditing: boolean;
}
export interface IEssay extends IQuestionData {}
......@@ -60,11 +58,6 @@ export interface IRadio extends IQuestionData {
};
}
interface IChoices {
value: number;
text: string;
}
export interface ICheckbox extends IQuestionData {
content: IBasicContent & { maxCount: number };
}
......@@ -87,21 +80,55 @@ export interface IRating extends IQuestionData {
};
}
export interface AnswersType {
questionId: string;
type: string;
answer: any;
export interface IAnswer {
question: IQuestionData;
surveyId: string;
guestId?: string;
requiredCheck: boolean;
content: any;
}
export interface AnswerType {
export interface IAnswerRequestData {
questionId: string;
surveyId: string;
guestId: string;
answers: AnswersType[];
guestId?: string;
content: any;
}
export interface AnswerProps {
// export interface IAnswerSurvey extends ISurvey {
// questions: IAnswerQuestion[];
// }
// export interface IAnswers {
// questionId: string;
// type: string;
// content: any;
// }
// export interface IAnswer {
// surveyId: string;
// guestId: string;
// answers: IAnswers[];
// }
export interface IAnswerProps {
element: IQuestionData;
answerQuestion: AnswerQuestionType;
answer: IAnswer;
// answers: AnswersType | undefined;
// handleAnswer: () => void;
}
export interface IQuestionProps {
element: CreateQuestionData;
// isEditing: boolean;
// handleEditing: Function;
handleQuestion: Function;
deleteQuestion: Function;
}
export type IQuestionFormProps = Pick<
IQuestionProps,
"element" | "handleQuestion"
> & { isEditing: boolean };
export type IQuestionType = keyof typeof QUESTION_TYPES;
......@@ -11,7 +11,7 @@
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
"target": "ES2020", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
"jsx": "react", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
......
import { NextFunction, Request, Response } from "express";
import { asyncWrap, isEmpty } from "../helpers";
import { TypedRequest } from "../types";
import { asyncWrap, formidableFilesToArray, isEmpty } from "../helpers";
import type { TypedRequest } from "../types";
import formidable from "formidable";
import { FileInfo } from "../models";
import { FileInfo, IAnswer } from "../models";
import { fileDb, userDb, answerDb, surveyDb } from "../db";
import fs from "fs/promises";
export const createAnswers = asyncWrap(async (reqExp, res) => {
const req = reqExp as TypedRequest;
const answer = req.body;
console.log("answer in create answers:", answer);
const answers = JSON.parse(answer.answers);
answer.answers = answers;
let files: any[] = [];
......@@ -47,7 +48,7 @@ export const createAnswers = asyncWrap(async (reqExp, res) => {
surveyId: answer.surveyId,
guestId: answer.guestId,
questionId: element.questionId,
answer: element.answer,
content: element.answer,
});
});
await Promise.all(c);
......@@ -63,6 +64,45 @@ export const createAnswers = asyncWrap(async (reqExp, res) => {
}
});
export const createAnswersWithoutFile = asyncWrap(async (req, res) => {
const answers = req.body as IAnswer[];
const newAnswers = await Promise.all(
answers.map(
async (answer) =>
await answerDb.createAnswer({
surveyId: answer.surveyId,
guestId: answer.guestId,
questionId: answer.questionId,
content: answer.content,
})
)
);
console.log("new answers:", newAnswers);
res.json(newAnswers);
});
export const createAnswerWithFile = asyncWrap(async (reqExp, res) => {
const req = reqExp as TypedRequest;
console.log("body:", req.body, "files:", req.files);
const answer = req.body;
let fileInfos;
const files = formidableFilesToArray(req.files.uploadFiles);
if (files) {
fileInfos = await Promise.all(
files.map(async (file) => await fileDb.createFile(file))
);
}
answer.content = fileInfos;
const newAnswer = await answerDb.createAnswer(answer);
console.log("new answer:", newAnswer);
res.json(newAnswer);
});
export const getAnswers = asyncWrap(async (reqExp, res) => {
const req = reqExp as TypedRequest;
const { surveyId } = req.params;
......
......@@ -12,7 +12,7 @@ export const getAnswers = async (surveyId: string) => {
{
$group: {
_id: "$questionId",
answers: { $push: "$answer" },
answers: { $push: "$content" },
},
},
{
......
import type { File } from "formidable";
export { asyncWrap } from "./asyncWrap";
export const isEmpty = (obj: any) => {
......@@ -7,3 +9,13 @@ export const isEmpty = (obj: any) => {
Object.getPrototypeOf(obj) === Object.prototype
);
};
export const formidableFilesToArray = (files: File | File[]): File[] | null => {
if (Array.isArray(files)) {
return files;
}
if (isEmpty(files)) {
return null;
}
return [files];
};
......@@ -5,7 +5,7 @@ export interface IAnswer {
surveyId?: Types.ObjectId;
questionId?: Types.ObjectId;
guestId?: string;
answer?: any;
content?: any;
}
const schema = new Schema<IAnswer>(
......@@ -13,7 +13,7 @@ const schema = new Schema<IAnswer>(
surveyId: { type: Schema.Types.ObjectId, ref: "Survey" },
questionId: { type: Schema.Types.ObjectId, ref: "Question" },
guestId: { type: String },
answer: { type: Object },
content: { type: Object },
},
{ timestamps: true }
);
......
......@@ -3,7 +3,12 @@ import { answerCtrl, authCtrl, fileCtrl, surveyCtrl } from "../controllers";
const router = express.Router();
router.route("/").post(fileCtrl.uploadFile, answerCtrl.createAnswers);
router.route("/").post(answerCtrl.createAnswersWithoutFile);
router
.route("/upload")
.post(fileCtrl.uploadFile, answerCtrl.createAnswerWithFile);
router
.route("/:surveyId")
.get(authCtrl.requireLogin, authCtrl.authenticate, answerCtrl.getAnswers);
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment