Fixed stats graph being filled below line. Added page to show after successful observation submission.

This commit is contained in:
neviyn 2018-10-08 14:10:19 +01:00
parent ed3b748469
commit 02ed4b8b08
7 changed files with 238 additions and 205 deletions

View File

@ -53,7 +53,8 @@ data class ChartDataset(
val label: String, val label: String,
val backgroundColor: String, val backgroundColor: String,
val borderColor: String, val borderColor: String,
val data: List<Double> val data: List<Double>,
val fill: Boolean = false
){ ){
constructor(label: String, color: String, data: List<Double>): this(label, color, color, data) constructor(label: String, color: String, data: List<Double>): this(label, color, color, data)
} }

View File

@ -10,9 +10,9 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.config.http.SessionCreationPolicy import org.springframework.security.config.http.SessionCreationPolicy
import org.springframework.security.core.AuthenticationException import org.springframework.security.core.AuthenticationException
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
import org.springframework.security.crypto.password.PasswordEncoder import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint
import org.springframework.stereotype.Component import org.springframework.stereotype.Component
import java.io.IOException import java.io.IOException
import javax.servlet.ServletException import javax.servlet.ServletException

View File

@ -6,6 +6,8 @@
<b-collapse is-nav id="nav_collapse"> <b-collapse is-nav id="nav_collapse">
<b-navbar-nav> <b-navbar-nav>
<b-nav-item to="/">New Observation</b-nav-item> <b-nav-item to="/">New Observation</b-nav-item>
<b-nav-item to="/observations">View Observations</b-nav-item>
<b-nav-item to="/stats">Graphs</b-nav-item>
</b-navbar-nav> </b-navbar-nav>
<!-- Right aligned nav items --> <!-- Right aligned nav items -->
<b-navbar-nav class="ml-auto"> <b-navbar-nav class="ml-auto">
@ -31,12 +33,4 @@
text-align: center; text-align: center;
color: #2c3e50; color: #2c3e50;
} }
#nav {
padding: 10px;
}
#nav a.router-link-exact-active {
color: #42b983;
}
</style> </style>

View File

@ -6,40 +6,46 @@ import Stats from "./views/Stats.vue";
import NewSite from "./views/NewSite.vue"; import NewSite from "./views/NewSite.vue";
import NewTutor from "./views/NewTutor.vue"; import NewTutor from "./views/NewTutor.vue";
import ViewObservations from "./views/ViewObservations.vue"; import ViewObservations from "./views/ViewObservations.vue";
import ObservationComplete from "./views/ObservationComplete.vue";
Vue.use(Router); Vue.use(Router);
export default new Router({ export default new Router({
routes: [ routes: [
{ {
path: "/", path: "/",
name: "home", name: "home",
component: Home component: Home
}, },
{ {
path: "/observation", path: "/observation",
name: "observation", name: "observation",
component: Observation component: Observation
}, },
{ {
path: "/stats", path: "/stats",
name: "stats", name: "stats",
component: Stats component: Stats
}, },
{ {
path: "/newsite", path: "/newsite",
name: "newSite", name: "newSite",
component: NewSite component: NewSite
}, },
{ {
path: "/newtutor", path: "/newtutor",
name: "newTutor", name: "newTutor",
component: NewTutor component: NewTutor
}, },
{ {
path: "/observations", path: "/observations",
name: "observations", name: "observations",
component: ViewObservations component: ViewObservations
} },
] {
path: "/complete",
name: "complete",
component: ObservationComplete
}
]
}); });

View File

@ -91,14 +91,14 @@
</b-row> </b-row>
</b-form> </b-form>
<b-modal id="submissionModal" <b-modal id="submissionModal"
ref="modal" ref="submissionModal"
title="Enter password to confirm submission" title="Enter password to confirm submission"
@ok="handleOk" @ok="handleOk"
@shown="clearPassword"> @shown="clearPassword">
<form @submit.stop.prevent="handleSubmit"> <form @submit.stop.prevent="handleSubmit">
<b-form-input type="password" <b-form-input type="password"
placeholder="Enter password" placeholder="Enter password"
v-model="name"></b-form-input> v-model="submitPassword"></b-form-input>
</form> </form>
</b-modal> </b-modal>
</b-container> </b-container>
@ -209,23 +209,26 @@
showModal() { showModal() {
this.$refs.submissionModal.show(); this.$refs.submissionModal.show();
}, },
hideModal() {
this.$refs.submissionModal.hide();
this.submitPassword = null;
},
clearPassword() { clearPassword() {
this.submitPassword = null this.submitPassword = null
}, },
handleOk(evt) { handleOk(evt) {
// Prevent modal from closing // Prevent modal from closing
evt.preventDefault(); evt.preventDefault();
if (this.password) { if (this.submitPassword !== null) {
this.handleSubmit() this.handleSubmit()
} }
}, },
handleSubmit() { handleSubmit() {
var form = document.getElementById("submission-form"); var form = document.getElementById("submission-form");
if (form.checkValidity()) { if (form.checkValidity()) {
let axiosConfig = {
auth: {
username: "admin",
password: this.submitPassword
}
};
var self = this;
Vue.axios Vue.axios
.post("/observation", { .post("/observation", {
site: this.site, site: this.site,
@ -234,9 +237,9 @@
whom: this.whom, whom: this.whom,
type: this.type, type: this.type,
entries: JSON.parse(JSON.stringify(this.observations)) entries: JSON.parse(JSON.stringify(this.observations))
}) }, axiosConfig)
.then(function (response) { .then(function (response) {
this.hideModal(); self.$router.push("/complete");
console.log(response); console.log(response);
}) })
.catch(function (error) { .catch(function (error) {

View File

@ -0,0 +1,23 @@
<template>
<b-container>
<h2>Submission Complete</h2>
<p>{{ type }}/{{ description }} has been submitted successfully.</p>
<b-button size="lg" variant="primary" to="/">
Start New Observation
</b-button>
</b-container>
</template>
<script>
import {mapState} from "vuex";
export default {
name: "ObservationComplete",
computed: {
...mapState(["description", "type", "whom", "site", "tutors"])
}
}
</script>
<style scoped>
</style>

View File

@ -1,164 +1,170 @@
<template> <template>
<b-container> <b-container>
<b-modal ref="errorModal" class="text-center" centered hide-header hide-footer> <b-modal ref="errorModal" class="text-center" centered hide-header hide-footer>
<div class="modal-header"> <div class="modal-header">
<h3 class="modal-title w-100">{{ errorStatus }}</h3> <h3 class="modal-title w-100">{{ errorStatus }}</h3>
</div> </div>
<div class="d-block"> <div class="d-block">
<br /> <br/>
<span style="font-size:20px;" v-html="errorMessage" /> <span style="font-size:20px;" v-html="errorMessage"/>
<button class="btn btn-warning" @click="$refs.errorModal.hide()"> <button class="btn btn-warning" @click="$refs.errorModal.hide()">
<v-icon name="exclamation-circle" /> Dismiss</button> <v-icon name="exclamation-circle"/>
</div> Dismiss
</b-modal> </button>
<b-row> </div>
<b-col> </b-modal>
<b-form-group label="Site"> <b-row>
<b-form-select class="text-center" v-model="siteSelection" :options="siteOptions" /> <b-col>
</b-form-group> <b-form-group label="Site">
</b-col> <b-form-select class="text-center" v-model="siteSelection" :options="siteOptions"/>
<b-col> </b-form-group>
<b-form-group label="Tutor"> </b-col>
<b-form-select class="text-center" v-model="tutorSelection" :options="tutorOptions" /> <b-col>
</b-form-group> <b-form-group label="Tutor">
</b-col> <b-form-select class="text-center" v-model="tutorSelection" :options="tutorOptions"/>
<b-col> </b-form-group>
<b-form-group label="Who"> </b-col>
<b-form-input class="text-center" v-model="whom" type="text"></b-form-input> <b-col>
</b-form-group> <b-form-group label="Who">
</b-col> <b-form-input class="text-center" v-model="whom" type="text"></b-form-input>
<b-col> </b-form-group>
<b-form-group label="From"> </b-col>
<date-picker v-model="startDate" @dp-change="changeStartDate" value="startDate" :config="dateOptions" /> <b-col>
</b-form-group> <b-form-group label="From">
</b-col> <date-picker v-model="startDate" @dp-change="changeStartDate" value="startDate"
<b-col> :config="dateOptions"/>
<b-form-group label="To"> </b-form-group>
<date-picker v-model="endDate" @dp-change="changeEndDate" value="endDate" :config="dateOptions" /> </b-col>
</b-form-group> <b-col>
</b-col> <b-form-group label="To">
<b-col> <date-picker v-model="endDate" @dp-change="changeEndDate" value="endDate" :config="dateOptions"/>
<b-button v-on:click="getFilteredAverage()">Refresh</b-button> </b-form-group>
</b-col> </b-col>
</b-row> <b-col>
<b-row> <b-button v-on:click="getFilteredAverage()">Refresh</b-button>
<b-col> </b-col>
<stats-view v-if="chartData != null && chartData != {}" v-bind:chartData="chartData" :options="options" /> </b-row>
</b-col> <b-row>
</b-row> <b-col>
</b-container> <stats-view v-if="chartData != null && chartData !== {}" v-bind:chartData="chartData"
style="position: relative; height:80vh" :options="options"/>
</b-col>
</b-row>
</b-container>
</template> </template>
<script> <script>
import Vue from "vue"; import Vue from "vue";
import StatsView from "../components/StatsView"; import StatsView from "../components/StatsView";
var moment = require("moment");
export default { var moment = require("moment");
name: "stats", export default {
components: { StatsView }, name: "stats",
data: function() { components: {StatsView},
return { data: function () {
chartData: null, return {
options: { chartData: null,
responsive: true, options: {
tooltips: { responsive: true,
mode: "index", maintainAspectRatio: false,
intersect: false tooltips: {
mode: "index",
intersect: false
},
hover: {
mode: "nearest",
intersect: true
},
scales: {
xAxes: [
{
display: true,
scaleLabel: {
display: true,
labelString: "Date"
}
}
],
yAxes: [
{
display: true,
scaleLabel: {
display: true,
labelString: "Average Score"
}
}
]
}
},
errorStatus: null,
errorMessage: null,
startDate: moment().subtract(7, "days"),
endDate: moment(),
dateOptions: {
format: "DD/MM/YYYY",
useCurrent: false
},
siteSelection: null,
siteOptions: [],
tutorSelection: null,
tutorOptions: [],
whom: null
};
}, },
hover: { methods: {
mode: "nearest", getFilteredAverage: function () {
intersect: true Vue.axios
.post("/observations/chartdata", {
'site': this.siteSelection,
'tutor': this.tutorSelection,
'startDate': moment(this.startDate).format("YYYY-MM-DD"),
'endDate': moment(this.endDate).format("YYYY-MM-DD"),
'whom': this.whom
})
.then(response => {
this.chartData = response.data;
})
.catch(error => {
this.errorStatus = error.response.status;
this.errorMessage = error.response.data;
this.$refs.errorModal.show();
});
},
getTutors: function () {
if (this.siteSelection != null) {
Vue.axios
.get("/site/" + this.siteSelection + "/tutors")
.then(response => {
this.tutorOptions = response.data;
});
}
},
changeStartDate: function (e) {
this.startDate = e.date;
},
changeEndDate: function (e) {
this.endDate = e.date;
}
}, },
scales: { watch: {
xAxes: [ siteSelection: function () {
{ this.tutorOptions = [];
display: true, this.tutorSelection = null;
scaleLabel: { this.getTutors();
display: true,
labelString: "Date"
}
} }
], },
yAxes: [ mounted() {
{ this.getFilteredAverage();
display: true, Vue.axios
scaleLabel: { .get("/site")
display: true, .then(response => {
labelString: "Average Score" this.siteOptions = response.data;
} })
} .catch(error => {
] this.errorStatus = error.response.status;
this.errorMessage = error.response.data;
this.$refs.errorModal.show();
});
} }
},
errorStatus: null,
errorMessage: null,
startDate: moment().subtract(7, "days"),
endDate: moment(),
dateOptions: {
format: "DD/MM/YYYY",
useCurrent: false
},
siteSelection: null,
siteOptions: [],
tutorSelection: null,
tutorOptions: null,
whom: null
}; };
},
methods: {
getFilteredAverage: function() {
Vue.axios
.post("/observations/chartdata", {
'site': this.siteSelection,
'tutor': this.tutorSelection,
'startDate': moment(this.startDate).format("YYYY-MM-DD"),
'endDate': moment(this.endDate).format("YYYY-MM-DD"),
'whom': this.whom
})
.then(response => {
this.chartData = response.data;
})
.catch(error => {
this.errorStatus = error.response.status;
this.errorMessage = error.response.data;
this.$refs.errorModal.show();
});
},
getTutors: function() {
if (this.siteSelection != null) {
Vue.axios
.get("/site/" + this.siteSelection + "/tutors")
.then(response => {
this.tutorOptions = response.data;
});
}
},
changeStartDate: function(e) {
this.startDate = e.date;
},
changeEndDate: function(e) {
this.endDate = e.date;
}
},
watch: {
siteSelection: function() {
this.tutorOptions = [];
this.tutorSelection = null;
this.getTutors();
}
},
mounted() {
this.getFilteredAverage();
Vue.axios
.get("/site")
.then(response => {
this.siteOptions = response.data;
})
.catch(error => {
this.errorStatus = error.response.status;
this.errorMessage = error.response.data;
this.$refs.errorModal.show();
});
}
};
</script> </script>