Fixed viewing observations and stats.

This commit is contained in:
neviyn 2018-10-05 12:26:13 +01:00
parent 76e19e9253
commit e6e69b2df9
8 changed files with 397 additions and 324 deletions

View File

@ -66,6 +66,11 @@
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-joda</artifactId>
<version>2.9.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>

View File

@ -27,12 +27,14 @@ class Controller {
}
@GetMapping("/site/{id}/tutors")
fun getTutorsForSite(@PathVariable(value = "id") id: Long): List<NameValue> =
siteRepository.findById(id).map { site ->
site.tutors.map { NameValue(it.name, it.id) }
}.get()
fun getTutorsForSite(@PathVariable(value = "id") id: Long): List<NameValue>? {
val site = siteRepository.findById(id)
if (site.isPresent)
return site.map { site1 -> site1.tutors.map { NameValue(it.name, it.id) } }.get()
return null
}
@GetMapping
@GetMapping("/tutor")
fun getAllTutors(): List<NameValue> = tutorRepository.findAll().map { NameValue(it.name, it.id) }
@PostMapping("/tutor")
@ -55,6 +57,7 @@ class Controller {
@PostMapping("/observation")
fun addObservation(@Valid @RequestBody newObservation: NewObservation): Long {
println(newObservation)
val site = siteRepository.findById(newObservation.site).get()
val tutors = tutorRepository.findAllById(newObservation.tutors).toSet()
val overallScores = newObservation.entries.asSequence().groupBy { it.type }
@ -80,51 +83,56 @@ class Controller {
it.observations.add(observation)
tutorRepository.save(it)
}
println(observation)
return observation.id
}
@GetMapping("/observations")
fun getObservations(@Valid @RequestBody observationsRequest: ObservationsRequest): List<Observation>? {
@PostMapping("/observations")
fun getObservations(@Valid @RequestBody observationsRequest: ObservationsRequest): List<Observation> {
print(observationsRequest)
if (observationsRequest.tutor != null) {
val tutor = tutorRepository.findById(observationsRequest.tutor).get()
return if (observationsRequest.whom == null) {
observationRepository.findByTutorsAndDateBetween(tutor = tutor,
return tutorRepository.findById(observationsRequest.tutor).map {
when {
(observationsRequest.whom == null || observationsRequest.whom.isEmpty()) -> observationRepository.findByTutorsAndDateBetween(tutor = it,
startDate = observationsRequest.startDate,
endDate = observationsRequest.endDate)
} else {
observationRepository.findByTutorsAndWhomAndDateBetween(tutor = tutor,
else -> observationRepository.findByTutorsAndWhomAndDateBetween(tutor = it,
whom = observationsRequest.whom,
startDate = observationsRequest.startDate,
endDate = observationsRequest.endDate)
}
}.orElse(listOf())
}
if (observationsRequest.site != null) {
val site = siteRepository.findById(observationsRequest.site).get()
return if (observationsRequest.whom == null) {
observationRepository.findBySiteAndDateBetween(site = site,
return siteRepository.findById(observationsRequest.site).map {
when {
(observationsRequest.whom == null || observationsRequest.whom.isEmpty()) -> observationRepository.findBySiteAndDateBetween(site = it,
startDate = observationsRequest.startDate,
endDate = observationsRequest.endDate)
} else {
observationRepository.findBySiteAndWhomAndDateBetween(site = site,
else -> observationRepository.findBySiteAndWhomAndDateBetween(site = it,
whom = observationsRequest.whom,
startDate = observationsRequest.startDate,
endDate = observationsRequest.endDate)
}
}.orElse(listOf())
}
return null
return listOf()
}
@GetMapping("/observations/chartdata")
@PostMapping("/observations/chartdata")
fun getObservationsChartData(@Valid @RequestBody observationsRequest: ObservationsRequest): ChartData? {
val data = getObservations(observationsRequest) ?: return null
val groupedData = data.asSequence().groupBy { it.date }.map{ entry -> AverageData(
val data = getObservations(observationsRequest)
if(data.isEmpty()) return ChartData(listOf(), listOf())
val groupedData = data.asSequence().groupBy { it.date }.map { entry ->
AverageData(
entry.value.asSequence().mapNotNull { it.monitoring }.average(),
entry.value.asSequence().mapNotNull { it.control }.average(),
entry.value.asSequence().mapNotNull { it.conservatism }.average(),
entry.value.asSequence().mapNotNull { it.teamwork }.average(),
entry.value.asSequence().mapNotNull { it.knowledge }.average(),
entry.key
)}.toList()
)
}.toList()
val dates = groupedData.map { it.date.toString("yyyy-MM-dd") }
return ChartData(
labels = dates,

View File

@ -1,5 +1,6 @@
package uk.co.neviyn.observationdatabase
import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonProperty
import org.joda.time.LocalDate
import javax.persistence.*
@ -43,6 +44,7 @@ data class Observation(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long = 0,
@JsonIgnore
@ManyToOne
val site: Site,
@Column(nullable = false)
@ -60,6 +62,7 @@ data class Observation(
val knowledge: Double?,
@ElementCollection
val entries: List<Entry>,
@JsonIgnore
@ManyToMany(mappedBy = "observations")
val tutors: Set<Tutor>
)
@ -72,10 +75,10 @@ data class Entry(
@Column(nullable = false)
@JsonProperty
val rating: Int,
@Column(nullable = false)
@Column(nullable = false, columnDefinition = "TEXT")
@JsonProperty
val strengths: String,
@Column(nullable = false)
@Column(nullable = false, columnDefinition = "TEXT")
@JsonProperty
val improvements: String
)

View File

@ -1 +1,3 @@
spring.jpa.properties.jadira.usertype.autoRegisterUserTypes = true
spring.datasource.url=jdbc:h2:file:./database
spring.jpa.hibernate.ddl-auto=update

View File

@ -1,16 +1,20 @@
<template>
<div id="app">
<!--
<div id="nav">
<router-link to="/">Home</router-link> |
<router-link to="/observation">Observation</router-link>
</div>
-->
<b-navbar id="nav" variant="info">
<b-navbar-brand to="/">Training Observations</b-navbar-brand>
<b-navbar-nav class="ml-auto">
<b-navbar toggleable="md" type="dark" variant="info">
<b-navbar-toggle target="nav_collapse"></b-navbar-toggle>
<b-navbar-brand to="/">Observation Database</b-navbar-brand>
<b-collapse is-nav id="nav_collapse">
<b-navbar-nav>
<b-nav-item to="/">New Observation</b-nav-item>
</b-navbar-nav>
<!-- Right aligned nav items -->
<b-navbar-nav class="ml-auto">
<b-nav-item-dropdown text="Admin" right>
<b-dropdown-item to="/newsite">New Site</b-dropdown-item>
<b-dropdown-item to="/newtutor">New Tutor</b-dropdown-item>
</b-nav-item-dropdown>
</b-navbar-nav>
</b-collapse>
</b-navbar>
<br/>
<transition>
@ -27,6 +31,7 @@
text-align: center;
color: #2c3e50;
}
#nav {
padding: 10px;
}

View File

@ -8,7 +8,9 @@
<br/>
<span style="font-size:20px;" v-html="errorMessage"/>
<button class="btn btn-warning" @click="$refs.errorModal.hide()">
<v-icon name="exclamation-circle" /> Dismiss</button>
<v-icon name="exclamation-circle"/>
Dismiss
</button>
</div>
</b-modal>
<b-container v-if="errorMessage">
@ -24,28 +26,32 @@
<b-row align-h="center">
<b-col>
<b-form-group label="Site">
<b-form-select :value="site" @input="setSite" :options="siteOptions" style="text-align:center;"></b-form-select>
<b-form-select :value="site" @input="setSite" :options="siteOptions"
style="text-align:center;" required></b-form-select>
</b-form-group>
</b-col>
</b-row>
<b-row align-h="center">
<b-col>
<b-form-group label="Who">
<b-form-input :value="whom" @input="setWhom" type="text" style="text-align:center;"></b-form-input>
<b-form-input :value="whom" @input="setWhom" type="text"
style="text-align:center;" required></b-form-input>
</b-form-group>
</b-col>
</b-row>
<b-row align-h="center">
<b-col>
<b-form-group label="Description">
<b-form-input :value="description" @input="setDescription" type="text" style="text-align:center;"></b-form-input>
<b-form-input :value="description" @input="setDescription" type="text"
style="text-align:center;" required></b-form-input>
</b-form-group>
</b-col>
</b-row>
<b-row align-h="center">
<b-col>
<b-form-group label="Type">
<b-form-select :value="type" @input="setType" style="text-align:center;">
<b-form-select :value="type" @input="setType" style="text-align:center;"
required>
<option :value=null>Select a training type</option>
<option value="INITIAL">INITIAL</option>
<option value="CONTINUING">CONTINUING</option>
@ -58,7 +64,9 @@
<b-form-group label="Tutor(s)">
<p v-if="site == null">Select a site first.</p>
<v-icon name="spinner" spin v-if="loadingTutors"/>
<b-form-checkbox-group :value="tutors" @input="setTutors" :options="tutorOptions"></b-form-checkbox-group>
<b-form-checkbox-group :value="tutors" @input="setTutors"
:options="tutorOptions"
required></b-form-checkbox-group>
</b-form-group>
</b-col>
</b-row>
@ -77,6 +85,7 @@ import Vue from "vue";
import "vue-awesome/icons/spinner";
import "vue-awesome/icons/exclamation-circle";
import {mapState, mapMutations} from "vuex";
export default {
name: "home",
data() {

View File

@ -14,12 +14,17 @@
<b-row>
<b-col>
<b-form-group label="Site">
<b-form-select v-model="siteSelection" :options="siteOptions" style="text-align:center;" />
<b-form-select class="text-center" v-model="siteSelection" :options="siteOptions" />
</b-form-group>
</b-col>
<b-col>
<b-form-group label="Tutor">
<b-form-select class="text-center" v-model="tutorSelection" :options="tutorOptions" />
</b-form-group>
</b-col>
<b-col>
<b-form-group label="Who">
<b-form-input v-model="whom" type="text" style="text-align:center;"></b-form-input>
<b-form-input class="text-center" v-model="whom" type="text"></b-form-input>
</b-form-group>
</b-col>
<b-col>
@ -103,12 +108,12 @@ export default {
methods: {
getFilteredAverage: function() {
Vue.axios
.get("/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
.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;
@ -137,7 +142,7 @@ export default {
},
watch: {
siteSelection: function() {
this.tutorOptions = null;
this.tutorOptions = [];
this.tutorSelection = null;
this.getTutors();
}
@ -145,7 +150,7 @@ export default {
mounted() {
this.getFilteredAverage();
Vue.axios
.get("/site/all")
.get("/site")
.then(response => {
this.siteOptions = response.data;
})

View File

@ -1,44 +1,59 @@
<template>
<b-container>
<b-container fluid>
<b-modal ref="errorModal" class="text-center" centered hide-header hide-footer>
<div class="modal-header">
<h3 class="modal-title w-100">{{ errorStatus }}</h3>
</div>
<div class="d-block">
<br/>
<span style="font-size:20px;" v-html="errorMessage"/>
<button class="btn btn-warning" @click="$refs.errorModal.hide()">
<v-icon name="exclamation-circle"/>
Dismiss
</button>
</div>
</b-modal>
<b-row>
<b-col>
<b-form-group label="Site">
<b-form-select v-model="siteSelection" :options="siteOptions" style="text-align:center;" />
<b-form-select class="text-center" v-model="siteSelection"
:options="siteOptions"/>
</b-form-group>
</b-col>
<b-col>
<b-form-group label="Tutor">
<b-form-select class="text-center" v-model="tutorSelection"
:options="tutorOptions"/>
</b-form-group>
</b-col>
<b-col>
<b-form-group label="Who">
<b-form-input v-model="whom" type="text" style="text-align:center;"></b-form-input>
<b-form-input v-model="whom" type="text" class="text-center"></b-form-input>
</b-form-group>
</b-col>
<b-col>
<b-form-group label="From">
<date-picker v-model="startDate" @dp-change="changeStartDate" value="startDate" :config="dateOptions" />
<date-picker v-model="startDate" @dp-change="changeStartDate" value="startDate"
:config="dateOptions"/>
</b-form-group>
</b-col>
<b-col>
<b-form-group label="To">
<date-picker v-model="endDate" @dp-change="changeEndDate" value="endDate" :config="dateOptions" />
<date-picker v-model="endDate" @dp-change="changeEndDate" value="endDate"
:config="dateOptions"/>
</b-form-group>
</b-col>
<b-col>
<b-button v-on:click="getFiltered()">Refresh</b-button>
</b-col>
</b-row>
<b-container v-if="observationData != null">
<b-container v-for="(observation, index) in observationData" v-bind:key="index">
<b-row>
<b-col>
<p>{{ observation.date }}-{{ observation.type }}/{{ observation.observed }}-{{ obsevation.whom }}</p>
</b-col>
</b-row>
<b-row v-for="(entry, index) in observation.observations" v-bind:key="index">
<b-row>
<b-col cols="3">
<p>{{ entry.type }}</p>
<p>{{ entry.rating }}</p>
</b-col>
<b-col>
<b-container v-if="observationData != null" fluid>
<b-card no-body>
<b-tabs pills card vertical>
<b-tab v-for="(observation, index) in observationData" v-bind:key="index">
<p slot="title">{{ observation.date }}<br/>{{ observation.whom }} {{ observation.type }}<br/>{{ observation.observed }}</p>
<div v-for="(entry, index2) in observation.entries" v-bind:key="index2">
<p>{{ entry.type }} - {{ entry.rating }}</p>
<b-form-group label="Strengths">
<b-form-textarea :value="entry.strengths" readonly>
</b-form-textarea>
@ -47,15 +62,18 @@
<b-form-textarea :value="entry.improvements" readonly>
</b-form-textarea>
</b-form-group>
</b-col>
</b-row>
</b-row>
</b-container>
</div>
</b-tab>
</b-tabs>
</b-card>
</b-container>
</b-container>
</template>
<script>
import Vue from "vue";
var moment = require("moment");
export default {
name: "viewobservations",
data: function () {
@ -69,14 +87,16 @@ export default {
siteSelection: null,
siteOptions: [],
tutorSelection: null,
tutorOptions: null,
tutorOptions: [],
whom: null,
observationData: null
observationData: null,
errorStatus: null,
errorMessage: null
};
},
mounted() {
Vue.axios
.get("/site/all")
.get("/site")
.then(response => {
this.siteOptions = response.data;
})
@ -87,7 +107,7 @@ export default {
methods: {
getFiltered: function () {
Vue.axios
.get("/observations", {
.post("/observations", {
site: this.siteSelection,
tutor: this.tutorSelection,
startDate: moment(this.startDate).format("YYYY-MM-DD"),
@ -95,7 +115,7 @@ export default {
whom: this.whom
})
.then(response => {
this.chartData = response.data;
this.observationData = response.data;
})
.catch(error => {
this.errorStatus = error.response.status;
@ -108,6 +128,22 @@ export default {
},
changeEndDate: function (e) {
this.endDate = e.date;
},
getTutors: function () {
if (this.siteSelection != null) {
Vue.axios
.get("/site/" + this.siteSelection + "/tutors")
.then(response => {
this.tutorOptions = response.data;
});
}
}
},
watch: {
siteSelection: function () {
this.tutorOptions = [];
this.tutorSelection = null;
this.getTutors();
}
}
};