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
e7ae6d3f
Commit
e7ae6d3f
authored
Aug 15, 2022
by
Yoon, Daeki
😅
Browse files
verion 0.1 시작
parent
1ea0c555
Changes
50
Show whitespace changes
Inline
Side-by-side
frontend/src/surveys/Preview.tsx
0 → 100644
View file @
e7ae6d3f
import
React
from
"
react
"
;
export
const
Preview
=
()
=>
{
return
<
div
>
Preview
</
div
>;
};
frontend/src/surveys/Profile.tsx
0 → 100644
View file @
e7ae6d3f
import
React
,
{
useEffect
,
useState
}
from
"
react
"
;
import
{
Link
,
useNavigate
}
from
"
react-router-dom
"
;
import
{
surveyApi
}
from
"
../apis
"
;
import
{
catchErrors
}
from
"
../helpers
"
;
import
{
ISurvey
}
from
"
../types
"
;
import
{
SurveyCard
}
from
"
./SurveyCard
"
;
export
const
Profile
=
()
=>
{
const
[
error
,
setError
]
=
useState
(
""
);
const
[
loading
,
setLoading
]
=
useState
(
false
);
const
[
surveys
,
setSurveys
]
=
useState
<
ISurvey
[]
>
([]);
useEffect
(()
=>
{
getSurveys
();
},
[]);
async
function
getSurveys
()
{
const
surveys
:
ISurvey
[]
=
await
surveyApi
.
getSurveys
();
// console.log(surveys);
setSurveys
(
surveys
);
}
async
function
deleteSurvey
(
id
:
string
)
{
if
(
window
.
confirm
(
"
해당 설문조사를 삭제하시겠습니까?
"
))
{
try
{
setLoading
(
true
);
const
result
=
await
surveyApi
.
deleteSurvey
(
id
);
console
.
log
(
"
deleted survey
"
,
result
);
setError
(
""
);
const
newItems
=
surveys
.
filter
((
survey
)
=>
survey
.
_id
!==
result
.
_id
);
// console.log("items left:", newItems);
setSurveys
(
newItems
);
alert
(
"
삭제되었습니다.
"
);
}
catch
(
error
)
{
console
.
log
(
"
에러발생
"
);
catchErrors
(
error
,
setError
);
}
finally
{
setLoading
(
false
);
}
}
}
return
(
<
div
className
=
"flex flex-col items-center"
>
<
div
className
=
"mt-10 text-xl font-bold"
>
나의 설문조사
</
div
>
<
div
className
=
"grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4 mt-6"
>
<
Link
to
=
{
"
/surveys/create
"
}
className
=
"flex w-40 h-48 md:h-60 md:w-52 items-center font-bold bg-gray-200 hover:bg-themeColor rounded-lg "
>
<
div
className
=
"text-center md:px-6 md:py-6 font-xs md:font-bold text-gray-500 place-items-center hover:text-white"
>
CREATE NEW SURVEY!
</
div
>
</
Link
>
{
surveys
.
map
((
survey
)
=>
{
return
(
<
SurveyCard
survey
=
{
survey
}
key
=
{
survey
.
_id
}
handleDelete
=
{
deleteSurvey
}
/>
);
})
}
</
div
>
</
div
>
);
};
frontend/src/surveys/Question.tsx
0 → 100644
View file @
e7ae6d3f
import
React
,
{
useState
}
from
"
react
"
;
import
{
getEnumKeyByEnumValue
,
QUESTION_TYPES
}
from
"
../commons
"
;
import
{
getElementByQuestionType
}
from
"
../helpers
"
;
import
{
IQuestionProps
}
from
"
../types
"
;
const
options
=
Object
.
entries
(
QUESTION_TYPES
).
map
(([
type
,
value
])
=>
(
<
option
key
=
{
type
}
value
=
{
value
}
>
{
value
}
</
option
>
));
export
const
Question
=
({
element
,
handleQuestion
,
deleteQuestion
,
}:
IQuestionProps
)
=>
{
const
[
question
,
setQuestion
]
=
useState
(
element
);
const
isEditing
=
question
.
isEditing
;
async
function
handleEditComplete
()
{
question
.
isEditing
=
false
;
console
.
log
(
"
editing completed:
"
,
question
);
handleQuestion
(
question
);
}
function
handleSelect
(
event
:
React
.
ChangeEvent
<
HTMLSelectElement
>
)
{
const
selectedType
=
event
.
currentTarget
.
value
;
console
.
log
(
selectedType
);
const
selectedKind
=
getEnumKeyByEnumValue
(
QUESTION_TYPES
,
selectedType
)
??
"
singletext
"
;
console
.
log
(
"
selected kind:
"
,
selectedKind
);
setQuestion
({
...
question
,
type
:
selectedKind
});
}
const
handleElement
=
()
=>
{
console
.
log
(
"
handle element
"
);
setQuestion
({
...
question
});
};
function
handleChange
(
event
:
React
.
ChangeEvent
<
HTMLInputElement
>
)
{
const
{
checked
,
name
,
value
}
=
event
.
currentTarget
;
if
(
name
===
"
isRequired
"
)
{
return
setQuestion
({
...
question
,
[
name
]:
checked
});
}
setQuestion
({
...
question
,
[
name
]:
value
});
}
const
onCancel
=
()
=>
{
const
originalQuestion
=
{
...
element
,
isEditing
:
false
};
setQuestion
(
originalQuestion
);
handleQuestion
(
originalQuestion
);
};
const
onDelete
=
()
=>
{
if
(
window
.
confirm
(
"
질문을 삭제하시겠습니까?
"
))
{
deleteQuestion
(
question
.
_id
);
}
};
const
onEdit
=
()
=>
{
setQuestion
({
...
question
,
isEditing
:
true
});
handleQuestion
({
...
question
,
isEditing
:
true
});
};
return
(
<
div
style
=
{
{
borderColor
:
isEditing
?
"
red
"
:
"
#0A8A8A
"
}
}
className
=
"flex flex-col container w-4/5 h-auto border-2 items-center m-3 py-2 rounded-lg"
>
<
div
className
=
"flex h-16 w-full place-content-center items-center"
>
<
input
type
=
"text"
name
=
"title"
id
=
{
question
.
_id
}
className
=
"text-xl font-bold border-b-2 w-11/12"
placeholder
=
{
"
Question Title
"
}
value
=
{
question
.
title
}
onChange
=
{
handleChange
}
disabled
=
{
!
isEditing
}
></
input
>
</
div
>
<
div
className
=
"flex w-full justify-center"
>
<
input
type
=
"text"
name
=
"comment"
id
=
{
question
.
_id
}
className
=
"border w-11/12"
placeholder
=
"질문에 대한 설명을 입력해주세요"
value
=
{
question
.
comment
}
onChange
=
{
handleChange
}
disabled
=
{
!
isEditing
}
></
input
>
</
div
>
{
getElementByQuestionType
(
question
,
handleElement
,
isEditing
)
}
<
div
className
=
"flex flex-row place-content-between w-11/12 py-2"
>
<
select
id
=
{
question
.
_id
}
name
=
"type"
onChange
=
{
handleSelect
}
disabled
=
{
!
isEditing
}
value
=
{
QUESTION_TYPES
[
question
.
type
]
}
className
=
"w-32 h-10 md:w-36 bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-themeColor"
>
{
options
}
</
select
>
<
div
className
=
"place-self-end py-2"
>
<
input
type
=
"checkbox"
id
=
"isRequired"
name
=
"isRequired"
onChange
=
{
handleChange
}
disabled
=
{
!
isEditing
}
checked
=
{
question
.
isRequired
}
/>
<
label
htmlFor
=
"isRequired"
className
=
"px-1"
>
필수
</
label
>
{
isEditing
?
(
<>
<
button
type
=
"button"
className
=
"px-1"
onClick
=
{
onCancel
}
>
취소
</
button
>
<
button
type
=
"button"
className
=
"px-1"
onClick
=
{
handleEditComplete
}
>
확인
</
button
>
</>
)
:
(
<>
<
button
type
=
"button"
className
=
"px-1"
onClick
=
{
onDelete
}
>
삭제
</
button
>
<
button
type
=
"button"
className
=
"px-1"
onClick
=
{
onEdit
}
>
수정
</
button
>
</>
)
}
</
div
>
</
div
>
</
div
>
);
};
frontend/src/surveys/QuestionsList.tsx
0 → 100644
View file @
e7ae6d3f
import
React
from
"
react
"
;
import
{
CreateQuestionData
}
from
"
../types
"
;
import
{
Question
}
from
"
./Question
"
;
type
Props
=
{
questions
:
CreateQuestionData
[];
handleQuestion
:
Function
;
deleteQuestion
:
Function
;
};
export
const
QuestionsList
=
({
questions
,
handleQuestion
,
deleteQuestion
,
}:
Props
)
=>
{
return
(
<>
{
questions
.
map
((
question
)
=>
(
<
Question
key
=
{
question
.
_id
}
element
=
{
question
}
handleQuestion
=
{
handleQuestion
}
deleteQuestion
=
{
deleteQuestion
}
/>
))
}
</>
);
};
frontend/src/surveys/SurveyCard.tsx
0 → 100644
View file @
e7ae6d3f
import
React
from
"
react
"
;
import
{
Link
}
from
"
react-router-dom
"
;
import
{
ISurvey
}
from
"
../types
"
;
import
{
DuplicateIcon
}
from
"
../icons
"
;
type
Props
=
{
survey
:
ISurvey
;
handleDelete
:
(
id
:
string
)
=>
Promise
<
void
>
;
};
export
const
SurveyCard
=
({
survey
,
handleDelete
}:
Props
)
=>
{
const
copyLink
=
async
()
=>
{
await
navigator
.
clipboard
.
writeText
(
`http://localhost:8080/answers/
${
survey
.
_id
}
`
);
alert
(
"
설문조사의 링크가 클립보드에 저장되었습니다.
"
);
};
const
onDelete
=
async
()
=>
{
survey
.
_id
&&
(
await
handleDelete
(
survey
.
_id
));
};
return
(
<
div
className
=
"w-40 h-48 md:w-52 md:h-60 rounded border-2 hover:border-2 hover:border-themeColor"
>
<
Link
to
=
{
`/surveys/
${
survey
.
_id
}
/edit`
}
state
=
{
survey
}
className
=
"w-full pt-1"
>
<
p
className
=
"font-bold"
>
{
survey
.
title
?
survey
.
title
:
"
제목없는 설문조사
"
}
</
p
>
<
div
className
=
"h-24 md:h-36 p-3 overflow-y-hidden hover:overflow-y-auto"
>
<
p
className
=
"text-gray-700 text-justify"
>
{
survey
.
comment
?
survey
.
comment
:
"
설명없는 설문조사
"
}
</
p
>
</
div
>
<
p
className
=
"text-gray-500 text-sm"
>
{
survey
.
updatedAt
?.
substring
(
0
,
10
)
}
</
p
>
</
Link
>
<
div
className
=
"flex justify-end pt-1 pr-1"
>
<
button
className
=
"flex place-self-center"
onClick
=
{
copyLink
}
>
링크복사
<
DuplicateIcon
className
=
"w-7 h-7"
/>
</
button
>
<
button
type
=
"button"
className
=
"bg-themeColor rounded text-white py-1 px-1.5 ml-1 mr-1.5"
onClick
=
{
onDelete
}
>
삭제
</
button
>
</
div
>
</
div
>
);
};
frontend/src/surveys/index.tsx
0 → 100644
View file @
e7ae6d3f
export
{
AnswerSurvey
}
from
"
./AnswerSurvey
"
;
export
{
CreateSurvey
}
from
"
./CreateSurvey
"
;
export
{
EditSurvey
}
from
"
./EditSurvey
"
;
export
{
Preview
}
from
"
./Preview
"
;
export
{
Profile
}
from
"
./Profile
"
;
frontend/src/types/index.ts
View file @
e7ae6d3f
...
...
@@ -10,44 +10,51 @@ export interface SignupUser {
password
:
string
;
}
export
interface
Survey
Type
{
export
interface
I
Survey
{
_id
:
string
;
user
:
any
;
title
:
string
;
comment
:
string
;
questions
:
Basic
Question
Type
[];
questions
:
I
Question
Data
[];
createdAt
?:
string
;
updatedAt
?:
string
;
}
export
interface
BasicQuestionType
{
interface
IChoice
{
value
:
number
;
text
:
string
;
}
interface
IBasicContent
{
choices
:
IChoice
[];
[
key
:
string
]:
any
;
}
export
interface
IQuestionData
{
_id
?:
string
;
order
:
number
;
type
:
string
;
_id
:
string
;
title
:
string
;
isRequired
:
boolean
;
comment
:
string
;
content
:
any
;
answers
?:
any
;
content
:
IBasicContent
;
//
answers?: any;
[
key
:
string
]:
string
|
number
|
boolean
|
any
;
}
export
interface
AnswerQuestionType
extends
Basic
Question
Type
{
export
interface
AnswerQuestionType
extends
I
Question
Data
{
requiredCheck
:
boolean
;
answer
:
any
;
}
export
interface
AnswerSurveyType
extends
Survey
Type
{
export
interface
AnswerSurveyType
extends
I
Survey
{
questions
:
AnswerQuestionType
[];
}
export
interface
EssayType
extends
BasicQuestionType
{}
export
interface
DateType
extends
BasicQuestionType
{}
export
interface
RadioType
extends
BasicQuestionType
{
content
:
{
choices
:
{
value
:
number
;
text
:
string
;
}[];
export
interface
IEssay
extends
IQuestionData
{}
export
interface
IDate
extends
IQuestionData
{}
export
interface
IRadio
extends
IQuestionData
{
content
:
IBasicContent
&
{
hasOther
:
boolean
;
otherText
:
string
;
};
...
...
@@ -58,33 +65,23 @@ interface IChoices {
text
:
string
;
}
export
interface
CheckboxType
extends
BasicQuestionType
{
content
:
{
choices
:
IChoices
[];
maxCount
:
number
;
};
export
interface
ICheckbox
extends
IQuestionData
{
content
:
IBasicContent
&
{
maxCount
:
number
};
}
export
interface
DropdownType
extends
BasicQuestionType
{
content
:
{
choices
:
IChoices
[];
hasNone
:
boolean
;
};
export
interface
IDropdown
extends
IQuestionData
{
content
:
IBasicContent
&
{
hasNone
:
boolean
};
}
export
interface
File
Type
extends
Basic
Question
Type
{
content
:
{
export
interface
I
File
extends
I
Question
Data
{
content
:
IBasicContent
&
{
filename
:
string
;
value
:
string
;
};
}
export
interface
RatingType
extends
BasicQuestionType
{
content
:
{
choices
:
{
value
:
number
;
text
:
string
;
}[];
export
interface
IRating
extends
IQuestionData
{
content
:
IBasicContent
&
{
minRateDescription
:
string
;
maxRateDescription
:
string
;
};
...
...
@@ -103,7 +100,7 @@ export interface AnswerType {
}
export
interface
AnswerProps
{
element
:
Basic
Question
Type
;
element
:
I
Question
Data
;
answerQuestion
:
AnswerQuestionType
;
// answers: AnswersType | undefined;
// handleAnswer: () => void;
...
...
src/controllers/survey.controller.ts
View file @
e7ae6d3f
import
{
NextFunction
,
Request
,
Response
}
from
"
express
"
;
import
{
Types
}
from
"
mongoose
"
;
import
{
surveyDb
}
from
"
../db
"
;
import
{
asyncWrap
}
from
"
../helpers/asyncWrap
"
;
import
{
ISurvey
}
from
"
../models
"
;
export
interface
TypedRequestAuth
<
T
>
extends
Request
{
auth
:
T
;
...
...
@@ -11,10 +13,9 @@ export const createSurvey = asyncWrap(
async
(
reqExp
:
Request
,
res
:
Response
)
=>
{
const
req
=
reqExp
as
TypedRequestAuth
<
{
userId
:
string
}
>
;
const
{
userId
}
=
req
.
auth
;
let
survey
=
req
.
body
;
survey
.
user
=
userId
;
let
survey
=
req
.
body
as
ISurvey
;
survey
.
user
=
new
Types
.
ObjectId
(
userId
)
;
console
.
log
(
"
survey body
"
,
survey
);
delete
survey
.
_id
;
const
newSurvey
=
await
surveyDb
.
createSurvey
(
survey
);
return
res
.
json
(
newSurvey
);
}
...
...
src/db/survey.db.ts
View file @
e7ae6d3f
import
{
Survey
,
ISurvey
}
from
"
../models
"
;
import
{
HydratedDocument
}
from
"
mongoose
"
;
import
{
Survey
,
ISurvey
,
Question
,
IQuestion
}
from
"
../models
"
;
export
const
findUserBySurveyId
=
async
(
surveyId
:
string
)
=>
{
const
survey
=
await
Survey
.
findById
(
surveyId
).
populate
(
"
user
"
);
...
...
@@ -10,8 +11,24 @@ export const findUserBySurveyId = async (surveyId: string) => {
return
null
;
};
export
const
createSurvey
=
async
(
survey
:
ISurvey
)
=>
{
const
newSurvey
=
await
Survey
.
create
(
survey
);
export
const
createSurvey
=
async
(
surveyData
:
ISurvey
)
=>
{
const
{
_id
,
questions
,
...
rest
}
=
surveyData
;
console
.
log
(
"
questions in survey db:
"
,
questions
,
"
rest:
"
,
rest
);
let
newQuestions
;
// questions 있으면 먼저 저장
if
(
questions
&&
questions
.
length
>
0
)
{
newQuestions
=
await
Promise
.
all
(
questions
.
map
(
async
(
question
)
=>
{
const
{
_id
,
...
questionsWithoutId
}
=
question
;
return
await
Question
.
create
(
questionsWithoutId
);
})
);
}
const
survey
=
new
Survey
({
...
rest
,
questions
:
newQuestions
,
});
const
newSurvey
=
await
(
await
survey
.
save
()).
populate
(
"
questions
"
);
return
newSurvey
;
};
...
...
@@ -22,11 +39,13 @@ export const getSurveyById = async (surveyId: string) => {
};
export
const
getSurveys
=
async
(
userId
:
string
)
=>
{
const
surveys
=
await
Survey
.
find
({
user
:
userId
}).
sort
({
updatedAt
:
-
1
});
const
surveys
=
await
Survey
.
find
({
user
:
userId
})
.
sort
({
updatedAt
:
-
1
})
.
populate
(
"
questions
"
);
return
surveys
;
};
export
const
updateSurvey
=
async
(
survey
:
ISurvey
)
=>
{
export
const
updateSurvey
=
async
(
survey
:
HydratedDocument
<
ISurvey
>
)
=>
{
const
newSurvey
=
await
Survey
.
findOneAndUpdate
({
_id
:
survey
.
_id
},
survey
);
return
newSurvey
;
};
...
...
src/routes/survey.route.ts
View file @
e7ae6d3f
...
...
@@ -3,18 +3,15 @@ import { authCtrl, surveyCtrl, questionCtrl } from "../controllers";
const
router
=
express
.
Router
();
router
.
route
(
"
/
"
).
get
(
authCtrl
.
requireLogin
,
surveyCtrl
.
getSurveys
);
router
.
route
(
"
/create
"
).
post
(
authCtrl
.
requireLogin
,
surveyCtrl
.
createSurvey
);
router
.
route
(
"
/:surveyId
"
).
get
(
surveyCtrl
.
getSurveyById
);
router
.
route
(
"
/
"
)
.
get
(
authCtrl
.
requireLogin
,
surveyCtrl
.
getSurveys
)
.
post
(
authCtrl
.
requireLogin
,
surveyCtrl
.
createSurvey
);
router
.
route
(
"
/:surveyId
/edit
"
)
.
route
(
"
/:surveyId
"
)
.
get
(
authCtrl
.
requireLogin
,
authCtrl
.
authenticate
,
surveyCtrl
.
getSurveyById
)
.
put
(
authCtrl
.
requireLogin
,
authCtrl
.
authenticate
,
surveyCtrl
.
updateSurvey
);
router
.
route
(
"
/:surveyId/delete
"
)
.
put
(
authCtrl
.
requireLogin
,
authCtrl
.
authenticate
,
surveyCtrl
.
updateSurvey
)
.
delete
(
authCtrl
.
requireLogin
,
authCtrl
.
authenticate
,
...
...
@@ -23,7 +20,11 @@ router
router
.
route
(
"
/:surveyId/questions
"
)
.
post
(
authCtrl
.
requireLogin
,
questionCtrl
.
createQuestion
);
.
post
(
authCtrl
.
requireLogin
,
authCtrl
.
authenticate
,
questionCtrl
.
createQuestion
);
router
.
param
(
"
surveyId
"
,
surveyCtrl
.
userBySurveyId
);
...
...
Prev
1
2
3
Next
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