Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

SurveyView.vue 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. <template lang="pug">
  2. main.view--survey.f-col.start.w-full
  3. header.w-full.f-col
  4. p survey for profile: {{ current }}
  5. article.match.w-full
  6. ul.w-full
  7. template(v-for="(q, i) in profileQuestions" :key="q.response_key_prompt")
  8. li(v-if="step == i + 1")
  9. p {{q.response_key_category}}:
  10. span in db:
  11. span(v-if="q.response_key_id") true - id:{{q.response_key_id}} |
  12. span(v-else) false |
  13. span {{q.response_key_prompt}}?
  14. span {{q.response_key_description}}
  15. //- Select
  16. div(v-if="q.responses.length")
  17. button(
  18. v-for="(res, index) in q.responses"
  19. :key="index"
  20. @click="storeResponseLike(step, q.response_key_id, q.response_key_prompt, res); step++"
  21. ).p-0 {{res}}
  22. //- Fill in the blank
  23. div(v-else-if="q.response_key_category === 'profile'")
  24. input(@input="storeResponseLike(step, q.response_key_id, q.response_key_prompt, profile[q.response_key_prompt])" v-model="profile[q.response_key_prompt]" @keyup.enter="step++")
  25. label >{{ profile[q.response_key_prompt]}}
  26. //- Aspects
  27. div(v-else).f-col
  28. input(type="range" min="-3" max="3" list="ticks" @input="storeResponseLike(step, q.response_key_id, q.response_key_prompt, aspects[q.response_key_category])" v-model="aspects[q.response_key_category]").w-full
  29. label {{ aspectResponses[parseInt(aspects[q.response_key_category]) + 3] }}
  30. nav.f-row
  31. button(:disabled="step == 0" @click="step--") back
  32. p {{step}} of {{profile.length}}
  33. button(
  34. v-if="(q.response_key_category === 'profile')"
  35. @click="storeResponseLike(step, q.response_key_id, q.response_key_prompt, profile[q.response_key_prompt]); step++"
  36. ) next
  37. button(
  38. v-else
  39. @click="storeResponseLike(step, q.response_key_id, q.response_key_prompt, aspects[q.response_key_category]); step++"
  40. ) next
  41. //- Confirmation
  42. li(v-if="step == profileQuestions.length + 1")
  43. p Does this look correct?
  44. h4 {{ profile }}
  45. h4 {{aspects}}
  46. nav.f-row
  47. button(@click="step--") back
  48. p(@click="step = 1").p-1 start over
  49. button(@click="onSave") save
  50. //- button(@click="$router.push({ name: 'HomeView' })") save
  51. footer
  52. button(@click="bypass") +30 user profiles
  53. </template>
  54. <script>
  55. import { surveyFactory, randomSurveyResponses } from '@/utils'
  56. import { allSteps, aspectResponses, possible } from '@/utils/lang'
  57. import { currentProfile, createProfileForUserId, fetchProfileByProfileId, scoreSurveyByProfileId, signupUser } from '@/services'
  58. import { maxDistanceKey } from '../../../backend/db/data-generator/config.json'
  59. export default {
  60. props: {
  61. pid: {
  62. type: Number,
  63. },
  64. },
  65. data() {
  66. return {
  67. bypassCount: 0,
  68. validSurvey: null,
  69. step: 1,
  70. responseLikes: {},
  71. profile: {},
  72. aspects: {
  73. grit: 0,
  74. openness: 0,
  75. bravery: 0,
  76. empathy: 0,
  77. honesty: 0,
  78. respect: 0,
  79. },
  80. aspectResponses: Object.values(aspectResponses.usa)
  81. }
  82. },
  83. computed: {
  84. current() {
  85. return currentProfile
  86. },
  87. profileQuestions() {
  88. if (!this.validSurvey) return []
  89. return this.validSurvey.steps
  90. },
  91. },
  92. async created() {
  93. /**
  94. * Before this ever gets called, surveyFactory.questionsFromDb
  95. * must be set by App.created()
  96. */
  97. this.validSurvey = await surveyFactory.createSurvey(
  98. allSteps['usa'],
  99. possible['usa']['roles'],
  100. )
  101. },
  102. methods: {
  103. // Just for testing quicker
  104. bypass() {
  105. const n = 30
  106. const toGen = [...Array(n).keys()]
  107. toGen.forEach(async () => {
  108. const randRes = randomSurveyResponses(this.bypassCount)
  109. randRes.forEach((tr, i) => {
  110. this.storeResponseLike(i, tr.idOrPrompt, tr.idOrPrompt, tr.val)
  111. })
  112. this.bypassCount++
  113. await this.onSave()
  114. })
  115. this.step = 21
  116. console.warn(`Auto-filling survey ${n} times...`)
  117. },
  118. storeResponseLike(step, id, prompt, val) {
  119. // Also update the form model state for now
  120. // We probably can refactor this out
  121. this.profile[prompt] = val
  122. /**
  123. * If NO response id is present, that means the answer
  124. * is required and associated with a User and NOT a Profile
  125. **/
  126. this.responseLikes[`step-${step}`] = {
  127. idOrPrompt: id ? id : prompt,
  128. val: val.toString()
  129. }
  130. },
  131. _formatIntoResponses(survey) {
  132. return survey.map(resLike => {
  133. resLike.response_key_id = resLike.idOrPrompt
  134. delete resLike.idOrPrompt
  135. return resLike
  136. })
  137. },
  138. /**
  139. * Survey responses have User information
  140. * mixed in with scoring information so we
  141. * need to separate them
  142. * @param {array} responseLikes answered by the survey
  143. */
  144. _separateUserInfoFromResponses(responseLikes) {
  145. let survey = []
  146. const user = {}
  147. responseLikes.forEach(resLike => {
  148. if(resLike.idOrPrompt && Number.isFinite(parseInt(resLike.idOrPrompt))) {
  149. survey.push(resLike)
  150. } else {
  151. user[resLike.idOrPrompt] = resLike.val
  152. }
  153. })
  154. return [user, this._formatIntoResponses(survey)]
  155. },
  156. async _createUserProfileRel(user, survey) {
  157. /** A User is required before creating a profile */
  158. const createdUser = await signupUser(user)
  159. if(!createdUser) return
  160. const userProfileRel = await createProfileForUserId(createdUser.user_id, survey)
  161. if(!userProfileRel) return
  162. return userProfileRel
  163. },
  164. async _getProfileWithScore(createdProfileId, maxDistance) {
  165. /** A Profile is associated with n:1 user and referenced by profile id */
  166. const fetchedProfile = await fetchProfileByProfileId(createdProfileId)
  167. if(!fetchedProfile) {
  168. console.error(`Could not fetch Profile ${createdProfileId}. Please check that a User and Profile were created.`)
  169. return
  170. }
  171. /** Use profile answers to compare against other profile answers */
  172. const scored = await scoreSurveyByProfileId(createdProfileId, maxDistance)
  173. if(!scored) {
  174. console.error(`Could not score Profile ${createdProfileId}.`)
  175. return
  176. }
  177. return fetchedProfile
  178. },
  179. async _setLoginForProfile(profile) {
  180. const currentId = currentProfile.login(profile.profile_id)
  181. if(currentId && profile.responses.length) {
  182. // Stores responses without fetching again
  183. currentProfile.setResponses(profile.responses)
  184. }
  185. if(!currentProfile.isComplete) {
  186. console.error(`Profile ${currentProfile.id} is incomplete. Please make sure all survey questions have been answered.`)
  187. return
  188. }
  189. },
  190. async onSave() {
  191. const [ user, survey] = this._separateUserInfoFromResponses(Object.values(this.responseLikes))
  192. const maxDistanceRes = survey.find(res => res.response_key_id == maxDistanceKey)
  193. /**
  194. * Creating a profile only returns the created
  195. * user id and profile id
  196. **/
  197. const userProfileRel = await this._createUserProfileRel(user, survey)
  198. const createdProfileId = userProfileRel.profile_id
  199. /** A Profile is associated with n:1 user and referenced by profile id */
  200. const fetchedProfile = await this._getProfileWithScore(createdProfileId, maxDistanceRes.val)
  201. /**
  202. * Login only after there is a user and
  203. * that user has a profile and all responses
  204. */
  205. this._setLoginForProfile(fetchedProfile)
  206. this.$router.push({ name: 'HomeView' })
  207. },
  208. back(prompt) {
  209. this[prompt] = ''
  210. this.step--
  211. },
  212. },
  213. }
  214. </script>
  215. <style lang="postcss">
  216. .slide-up
  217. &-leave, &-enter
  218. &-active
  219. transition-delay: 500ms
  220. transition-property: opacity, background-color, font-size, transform, color
  221. transition-timing-function: cubic-bezier(1, 0.3, 0.5, 1)
  222. transition-duration: 300ms
  223. &-enter-from, &-leave-to
  224. opacity: 0
  225. .fade
  226. animation: fadeIn 1200ms ease-in
  227. @keyframes fadeIn
  228. 0%
  229. opacity: 0
  230. 50%
  231. opacity: 0.5
  232. 100%
  233. opacity:1
  234. h1
  235. color: white
  236. main
  237. padding: 5vh
  238. display: flex
  239. header
  240. p
  241. color: #666
  242. line-height: 1.3em
  243. color: #ccc
  244. font-style: italic
  245. text-align: left
  246. article
  247. height: 100%
  248. width: 100%
  249. flex-direction: column
  250. ul
  251. height: 100%
  252. list-style: none
  253. button
  254. padding: 1em
  255. p
  256. padding: 1em
  257. color: white
  258. .form
  259. border: 1px solid #fff
  260. width: 100%
  261. header, footer, ul
  262. padding: 1vh
  263. ul
  264. display: flex
  265. width: 100%
  266. > li
  267. flex-direction: column
  268. border: 0px solid yellow
  269. width: 100%
  270. padding: 0
  271. text-align: left
  272. label
  273. margin-right: 1vh
  274. > h3
  275. text-transform: capitalize
  276. padding: 1vh 0
  277. > div > span
  278. display: block
  279. text-align: center
  280. </style>