Commit 10f7f53d authored by Jiwon Yoon's avatar Jiwon Yoon
Browse files

result aggregate, answer props

parent 77262376
...@@ -13,6 +13,7 @@ export const save = async (answers: IAnswerRequestData[]) => { ...@@ -13,6 +13,7 @@ export const save = async (answers: IAnswerRequestData[]) => {
}; };
export const saveForm = async (answerForm: FormData) => { export const saveForm = async (answerForm: FormData) => {
console.log("formdata", answerForm);
const { data } = await axios.post(`${baseUrl}/answers/upload`, answerForm); const { data } = await axios.post(`${baseUrl}/answers/upload`, answerForm);
return data; return data;
}; };
......
...@@ -3,10 +3,11 @@ import { IQuestionData } from "../types"; ...@@ -3,10 +3,11 @@ import { IQuestionData } from "../types";
type Props = { type Props = {
question: IQuestionData; question: IQuestionData;
answers: any;
}; };
export const RCheckbox = ({ question }: Props) => { export const RCheckbox = ({ question, answers }: Props) => {
const result = question.answers.flat().reduce((acc: any, cur: any) => { const result = answers.flat().reduce((acc: any, cur: any) => {
acc[cur] = (acc[cur] || 0) + 1; acc[cur] = (acc[cur] || 0) + 1;
return acc; return acc;
}, {}); }, {});
......
...@@ -3,12 +3,13 @@ import { IQuestionData } from "../types"; ...@@ -3,12 +3,13 @@ import { IQuestionData } from "../types";
type Props = { type Props = {
question: IQuestionData; question: IQuestionData;
answers: any;
}; };
export const RDate = ({ question }: Props) => { export const RDate = ({ question, answers }: Props) => {
return ( return (
<div className="m-5"> <div className="m-5">
{question.answers.map((answer: any) => ( {answers.map((answer: any) => (
<div key={answer} className="font-bold"> <div key={answer} className="font-bold">
{answer} {answer}
</div> </div>
......
...@@ -3,10 +3,11 @@ import { IQuestionData } from "../types"; ...@@ -3,10 +3,11 @@ import { IQuestionData } from "../types";
type Props = { type Props = {
question: IQuestionData; question: IQuestionData;
answers: any;
}; };
export const RDropdown = ({ question }: Props) => { export const RDropdown = ({ question, answers }: Props) => {
const result = question.answers.reduce((acc: any, cur: any) => { const result = answers.reduce((acc: any, cur: any) => {
acc[cur] = (acc[cur] || 0) + 1; acc[cur] = (acc[cur] || 0) + 1;
return acc; return acc;
}, {}); }, {});
......
...@@ -3,12 +3,13 @@ import { IQuestionData } from "../types"; ...@@ -3,12 +3,13 @@ import { IQuestionData } from "../types";
type Props = { type Props = {
question: IQuestionData; question: IQuestionData;
answers: any;
}; };
export const REssay = ({ question }: Props) => { export const REssay = ({ question, answers }: Props) => {
return ( return (
<div className="m-5"> <div className="m-5">
{question.answers.map((answer: any, index: number) => ( {answers.map((answer: any, index: number) => (
<div key={index} className="font-bold"> <div key={index} className="font-bold">
{answer} {answer}
</div> </div>
......
...@@ -3,13 +3,14 @@ import { baseImageUrl } from "../apis"; ...@@ -3,13 +3,14 @@ import { baseImageUrl } from "../apis";
type Props = { type Props = {
question: any; question: any;
answers: any;
}; };
export const RFile = ({ question }: Props) => { export const RFile = ({ question, answers }: Props) => {
console.log("question", question); console.log("question", question);
return ( return (
<div className="m-5 flex justify-start items-center"> <div className="m-5 flex justify-start items-center">
{question.answers.map((answer: any, index: number) => ( {answers.map((answer: any, index: number) => (
<Fragment key={index}> <Fragment key={index}>
<img <img
className="h-14" className="h-14"
......
...@@ -3,10 +3,11 @@ import { IQuestionData } from "../types"; ...@@ -3,10 +3,11 @@ import { IQuestionData } from "../types";
type Props = { type Props = {
question: IQuestionData; question: IQuestionData;
answers: any;
}; };
export const RRadio = ({ question }: Props) => { export const RRadio = ({ question, answers }: Props) => {
const result = question.answers.reduce((acc: any, cur: any) => { const result = answers.reduce((acc: any, cur: any) => {
acc[cur] = (acc[cur] || 0) + 1; acc[cur] = (acc[cur] || 0) + 1;
return acc; return acc;
}, {}); }, {});
......
...@@ -3,10 +3,11 @@ import { IQuestionData } from "../types"; ...@@ -3,10 +3,11 @@ import { IQuestionData } from "../types";
type Props = { type Props = {
question: IQuestionData; question: IQuestionData;
answers: any;
}; };
export const RRating = ({ question }: Props) => { export const RRating = ({ question, answers }: Props) => {
const result = question.answers.reduce((acc: any, cur: any) => { const result = answers.reduce((acc: any, cur: any) => {
acc[cur] = (acc[cur] || 0) + 1; acc[cur] = (acc[cur] || 0) + 1;
return acc; return acc;
}, {}); }, {});
......
...@@ -109,22 +109,25 @@ export const getAnswerElementByType = ( ...@@ -109,22 +109,25 @@ export const getAnswerElementByType = (
} }
}; };
export const getResultElementByType = (question: IQuestionData) => { export const getResultElementByType = (
question: IQuestionData,
answers: any
) => {
switch (question.type) { switch (question.type) {
case "singletext": case "singletext":
return <REssay question={question} />; return <REssay question={question} answers={answers} />;
case "radio": case "radio":
return <RRadio question={question} />; return <RRadio question={question} answers={answers} />;
case "checkbox": case "checkbox":
return <RCheckbox question={question} />; return <RCheckbox question={question} answers={answers} />;
case "dropdown": case "dropdown":
return <RDropdown question={question} />; return <RDropdown question={question} answers={answers} />;
case "file": case "file":
return <RFile question={question} />; return <RFile question={question} answers={answers} />;
case "rating": case "rating":
return <RRating question={question} />; return <RRating question={question} answers={answers} />;
case "date": case "date":
return <RDate question={question} />; return <RDate question={question} answers={answers} />;
default: default:
return <></>; return <></>;
} }
......
...@@ -4,9 +4,10 @@ import { getResultElementByType } from "../helpers/question.helper"; ...@@ -4,9 +4,10 @@ import { getResultElementByType } from "../helpers/question.helper";
type AccordionProps = { type AccordionProps = {
question: IQuestionData; question: IQuestionData;
answers: any;
}; };
export const Accordion = ({ question }: AccordionProps) => { export const Accordion = ({ question, answers }: AccordionProps) => {
const [isOpened, setOpened] = useState<boolean>(false); const [isOpened, setOpened] = useState<boolean>(false);
const [height, setHeight] = useState<string>("0px"); const [height, setHeight] = useState<string>("0px");
const contentElement = useRef<HTMLDivElement>(null); const contentElement = useRef<HTMLDivElement>(null);
...@@ -32,7 +33,7 @@ export const Accordion = ({ question }: AccordionProps) => { ...@@ -32,7 +33,7 @@ export const Accordion = ({ question }: AccordionProps) => {
style={{ height: height }} style={{ height: height }}
className="bg-gray-100 overflow-hidden transition-all duration-300" className="bg-gray-100 overflow-hidden transition-all duration-300"
> >
{question.answers && getResultElementByType(question)} {answers && getResultElementByType(question, answers)}
</div> </div>
</div> </div>
</div> </div>
......
...@@ -20,7 +20,9 @@ export const AnswerSurvey = () => { ...@@ -20,7 +20,9 @@ export const AnswerSurvey = () => {
const handleSubmit = async (e: FormEvent) => { const handleSubmit = async (e: FormEvent) => {
e.preventDefault(); e.preventDefault();
console.log("answers:", answers); console.log("answers:", answers);
const needAnswer = answers.some((answer) => !answer.requiredCheck); const needAnswer = answers.some(
(answer) => answer.question.isRequired && !answer.requiredCheck
);
if (needAnswer) { if (needAnswer) {
alert("필수질문에 응답하셔야 합니다."); alert("필수질문에 응답하셔야 합니다.");
return; return;
...@@ -46,12 +48,14 @@ export const AnswerSurvey = () => { ...@@ -46,12 +48,14 @@ export const AnswerSurvey = () => {
formData.append("guestId", "guest"); formData.append("guestId", "guest");
const files: FileList = answer.content; const files: FileList = answer.content;
files &&
[...files].map((f) => { [...files].map((f) => {
console.log("파일 없음", f);
formData.append("uploadFiles", f); formData.append("uploadFiles", f);
}); });
return formData; return formData;
}); });
console.log("forms", forms);
setError(""); setError("");
const results = await answerApi.save( const results = await answerApi.save(
otherAnswers.map((answer) => ({ otherAnswers.map((answer) => ({
......
...@@ -25,9 +25,9 @@ export const ResultSurvey = () => { ...@@ -25,9 +25,9 @@ export const ResultSurvey = () => {
async function getAnswers() { async function getAnswers() {
try { try {
if (surveyId) { if (surveyId) {
const survey = await answerApi.getAnswers(surveyId); const result = await answerApi.getAnswers(surveyId);
// console.log(survey); console.log(result);
setSurvey(survey); setSurvey(result);
} else { } else {
setLoading(true); setLoading(true);
} }
...@@ -51,7 +51,11 @@ export const ResultSurvey = () => { ...@@ -51,7 +51,11 @@ export const ResultSurvey = () => {
<div className="container w-11/12 place-self-center"> <div className="container w-11/12 place-self-center">
{survey.questions.map((question) => ( {survey.questions.map((question) => (
<Accordion key={question._id} question={question} /> <Accordion
key={question._id}
question={question.questionInfo}
answers={question.answers}
/>
))} ))}
</div> </div>
</div> </div>
......
...@@ -22,8 +22,8 @@ export const SurveyCard = ({ survey, handleDelete }: Props) => { ...@@ -22,8 +22,8 @@ export const SurveyCard = ({ survey, handleDelete }: Props) => {
return ( return (
<div className="w-40 h-48 md:w-52 md:h-60 rounded border-2 hover:border-2 hover:border-themeColor"> <div className="w-40 h-48 md:w-52 md:h-60 rounded border-2 hover:border-2 hover:border-themeColor">
<Link to={`${survey._id}`} state={survey} className="w-full pt-1"> <Link to={`${survey._id}`} state={survey} className="w-full">
<p className="font-bold"> <p className="font-bold text-center mt-1.5">
{survey.title ? survey.title : "제목없는 설문조사"} {survey.title ? survey.title : "제목없는 설문조사"}
</p> </p>
...@@ -32,7 +32,7 @@ export const SurveyCard = ({ survey, handleDelete }: Props) => { ...@@ -32,7 +32,7 @@ export const SurveyCard = ({ survey, handleDelete }: Props) => {
{survey.comment ? survey.comment : "설명없는 설문조사"} {survey.comment ? survey.comment : "설명없는 설문조사"}
</p> </p>
</div> </div>
<p className="text-gray-500 text-sm"> <p className="text-gray-500 text-sm text-center">
{survey.updatedAt?.substring(0, 10)} {survey.updatedAt?.substring(0, 10)}
</p> </p>
</Link> </Link>
......
...@@ -90,6 +90,7 @@ export const createAnswerWithFile = asyncWrap(async (reqExp, res) => { ...@@ -90,6 +90,7 @@ export const createAnswerWithFile = asyncWrap(async (reqExp, res) => {
let fileInfos; let fileInfos;
const files = formidableFilesToArray(req.files.uploadFiles); const files = formidableFilesToArray(req.files.uploadFiles);
console.log("files=", files);
if (files) { if (files) {
fileInfos = await Promise.all( fileInfos = await Promise.all(
files.map(async (file) => await fileDb.createFile(file)) files.map(async (file) => await fileDb.createFile(file))
...@@ -103,30 +104,41 @@ export const createAnswerWithFile = asyncWrap(async (reqExp, res) => { ...@@ -103,30 +104,41 @@ export const createAnswerWithFile = asyncWrap(async (reqExp, res) => {
res.json(newAnswer); res.json(newAnswer);
}); });
// export const getAnswers = asyncWrap(async (reqExp, res) => {
// const req = reqExp as TypedRequest;
// const { surveyId } = req.params;
// try {
// const survey = await surveyDb.getSurveyById(surveyId);
// const answers = await answerDb.getAnswers(surveyId);
// console.log(answers);
// const jsonSurvey = survey?.toJSON();
// if (jsonSurvey && answers) {
// const a = answers.map(async (a) => {
// const targetObj = jsonSurvey.questions.find(
// (q: any) => String(q._id) === String(a._id)
// ) as any;
// if (targetObj) {
// if (a.file.length) {
// targetObj.answers = a.file;
// } else {
// targetObj.answers = a.answers;
// }
// }
// });
// await Promise.all(a);
// }
// return res.json(jsonSurvey);
// } catch (error: any) {
// res.status(422).send(error.message || "설문조사 결과 불러오기 오류");
// }
// });
export const getAnswers = asyncWrap(async (reqExp, res) => { export const getAnswers = asyncWrap(async (reqExp, res) => {
const req = reqExp as TypedRequest; const req = reqExp as TypedRequest;
const { surveyId } = req.params; const { surveyId } = req.params;
try { try {
const survey = await surveyDb.getSurveyById(surveyId); const result = await answerDb.getAnswers(surveyId);
const answers = await answerDb.getAnswers(surveyId); console.log("result===", result);
console.log(answers); return res.json(result);
const jsonSurvey = survey?.toJSON();
if (jsonSurvey && answers) {
const a = answers.map(async (a) => {
const targetObj = jsonSurvey.questions.find(
(q: any) => String(q._id) === String(a._id)
) as any;
if (targetObj) {
if (a.file.length) {
targetObj.answers = a.file;
} else {
targetObj.answers = a.answers;
}
}
});
await Promise.all(a);
}
return res.json(jsonSurvey);
} catch (error: any) { } catch (error: any) {
res.status(422).send(error.message || "설문조사 결과 불러오기 오류"); res.status(422).send(error.message || "설문조사 결과 불러오기 오류");
} }
......
...@@ -7,22 +7,62 @@ export const createAnswer = async (answer: IAnswer) => { ...@@ -7,22 +7,62 @@ export const createAnswer = async (answer: IAnswer) => {
}; };
export const getAnswers = async (surveyId: string) => { export const getAnswers = async (surveyId: string) => {
const answers = await Answer.aggregate([ const result = await Answer.aggregate([
// surveyId에 해당하는 답변들 find
{ $match: { surveyId: new Types.ObjectId(surveyId) } }, { $match: { surveyId: new Types.ObjectId(surveyId) } },
// 같은 question에 대한 답변들을 answers[]에 push
// {surveyId,questionId,guestId,content} => {_id:questionId, surveyId, answers:[content,content]}
{ {
$group: { $group: {
_id: "$questionId", _id: "$questionId",
surveyId: { $first: "$surveyId" },
answers: { $push: "$content" }, answers: { $push: "$content" },
}, },
}, },
// question DB popluate
{
$lookup: {
from: "questions",
localField: "_id",
foreignField: "_id",
as: "questionInfo",
},
},
{
$unwind: "$questionInfo",
},
// 질문 순서대로 정렬
{ $sort: { "questionInfo.order": 1 } },
// surveyId로 묶고 questions 내에 { questionInfo, answers }[]
{
$group: {
_id: "$surveyId",
questions: {
$push: { questionInfo: "$questionInfo", answers: "$answers" },
},
},
},
// survey DB populate
{ {
$lookup: { $lookup: {
from: "fileinfos", from: "surveys",
localField: "answers", localField: "_id",
foreignField: "_id", foreignField: "_id",
as: "file", as: "survey",
}, },
}, },
{
$unwind: "$survey",
},
//밖에 있던 questions를 survey 내부로 이동시키고 survey를 가장 root로 변경
{ $set: { "survey.questions": "$questions" } },
{ $replaceRoot: { newRoot: "$survey" } },
]); ]);
return answers; return result[0];
}; };
...@@ -4,8 +4,8 @@ export { asyncWrap } from "./asyncWrap"; ...@@ -4,8 +4,8 @@ export { asyncWrap } from "./asyncWrap";
export const isEmpty = (obj: any) => { export const isEmpty = (obj: any) => {
return ( return (
obj && // 👈 null and undefined check !obj || // 👈 null and undefined check
Object.keys(obj).length === 0 && Object.keys(obj).length === 0 ||
Object.getPrototypeOf(obj) === Object.prototype Object.getPrototypeOf(obj) === Object.prototype
); );
}; };
......
...@@ -3,6 +3,7 @@ import { model, ObjectId, Schema, Types } from "mongoose"; ...@@ -3,6 +3,7 @@ import { model, ObjectId, Schema, Types } from "mongoose";
export interface IQuestion { export interface IQuestion {
_id?: Types.ObjectId; _id?: Types.ObjectId;
user?: Types.ObjectId; user?: Types.ObjectId;
order: number;
type: string; type: string;
title?: string; title?: string;
isRequired: boolean; isRequired: boolean;
...@@ -13,7 +14,8 @@ export interface IQuestion { ...@@ -13,7 +14,8 @@ export interface IQuestion {
const schema = new Schema<IQuestion>( const schema = new Schema<IQuestion>(
{ {
user: { type: Schema.Types.ObjectId, ref: "User" }, user: { type: Schema.Types.ObjectId, ref: "User" },
type: { type: String }, order: { type: Number },
type: { type: String, required: true },
title: { type: String }, title: { type: String },
isRequired: { type: Boolean }, isRequired: { type: Boolean },
comment: { type: String }, comment: { type: String },
......
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