Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
students
survey
Commits
10f7f53d
Commit
10f7f53d
authored
Aug 20, 2022
by
Jiwon Yoon
Browse files
result aggregate, answer props
parent
77262376
Changes
17
Show whitespace changes
Inline
Side-by-side
frontend/src/apis/answer.api.ts
View file @
10f7f53d
...
...
@@ -13,6 +13,7 @@ export const save = async (answers: IAnswerRequestData[]) => {
};
export
const
saveForm
=
async
(
answerForm
:
FormData
)
=>
{
console
.
log
(
"
formdata
"
,
answerForm
);
const
{
data
}
=
await
axios
.
post
(
`
${
baseUrl
}
/answers/upload`
,
answerForm
);
return
data
;
};
...
...
frontend/src/forms/RCheckbox.tsx
View file @
10f7f53d
...
...
@@ -3,10 +3,11 @@ import { IQuestionData } from "../types";
type
Props
=
{
question
:
IQuestionData
;
answers
:
any
;
};
export
const
RCheckbox
=
({
question
}:
Props
)
=>
{
const
result
=
question
.
answers
.
flat
().
reduce
((
acc
:
any
,
cur
:
any
)
=>
{
export
const
RCheckbox
=
({
question
,
answers
}:
Props
)
=>
{
const
result
=
answers
.
flat
().
reduce
((
acc
:
any
,
cur
:
any
)
=>
{
acc
[
cur
]
=
(
acc
[
cur
]
||
0
)
+
1
;
return
acc
;
},
{});
...
...
frontend/src/forms/RDate.tsx
View file @
10f7f53d
...
...
@@ -3,12 +3,13 @@ import { IQuestionData } from "../types";
type
Props
=
{
question
:
IQuestionData
;
answers
:
any
;
};
export
const
RDate
=
({
question
}:
Props
)
=>
{
export
const
RDate
=
({
question
,
answers
}:
Props
)
=>
{
return
(
<
div
className
=
"m-5"
>
{
question
.
answers
.
map
((
answer
:
any
)
=>
(
{
answers
.
map
((
answer
:
any
)
=>
(
<
div
key
=
{
answer
}
className
=
"font-bold"
>
{
answer
}
</
div
>
...
...
frontend/src/forms/RDropdown.tsx
View file @
10f7f53d
...
...
@@ -3,10 +3,11 @@ import { IQuestionData } from "../types";
type
Props
=
{
question
:
IQuestionData
;
answers
:
any
;
};
export
const
RDropdown
=
({
question
}:
Props
)
=>
{
const
result
=
question
.
answers
.
reduce
((
acc
:
any
,
cur
:
any
)
=>
{
export
const
RDropdown
=
({
question
,
answers
}:
Props
)
=>
{
const
result
=
answers
.
reduce
((
acc
:
any
,
cur
:
any
)
=>
{
acc
[
cur
]
=
(
acc
[
cur
]
||
0
)
+
1
;
return
acc
;
},
{});
...
...
frontend/src/forms/REssay.tsx
View file @
10f7f53d
...
...
@@ -3,12 +3,13 @@ import { IQuestionData } from "../types";
type
Props
=
{
question
:
IQuestionData
;
answers
:
any
;
};
export
const
REssay
=
({
question
}:
Props
)
=>
{
export
const
REssay
=
({
question
,
answers
}:
Props
)
=>
{
return
(
<
div
className
=
"m-5"
>
{
question
.
answers
.
map
((
answer
:
any
,
index
:
number
)
=>
(
{
answers
.
map
((
answer
:
any
,
index
:
number
)
=>
(
<
div
key
=
{
index
}
className
=
"font-bold"
>
{
answer
}
</
div
>
...
...
frontend/src/forms/RFile.tsx
View file @
10f7f53d
...
...
@@ -3,13 +3,14 @@ import { baseImageUrl } from "../apis";
type
Props
=
{
question
:
any
;
answers
:
any
;
};
export
const
RFile
=
({
question
}:
Props
)
=>
{
export
const
RFile
=
({
question
,
answers
}:
Props
)
=>
{
console
.
log
(
"
question
"
,
question
);
return
(
<
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
}
>
<
img
className
=
"h-14"
...
...
frontend/src/forms/RRadio.tsx
View file @
10f7f53d
...
...
@@ -3,10 +3,11 @@ import { IQuestionData } from "../types";
type
Props
=
{
question
:
IQuestionData
;
answers
:
any
;
};
export
const
RRadio
=
({
question
}:
Props
)
=>
{
const
result
=
question
.
answers
.
reduce
((
acc
:
any
,
cur
:
any
)
=>
{
export
const
RRadio
=
({
question
,
answers
}:
Props
)
=>
{
const
result
=
answers
.
reduce
((
acc
:
any
,
cur
:
any
)
=>
{
acc
[
cur
]
=
(
acc
[
cur
]
||
0
)
+
1
;
return
acc
;
},
{});
...
...
frontend/src/forms/RRating.tsx
View file @
10f7f53d
...
...
@@ -3,10 +3,11 @@ import { IQuestionData } from "../types";
type
Props
=
{
question
:
IQuestionData
;
answers
:
any
;
};
export
const
RRating
=
({
question
}:
Props
)
=>
{
const
result
=
question
.
answers
.
reduce
((
acc
:
any
,
cur
:
any
)
=>
{
export
const
RRating
=
({
question
,
answers
}:
Props
)
=>
{
const
result
=
answers
.
reduce
((
acc
:
any
,
cur
:
any
)
=>
{
acc
[
cur
]
=
(
acc
[
cur
]
||
0
)
+
1
;
return
acc
;
},
{});
...
...
frontend/src/helpers/question.helper.tsx
View file @
10f7f53d
...
...
@@ -109,22 +109,25 @@ export const getAnswerElementByType = (
}
};
export
const
getResultElementByType
=
(
question
:
IQuestionData
)
=>
{
export
const
getResultElementByType
=
(
question
:
IQuestionData
,
answers
:
any
)
=>
{
switch
(
question
.
type
)
{
case
"
singletext
"
:
return
<
REssay
question
=
{
question
}
/>;
return
<
REssay
question
=
{
question
}
answers
=
{
answers
}
/>;
case
"
radio
"
:
return
<
RRadio
question
=
{
question
}
/>;
return
<
RRadio
question
=
{
question
}
answers
=
{
answers
}
/>;
case
"
checkbox
"
:
return
<
RCheckbox
question
=
{
question
}
/>;
return
<
RCheckbox
question
=
{
question
}
answers
=
{
answers
}
/>;
case
"
dropdown
"
:
return
<
RDropdown
question
=
{
question
}
/>;
return
<
RDropdown
question
=
{
question
}
answers
=
{
answers
}
/>;
case
"
file
"
:
return
<
RFile
question
=
{
question
}
/>;
return
<
RFile
question
=
{
question
}
answers
=
{
answers
}
/>;
case
"
rating
"
:
return
<
RRating
question
=
{
question
}
/>;
return
<
RRating
question
=
{
question
}
answers
=
{
answers
}
/>;
case
"
date
"
:
return
<
RDate
question
=
{
question
}
/>;
return
<
RDate
question
=
{
question
}
answers
=
{
answers
}
/>;
default
:
return
<></>;
}
...
...
frontend/src/surveys/Accordion.tsx
View file @
10f7f53d
...
...
@@ -4,9 +4,10 @@ import { getResultElementByType } from "../helpers/question.helper";
type
AccordionProps
=
{
question
:
IQuestionData
;
answers
:
any
;
};
export
const
Accordion
=
({
question
}:
AccordionProps
)
=>
{
export
const
Accordion
=
({
question
,
answers
}:
AccordionProps
)
=>
{
const
[
isOpened
,
setOpened
]
=
useState
<
boolean
>
(
false
);
const
[
height
,
setHeight
]
=
useState
<
string
>
(
"
0px
"
);
const
contentElement
=
useRef
<
HTMLDivElement
>
(
null
);
...
...
@@ -32,7 +33,7 @@ export const Accordion = ({ question }: AccordionProps) => {
style
=
{
{
height
:
height
}
}
className
=
"bg-gray-100 overflow-hidden transition-all duration-300"
>
{
question
.
answers
&&
getResultElementByType
(
question
)
}
{
answers
&&
getResultElementByType
(
question
,
answers
)
}
</
div
>
</
div
>
</
div
>
...
...
frontend/src/surveys/AnswerSurvey.tsx
View file @
10f7f53d
...
...
@@ -20,7 +20,9 @@ export const AnswerSurvey = () => {
const
handleSubmit
=
async
(
e
:
FormEvent
)
=>
{
e
.
preventDefault
();
console
.
log
(
"
answers:
"
,
answers
);
const
needAnswer
=
answers
.
some
((
answer
)
=>
!
answer
.
requiredCheck
);
const
needAnswer
=
answers
.
some
(
(
answer
)
=>
answer
.
question
.
isRequired
&&
!
answer
.
requiredCheck
);
if
(
needAnswer
)
{
alert
(
"
필수질문에 응답하셔야 합니다.
"
);
return
;
...
...
@@ -46,12 +48,14 @@ export const AnswerSurvey = () => {
formData
.
append
(
"
guestId
"
,
"
guest
"
);
const
files
:
FileList
=
answer
.
content
;
files
&&
[...
files
].
map
((
f
)
=>
{
console
.
log
(
"
파일 없음
"
,
f
);
formData
.
append
(
"
uploadFiles
"
,
f
);
});
return
formData
;
});
console
.
log
(
"
forms
"
,
forms
);
setError
(
""
);
const
results
=
await
answerApi
.
save
(
otherAnswers
.
map
((
answer
)
=>
({
...
...
frontend/src/surveys/ResultSurvey.tsx
View file @
10f7f53d
...
...
@@ -25,9 +25,9 @@ export const ResultSurvey = () => {
async
function
getAnswers
()
{
try
{
if
(
surveyId
)
{
const
survey
=
await
answerApi
.
getAnswers
(
surveyId
);
//
console.log(
survey
);
setSurvey
(
survey
);
const
result
=
await
answerApi
.
getAnswers
(
surveyId
);
console
.
log
(
result
);
setSurvey
(
result
);
}
else
{
setLoading
(
true
);
}
...
...
@@ -51,7 +51,11 @@ export const ResultSurvey = () => {
<
div
className
=
"container w-11/12 place-self-center"
>
{
survey
.
questions
.
map
((
question
)
=>
(
<
Accordion
key
=
{
question
.
_id
}
question
=
{
question
}
/>
<
Accordion
key
=
{
question
.
_id
}
question
=
{
question
.
questionInfo
}
answers
=
{
question
.
answers
}
/>
))
}
</
div
>
</
div
>
...
...
frontend/src/surveys/SurveyCard.tsx
View file @
10f7f53d
...
...
@@ -22,8 +22,8 @@ export const SurveyCard = ({ survey, handleDelete }: Props) => {
return
(
<
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
"
>
<
p
className
=
"font-bold"
>
<
Link
to
=
{
`
${
survey
.
_id
}
`
}
state
=
{
survey
}
className
=
"w-full"
>
<
p
className
=
"font-bold
text-center mt-1.5
"
>
{
survey
.
title
?
survey
.
title
:
"
제목없는 설문조사
"
}
</
p
>
...
...
@@ -32,7 +32,7 @@ export const SurveyCard = ({ survey, handleDelete }: Props) => {
{
survey
.
comment
?
survey
.
comment
:
"
설명없는 설문조사
"
}
</
p
>
</
div
>
<
p
className
=
"text-gray-500 text-sm"
>
<
p
className
=
"text-gray-500 text-sm
text-center
"
>
{
survey
.
updatedAt
?.
substring
(
0
,
10
)
}
</
p
>
</
Link
>
...
...
src/controllers/answer.controller.ts
View file @
10f7f53d
...
...
@@ -90,6 +90,7 @@ export const createAnswerWithFile = asyncWrap(async (reqExp, res) => {
let
fileInfos
;
const
files
=
formidableFilesToArray
(
req
.
files
.
uploadFiles
);
console
.
log
(
"
files=
"
,
files
);
if
(
files
)
{
fileInfos
=
await
Promise
.
all
(
files
.
map
(
async
(
file
)
=>
await
fileDb
.
createFile
(
file
))
...
...
@@ -103,30 +104,41 @@ export const createAnswerWithFile = asyncWrap(async (reqExp, res) => {
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
)
=>
{
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
);
const
result
=
await
answerDb
.
getAnswers
(
surveyId
);
console
.
log
(
"
result===
"
,
result
);
return
res
.
json
(
result
);
}
catch
(
error
:
any
)
{
res
.
status
(
422
).
send
(
error
.
message
||
"
설문조사 결과 불러오기 오류
"
);
}
...
...
src/db/answer.db.ts
View file @
10f7f53d
...
...
@@ -7,22 +7,62 @@ export const createAnswer = async (answer: IAnswer) => {
};
export
const
getAnswers
=
async
(
surveyId
:
string
)
=>
{
const
answers
=
await
Answer
.
aggregate
([
const
result
=
await
Answer
.
aggregate
([
// surveyId에 해당하는 답변들 find
{
$match
:
{
surveyId
:
new
Types
.
ObjectId
(
surveyId
)
}
},
// 같은 question에 대한 답변들을 answers[]에 push
// {surveyId,questionId,guestId,content} => {_id:questionId, surveyId, answers:[content,content]}
{
$group
:
{
_id
:
"
$questionId
"
,
surveyId
:
{
$first
:
"
$surveyId
"
},
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
:
{
from
:
"
fileinfo
s
"
,
localField
:
"
answers
"
,
from
:
"
survey
s
"
,
localField
:
"
_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
]
;
};
src/helpers/index.ts
View file @
10f7f53d
...
...
@@ -4,8 +4,8 @@ export { asyncWrap } from "./asyncWrap";
export
const
isEmpty
=
(
obj
:
any
)
=>
{
return
(
obj
&&
// 👈 null and undefined check
Object
.
keys
(
obj
).
length
===
0
&&
!
obj
||
// 👈 null and undefined check
Object
.
keys
(
obj
).
length
===
0
||
Object
.
getPrototypeOf
(
obj
)
===
Object
.
prototype
);
};
...
...
src/models/question.model.ts
View file @
10f7f53d
...
...
@@ -3,6 +3,7 @@ import { model, ObjectId, Schema, Types } from "mongoose";
export
interface
IQuestion
{
_id
?:
Types
.
ObjectId
;
user
?:
Types
.
ObjectId
;
order
:
number
;
type
:
string
;
title
?:
string
;
isRequired
:
boolean
;
...
...
@@ -13,7 +14,8 @@ export interface IQuestion {
const
schema
=
new
Schema
<
IQuestion
>
(
{
user
:
{
type
:
Schema
.
Types
.
ObjectId
,
ref
:
"
User
"
},
type
:
{
type
:
String
},
order
:
{
type
:
Number
},
type
:
{
type
:
String
,
required
:
true
},
title
:
{
type
:
String
},
isRequired
:
{
type
:
Boolean
},
comment
:
{
type
:
String
},
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment