Finished creating result statistic views
This commit is contained in:
		
							
								
								
									
										23
									
								
								client/src/components/icons/ChevronDoubleRight.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								client/src/components/icons/ChevronDoubleRight.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| <template> | ||||
|   <svg-icon type="mdi" :path="path"></svg-icon> | ||||
| </template> | ||||
|  | ||||
|  | ||||
| <script> | ||||
| import SvgIcon from '@jamescoyle/vue-icon' | ||||
| import { mdiChevronDoubleRight } from '@mdi/js' | ||||
|  | ||||
| export default { | ||||
|     name: "mdiChefronDoubleRight", | ||||
|  | ||||
|     components: { | ||||
|         SvgIcon | ||||
|     }, | ||||
|  | ||||
|     data() { | ||||
|         return { | ||||
|             path: mdiChevronDoubleRight, | ||||
|         } | ||||
|     } | ||||
| } | ||||
| </script> | ||||
| @@ -50,7 +50,7 @@ | ||||
|             <transition enter-active-class="transition ease-out duration-100" enter-from-class="transform opacity-0 scale-95" enter-to-class="transform opacity-100 scale-100" leave-active-class="transition ease-in duration-75" leave-from-class="transform opacity-100 scale-100" leave-to-class="transform opacity-0 scale-95"> | ||||
|             <MenuItems class="origin-top-right absolute right-0 mt-2 p-1 space-y-2 w-56 rounded-md shadow-lg bg-lime-700 ring-1 ring-black ring-opacity-5 focus:outline-none"> | ||||
|                 <div class="py-1"> | ||||
|                 <MenuItem v-slot="{ active }" v-for="route in $router.options.routes" :key="route.path" > | ||||
|                 <MenuItem v-slot="{ active }" v-for="route in $router.options.routes.filter( value => value.meta.index >= 0 )" :key="route.path" > | ||||
|                     <a :href="route.path" target="_blank" rel="noopener noreferrer" :class="[active ? 'text-orange-300' : 'text-white', 'flex px-4 py-2  active:text-orange-500 rounded-lg h-fit align-middle transition-all ease-in-out duration-500']" v-if="route.path.startsWith('http')"> | ||||
|                         <div class="inline-flex px-3 py-1 space-x-1"> | ||||
|                             <span class="scale-75"> | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| import { createRouter, createWebHistory } from 'vue-router' | ||||
| import { mdiAccountBoxMultiple, mdiAccountGroupOutline, mdiAccountQuestion, mdiChartBox, mdiCodeTags, mdiCounter, mdiEarth, mdiHandCoin, mdiHome, mdiInformation, mdiWeb } from '@mdi/js' | ||||
| import { mdiAccountBoxMultiple, mdiAccountGroupOutline, mdiAccountQuestion, mdiChartBox, mdiCodeTags, mdiCompare, mdiCounter, mdiEarth, mdiHandCoin, mdiHome, mdiInformation, mdiWeb } from '@mdi/js' | ||||
| import About from '@/views/about/Index.vue' | ||||
| import AboutPage from '@/views/about/About.vue' | ||||
| import Acknowledgements from '@/views/about/Acknowledgements.vue' | ||||
| @@ -13,8 +13,9 @@ import Refused from '@/views/errors/Refused.vue' | ||||
| import Results from '@/views/results/Index.vue' | ||||
| import ResultsPage from '@/views/results/Results.vue' | ||||
| import Scores from '@/views/results/Scores.vue' | ||||
| import Answers from '@/views/results/Answers.vue' | ||||
| import CompareAnswers from '@/views/results/CompareAnswers.vue' | ||||
| import Statistics from '@/views/results/Statistics.vue' | ||||
| import CompareResults from '@/views/results/CompareResults.vue' | ||||
|  | ||||
| const routes = [ | ||||
|     { | ||||
| @@ -144,17 +145,28 @@ const routes = [ | ||||
|                 component: Scores, | ||||
|                 meta: { | ||||
|                     index: 1, | ||||
|                     title: 'All Scores', | ||||
|                     title: 'Your Scores', | ||||
|                     svgPath: mdiCounter, | ||||
|                     parentIndex: -1 | ||||
|                 } | ||||
|             }, | ||||
|             { | ||||
|                 path: 'compare', | ||||
|                 name: 'CompareResults', | ||||
|                 component: CompareResults, | ||||
|                 meta: { | ||||
|                     index: 2, | ||||
|                     title: 'Compare Results', | ||||
|                     svgPath: mdiCompare, | ||||
|                     parentIndex: -1 | ||||
|                 } | ||||
|             }, | ||||
|             { | ||||
|                 path: 'statistics', | ||||
|                 name: 'Statistics', | ||||
|                 component: Statistics, | ||||
|                 meta: { | ||||
|                     index: 2, | ||||
|                     index: 3, | ||||
|                     title: 'Statistics', | ||||
|                     svgPath: mdiChartBox, | ||||
|                     parentIndex: -1 | ||||
| @@ -163,10 +175,10 @@ const routes = [ | ||||
|             { | ||||
|                 path: 'answers', | ||||
|                 name: 'Answers', | ||||
|                 component: Answers, | ||||
|                 component: CompareAnswers, | ||||
|                 meta: { | ||||
|                     index: 3, | ||||
|                     title: 'Global Answers', | ||||
|                     index: 4, | ||||
|                     title: 'Compare Answers', | ||||
|                     svgPath: mdiEarth, | ||||
|                     parentIndex: -1 | ||||
|                 } | ||||
|   | ||||
| @@ -4,15 +4,19 @@ export const useAppStore = defineStore({ | ||||
|     id: 'app', | ||||
|     state: () => ({ | ||||
|         hasData: false, | ||||
|         results: {} | ||||
|         answers: [], | ||||
|         playbooks: {}, | ||||
|         results: {}, | ||||
|         scores: {}, | ||||
|         count: null | ||||
|     }), | ||||
|     actions: { | ||||
|         toggleData() { | ||||
|             this.hasData = !this.hasData | ||||
|             console.log('Toggled Has Data. New value', this.hasData) | ||||
|         }, | ||||
|         storeResults(results) { | ||||
|             this.results = JSON.parse(JSON.stringify(results)) | ||||
|         store(key, data) { | ||||
|             this[key] = JSON.parse(JSON.stringify(data)) | ||||
|         } | ||||
|     } | ||||
| }) | ||||
| @@ -93,7 +93,7 @@ | ||||
|                        }  | ||||
|                     } | ||||
|                 ).then( (response) => { | ||||
|                     this.appStore.storeResults(response.data) | ||||
|                     this.appStore.store('results', response.data) | ||||
|                     console.log('Results fetched from the server.') | ||||
|                     this.$router.push('/results') | ||||
|                 }).catch( (error) => { | ||||
| @@ -125,7 +125,7 @@ | ||||
|                     <div v-for="(answer, index) in questionStore.questions[this.id].answers" :key="`q${this.id}-o${index}`" class="inline-flex"> | ||||
|                         <input :type="this.questionStore.questions[this.id].select > 1 ? 'checkbox' : 'radio'" class="checkbox" :id="`q${this.id}-o${index}`" :value=index :key="`q${this.id}-o${index}`" v-model="answersStore.answers[this.id]" :disabled="this.questionStore.questions[this.id].select > 1 && this.answersStore.answers[this.id].length == this.questionStore.questions[this.id].select && !this.answersStore.answers[this.id].includes(index)"> | ||||
|                         <label :for="`q${this.id}-o${index}`"> | ||||
|                             <div v-html="answer" class="uncial-antiqua w-full h-full p-2 transition-all duration-100 ease-in-out hover:bg-lime-200 hover:text-orange-600 hover:cursor-pointer" :aria-label="answer" :title="answer"></div> | ||||
|                             <div v-html="answer" class="uncial-antiqua w-full h-full p-2 select-none transition-all duration-100 ease-in-out hover:bg-lime-200 hover:text-orange-600 hover:cursor-pointer" :aria-label="answer" :title="answer"></div> | ||||
|                         </label> | ||||
|                     </div> | ||||
|                 </div> | ||||
|   | ||||
| @@ -14,7 +14,7 @@ | ||||
| <template> | ||||
|     <TextFrame> | ||||
|         <Header> | ||||
|             Global Answers | ||||
|             Compare Answers | ||||
|         </Header> | ||||
|         <Content> | ||||
|              | ||||
							
								
								
									
										101
									
								
								client/src/views/results/CompareResults.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								client/src/views/results/CompareResults.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,101 @@ | ||||
| <script> | ||||
|     import axios from 'axios' | ||||
|     import Config from '@/config.js' | ||||
|     import Content from '@/components/Content.vue' | ||||
|     import Header from '@/components/Header.vue' | ||||
|     import TextFrame from '@/components/TextFrame.vue' | ||||
|     import { useAppStore } from '@/stores/app.js' | ||||
|     import ChevronDoubleRight from '@/components/icons/ChevronDoubleRight.vue' | ||||
|  | ||||
|     export default { | ||||
|         setup() { | ||||
|             const appStore = useAppStore() | ||||
|  | ||||
|             return { | ||||
|                 appStore | ||||
|             } | ||||
|         }, | ||||
|         mounted() { | ||||
|             this.getResults() | ||||
|         }, | ||||
|         name: 'CompareResults', | ||||
|         components: { | ||||
|             ChevronDoubleRight, | ||||
|             Content, | ||||
|             Header, | ||||
|             TextFrame | ||||
|         }, | ||||
|         methods: { | ||||
|             getResults() { | ||||
|                 axios.get(`${Config.SERVER}api/playbooks/`) | ||||
|                     .then((response) => { | ||||
|                         this.error = false | ||||
|                         this.appStore.store('playbooks', response.data) | ||||
|                     }) | ||||
|                     .catch( error => { | ||||
|                         this.$router.push('/err_refused') | ||||
|                     }) | ||||
|                 axios.get(`${Config.SERVER}api/count/`) | ||||
|                     .then((response) => { | ||||
|                         this.error = false | ||||
|                         this.appStore.store('count', response.data) | ||||
|                     }) | ||||
|                     .catch( error => { | ||||
|                         this.$router.push('/err_refused') | ||||
|                     }) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| </script> | ||||
| <template> | ||||
|     <TextFrame> | ||||
|         <Header> | ||||
|             Compare Results | ||||
|         </Header> | ||||
|         <Content> | ||||
|             <p class="text-leader"> | ||||
|                 Based on others who have taken this quiz, here are the playbooks of fellow travellers you are likely to meet along the way. | ||||
|             </p> | ||||
|             <table class="table-auto w-full max-w-sm mx-auto text-left"> | ||||
|                 <thead class="text-xs text-gray-700 uppercase bg-lime-100 dark:bg-gray-700 dark:text-gray-400"> | ||||
|                     <tr> | ||||
|                         <th> | ||||
|  | ||||
|                         </th> | ||||
|                         <th> | ||||
|                             Playbook | ||||
|                         </th> | ||||
|                         <th> | ||||
|                             Number | ||||
|                         </th> | ||||
|                         <th> | ||||
|                             Percentage | ||||
|                         </th> | ||||
|                     </tr> | ||||
|                 </thead> | ||||
|                 <tbody> | ||||
|                     <tr v-for="(number, playbook, index) in this.appStore.playbooks" :key="playbook" class="hover:bg-yellow-50" :class="index % 2 == 0? `bg-lime-50 border-b dark:bg-gray-900 dark:border-gray-700` : `bg-lime-100 border-b dark:bg-gray-800 dark:border-gray-700`"> | ||||
|                         <td v-if="this.appStore.results.playbooks.some(obj => obj.hasOwnProperty(playbook))"> | ||||
|                             <ChevronDoubleRight/> | ||||
|                         </td> | ||||
|                         <td v-else> | ||||
|  | ||||
|                         </td> | ||||
|                         <td scope="col" class="py-1 capitalize"> | ||||
|                             The {{ playbook }} | ||||
|                         </td> | ||||
|                         <td scope="col" class="py-1"> | ||||
|                             {{ number }} | ||||
|                         </td> | ||||
|                         <td scope="col" class="py-1"> | ||||
|                             {{ Math.round(100*number/this.appStore.count) }} % | ||||
|                         </td> | ||||
|                     </tr> | ||||
|                     <tr> | ||||
|                         <td colspan="4" class="text-center text-lg">Out of a total {{ this.appStore.count }} users</td> | ||||
|                     </tr> | ||||
|                 </tbody> | ||||
|             </table> | ||||
|         </Content> | ||||
|     </TextFrame> | ||||
| </template> | ||||
| @@ -49,12 +49,11 @@ | ||||
|                 <section v-for="playbook in this.appStore.results.playbooks" :key="Object.keys(playbook)[0]" class="border-solid border-b-2 border-lime-600 border-opacity-25"> | ||||
|                     <h2 class="flex justify-between"> | ||||
|                         <span class="uncial-antiqua align-bottom"> | ||||
|                             <span class="text-xl">The</span> <span class="text-3xl">{{ Object.keys(playbook)[0].slice(0,1).toUpperCase()+Object.keys(playbook)[0].slice(1) }}</span> | ||||
|                             <span class="text-xl">The</span> <span class="text-3xl capitalize">{{ Object.keys(playbook)[0] }}</span> | ||||
|                         </span> | ||||
|                         <span class="text-sm">Pages {{ Object.values(playbook)[0].pages }}</span> | ||||
|                     </h2> | ||||
|                     <p class="text-leader"> | ||||
|                         {{ Object.values(playbook)[0].flavour }} | ||||
|                     <p class="text-leader" v-html="Object.values(playbook)[0].flavour"> | ||||
|                     </p> | ||||
|                     <p> | ||||
|                         {{ Object.values(playbook)[0].blurb }} | ||||
|   | ||||
| @@ -23,16 +23,16 @@ | ||||
| <template> | ||||
|     <TextFrame> | ||||
|         <Header> | ||||
|             Scores | ||||
|             Your Scores | ||||
|         </Header> | ||||
|         <Content> | ||||
|             <p> | ||||
|                 The following are your scores for each playbook: | ||||
|             <p class="text-leader"> | ||||
|                 While the playbook you are is the one most reflected by your answers, here are the other playbooks and the extent to which they have a bearing upon your journey. | ||||
|             </p> | ||||
|             <table class="table-auto w-full max-w-xs mx-auto"> | ||||
|                 <thead> | ||||
|             <table class="table-auto w-full max-w-sm mx-auto text-left"> | ||||
|                 <thead class="text-xs text-gray-700 uppercase bg-lime-100 dark:bg-gray-700 dark:text-gray-400"> | ||||
|                     <tr> | ||||
|                         <th> | ||||
|                         <th class="pl-10"> | ||||
|                             Playbook | ||||
|                         </th> | ||||
|                         <th> | ||||
| @@ -41,11 +41,11 @@ | ||||
|                     </tr> | ||||
|                 </thead> | ||||
|                 <tbody> | ||||
|                     <tr v-for="(score, playbook) in this.appStore.results.all_playbooks" :key="playbook"> | ||||
|                         <td> | ||||
|                             The {{ playbook.slice(0,1).toUpperCase() + playbook.slice(1) }} | ||||
|                     <tr v-for="(score, playbook, index) in this.appStore.results.all_playbooks" :key="playbook" class="hover:bg-yellow-50" :class="index % 2 == 0? `bg-lime-50 border-b dark:bg-gray-900 dark:border-gray-700` : `bg-lime-100 border-b dark:bg-gray-800 dark:border-gray-700`"> | ||||
|                         <td scope="col" class="py-1 pl-10 capitalize"> | ||||
|                             The {{ playbook }} | ||||
|                         </td> | ||||
|                         <td> | ||||
|                         <td scope="col" class="py-1"> | ||||
|                             {{ Math.round(100*score/this.appStore.results.max_score) }} % | ||||
|                         </td> | ||||
|                     </tr> | ||||
|   | ||||
| @@ -1,13 +1,49 @@ | ||||
| <script> | ||||
|     import axios from 'axios' | ||||
|     import Config from '@/config.js' | ||||
|     import Content from '@/components/Content.vue' | ||||
|     import Header from '@/components/Header.vue' | ||||
|     import TextFrame from '@/components/TextFrame.vue' | ||||
|     import { useAppStore } from '@/stores/app.js' | ||||
|     import ChevronDoubleRight from '@/components/icons/ChevronDoubleRight.vue' | ||||
|  | ||||
|     export default { | ||||
|         setup() { | ||||
|             const appStore = useAppStore() | ||||
|  | ||||
|             return { | ||||
|                 appStore | ||||
|             } | ||||
|         }, | ||||
|         mounted() { | ||||
|             this.getScores() | ||||
|         }, | ||||
|         name: 'Statistics', | ||||
|         components: { | ||||
|             ChevronDoubleRight, | ||||
|             Content, | ||||
|             Header, | ||||
|             TextFrame | ||||
|         }, | ||||
|         methods: { | ||||
|             getScores() { | ||||
|                 axios.get(`${Config.SERVER}api/scores/`) | ||||
|                     .then((response) => { | ||||
|                         this.error = false | ||||
|                         this.appStore.store('scores', response.data) | ||||
|                     }) | ||||
|                     .catch( error => { | ||||
|                         this.$router.push('/err_refused') | ||||
|                     }) | ||||
|                 axios.get(`${Config.SERVER}api/count/`) | ||||
|                     .then((response) => { | ||||
|                         this.error = false | ||||
|                         this.appStore.store('count', response.data) | ||||
|                     }) | ||||
|                     .catch( error => { | ||||
|                         this.$router.push('/err_refused') | ||||
|                     }) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| </script> | ||||
| @@ -17,7 +53,62 @@ | ||||
|             Statistics | ||||
|         </Header> | ||||
|         <Content> | ||||
|             <p class="text-leader"> | ||||
|                 Sometimes, it can help to have some additional insight into the likelihood of meeting different people. | ||||
|                 Or, indeed, it can provide insight into how the questions we ask of each other bias the way we perceive the world. | ||||
|             </p> | ||||
|             <table class="table-auto w-full max-w-lg mx-auto text-left"> | ||||
|                 <thead class="text-xs text-gray-700 uppercase bg-lime-100 dark:bg-gray-700 dark:text-gray-400"> | ||||
|                     <tr> | ||||
|                         <th> | ||||
|  | ||||
|                         </th> | ||||
|                         <th> | ||||
|                             Playbook | ||||
|                         </th> | ||||
|                         <th> | ||||
|                             Your Score | ||||
|                         </th> | ||||
|                         <th> | ||||
|                             Mean Score | ||||
|                         </th> | ||||
|                         <th> | ||||
|                             Median Score | ||||
|                         </th> | ||||
|                         <th> | ||||
|                             Standard Deviation | ||||
|                         </th> | ||||
|                     </tr> | ||||
|                 </thead> | ||||
|                 <tbody> | ||||
|                     <tr v-for="(stats, playbook, index) in this.appStore.scores" :key="playbook" class="hover:bg-yellow-50" :class="index % 2 == 0? `bg-lime-50 border-b dark:bg-gray-900 dark:border-gray-700` : `bg-lime-100 border-b dark:bg-gray-800 dark:border-gray-700`"> | ||||
|                         <td v-if="this.appStore.results.playbooks.some(obj => obj.hasOwnProperty(playbook))"> | ||||
|                             <ChevronDoubleRight/> | ||||
|                         </td> | ||||
|                         <td v-else> | ||||
|  | ||||
|                         </td> | ||||
|                         <td scope="col" class="py-1 capitalize"> | ||||
|                             The {{ playbook }} | ||||
|                         </td> | ||||
|                         <td> | ||||
|                             {{ Math.round(100*this.appStore.results.all_playbooks[playbook]/this.appStore.results.max_score) }} | ||||
|                         </td> | ||||
|                         <td scope="col" class="py-1"> | ||||
|                             {{ Math.round(stats.mean * 100) / 100 }} | ||||
|                         </td> | ||||
|                         <td scope="col" class="py-1"> | ||||
|                             {{ Math.round(stats.median * 100) / 100 }} | ||||
|                         </td> | ||||
|                         <td> | ||||
|                             {{ Math.round(stats.standard_deviation * 100) / 100 }} | ||||
|                         </td> | ||||
|                     </tr> | ||||
|                     <tr> | ||||
|                         <td colspan="4" class="text-center text-lg">Out of a total {{ this.appStore.count }} users</td> | ||||
|                     </tr> | ||||
|                 </tbody> | ||||
|             </table> | ||||
|         </Content> | ||||
|     </TextFrame> | ||||
| </template> | ||||
		Reference in New Issue
	
	Block a user