commit
ab752160c9
@ -21,9 +21,9 @@
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
<java.version>1.8</java.version>
|
||||
<kotlin.version>1.3.40</kotlin.version>
|
||||
<kotlin.version>1.4.10</kotlin.version>
|
||||
<kotlin.compiler.jvmTarget>1.8</kotlin.compiler.jvmTarget>
|
||||
<spring.version>2.1.6.RELEASE</spring.version>
|
||||
<spring.version>2.3.3.RELEASE</spring.version>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
@ -103,11 +103,12 @@
|
||||
<dependency>
|
||||
<groupId>joda-time</groupId>
|
||||
<artifactId>joda-time</artifactId>
|
||||
<version>2.10.6</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-joda</artifactId>
|
||||
<version>2.9.9</version>
|
||||
<version>2.11.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jadira.usertype</groupId>
|
||||
@ -117,19 +118,23 @@
|
||||
<dependency>
|
||||
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||
<artifactId>caffeine</artifactId>
|
||||
<version>2.6.2</version>
|
||||
<version>2.8.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.simplejavamail</groupId>
|
||||
<artifactId>simple-java-mail</artifactId>
|
||||
<version>6.0.3</version>
|
||||
<version>6.4.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlinx</groupId>
|
||||
<artifactId>kotlinx-coroutines-core</artifactId>
|
||||
<version>1.3.0-M2</version>
|
||||
<version>1.3.9</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.validation</groupId>
|
||||
<artifactId>validation-api</artifactId>
|
||||
<version>2.0.1.Final</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
|
@ -93,7 +93,7 @@ data class Observation(
|
||||
) {
|
||||
fun toCsvFormat(): String {
|
||||
fun escapeSpecialCharacters(data: String): String {
|
||||
return data.replace("\"", "\"\"")
|
||||
return data.replace("\"", "\"\"").replace("\n", "")
|
||||
}
|
||||
val dataPortion = "${date.toString("dd/MM/yyyy")},\"$person, ${type.name} ${scenarios.joinToString { it.title }}\"," +
|
||||
"\"Training\",\"Operations - Shift Operations\",\"${site.name}\",\"N/A\"," +
|
||||
@ -121,7 +121,7 @@ data class Observation(
|
||||
}
|
||||
|
||||
private fun roundScore(input: Double?): String {
|
||||
if (input != null) {
|
||||
if (input != null && input > 0) {
|
||||
return input.roundToInt().toString()
|
||||
}
|
||||
return ""
|
||||
@ -185,7 +185,7 @@ data class Scenario(
|
||||
|
||||
) {
|
||||
private fun ratingValid(rating: Byte): Boolean {
|
||||
return rating in 1..5
|
||||
return rating in 0..5
|
||||
}
|
||||
fun ratingsAllValid(): Boolean {
|
||||
return ratingValid(monitoringRating) &&
|
||||
|
@ -1,218 +0,0 @@
|
||||
package uk.co.neviyn.observationdatabase.controller
|
||||
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import org.joda.time.LocalDate
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.core.env.Environment
|
||||
import org.springframework.core.env.get
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.messaging.simp.SimpMessagingTemplate
|
||||
import org.springframework.web.bind.annotation.CrossOrigin
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.PathVariable
|
||||
import org.springframework.web.bind.annotation.PostMapping
|
||||
import org.springframework.web.bind.annotation.RequestBody
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import org.springframework.web.server.ResponseStatusException
|
||||
import uk.co.neviyn.observationdatabase.Email
|
||||
import uk.co.neviyn.observationdatabase.GroupObservation
|
||||
import uk.co.neviyn.observationdatabase.GroupObservationInit
|
||||
import uk.co.neviyn.observationdatabase.GroupSessionManager
|
||||
import uk.co.neviyn.observationdatabase.Observation
|
||||
import uk.co.neviyn.observationdatabase.ObservationRepository
|
||||
import uk.co.neviyn.observationdatabase.SiteRepository
|
||||
import uk.co.neviyn.observationdatabase.TutorRepository
|
||||
import java.net.Inet4Address
|
||||
import java.net.NetworkInterface
|
||||
import javax.validation.Valid
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/grpob")
|
||||
@CrossOrigin
|
||||
class GroupSessionController {
|
||||
|
||||
private val logger: Logger = LoggerFactory.getLogger(javaClass)!!
|
||||
|
||||
@Autowired
|
||||
lateinit var environment: Environment
|
||||
@Autowired
|
||||
lateinit var siteRepository: SiteRepository
|
||||
@Autowired
|
||||
lateinit var tutorRepository: TutorRepository
|
||||
@Autowired
|
||||
lateinit var observationRepository: ObservationRepository
|
||||
@Autowired
|
||||
lateinit var websocketMessenger: SimpMessagingTemplate
|
||||
@Autowired
|
||||
lateinit var mailer: Email
|
||||
|
||||
/**
|
||||
* Start a new Group Observation session
|
||||
*/
|
||||
@PostMapping("/start")
|
||||
fun startGroupObservation(@Valid @RequestBody initData: GroupObservationInit): Map<String, String> {
|
||||
val site = siteRepository.findById(initData.site)
|
||||
val tutors = tutorRepository.findAllById(initData.tutors).toSet()
|
||||
if (!site.isPresent) {
|
||||
logger.info("Attempted to add Observation without a site.")
|
||||
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Site required")
|
||||
}
|
||||
if (tutors.isEmpty() || tutors.size != initData.tutors.size) {
|
||||
logger.info("Attempted to add Observation without a tutor")
|
||||
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Tutor required")
|
||||
}
|
||||
val sessionId = GroupSessionManager.startNewSession(site.get(), tutors, initData.type, initData.scenarioTitles)
|
||||
return getConnectionDetails().plus("id" to sessionId.toString())
|
||||
}
|
||||
|
||||
/**
|
||||
* Reconnect to the last Group Observation session
|
||||
*/
|
||||
@GetMapping("/recover")
|
||||
fun reconnectToGroupObservation(): Map<String, Any> {
|
||||
if (GroupSessionManager.isValid()) {
|
||||
logger.info("Previous group observation requested, current id ${GroupSessionManager.sessionId}")
|
||||
return getConnectionDetails().plus(mapOf("id" to GroupSessionManager.sessionId.toString(), "scenarios" to GroupSessionManager.asScenarioView()))
|
||||
}
|
||||
logger.info("Tried to recover a session but no session is active")
|
||||
throw ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "No group session is currently running")
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether there is an active group session with [id]
|
||||
*/
|
||||
@GetMapping("/valid/{id}")
|
||||
fun checkGroupObservationValidityById(@PathVariable id: Int): Map<String, Any> {
|
||||
if (GroupSessionManager.isValid(id)) {
|
||||
return mapOf("titles" to GroupSessionManager.scenarioTitles!!)
|
||||
}
|
||||
if (GroupSessionManager.isValid()) {
|
||||
logger.warn("Group observation requested with id $id but id is currently ${GroupSessionManager.sessionId})")
|
||||
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Session ID incorrect")
|
||||
}
|
||||
logger.warn("Group observation requested with id $id but there is no valid session")
|
||||
throw ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "No group session is currently running")
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether there is any valid group session
|
||||
*/
|
||||
@GetMapping("/valid")
|
||||
fun checkGroupObservationValidity(): Boolean {
|
||||
return GroupSessionManager.isValid()
|
||||
}
|
||||
|
||||
/**
|
||||
* Save an Observation to the database
|
||||
*/
|
||||
fun saveObservation(observation: Observation): Observation {
|
||||
logger.debug("Saving new Observation to database")
|
||||
val committedObservation = observationRepository.save(observation)
|
||||
logger.debug("Adding Observation data to Tutor records")
|
||||
committedObservation.tutors.forEach {
|
||||
it.observations.add(committedObservation)
|
||||
tutorRepository.save(it)
|
||||
}
|
||||
logger.debug("Observation addition completed")
|
||||
return committedObservation
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current observation data for a user with [name] in the current session.
|
||||
*/
|
||||
@GetMapping("/participant/{name}")
|
||||
fun getParticipantData(@PathVariable name: String): GroupObservation {
|
||||
if (GroupSessionManager.participantExistsInSession(name))
|
||||
return GroupSessionManager.getObservationDataForParticipant(name)!!
|
||||
throw ResponseStatusException(HttpStatus.NOT_FOUND, "No participant with the name:'$name'")
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit an observation to be added to the session state and actual database
|
||||
*/
|
||||
@PostMapping("/submit")
|
||||
fun addGroupObservation(@Valid @RequestBody observationData: GroupObservation) {
|
||||
val titles = observationData.scenarios.map { it.title }
|
||||
if (GroupSessionManager.scenarioTitles!!.size != titles.size || !GroupSessionManager.scenarioTitles!!.containsAll(titles)) {
|
||||
logger.warn("Received scenario data but titles did not match\nInput:$titles\nRequired:${GroupSessionManager.scenarioTitles}")
|
||||
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Submission data contains non-matching title(s)")
|
||||
}
|
||||
GroupSessionManager.updateObservationData(observationData)
|
||||
websocketMessenger.convertAndSend("/ws/scenarios", mapOf("scenarios" to GroupSessionManager.asScenarioView()))
|
||||
}
|
||||
|
||||
@PostMapping("/complete")
|
||||
fun pushObservationsToDatabase(): Map<String, String> {
|
||||
if (GroupSessionManager.isValid() && GroupSessionManager.dataComplete()) {
|
||||
logger.info("Completing session ${GroupSessionManager.sessionId}")
|
||||
val tutors = tutorRepository.findAllById(GroupSessionManager.tutors!!).toSet()
|
||||
val observations = mutableListOf<Observation>()
|
||||
GroupSessionManager.observations.values.forEach { x ->
|
||||
val observation = Observation(
|
||||
site = GroupSessionManager.site!!,
|
||||
date = LocalDate.now(),
|
||||
type = GroupSessionManager.trainingType!!,
|
||||
observed = x.scenarios.joinToString { it.title },
|
||||
monitoring = x.scenarios.map { it.monitoringRating }.average(),
|
||||
conservatism = x.scenarios.map { it.conservatismRating }.average(),
|
||||
controlProcedural = x.scenarios.map { it.controlProceduralRating }.average(),
|
||||
control = x.scenarios.map { it.controlRating }.average(),
|
||||
teamworkCommunications = x.scenarios.map { it.teamworkCommunicationsRating }.average(),
|
||||
teamworkLeadership = x.scenarios.map { it.teamworkLeadershipRating }.average(),
|
||||
teamworkWorkload = x.scenarios.map { it.teamworkWorkloadRating }.average(),
|
||||
knowledge = x.scenarios.map { it.knowledgeRating }.average(),
|
||||
scenarios = x.scenarios,
|
||||
tutors = tutors,
|
||||
person = x.person.toUpperCase()
|
||||
)
|
||||
saveObservation(observation)
|
||||
observations.add(observation)
|
||||
}
|
||||
GroupSessionManager.invalidate()
|
||||
if (this::environment.isInitialized && environment.getProperty("smtp.autosendoncomplete")!!.toBoolean())
|
||||
GlobalScope.launch {
|
||||
if (::mailer.isInitialized)
|
||||
mailer.sendObservationData(observations)
|
||||
else
|
||||
logger.error("Mailer has not been initialized.")
|
||||
}
|
||||
else
|
||||
logger.debug("Environment or Mailer is unavailable")
|
||||
websocketMessenger.convertAndSend("/ws/status", mapOf("status" to "complete"))
|
||||
return mapOf("success" to "The submission was successfully completed.")
|
||||
} else if (!GroupSessionManager.dataComplete()) {
|
||||
logger.info("Tried to complete a session whilst data was incomplete\n${GroupSessionManager.observations}")
|
||||
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Data is incomplete")
|
||||
}
|
||||
throw ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "No valid session")
|
||||
}
|
||||
|
||||
/**
|
||||
* Get details needed to connect to this group session on the LAN
|
||||
*/
|
||||
@GetMapping("/address")
|
||||
fun getConnectionDetails(): Map<String, String> {
|
||||
var ipv4: String? = null
|
||||
var retryCount = 0
|
||||
while (ipv4 == null && retryCount < 3) {
|
||||
ipv4 = NetworkInterface.getNetworkInterfaces().asSequence()
|
||||
.filter { !it.isLoopback }.map { x -> x.inetAddresses.asSequence()
|
||||
.filter { it is Inet4Address }.map { it.hostAddress } }.flatten().firstOrNull()
|
||||
retryCount++
|
||||
Thread.sleep(1_000) // Sleep for 1 second
|
||||
}
|
||||
return if (ipv4 != null && this::environment.isInitialized)
|
||||
mapOf("ip" to ipv4, "port" to environment["local.server.port"]!!)
|
||||
else if (ipv4 == null) {
|
||||
logger.error("IP Address could not be determined")
|
||||
mapOf("error" to "Could not determine IP Address")
|
||||
} else {
|
||||
logger.error("Port could not be determined, environment not initialised")
|
||||
mapOf("error" to "Could not determine port")
|
||||
}
|
||||
}
|
||||
}
|
@ -1,13 +1,11 @@
|
||||
package uk.co.neviyn.observationdatabase
|
||||
|
||||
import junit.framework.TestCase.assertFalse
|
||||
import junit.framework.TestCase.assertNotNull
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.boot.test.context.SpringBootTest
|
||||
import org.springframework.test.context.junit4.SpringRunner
|
||||
import uk.co.neviyn.observationdatabase.controller.GroupSessionController
|
||||
import uk.co.neviyn.observationdatabase.controller.ObservationsController
|
||||
|
||||
@RunWith(SpringRunner::class)
|
||||
@ -16,13 +14,9 @@ class ObservationDatabaseApplicationTests {
|
||||
|
||||
@Autowired
|
||||
lateinit var observationsController: ObservationsController
|
||||
@Autowired
|
||||
lateinit var groupSessionController: GroupSessionController
|
||||
|
||||
@Test
|
||||
fun contextLoads() {
|
||||
assertNotNull(observationsController)
|
||||
assertNotNull(groupSessionController)
|
||||
assertFalse(GroupSessionManager.isValid())
|
||||
}
|
||||
}
|
||||
|
@ -1,167 +0,0 @@
|
||||
package uk.co.neviyn.observationdatabase.controller
|
||||
|
||||
import org.joda.time.LocalDate
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.ArgumentMatchers.any
|
||||
import org.mockito.InjectMocks
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito
|
||||
import org.mockito.Mockito.times
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.junit.MockitoJUnitRunner
|
||||
import org.springframework.messaging.simp.SimpMessagingTemplate
|
||||
import org.springframework.web.server.ResponseStatusException
|
||||
import uk.co.neviyn.observationdatabase.Email
|
||||
import uk.co.neviyn.observationdatabase.GroupObservation
|
||||
import uk.co.neviyn.observationdatabase.GroupObservationInit
|
||||
import uk.co.neviyn.observationdatabase.GroupSessionManager
|
||||
import uk.co.neviyn.observationdatabase.Observation
|
||||
import uk.co.neviyn.observationdatabase.ObservationRepository
|
||||
import uk.co.neviyn.observationdatabase.Scenario
|
||||
import uk.co.neviyn.observationdatabase.Site
|
||||
import uk.co.neviyn.observationdatabase.SiteRepository
|
||||
import uk.co.neviyn.observationdatabase.TrainingType
|
||||
import uk.co.neviyn.observationdatabase.Tutor
|
||||
import uk.co.neviyn.observationdatabase.TutorRepository
|
||||
import java.util.Optional
|
||||
|
||||
@RunWith(MockitoJUnitRunner::class)
|
||||
class GroupSessionControllerTest {
|
||||
|
||||
@InjectMocks
|
||||
lateinit var controller: GroupSessionController
|
||||
|
||||
@Mock
|
||||
lateinit var websocketMessenger: SimpMessagingTemplate
|
||||
@Mock
|
||||
lateinit var siteRepository: SiteRepository
|
||||
@Mock
|
||||
lateinit var tutorRepository: TutorRepository
|
||||
@Mock
|
||||
lateinit var observationRepository: ObservationRepository
|
||||
@Mock
|
||||
lateinit var mailer: Email
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
GroupSessionManager.invalidate()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStartSession() {
|
||||
val site = Site(1, "Test site")
|
||||
Mockito.doReturn(Optional.of(site)).`when`(siteRepository).findById(1)
|
||||
Mockito.doReturn(listOf(Tutor(1, "Mr X", site))).`when`(tutorRepository).findAllById(listOf(1))
|
||||
controller.startGroupObservation(GroupObservationInit(1, TrainingType.INITIAL, listOf(1), listOf("Sample title")))
|
||||
assertTrue(GroupSessionManager.isValid())
|
||||
}
|
||||
|
||||
@Test(expected = ResponseStatusException::class)
|
||||
fun testStartSession_NoSite() {
|
||||
controller.startGroupObservation(GroupObservationInit(0, TrainingType.INITIAL, listOf(1), listOf("Sample title")))
|
||||
}
|
||||
|
||||
@Test(expected = ResponseStatusException::class)
|
||||
fun testStartSession_NoTutor() {
|
||||
val site = Site(1, "Test site")
|
||||
Mockito.doReturn(Optional.of(site)).`when`(siteRepository).findById(1)
|
||||
controller.startGroupObservation(GroupObservationInit(1, TrainingType.INITIAL, listOf(0), listOf("Sample title")))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRecoverSession() {
|
||||
val site = Site(1, "Test site")
|
||||
Mockito.doReturn(Optional.of(site)).`when`(siteRepository).findById(1)
|
||||
Mockito.doReturn(listOf(Tutor(1, "Mr X", site))).`when`(tutorRepository).findAllById(listOf(1))
|
||||
controller.startGroupObservation(GroupObservationInit(1, TrainingType.INITIAL, listOf(1), listOf("Sample title")))
|
||||
assertNotNull(controller.reconnectToGroupObservation())
|
||||
}
|
||||
|
||||
@Test(expected = ResponseStatusException::class)
|
||||
fun testRecoverSession_NoActiveSession() {
|
||||
controller.reconnectToGroupObservation()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCompleteSession() {
|
||||
val site = Site(1, "Test site")
|
||||
val tutor = Tutor(1, "Mr X", site)
|
||||
Mockito.doReturn(Optional.of(site)).`when`(siteRepository).findById(1)
|
||||
Mockito.doReturn(listOf(tutor)).`when`(tutorRepository).findAllById(listOf(1))
|
||||
val person = "A Student"
|
||||
val scenario = Scenario(0, "Sample title", 5, "", "", 5, "", "", 5, "", "", 5, "", "", 5, "", "", 5, "", "", 5, "", "", 5, "", "")
|
||||
Mockito.doReturn(Observation(1, site, LocalDate.now(), TrainingType.INITIAL, "Sample title", 5.0, 5.0, 5.0, 5.0, 5.0, 5.0, 5.0, 5.0, listOf(scenario), setOf(tutor), person)).`when`(observationRepository).save(any())
|
||||
controller.startGroupObservation(GroupObservationInit(1, TrainingType.INITIAL, listOf(1), listOf("Sample title")))
|
||||
controller.addGroupObservation(GroupObservation("A Student", listOf(scenario)))
|
||||
controller.pushObservationsToDatabase()
|
||||
verify(observationRepository, times(1)).save(any())
|
||||
verify(websocketMessenger, times(1)).convertAndSend("/ws/scenarios", mapOf("scenarios" to mapOf("Sample title" to listOf(scenario.copy(title = "A Student")))))
|
||||
assertEquals(1, tutor.observations.size)
|
||||
}
|
||||
|
||||
@Test(expected = ResponseStatusException::class)
|
||||
fun testCompleteSession_NoObservationData() {
|
||||
val site = Site(1, "Test site")
|
||||
Mockito.doReturn(Optional.of(site)).`when`(siteRepository).findById(1)
|
||||
Mockito.doReturn(listOf(Tutor(1, "Mr X", site))).`when`(tutorRepository).findAllById(listOf(1))
|
||||
controller.startGroupObservation(GroupObservationInit(1, TrainingType.INITIAL, listOf(1), listOf("Sample title")))
|
||||
controller.pushObservationsToDatabase()
|
||||
}
|
||||
|
||||
@Test(expected = ResponseStatusException::class)
|
||||
fun testCompleteSession_PartialObservationData() {
|
||||
val site = Site(1, "Test site")
|
||||
Mockito.doReturn(Optional.of(site)).`when`(siteRepository).findById(1)
|
||||
Mockito.doReturn(listOf(Tutor(1, "Mr X", site))).`when`(tutorRepository).findAllById(listOf(1))
|
||||
controller.startGroupObservation(GroupObservationInit(1, TrainingType.INITIAL, listOf(1), listOf("Sample title")))
|
||||
controller.addGroupObservation(GroupObservation("A Student", listOf(Scenario(0, "Sample title", 0, "", "", 5, "", "", 5, "", "", 5, "", "", 5, "", "", 5, "", "", 5, "", "", 5, "", ""))))
|
||||
verify(websocketMessenger, times(1)).convertAndSend("/ws/scenarios", mapOf("scenarios" to mapOf("Sample title" to listOf(Scenario(0, "A Student", 0, "", "", 5, "", "", 5, "", "", 5, "", "", 5, "", "", 5, "", "", 5, "", "", 5, "", "")))))
|
||||
controller.pushObservationsToDatabase()
|
||||
}
|
||||
|
||||
@Test(expected = ResponseStatusException::class)
|
||||
fun testSubmit_NonMatchingTitles() {
|
||||
val site = Site(1, "Test site")
|
||||
Mockito.doReturn(Optional.of(site)).`when`(siteRepository).findById(1)
|
||||
Mockito.doReturn(listOf(Tutor(1, "Mr X", site))).`when`(tutorRepository).findAllById(listOf(1))
|
||||
controller.startGroupObservation(GroupObservationInit(1, TrainingType.INITIAL, listOf(1), listOf("Sample title")))
|
||||
controller.addGroupObservation(GroupObservation("A Student", listOf(Scenario(0, "Different Title", 5, "", "", 5, "", "", 5, "", "", 5, "", "", 5, "", "", 5, "", "", 5, "", "", 5, "", ""))))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSubmit_PartialData() {
|
||||
val site = Site(1, "Test site")
|
||||
Mockito.doReturn(Optional.of(site)).`when`(siteRepository).findById(1)
|
||||
Mockito.doReturn(listOf(Tutor(1, "Mr X", site))).`when`(tutorRepository).findAllById(listOf(1))
|
||||
controller.startGroupObservation(GroupObservationInit(1, TrainingType.INITIAL, listOf(1), listOf("Sample title")))
|
||||
controller.addGroupObservation(GroupObservation("A Student", listOf(Scenario(0, "Sample title", 0, "", "", 5, "", "", 5, "", "", 5, "", "", 5, "", "", 5, "", "", 5, "", "", 5, "", ""))))
|
||||
assertEquals(1, GroupSessionManager.observations.size)
|
||||
assertEquals("A Student", GroupSessionManager.observations.keys.first())
|
||||
assertEquals("A Student", GroupSessionManager.observations.values.first().person)
|
||||
}
|
||||
|
||||
@Test(expected = ResponseStatusException::class)
|
||||
fun testGetParticipantData_NoParticipant() {
|
||||
controller.getParticipantData("Someone")
|
||||
}
|
||||
|
||||
@Test(expected = ResponseStatusException::class)
|
||||
fun testGetParticipantData_WrongName() {
|
||||
val testData = GroupObservation("A Student", listOf(Scenario(0, "Sample title", 5, "", "", 5, "", "", 5, "", "", 5, "", "", 5, "", "", 5, "", "", 5, "", "", 5, "", "")))
|
||||
GroupSessionManager.observations["A Student"] = testData
|
||||
controller.getParticipantData("Another Student")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetParticipantData() {
|
||||
val testData = GroupObservation("A Student", listOf(Scenario(0, "Sample title", 5, "", "", 5, "", "", 5, "", "", 5, "", "", 5, "", "", 5, "", "", 5, "", "", 5, "", "")))
|
||||
GroupSessionManager.observations["A Student"] = testData
|
||||
val output = controller.getParticipantData("A Student")
|
||||
assertEquals(testData, output)
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@
|
||||
<b-row>
|
||||
<b-col cols="3">
|
||||
<p>{{ description }}</p>
|
||||
<h4 v-bind:class="{ scorewarn: rating < 3 }">
|
||||
<h4 v-bind:class="{ scorewarn: rating < 3}" v-if="rating > 0">
|
||||
{{ rating }}
|
||||
</h4>
|
||||
</b-col>
|
||||
|
@ -3,11 +3,12 @@
|
||||
<b-form-radio-group
|
||||
buttons
|
||||
button-variant="outline-info"
|
||||
size="lg"
|
||||
size="custom"
|
||||
v-model="propModel"
|
||||
invalid-feedback="Please select a score."
|
||||
required
|
||||
>
|
||||
<b-form-radio button-variant="0" value="0"> </b-form-radio>
|
||||
<b-form-radio button-variant="1" value="1">1</b-form-radio>
|
||||
<b-form-radio button-variant="2" value="2">2</b-form-radio>
|
||||
<b-form-radio button-variant="3" value="3">3</b-form-radio>
|
||||
@ -27,13 +28,16 @@ export default {
|
||||
},
|
||||
set(value) {
|
||||
this.$emit("newselection", value);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
b-form-radio-group {
|
||||
font-family: monospace;
|
||||
}
|
||||
.btn-1 {
|
||||
color: #ffffff;
|
||||
background-color: #cc3232;
|
||||
@ -243,4 +247,51 @@ fieldset[disabled] .btn-5.active {
|
||||
color: #2dc937;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
.btn-0 {
|
||||
color: #ffffff;
|
||||
background-color: #6c757d;
|
||||
border-color: #000000;
|
||||
}
|
||||
.btn-0:hover,
|
||||
.btn-0:focus,
|
||||
.btn-0:active,
|
||||
.btn-0.active,
|
||||
.open .dropdown-toggle.btn-0 {
|
||||
color: #ffffff;
|
||||
background-color: #4285f4;
|
||||
border-color: #000000;
|
||||
}
|
||||
.btn-0:active,
|
||||
.btn-0.active,
|
||||
.open .dropdown-toggle.btn-0 {
|
||||
background-image: none;
|
||||
}
|
||||
.btn-0.disabled,
|
||||
.btn-0[disabled],
|
||||
fieldset[disabled] .btn-0,
|
||||
.btn-0.disabled:hover,
|
||||
.btn-0[disabled]:hover,
|
||||
fieldset[disabled] .btn-0:hover,
|
||||
.btn-0.disabled:focus,
|
||||
.btn-0[disabled]:focus,
|
||||
fieldset[disabled] .btn-0:focus,
|
||||
.btn-0.disabled:active,
|
||||
.btn-0[disabled]:active,
|
||||
fieldset[disabled] .btn-0:active,
|
||||
.btn-0.disabled.active,
|
||||
.btn-0[disabled].active,
|
||||
fieldset[disabled] .btn-0.active {
|
||||
background-color: #6c757d;
|
||||
border-color: #4285f4;
|
||||
}
|
||||
.btn-0 .badge {
|
||||
color: #6c757d;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.btn-custom {
|
||||
padding: 10px 13px;
|
||||
font-size: 20px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
</style>
|
||||
|
@ -9,8 +9,6 @@ const ViewObservations = () => import("./views/ViewObservations.vue");
|
||||
const ObservationComplete = () => import("./views/ObservationComplete.vue");
|
||||
const DBError = () => import("./views/DatabaseUnavailable.vue");
|
||||
const About = () => import("./views/About.vue");
|
||||
const GroupSession = () => import("./views/GroupSession.vue");
|
||||
const GroupSessionInput = () => import("./views/GroupSessionInput.vue");
|
||||
|
||||
Vue.use(Router);
|
||||
|
||||
@ -60,17 +58,6 @@ export default new Router({
|
||||
path: "/about",
|
||||
name: "about",
|
||||
component: About
|
||||
},
|
||||
{
|
||||
path: "/groupsession",
|
||||
name: "groupsession",
|
||||
component: GroupSession
|
||||
},
|
||||
{
|
||||
path: "/groupsession/:id",
|
||||
name: "groupsessioninput",
|
||||
component: GroupSessionInput,
|
||||
props: true
|
||||
}
|
||||
]
|
||||
});
|
||||
|
@ -1,375 +0,0 @@
|
||||
<template>
|
||||
<b-container fluid>
|
||||
<b-row v-if="active">
|
||||
<b-col cols="12" md="3">
|
||||
<vue-qrcode v-model="qrdata" :options="{ width: 250 }"></vue-qrcode>
|
||||
<p>
|
||||
Scan the code or navigate to
|
||||
<br />
|
||||
{{ qrdata }}
|
||||
</p>
|
||||
</b-col>
|
||||
<b-col cols="12" md="9">
|
||||
<b-card-group v-for="(item, index) in data" v-bind:key="index">
|
||||
<b-card
|
||||
border-variant="secondary"
|
||||
header-border-variant="secondary"
|
||||
:header="index"
|
||||
>
|
||||
<b-card-text>
|
||||
<b-table :fields="tableFields" :items="item"></b-table>
|
||||
</b-card-text>
|
||||
</b-card>
|
||||
</b-card-group>
|
||||
</b-col>
|
||||
<b-col>
|
||||
<b-button size="lg" variant="primary" v-on:click="showCompletionModal()"
|
||||
>Finish Session</b-button
|
||||
>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-row v-else-if="complete">
|
||||
<b-col>
|
||||
<b-row>
|
||||
<b-col>
|
||||
<h2>Session Complete</h2>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-row>
|
||||
<b-col>
|
||||
<p>
|
||||
Observation data for this session is now saved in the database.
|
||||
</p>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-row>
|
||||
<b-col>
|
||||
<b-button
|
||||
size="lg"
|
||||
variant="primary"
|
||||
v-on:click="window.location.reload()"
|
||||
>Start a New Session</b-button
|
||||
>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-row v-else>
|
||||
<b-col>
|
||||
<b-form id="submission-form" novalidate @submit="onSubmit">
|
||||
<b-row align-h="center">
|
||||
<b-col>
|
||||
<b-form-group label="Site">
|
||||
<b-form-select
|
||||
v-model="site"
|
||||
: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="Type">
|
||||
<b-form-select
|
||||
v-model="type"
|
||||
style="text-align:center;"
|
||||
required
|
||||
>
|
||||
<option :value="null">Select a training type</option>
|
||||
<option value="INITIAL">INITIAL</option>
|
||||
<option value="CONTINUING">CONTINUING</option>
|
||||
</b-form-select>
|
||||
</b-form-group>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-row align-h="center">
|
||||
<b-col>
|
||||
<b-form-group label="Tutor(s)">
|
||||
<p v-if="site == null">Select a site first.</p>
|
||||
<b-form-checkbox-group
|
||||
v-model="tutors"
|
||||
:options="tutorOptions"
|
||||
></b-form-checkbox-group>
|
||||
</b-form-group>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-row
|
||||
v-for="(item, index) in scenarioTitles"
|
||||
v-bind:key="index"
|
||||
class="border bottom-buffer"
|
||||
fluid
|
||||
>
|
||||
<b-col>
|
||||
<b-form-input
|
||||
v-model="item.data"
|
||||
type="text"
|
||||
placeholder="Enter scenario description."
|
||||
></b-form-input>
|
||||
</b-col>
|
||||
<b-col cols="1">
|
||||
<b-button
|
||||
v-on:click="scenarioTitles.splice(index, 1)"
|
||||
variant="danger"
|
||||
>
|
||||
<b>Delete</b>
|
||||
</b-button>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-row align-h="center">
|
||||
<b-col>
|
||||
<b-button
|
||||
v-on:click="scenarioTitles.push({ data: '' })"
|
||||
size="lg"
|
||||
variant="primary"
|
||||
>Add Another Scenario</b-button
|
||||
>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-row>
|
||||
<b-col>
|
||||
<br />
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-row align-h="center">
|
||||
<b-col>
|
||||
<b-button type="submit" size="lg" variant="primary"
|
||||
>Start</b-button
|
||||
>
|
||||
</b-col>
|
||||
<b-col>
|
||||
<b-button
|
||||
size="lg"
|
||||
variant="secondary"
|
||||
v-on:click="connectToPrevious()"
|
||||
>Connect to Previous Session</b-button
|
||||
>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-form>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-modal
|
||||
id="submissionModal"
|
||||
ref="submissionModal"
|
||||
title="Enter password to confirm session start"
|
||||
@ok="handleOk"
|
||||
@shown="clearPassword"
|
||||
>
|
||||
<form @submit.stop.prevent="handleSubmit">
|
||||
<b-form-input
|
||||
type="password"
|
||||
placeholder="Enter password"
|
||||
v-model="submitPassword"
|
||||
></b-form-input>
|
||||
</form>
|
||||
</b-modal>
|
||||
<b-modal
|
||||
id="completionModal"
|
||||
ref="completionModal"
|
||||
title="Enter password to confirm submission"
|
||||
@ok="handleSubmitComplete"
|
||||
@shown="clearPassword"
|
||||
>
|
||||
<form @submit.stop.prevent="handleSubmit">
|
||||
<b-form-input
|
||||
type="password"
|
||||
placeholder="Enter password"
|
||||
v-model="submitPassword"
|
||||
></b-form-input>
|
||||
</form>
|
||||
</b-modal>
|
||||
</b-container>
|
||||
</template>
|
||||
<script>
|
||||
import Vue from "vue";
|
||||
import VueQrcode from "@chenfengyuan/vue-qrcode";
|
||||
import webstomp from "webstomp-client";
|
||||
import SockJS from "sockjs-client";
|
||||
export default {
|
||||
name: "groupsession",
|
||||
title: "Group Session",
|
||||
components: { VueQrcode },
|
||||
data: function() {
|
||||
return {
|
||||
active: false,
|
||||
stompclient: null,
|
||||
complete: false,
|
||||
qrdata: "N/A",
|
||||
data: [{ person: { name: "No data yet received." } }],
|
||||
site: null,
|
||||
tutors: null,
|
||||
siteOptions: [],
|
||||
tutorOptions: [],
|
||||
scenarioTitles: [{ data: "" }, { data: "" }, { data: "" }],
|
||||
type: null,
|
||||
submitPassword: null,
|
||||
tableFields: [
|
||||
{ key: "title", label: "Name" },
|
||||
{ key: "monitoring.rating", label: "Monitoring" },
|
||||
{ key: "controlProcedural.rating", label: "Control Procedural" },
|
||||
{ key: "control.rating", label: "Control" },
|
||||
{ key: "conservatism.rating", label: "Conservatism" },
|
||||
{
|
||||
key: "teamworkCommunications.rating",
|
||||
label: "Teamwork Communications"
|
||||
},
|
||||
{ key: "teamworkLeadership.rating", label: "Teamwork Leadership" },
|
||||
{ key: "teamworkWorkload.rating", label: "Teamwork Workload" },
|
||||
{ key: "knowledge.rating", label: "Knowledge" }
|
||||
]
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
site: function() {
|
||||
this.tutorOptions = [];
|
||||
this.getTutors();
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
Vue.axios
|
||||
.get("/site")
|
||||
.then(response => {
|
||||
this.siteOptions = response.data;
|
||||
})
|
||||
.catch(error => {
|
||||
if (error.response.status === 404) {
|
||||
//this.$router.push("/dberror");
|
||||
return;
|
||||
}
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
startSession: function() {
|
||||
var self = this;
|
||||
let axiosConfig = {
|
||||
auth: {
|
||||
username: "admin",
|
||||
password: this.submitPassword
|
||||
}
|
||||
};
|
||||
Vue.axios
|
||||
.post(
|
||||
"/grpob/start",
|
||||
{
|
||||
site: self.site,
|
||||
tutors: self.tutors,
|
||||
scenarioTitles: self.scenarioTitles.map(x => x.data),
|
||||
type: self.type
|
||||
},
|
||||
axiosConfig
|
||||
)
|
||||
.then(function(response) {
|
||||
if (!("error" in response.data)) {
|
||||
self.setupSession(response.data);
|
||||
}
|
||||
})
|
||||
.catch(function() {
|
||||
self.active = false;
|
||||
});
|
||||
},
|
||||
connectToPrevious: function() {
|
||||
var self = this;
|
||||
Vue.axios
|
||||
.get("/grpob/recover")
|
||||
.then(function(response) {
|
||||
if (!("error" in response.data)) {
|
||||
self.setupSession(response.data);
|
||||
}
|
||||
})
|
||||
.catch(function(error) {
|
||||
if (error.response.status === 404) {
|
||||
this.$router.push("/dberror");
|
||||
return;
|
||||
}
|
||||
});
|
||||
},
|
||||
setupSession: function(rdata) {
|
||||
var self = this;
|
||||
self.qrdata = `http://${rdata.ip}:${rdata.port}/#/groupsession/${
|
||||
rdata.id
|
||||
}`;
|
||||
self.active = true;
|
||||
self.stompclient = webstomp.over(
|
||||
new SockJS("http://127.0.0.1:8080/websocket", {
|
||||
heartbeat: false
|
||||
})
|
||||
);
|
||||
self.stompclient.connect([], function() {
|
||||
self.stompclient.subscribe("/ws/scenarios", function(incomingData) {
|
||||
self.data = JSON.parse(incomingData.body).scenarios;
|
||||
});
|
||||
});
|
||||
self.$refs.submissionModal.hide();
|
||||
self.clearPassword();
|
||||
},
|
||||
getTutors: function() {
|
||||
if (this.site != null) {
|
||||
Vue.axios
|
||||
.get("/site/" + this.site + "/tutors")
|
||||
.then(response => {
|
||||
this.tutorOptions = response.data;
|
||||
})
|
||||
.catch(error => {
|
||||
if (error.response.status === 404) {
|
||||
this.$router.push("/dberror");
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
onSubmit: function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
var form = document.getElementById("submission-form");
|
||||
if (form.checkValidity()) {
|
||||
this.showModal();
|
||||
}
|
||||
form.classList.add("was-validated");
|
||||
},
|
||||
showModal() {
|
||||
this.$refs.submissionModal.show();
|
||||
},
|
||||
showCompletionModal() {
|
||||
this.$refs.completionModal.show();
|
||||
},
|
||||
clearPassword() {
|
||||
this.submitPassword = null;
|
||||
},
|
||||
handleOk(evt) {
|
||||
// Prevent modal from closing
|
||||
evt.preventDefault();
|
||||
if (this.submitPassword !== null) {
|
||||
this.startSession();
|
||||
}
|
||||
},
|
||||
handleSubmitComplete(evt) {
|
||||
// Prevent modal from closing
|
||||
var self = this;
|
||||
evt.preventDefault();
|
||||
if (this.submitPassword !== null) {
|
||||
let axiosConfig = {
|
||||
auth: {
|
||||
username: "admin",
|
||||
password: this.submitPassword
|
||||
}
|
||||
};
|
||||
Vue.axios
|
||||
.post("/grpob/complete", {}, axiosConfig)
|
||||
.then(function(response) {
|
||||
if ("success" in response.data) {
|
||||
self.complete = true;
|
||||
self.active = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.stompclient != null) {
|
||||
this.stompclient.disconnect();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
@ -1,462 +0,0 @@
|
||||
<template>
|
||||
<b-container fluid>
|
||||
<b-container v-if="error !== null">
|
||||
<p>An error has occurred.</p>
|
||||
<p>{{ error.status }}</p>
|
||||
<p>{{ error.data }}</p>
|
||||
</b-container>
|
||||
<b-container v-else-if="complete">
|
||||
<h2>Submission Complete</h2>
|
||||
<p>Thank you.</p>
|
||||
<p>
|
||||
This observation session is now closed and your data submitted to the
|
||||
database.
|
||||
</p>
|
||||
</b-container>
|
||||
<b-container v-else-if="!valid">
|
||||
<p>Getting session data from server</p>
|
||||
</b-container>
|
||||
<b-container v-else-if="scenarios.length === 0">
|
||||
<p>
|
||||
No scenarios defined for this session, please setup a new group session
|
||||
</p>
|
||||
</b-container>
|
||||
<b-container v-else fluid>
|
||||
<b-form>
|
||||
<b-row align-h="center">
|
||||
<b-col>
|
||||
<b-form-group label="Participant">
|
||||
<b-form-input
|
||||
v-model="participant"
|
||||
type="text"
|
||||
style="text-align:center;"
|
||||
placeholder="Enter your name (Initial and Surname)"
|
||||
required
|
||||
></b-form-input>
|
||||
</b-form-group>
|
||||
</b-col>
|
||||
<b-col>
|
||||
<b-button v-on:click="attemptReconnect()">Reconnect </b-button>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-row
|
||||
v-for="(item, index) in scenarios"
|
||||
v-bind:key="index"
|
||||
class="border bottom-buffer"
|
||||
>
|
||||
<b-col>
|
||||
<b-row>
|
||||
<b-col>
|
||||
<b-form-input
|
||||
v-model="item.title"
|
||||
type="text"
|
||||
readonly
|
||||
></b-form-input>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-row>
|
||||
<b-col cols="12" md="6" xl="3" class="border">
|
||||
<b-row>
|
||||
<b-col>
|
||||
<h5>Monitoring</h5>
|
||||
<score-selector
|
||||
:score-value="item.monitoringRating"
|
||||
v-on:newselection="
|
||||
item.monitoringRating = $event;
|
||||
actuallySubmit();
|
||||
"
|
||||
></score-selector>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-row>
|
||||
<b-col>
|
||||
<b-form-textarea
|
||||
v-model="item.monitoringStrengths"
|
||||
placeholder="Strengths"
|
||||
:rows="1"
|
||||
:max-rows="2"
|
||||
no-resize
|
||||
class="strength"
|
||||
></b-form-textarea>
|
||||
<b-form-textarea
|
||||
v-model="item.monitoringImprovements"
|
||||
placeholder="AFIs"
|
||||
:rows="1"
|
||||
:max-rows="2"
|
||||
no-resize
|
||||
class="afi"
|
||||
></b-form-textarea>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-col>
|
||||
<b-col cols="12" md="6" xl="3" class="border">
|
||||
<b-row>
|
||||
<b-col>
|
||||
<h5>Control Procedural</h5>
|
||||
<score-selector
|
||||
:score-value="item.controlProceduralRating"
|
||||
v-on:newselection="
|
||||
item.controlProceduralRating = $event;
|
||||
actuallySubmit();
|
||||
"
|
||||
></score-selector>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-row>
|
||||
<b-col>
|
||||
<b-form-textarea
|
||||
v-model="item.controlProceduralStrengths"
|
||||
placeholder="Strengths"
|
||||
:rows="1"
|
||||
:max-rows="2"
|
||||
no-resize
|
||||
class="strength"
|
||||
></b-form-textarea>
|
||||
<b-form-textarea
|
||||
v-model="item.controlProceduralImprovements"
|
||||
placeholder="AFIs"
|
||||
:rows="1"
|
||||
:max-rows="2"
|
||||
no-resize
|
||||
class="afi"
|
||||
></b-form-textarea>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-col>
|
||||
<b-col cols="12" md="6" xl="3" class="border">
|
||||
<b-row>
|
||||
<b-col>
|
||||
<h5>Control</h5>
|
||||
<score-selector
|
||||
:score-value="item.controlRating"
|
||||
v-on:newselection="
|
||||
item.controlRating = $event;
|
||||
actuallySubmit();
|
||||
"
|
||||
></score-selector>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-row>
|
||||
<b-col>
|
||||
<b-form-textarea
|
||||
v-model="item.controlStrengths"
|
||||
placeholder="Strengths"
|
||||
:rows="1"
|
||||
:max-rows="2"
|
||||
no-resize
|
||||
class="strength"
|
||||
></b-form-textarea>
|
||||
<b-form-textarea
|
||||
v-model="item.controlImprovements"
|
||||
placeholder="AFIs"
|
||||
:rows="1"
|
||||
:max-rows="2"
|
||||
no-resize
|
||||
class="afi"
|
||||
></b-form-textarea>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-col>
|
||||
<b-col cols="12" md="6" xl="3" class="border">
|
||||
<b-row>
|
||||
<b-col>
|
||||
<h5>Conservatism</h5>
|
||||
<score-selector
|
||||
:score-value="item.conservatismRating"
|
||||
v-on:newselection="
|
||||
item.conservatismRating = $event;
|
||||
actuallySubmit();
|
||||
"
|
||||
></score-selector>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-row>
|
||||
<b-col>
|
||||
<b-form-textarea
|
||||
v-model="item.conservatismStrengths"
|
||||
placeholder="Strengths"
|
||||
:rows="1"
|
||||
:max-rows="2"
|
||||
no-resize
|
||||
class="strength"
|
||||
></b-form-textarea>
|
||||
<b-form-textarea
|
||||
v-model="item.conservatismImprovements"
|
||||
placeholder="AFIs"
|
||||
:rows="1"
|
||||
:max-rows="2"
|
||||
no-resize
|
||||
class="afi"
|
||||
></b-form-textarea>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-col>
|
||||
<b-col cols="12" md="6" xl="3" class="border">
|
||||
<b-row>
|
||||
<b-col>
|
||||
<h5>Teamwork Communications</h5>
|
||||
<score-selector
|
||||
:score-value="item.teamworkCommunicationsRating"
|
||||
v-on:newselection="
|
||||
item.teamworkCommunicationsRating = $event;
|
||||
actuallySubmit();
|
||||
"
|
||||
></score-selector>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-row>
|
||||
<b-col>
|
||||
<b-form-textarea
|
||||
v-model="item.teamworkCommunicationsStrengths"
|
||||
placeholder="Strengths"
|
||||
:rows="1"
|
||||
:max-rows="2"
|
||||
no-resize
|
||||
class="strength"
|
||||
></b-form-textarea>
|
||||
<b-form-textarea
|
||||
v-model="item.teamworkCommunicationsImprovements"
|
||||
placeholder="AFIs"
|
||||
:rows="1"
|
||||
:max-rows="2"
|
||||
no-resize
|
||||
class="afi"
|
||||
></b-form-textarea>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-col>
|
||||
<b-col cols="12" md="6" xl="3" class="border">
|
||||
<b-row>
|
||||
<b-col>
|
||||
<h5>Teamwork Leadership</h5>
|
||||
<score-selector
|
||||
:score-value="item.teamworkLeadershipRating"
|
||||
v-on:newselection="
|
||||
item.teamworkLeadershipRating = $event;
|
||||
actuallySubmit();
|
||||
"
|
||||
></score-selector>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-row>
|
||||
<b-col>
|
||||
<b-form-textarea
|
||||
v-model="item.teamworkLeadershipStrengths"
|
||||
placeholder="Strengths"
|
||||
:rows="1"
|
||||
:max-rows="2"
|
||||
no-resize
|
||||
class="strength"
|
||||
></b-form-textarea>
|
||||
<b-form-textarea
|
||||
v-model="item.teamworkLeadershipImprovements"
|
||||
placeholder="AFIs"
|
||||
:rows="1"
|
||||
:max-rows="2"
|
||||
no-resize
|
||||
class="afi"
|
||||
></b-form-textarea>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-col>
|
||||
<b-col cols="12" md="6" xl="3" class="border">
|
||||
<b-row>
|
||||
<b-col>
|
||||
<h5>Teamwork Workload</h5>
|
||||
<score-selector
|
||||
:score-value="item.teamworkWorkloadRating"
|
||||
v-on:newselection="
|
||||
item.teamworkWorkloadRating = $event;
|
||||
actuallySubmit();
|
||||
"
|
||||
></score-selector>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-row>
|
||||
<b-col>
|
||||
<b-form-textarea
|
||||
v-model="item.teamworkWorkloadStrengths"
|
||||
placeholder="Strengths"
|
||||
:rows="1"
|
||||
:max-rows="2"
|
||||
no-resize
|
||||
class="strength"
|
||||
></b-form-textarea>
|
||||
<b-form-textarea
|
||||
v-model="item.teamworkWorkloadImprovements"
|
||||
placeholder="AFIs"
|
||||
:rows="1"
|
||||
:max-rows="2"
|
||||
no-resize
|
||||
class="afi"
|
||||
></b-form-textarea>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-col>
|
||||
<b-col cols="12" md="6" xl="3" class="border">
|
||||
<b-row>
|
||||
<b-col>
|
||||
<h5>Knowledge</h5>
|
||||
<score-selector
|
||||
:score-value="item.knowledgeRating"
|
||||
v-on:newselection="
|
||||
item.knowledgeRating = $event;
|
||||
actuallySubmit();
|
||||
"
|
||||
></score-selector>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-row>
|
||||
<b-col>
|
||||
<b-form-textarea
|
||||
v-model="item.knowledgeStrengths"
|
||||
placeholder="Strengths"
|
||||
:rows="1"
|
||||
:max-rows="2"
|
||||
no-resize
|
||||
class="strength"
|
||||
></b-form-textarea>
|
||||
<b-form-textarea
|
||||
v-model="item.knowledgeImprovements"
|
||||
placeholder="AFIs"
|
||||
:rows="1"
|
||||
:max-rows="2"
|
||||
no-resize
|
||||
class="afi"
|
||||
></b-form-textarea>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<br />
|
||||
<b-button variant="primary" v-on:click="actuallySubmit()"
|
||||
>Update</b-button
|
||||
>
|
||||
</b-form>
|
||||
</b-container>
|
||||
</b-container>
|
||||
</template>
|
||||
<script>
|
||||
import Vue from "vue";
|
||||
import ScoreSelector from "../components/ScoreSelector.vue";
|
||||
import webstomp from "webstomp-client";
|
||||
import SockJS from "sockjs-client";
|
||||
export default {
|
||||
name: "groupsessioninput",
|
||||
title: "Group Session - Input",
|
||||
props: ["id"],
|
||||
components: { ScoreSelector },
|
||||
data: function() {
|
||||
return {
|
||||
scenarios: [],
|
||||
participant: null,
|
||||
valid: false,
|
||||
complete: false,
|
||||
error: null,
|
||||
stompclient: null
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
let self = this;
|
||||
if (this.id != null) {
|
||||
Vue.axios
|
||||
.get(`/grpob/valid/${this.id}`)
|
||||
.then(function(response) {
|
||||
if (response.data.titles != null) {
|
||||
response.data.titles.forEach(function(x) {
|
||||
self.addAnotherObservation(x);
|
||||
});
|
||||
self.valid = true;
|
||||
self.setupSession();
|
||||
}
|
||||
})
|
||||
.catch(function(error) {
|
||||
if (error.response.status === 404) {
|
||||
self.$router.push("/dberror");
|
||||
return;
|
||||
}
|
||||
self.error = error.response;
|
||||
});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
addAnotherObservation: function(newTitle) {
|
||||
this.scenarios.push({
|
||||
title: newTitle,
|
||||
monitoringRating: 0,
|
||||
monitoringStrengths: "",
|
||||
monitoringImprovements: "",
|
||||
controlProceduralRating: 0,
|
||||
controlProceduralStrengths: "",
|
||||
controlProceduralImprovements: "",
|
||||
controlRating: 0,
|
||||
controlStrengths: "",
|
||||
controlImprovements: "",
|
||||
conservatismRating: 0,
|
||||
conservatismStrengths: "",
|
||||
conservatismImprovements: "",
|
||||
teamworkCommunicationsRating: 0,
|
||||
teamworkCommunicationsStrengths: "",
|
||||
teamworkCommunicationsImprovements: "",
|
||||
teamworkLeadershipRating: 0,
|
||||
teamworkLeadershipStrengths: "",
|
||||
teamworkLeadershipImprovements: "",
|
||||
teamworkWorkloadRating: 0,
|
||||
teamworkWorkloadStrengths: "",
|
||||
teamworkWorkloadImprovements: "",
|
||||
knowledgeRating: 0,
|
||||
knowledgeStrengths: "",
|
||||
knowledgeImprovements: ""
|
||||
});
|
||||
},
|
||||
actuallySubmit() {
|
||||
var self = this;
|
||||
var payload = {
|
||||
person: self.participant,
|
||||
scenarios: self.scenarios
|
||||
};
|
||||
Vue.axios.post("/grpob/submit", payload).catch(function(error) {
|
||||
self.error = error;
|
||||
});
|
||||
},
|
||||
setupSession: function() {
|
||||
var self = this;
|
||||
self.stompclient = webstomp.over(
|
||||
new SockJS(`http://${window.location.host}/websocket`, {
|
||||
heartbeat: false
|
||||
})
|
||||
);
|
||||
self.stompclient.connect([], function() {
|
||||
self.stompclient.subscribe("/ws/status", function(incomingData) {
|
||||
var data = JSON.parse(incomingData.body);
|
||||
if (data.status === "complete") {
|
||||
self.complete = true;
|
||||
self.stompclient.disconnect();
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
attemptReconnect: function() {
|
||||
var self = this;
|
||||
Vue.axios
|
||||
.get(`/grpob/participant/${self.participant}`)
|
||||
.then(function(response) {
|
||||
self.scenarios = response.data;
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.strength {
|
||||
background-color: honeydew;
|
||||
}
|
||||
.afi {
|
||||
background-color: mistyrose;
|
||||
}
|
||||
.bottom-buffer {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
</style>
|
@ -27,15 +27,6 @@
|
||||
>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<!--
|
||||
<b-row class="my-3" align-h="center">
|
||||
<b-col lg="6" sm="12">
|
||||
<b-button class="scale-in-center" size="lg" to="/groupsession" block
|
||||
><v-icon name="users" /> Start a Group Session</b-button
|
||||
>
|
||||
</b-col>
|
||||
</b-row>
|
||||
-->
|
||||
</b-container>
|
||||
</template>
|
||||
|
||||
|
@ -1,125 +1,121 @@
|
||||
<template>
|
||||
<b-container fluid>
|
||||
<b-container
|
||||
v-if="type != null && whom != null && site != null && tutors != null"
|
||||
fluid
|
||||
style="padding-left: 130px;"
|
||||
>
|
||||
<b-container v-if="type != null && whom != null && site != null && tutors != null" fluid style="padding-left: 130px;">
|
||||
<h3>
|
||||
<v-icon name="tag" scale="1.5" />
|
||||
{{ type }} / {{ whom }}
|
||||
</h3>
|
||||
<b-container class="sidebar">
|
||||
<b-row
|
||||
align-v="center"
|
||||
class="sidebar-vert-padding"
|
||||
v-if="totals[0] > 0"
|
||||
>
|
||||
<b-row align-v="center" class="sidebar-vert-padding">
|
||||
<b-col class="centered-image">
|
||||
<img
|
||||
src="../assets/Monitoring.svg"
|
||||
class="image-opacity"
|
||||
v-bind:class="{ scorewarning: totals[0] < warningBound }"
|
||||
v-bind:class="{
|
||||
scorewarning: totals[0] < warningBound && totals[0] > 0,
|
||||
}"
|
||||
/>
|
||||
<div class="image-centered-text">{{ totals[0] }}</div>
|
||||
<div class="image-centered-text" v-if="totals[0] > 0">
|
||||
{{ totals[0] }}
|
||||
</div>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-row
|
||||
align-v="center"
|
||||
class="sidebar-vert-padding"
|
||||
v-if="totals[1] > 0"
|
||||
>
|
||||
<b-row align-v="center" class="sidebar-vert-padding">
|
||||
<b-col class="centered-image">
|
||||
<img
|
||||
src="../assets/Control.svg"
|
||||
class="image-opacity"
|
||||
v-bind:class="{ scorewarning: totals[1] < warningBound }"
|
||||
v-bind:class="{
|
||||
scorewarning: totals[1] < warningBound && totals[1] > 0,
|
||||
}"
|
||||
/>
|
||||
<div class="image-centered-text">{{ totals[1] }}</div>
|
||||
<div class="image-centered-text" v-if="totals[1] > 0">
|
||||
{{ totals[1] }}
|
||||
</div>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-row
|
||||
align-v="center"
|
||||
class="sidebar-vert-padding"
|
||||
v-if="totals[2] > 0"
|
||||
>
|
||||
<b-row align-v="center" class="sidebar-vert-padding">
|
||||
<b-col class="centered-image">
|
||||
<img
|
||||
src="../assets/Control.svg"
|
||||
class="image-opacity"
|
||||
v-bind:class="{ scorewarning: totals[2] < warningBound }"
|
||||
v-bind:class="{
|
||||
scorewarning: totals[2] < warningBound && totals[2] > 0,
|
||||
}"
|
||||
/>
|
||||
<div class="image-centered-text">{{ totals[2] }}</div>
|
||||
<div class="image-centered-text" v-if="totals[2] > 0">
|
||||
{{ totals[2] }}
|
||||
</div>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-row
|
||||
align-v="center"
|
||||
class="sidebar-vert-padding"
|
||||
v-if="totals[3] > 0"
|
||||
>
|
||||
<b-row align-v="center" class="sidebar-vert-padding">
|
||||
<b-col class="centered-image">
|
||||
<img
|
||||
src="../assets/Conservatism.svg"
|
||||
class="image-opacity"
|
||||
v-bind:class="{ scorewarning: totals[3] < warningBound }"
|
||||
v-bind:class="{
|
||||
scorewarning: totals[3] < warningBound && totals[3] > 0,
|
||||
}"
|
||||
/>
|
||||
<div class="image-centered-text">{{ totals[3] }}</div>
|
||||
<div class="image-centered-text" v-if="totals[3] > 0">
|
||||
{{ totals[3] }}
|
||||
</div>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-row
|
||||
align-v="center"
|
||||
class="sidebar-vert-padding"
|
||||
v-if="totals[4] > 0"
|
||||
>
|
||||
<b-row align-v="center" class="sidebar-vert-padding">
|
||||
<b-col class="centered-image">
|
||||
<img
|
||||
src="../assets/Teamwork.svg"
|
||||
class="image-opacity"
|
||||
v-bind:class="{ scorewarning: totals[4] < warningBound }"
|
||||
v-bind:class="{
|
||||
scorewarning: totals[4] < warningBound && totals[4] > 0,
|
||||
}"
|
||||
/>
|
||||
<div class="image-centered-text">{{ totals[4] }}</div>
|
||||
<div class="image-centered-text" v-if="totals[4] > 0">
|
||||
{{ totals[4] }}
|
||||
</div>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-row
|
||||
align-v="center"
|
||||
class="sidebar-vert-padding"
|
||||
v-if="totals[5] > 0"
|
||||
>
|
||||
<b-row align-v="center" class="sidebar-vert-padding">
|
||||
<b-col class="centered-image">
|
||||
<img
|
||||
src="../assets/Teamwork.svg"
|
||||
class="image-opacity"
|
||||
v-bind:class="{ scorewarning: totals[5] < warningBound }"
|
||||
v-bind:class="{
|
||||
scorewarning: totals[5] < warningBound && totals[5] > 0,
|
||||
}"
|
||||
/>
|
||||
<div class="image-centered-text">{{ totals[5] }}</div>
|
||||
<div class="image-centered-text" v-if="totals[5] > 0">
|
||||
{{ totals[5] }}
|
||||
</div>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-row
|
||||
align-v="center"
|
||||
class="sidebar-vert-padding"
|
||||
v-if="totals[6] > 0"
|
||||
>
|
||||
<b-row align-v="center" class="sidebar-vert-padding">
|
||||
<b-col class="centered-image">
|
||||
<img
|
||||
src="../assets/Teamwork.svg"
|
||||
class="image-opacity"
|
||||
v-bind:class="{ scorewarning: totals[6] < warningBound }"
|
||||
v-bind:class="{
|
||||
scorewarning: totals[6] < warningBound && totals[6] > 0,
|
||||
}"
|
||||
/>
|
||||
<div class="image-centered-text">{{ totals[6] }}</div>
|
||||
<div class="image-centered-text" v-if="totals[6] > 0">
|
||||
{{ totals[6] }}
|
||||
</div>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-row
|
||||
align-v="center"
|
||||
class="sidebar-vert-padding"
|
||||
v-if="totals[7] > 0"
|
||||
>
|
||||
<b-row align-v="center" class="sidebar-vert-padding">
|
||||
<b-col class="centered-image">
|
||||
<img
|
||||
src="../assets/Knowledge.svg"
|
||||
class="image-opacity"
|
||||
v-bind:class="{ scorewarning: totals[7] < warningBound }"
|
||||
v-bind:class="{
|
||||
scorewarning: totals[7] < warningBound && totals[7] != 0,
|
||||
}"
|
||||
/>
|
||||
<div class="image-centered-text">{{ totals[7] }}</div>
|
||||
<div class="image-centered-text" v-if="totals[7] > 0">
|
||||
{{ totals[7] }}
|
||||
</div>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-container>
|
||||
@ -140,27 +136,49 @@
|
||||
></b-form-input>
|
||||
</b-col>
|
||||
<b-col cols="1">
|
||||
<b-button v-on:click="deleteObservation(index)" variant="danger"
|
||||
<b-button
|
||||
v-on:click="deleteObservation(index)"
|
||||
variant="outline-danger"
|
||||
><b>Delete</b></b-button
|
||||
>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-row>
|
||||
<b-col cols="6" class="border">
|
||||
<b-row cols="2">
|
||||
<div
|
||||
v-for="(entry, entryIndex) in item.entries"
|
||||
v-bind:key="entryIndex"
|
||||
>
|
||||
<b-col class="border">
|
||||
<b-row>
|
||||
<b-col cols="4">
|
||||
<h5>Monitoring</h5>
|
||||
<b-form-select
|
||||
v-model="entry.type"
|
||||
:options="entryTypeOptions"
|
||||
v-on:change="updateTotals()"
|
||||
class="my-3"
|
||||
></b-form-select>
|
||||
<div class="d-flex">
|
||||
<b-button
|
||||
class="mr-2"
|
||||
variant="outline-danger"
|
||||
size="sm"
|
||||
v-on:click="deleteEntry(index, entryIndex)"
|
||||
v-b-tooltip.hover
|
||||
title="Delete this entry"
|
||||
>X</b-button
|
||||
>
|
||||
<score-selector
|
||||
:score-value="item.monitoringRating"
|
||||
:score-value="entry.rating"
|
||||
v-on:newselection="
|
||||
item.monitoringRating = $event;
|
||||
entry.rating = $event;
|
||||
updateTotals();
|
||||
"
|
||||
></score-selector>
|
||||
</div>
|
||||
</b-col>
|
||||
<b-col cols="8">
|
||||
<b-form-textarea
|
||||
v-model="item.monitoringStrengths"
|
||||
v-model="entry.strengths"
|
||||
placeholder="Strengths"
|
||||
:rows="1"
|
||||
:max-rows="2"
|
||||
@ -168,7 +186,7 @@
|
||||
class="strength"
|
||||
></b-form-textarea>
|
||||
<b-form-textarea
|
||||
v-model="item.monitoringImprovements"
|
||||
v-model="entry.AFIs"
|
||||
placeholder="AFIs"
|
||||
:rows="1"
|
||||
:max-rows="2"
|
||||
@ -178,235 +196,15 @@
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-col>
|
||||
<b-col cols="6" class="border">
|
||||
<b-row>
|
||||
<b-col cols="4">
|
||||
<h5>Control Procedural</h5>
|
||||
<score-selector
|
||||
:score-value="item.controlProceduralRating"
|
||||
v-on:newselection="
|
||||
item.controlProceduralRating = $event;
|
||||
updateTotals();
|
||||
"
|
||||
></score-selector>
|
||||
</b-col>
|
||||
<b-col cols="8">
|
||||
<b-form-textarea
|
||||
v-model="item.controlProceduralStrengths"
|
||||
placeholder="Strengths"
|
||||
:rows="1"
|
||||
:max-rows="2"
|
||||
no-resize
|
||||
class="strength"
|
||||
></b-form-textarea>
|
||||
<b-form-textarea
|
||||
v-model="item.controlProceduralImprovements"
|
||||
placeholder="AFIs"
|
||||
:rows="1"
|
||||
:max-rows="2"
|
||||
no-resize
|
||||
class="afi"
|
||||
></b-form-textarea>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-row>
|
||||
<b-col cols="6" class="border">
|
||||
<b-row>
|
||||
<b-col cols="4">
|
||||
<h5>Control</h5>
|
||||
<score-selector
|
||||
:score-value="item.controlRating"
|
||||
v-on:newselection="
|
||||
item.controlRating = $event;
|
||||
updateTotals();
|
||||
"
|
||||
></score-selector>
|
||||
</b-col>
|
||||
<b-col cols="8">
|
||||
<b-form-textarea
|
||||
v-model="item.controlStrengths"
|
||||
placeholder="Strengths"
|
||||
:rows="1"
|
||||
:max-rows="2"
|
||||
no-resize
|
||||
class="strength"
|
||||
></b-form-textarea>
|
||||
<b-form-textarea
|
||||
v-model="item.controlImprovements"
|
||||
placeholder="AFIs"
|
||||
:rows="1"
|
||||
:max-rows="2"
|
||||
no-resize
|
||||
class="afi"
|
||||
></b-form-textarea>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-col>
|
||||
<b-col cols="6" class="border">
|
||||
<b-row>
|
||||
<b-col cols="4">
|
||||
<h5>Conservatism</h5>
|
||||
<score-selector
|
||||
:score-value="item.conservatismRating"
|
||||
v-on:newselection="
|
||||
item.conservatismRating = $event;
|
||||
updateTotals();
|
||||
"
|
||||
></score-selector>
|
||||
</b-col>
|
||||
<b-col cols="8">
|
||||
<b-form-textarea
|
||||
v-model="item.conservatismStrengths"
|
||||
placeholder="Strengths"
|
||||
:rows="1"
|
||||
:max-rows="2"
|
||||
no-resize
|
||||
class="strength"
|
||||
></b-form-textarea>
|
||||
<b-form-textarea
|
||||
v-model="item.conservatismImprovements"
|
||||
placeholder="AFIs"
|
||||
:rows="1"
|
||||
:max-rows="2"
|
||||
no-resize
|
||||
class="afi"
|
||||
></b-form-textarea>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-row>
|
||||
<b-col cols="6" class="border">
|
||||
<b-row>
|
||||
<b-col cols="4">
|
||||
<h5>Teamwork Communications</h5>
|
||||
<score-selector
|
||||
:score-value="item.teamworkCommunicationsRating"
|
||||
v-on:newselection="
|
||||
item.teamworkCommunicationsRating = $event;
|
||||
updateTotals();
|
||||
"
|
||||
></score-selector>
|
||||
</b-col>
|
||||
<b-col cols="8">
|
||||
<b-form-textarea
|
||||
v-model="item.teamworkCommunicationsStrengths"
|
||||
placeholder="Strengths"
|
||||
:rows="1"
|
||||
:max-rows="2"
|
||||
no-resize
|
||||
class="strength"
|
||||
></b-form-textarea>
|
||||
<b-form-textarea
|
||||
v-model="item.teamworkCommunicationsImprovements"
|
||||
placeholder="AFIs"
|
||||
:rows="1"
|
||||
:max-rows="2"
|
||||
no-resize
|
||||
class="afi"
|
||||
></b-form-textarea>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-col>
|
||||
<b-col cols="6" class="border">
|
||||
<b-row>
|
||||
<b-col cols="4">
|
||||
<h5>Teamwork Leadership</h5>
|
||||
<score-selector
|
||||
:score-value="item.teamworkLeadershipRating"
|
||||
v-on:newselection="
|
||||
item.teamworkLeadershipRating = $event;
|
||||
updateTotals();
|
||||
"
|
||||
></score-selector>
|
||||
</b-col>
|
||||
<b-col cols="8">
|
||||
<b-form-textarea
|
||||
v-model="item.teamworkLeadershipStrengths"
|
||||
placeholder="Strengths"
|
||||
:rows="1"
|
||||
:max-rows="2"
|
||||
no-resize
|
||||
class="strength"
|
||||
></b-form-textarea>
|
||||
<b-form-textarea
|
||||
v-model="item.teamworkLeadershipImprovements"
|
||||
placeholder="AFIs"
|
||||
:rows="1"
|
||||
:max-rows="2"
|
||||
no-resize
|
||||
class="afi"
|
||||
></b-form-textarea>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-row>
|
||||
<b-col cols="6" class="border">
|
||||
<b-row>
|
||||
<b-col cols="4">
|
||||
<h5>Teamwork Workload</h5>
|
||||
<score-selector
|
||||
:score-value="item.teamworkWorkloadRating"
|
||||
v-on:newselection="
|
||||
item.teamworkWorkloadRating = $event;
|
||||
updateTotals();
|
||||
"
|
||||
></score-selector>
|
||||
</b-col>
|
||||
<b-col cols="8">
|
||||
<b-form-textarea
|
||||
v-model="item.teamworkWorkloadStrengths"
|
||||
placeholder="Strengths"
|
||||
:rows="1"
|
||||
:max-rows="2"
|
||||
no-resize
|
||||
class="strength"
|
||||
></b-form-textarea>
|
||||
<b-form-textarea
|
||||
v-model="item.teamworkWorkloadImprovements"
|
||||
placeholder="AFIs"
|
||||
:rows="1"
|
||||
:max-rows="2"
|
||||
no-resize
|
||||
class="afi"
|
||||
></b-form-textarea>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-col>
|
||||
<b-col cols="6" class="border">
|
||||
<b-row>
|
||||
<b-col cols="4">
|
||||
<h5>Knowledge</h5>
|
||||
<score-selector
|
||||
:score-value="item.knowledgeRating"
|
||||
v-on:newselection="
|
||||
item.knowledgeRating = $event;
|
||||
updateTotals();
|
||||
"
|
||||
></score-selector>
|
||||
</b-col>
|
||||
<b-col cols="8">
|
||||
<b-form-textarea
|
||||
v-model="item.knowledgeStrengths"
|
||||
placeholder="Strengths"
|
||||
:rows="1"
|
||||
:max-rows="2"
|
||||
no-resize
|
||||
class="strength"
|
||||
></b-form-textarea>
|
||||
<b-form-textarea
|
||||
v-model="item.knowledgeImprovements"
|
||||
placeholder="AFIs"
|
||||
:rows="1"
|
||||
:max-rows="2"
|
||||
no-resize
|
||||
class="afi"
|
||||
></b-form-textarea>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</div>
|
||||
<b-col cols="6" align-self="center">
|
||||
<b-button
|
||||
v-on:click="addAnotherEntry(item)"
|
||||
size="lg"
|
||||
style="font-size: 200%"
|
||||
class="py-4 my-4"
|
||||
> + </b-button
|
||||
>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-container>
|
||||
@ -479,68 +277,46 @@ export default {
|
||||
scenarios: [
|
||||
{
|
||||
title: "",
|
||||
monitoringRating: null,
|
||||
monitoringStrengths: "",
|
||||
monitoringImprovements: "",
|
||||
controlProceduralRating: null,
|
||||
controlProceduralStrengths: "",
|
||||
controlProceduralImprovements: "",
|
||||
controlRating: null,
|
||||
controlStrengths: "",
|
||||
controlImprovements: "",
|
||||
conservatismRating: null,
|
||||
conservatismStrengths: "",
|
||||
conservatismImprovements: "",
|
||||
teamworkCommunicationsRating: null,
|
||||
teamworkCommunicationsStrengths: "",
|
||||
teamworkCommunicationsImprovements: "",
|
||||
teamworkLeadershipRating: null,
|
||||
teamworkLeadershipStrengths: "",
|
||||
teamworkLeadershipImprovements: "",
|
||||
teamworkWorkloadRating: null,
|
||||
teamworkWorkloadStrengths: "",
|
||||
teamworkWorkloadImprovements: "",
|
||||
knowledgeRating: null,
|
||||
knowledgeStrengths: "",
|
||||
knowledgeImprovements: ""
|
||||
}
|
||||
entries: [
|
||||
{
|
||||
type: null,
|
||||
rating: 0,
|
||||
strengths: "",
|
||||
AFIs: "",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
totals: [0, 0, 0, 0, 0, 0, 0, 0],
|
||||
warningBound: 2.5,
|
||||
submitPassword: null
|
||||
submitPassword: null,
|
||||
entryTypeOptions: [
|
||||
{ value: "monitoring", text: "Monitoring" },
|
||||
{ value: "controlProcedural", text: "Control Procedural" },
|
||||
{ value: "control", text: "Control" },
|
||||
{ value: "conservatism", text: "Conservatism" },
|
||||
{ value: "teamworkCommunications", text: "Teamwork Communications" },
|
||||
{ value: "teamworkLeadership", text: "Teamwork Leadership" },
|
||||
{ value: "teamworkWorkload", text: "Teamwork Workload" },
|
||||
{ value: "knowledge", text: "Knowledge" },
|
||||
],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(["type", "whom", "site", "tutors"])
|
||||
...mapState(["type", "whom", "site", "tutors"]),
|
||||
},
|
||||
methods: {
|
||||
addAnotherObservation: function() {
|
||||
this.scenarios.push({
|
||||
title: "",
|
||||
monitoringRating: null,
|
||||
monitoringStrengths: "",
|
||||
monitoringImprovements: "",
|
||||
controlProceduralRating: null,
|
||||
controlProceduralStrengths: "",
|
||||
controlProceduralImprovements: "",
|
||||
controlRating: null,
|
||||
controlStrengths: "",
|
||||
controlImprovements: "",
|
||||
conservatismRating: null,
|
||||
conservatismStrengths: "",
|
||||
conservatismImprovements: "",
|
||||
teamworkCommunicationsRating: null,
|
||||
teamworkCommunicationsStrengths: "",
|
||||
teamworkCommunicationsImprovements: "",
|
||||
teamworkLeadershipRating: null,
|
||||
teamworkLeadershipStrengths: "",
|
||||
teamworkLeadershipImprovements: "",
|
||||
teamworkWorkloadRating: null,
|
||||
teamworkWorkloadStrengths: "",
|
||||
teamworkWorkloadImprovements: "",
|
||||
knowledgeRating: null,
|
||||
knowledgeStrengths: "",
|
||||
knowledgeImprovements: ""
|
||||
entries: [
|
||||
{
|
||||
type: null,
|
||||
rating: 0,
|
||||
strengths: "",
|
||||
AFIs: "",
|
||||
},
|
||||
],
|
||||
});
|
||||
Vue.nextTick(function() {
|
||||
window.scrollTo(
|
||||
@ -556,43 +332,42 @@ export default {
|
||||
}
|
||||
this.updateTotals();
|
||||
},
|
||||
addAnotherEntry: function(scenario) {
|
||||
scenario.entries.push({
|
||||
type: null,
|
||||
rating: 0,
|
||||
strengths: "",
|
||||
AFIs: "",
|
||||
});
|
||||
},
|
||||
deleteEntry: function(scenarioIndex, entryIndex) {
|
||||
this.$delete(this.scenarios[scenarioIndex].entries, entryIndex);
|
||||
if (this.scenarios[scenarioIndex].entries.length === 0) {
|
||||
this.addAnotherEntry(this.scenarios[scenarioIndex]);
|
||||
}
|
||||
this.updateTotals();
|
||||
},
|
||||
updateTotals: function() {
|
||||
var iTotals = [0, 0, 0, 0, 0, 0, 0, 0];
|
||||
var counts = [0, 0, 0, 0, 0, 0, 0, 0];
|
||||
this.scenarios.forEach(function(element) {
|
||||
if (element.monitoringRating) {
|
||||
iTotals[0] += parseInt(element.monitoringRating);
|
||||
counts[0] += 1;
|
||||
}
|
||||
if (element.controlProceduralRating) {
|
||||
iTotals[1] += parseInt(element.controlProceduralRating);
|
||||
counts[1] += 1;
|
||||
}
|
||||
if (element.controlRating) {
|
||||
iTotals[2] += parseInt(element.controlRating);
|
||||
counts[2] += 1;
|
||||
}
|
||||
if (element.conservatismRating) {
|
||||
iTotals[3] += parseInt(element.conservatismRating);
|
||||
counts[3] += 1;
|
||||
}
|
||||
if (element.teamworkCommunicationsRating) {
|
||||
iTotals[4] += parseInt(element.teamworkCommunicationsRating);
|
||||
counts[4] += 1;
|
||||
}
|
||||
if (element.teamworkLeadershipRating) {
|
||||
iTotals[5] += parseInt(element.teamworkLeadershipRating);
|
||||
counts[5] += 1;
|
||||
}
|
||||
if (element.teamworkWorkloadRating) {
|
||||
iTotals[6] += parseInt(element.teamworkWorkloadRating);
|
||||
counts[6] += 1;
|
||||
}
|
||||
if (element.knowledgeRating) {
|
||||
iTotals[7] += parseInt(element.knowledgeRating);
|
||||
counts[7] += 1;
|
||||
var indices = {
|
||||
monitoring: 0,
|
||||
controlProcedural: 1,
|
||||
control: 2,
|
||||
conservatism: 3,
|
||||
teamworkCommunications: 4,
|
||||
teamworkLeadership: 5,
|
||||
teamworkWorkload: 6,
|
||||
knowledge: 7,
|
||||
};
|
||||
this.scenarios.forEach(function(scenario) {
|
||||
scenario.entries.forEach(function(entry) {
|
||||
if (entry.type !== null && entry.rating > 0) {
|
||||
iTotals[indices[entry.type]] += parseInt(entry.rating);
|
||||
counts[indices[entry.type]] += 1;
|
||||
}
|
||||
});
|
||||
});
|
||||
for (var i = 0; i < iTotals.length; i++) {
|
||||
if (counts[i] !== 0) {
|
||||
Vue.set(this.totals, i, (iTotals[i] / counts[i]).toFixed(1));
|
||||
@ -625,14 +400,155 @@ export default {
|
||||
},
|
||||
handleSubmit() {
|
||||
var form = document.getElementById("submission-form");
|
||||
var notObservedValue = 0
|
||||
if (form.checkValidity()) {
|
||||
let axiosConfig = {
|
||||
auth: {
|
||||
username: "admin",
|
||||
password: this.submitPassword
|
||||
}
|
||||
password: this.submitPassword,
|
||||
},
|
||||
};
|
||||
var self = this;
|
||||
var transformedScenarioData = [];
|
||||
this.scenarios.forEach(function(scenario) {
|
||||
var monitoring = scenario.entries.filter(
|
||||
(entry) => entry.type === "monitoring"
|
||||
);
|
||||
var controlProcedural = scenario.entries.filter(
|
||||
(entry) => entry.type === "controlProcedural"
|
||||
);
|
||||
var control = scenario.entries.filter(
|
||||
(entry) => entry.type === "control"
|
||||
);
|
||||
var conservatism = scenario.entries.filter(
|
||||
(entry) => entry.type === "conservatism"
|
||||
);
|
||||
var teamworkCommunications = scenario.entries.filter(
|
||||
(entry) => entry.type === "teamworkCommunications"
|
||||
);
|
||||
var teamworkLeadership = scenario.entries.filter(
|
||||
(entry) => entry.type === "teamworkLeadership"
|
||||
);
|
||||
var teamworkWorkload = scenario.entries.filter(
|
||||
(entry) => entry.type === "teamworkWorkload"
|
||||
);
|
||||
var knowledge = scenario.entries.filter(
|
||||
(entry) => entry.type === "knowledge"
|
||||
);
|
||||
transformedScenarioData.push({
|
||||
title: scenario.title,
|
||||
monitoringRating:
|
||||
monitoring.length > 0
|
||||
? monitoring.length > 1
|
||||
? monitoring.reduce(
|
||||
(a, b) => parseInt(a.rating) + parseInt(b.rating)
|
||||
) / monitoring.length
|
||||
: monitoring[0].rating
|
||||
: notObservedValue,
|
||||
monitoringStrengths: monitoring
|
||||
.map((entry) => entry.strengths)
|
||||
.join("; "),
|
||||
monitoringImprovements: monitoring
|
||||
.map((entry) => entry.AFIs)
|
||||
.join("; "),
|
||||
controlProceduralRating:
|
||||
controlProcedural.length > 0
|
||||
? controlProcedural.length > 1
|
||||
? controlProcedural.reduce(
|
||||
(a, b) => parseInt(a.rating) + parseInt(b.rating)
|
||||
) / controlProcedural.length
|
||||
: controlProcedural[0].rating
|
||||
: notObservedValue,
|
||||
controlProceduralStrengths: controlProcedural
|
||||
.map((entry) => entry.strengths)
|
||||
.join("; "),
|
||||
controlProceduralImprovements: controlProcedural
|
||||
.map((entry) => entry.AFIs)
|
||||
.join("; "),
|
||||
controlRating:
|
||||
control.length > 0
|
||||
? control.length > 1
|
||||
? control.reduce(
|
||||
(a, b) => parseInt(a.rating) + parseInt(b.rating)
|
||||
) / control.length
|
||||
: control[0].rating
|
||||
: notObservedValue,
|
||||
controlStrengths: control
|
||||
.map((entry) => entry.strengths)
|
||||
.join("; "),
|
||||
controlImprovements: control.map((entry) => entry.AFIs).join("; "),
|
||||
conservatismRating:
|
||||
conservatism.length > 0
|
||||
? conservatism.length > 1
|
||||
? conservatism.reduce(
|
||||
(a, b) => parseInt(a.rating) + parseInt(b.rating)
|
||||
) / conservatism.length
|
||||
: conservatism[0].rating
|
||||
: notObservedValue,
|
||||
conservatismStrengths: conservatism
|
||||
.map((entry) => entry.strengths)
|
||||
.join(";"),
|
||||
conservatismImprovements: conservatism
|
||||
.map((entry) => entry.strengths)
|
||||
.join(";"),
|
||||
teamworkCommunicationsRating:
|
||||
teamworkCommunications.length > 0
|
||||
? teamworkCommunications.length > 1
|
||||
? teamworkCommunications.reduce(
|
||||
(a, b) => parseInt(a.rating) + parseInt(b.rating)
|
||||
) / teamworkCommunications.length
|
||||
: teamworkCommunications[0].rating
|
||||
: notObservedValue,
|
||||
teamworkCommunicationsStrengths: teamworkCommunications
|
||||
.map((entry) => entry.strengths)
|
||||
.join(";"),
|
||||
teamworkCommunicationsImprovements: teamworkCommunications
|
||||
.map((entry) => entry.AFIs)
|
||||
.join(";"),
|
||||
teamworkLeadershipRating:
|
||||
teamworkLeadership.length > 0
|
||||
? teamworkLeadership.length > 1
|
||||
? teamworkLeadership.reduce(
|
||||
(a, b) => parseInt(a.rating) + parseInt(b.rating)
|
||||
) / teamworkLeadership.length
|
||||
: teamworkLeadership[0].rating
|
||||
: notObservedValue,
|
||||
teamworkLeadershipStrengths: teamworkLeadership
|
||||
.map((entry) => entry.strengths)
|
||||
.join(";"),
|
||||
teamworkLeadershipImprovements: teamworkLeadership
|
||||
.map((entry) => entry.AFIs)
|
||||
.join(";"),
|
||||
teamworkWorkloadRating:
|
||||
teamworkWorkload.length > 0
|
||||
? teamworkWorkload.length > 1
|
||||
? teamworkWorkload.reduce(
|
||||
(a, b) => parseInt(a.rating) + parseInt(b.rating)
|
||||
) / teamworkWorkload.length
|
||||
: teamworkWorkload[0].rating
|
||||
: notObservedValue,
|
||||
teamworkWorkloadStrengths: teamworkWorkload
|
||||
.map((entry) => entry.strengths)
|
||||
.join(";"),
|
||||
teamworkWorkloadImprovements: teamworkWorkload
|
||||
.map((entry) => entry.AFIs)
|
||||
.join(";"),
|
||||
knowledgeRating:
|
||||
knowledge.length > 0
|
||||
? knowledge.length > 1
|
||||
? knowledge.reduce(
|
||||
(a, b) => parseInt(a.rating) + parseInt(b.rating)
|
||||
) / knowledge.length
|
||||
: knowledge[0].rating
|
||||
: notObservedValue,
|
||||
knowledgeStrengths: knowledge
|
||||
.map((entry) => entry.strengths)
|
||||
.join(";"),
|
||||
knowledgeImprovements: knowledge
|
||||
.map((entry) => entry.AFIs)
|
||||
.join(";"),
|
||||
});
|
||||
});
|
||||
Vue.axios
|
||||
.post(
|
||||
"/observation",
|
||||
@ -641,8 +557,8 @@ export default {
|
||||
tutors: this.tutors,
|
||||
person: this.whom,
|
||||
type: this.type,
|
||||
observed: this.scenarios.map(x => x.title).join(", "),
|
||||
scenarios: JSON.parse(JSON.stringify(this.scenarios))
|
||||
observed: this.scenarios.map((x) => x.title).join("; "),
|
||||
scenarios: JSON.parse(JSON.stringify(transformedScenarioData)),
|
||||
},
|
||||
axiosConfig
|
||||
)
|
||||
@ -654,8 +570,8 @@ export default {
|
||||
}
|
||||
this.clearPassword();
|
||||
form.classList.add("was-validated");
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@ -698,7 +614,7 @@ export default {
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 160px; /* Set the width of the sidebar */
|
||||
width: 8%; /* Set the width of the sidebar */
|
||||
position: fixed; /* Fixed Sidebar (stay in place on scroll) */
|
||||
z-index: 1; /* Stay on top */
|
||||
top: 10%; /* Stay at the top */
|
||||
|
@ -148,102 +148,99 @@
|
||||
</b-row>
|
||||
<br />
|
||||
<b-row class="mb-2">
|
||||
<b-col class="centered-image" v-if="observation.monitoring">
|
||||
<b-col class="centered-image">
|
||||
<img
|
||||
src="../assets/Monitoring.svg"
|
||||
class="image-opacity"
|
||||
v-bind:class="{ scorewarning: observation.monitoring < 2.5 }"
|
||||
v-bind:class="{ scorewarning: observation.monitoring < 2.5 && observation.monitoring > 0}"
|
||||
/>
|
||||
<div class="image-centered-text">
|
||||
<div class="image-centered-text" v-if="observation.monitoring > 0">
|
||||
{{ observation.monitoring.toFixed(1) }}
|
||||
</div>
|
||||
</b-col>
|
||||
<b-col
|
||||
class="centered-image"
|
||||
v-if="observation.controlProcedural"
|
||||
>
|
||||
<img
|
||||
src="../assets/Control.svg"
|
||||
class="image-opacity"
|
||||
v-bind:class="{
|
||||
scorewarning: observation.controlProcedural < 2.5
|
||||
scorewarning: observation.controlProcedural < 2.5 && observation.controlProcedural > 0
|
||||
}"
|
||||
/>
|
||||
<div class="image-centered-text">
|
||||
<div class="image-centered-text" v-if="observation.controlProcedural > 0">
|
||||
{{ observation.controlProcedural.toFixed(1) }}
|
||||
</div>
|
||||
</b-col>
|
||||
<b-col class="centered-image" v-if="observation.control">
|
||||
<b-col class="centered-image">
|
||||
<img
|
||||
src="../assets/Control.svg"
|
||||
class="image-opacity"
|
||||
v-bind:class="{ scorewarning: observation.control < 2.5 }"
|
||||
v-bind:class="{ scorewarning: observation.control < 2.5 && observation.control > 0}"
|
||||
/>
|
||||
<div class="image-centered-text">
|
||||
<div class="image-centered-text" v-if="observation.control > 0">
|
||||
{{ observation.control.toFixed(1) }}
|
||||
</div>
|
||||
</b-col>
|
||||
<b-col class="centered-image" v-if="observation.conservatism">
|
||||
<b-col class="centered-image">
|
||||
<img
|
||||
src="../assets/Conservatism.svg"
|
||||
class="image-opacity"
|
||||
v-bind:class="{
|
||||
scorewarning: observation.conservatism < 2.5
|
||||
scorewarning: observation.conservatism < 2.5 && observation.conservatism > 0
|
||||
}"
|
||||
/>
|
||||
<div class="image-centered-text">
|
||||
<div class="image-centered-text" v-if="observation.conservatism > 0">
|
||||
{{ observation.conservatism.toFixed(1) }}
|
||||
</div>
|
||||
</b-col>
|
||||
<b-col
|
||||
class="centered-image"
|
||||
v-if="observation.teamworkCommunications"
|
||||
>
|
||||
<img
|
||||
src="../assets/Teamwork.svg"
|
||||
class="image-opacity"
|
||||
v-bind:class="{
|
||||
scorewarning: observation.teamworkCommunications < 2.5
|
||||
scorewarning: observation.teamworkCommunications < 2.5 && observation.teamworkCommunications > 0
|
||||
}"
|
||||
/>
|
||||
<div class="image-centered-text">
|
||||
<div class="image-centered-text" v-if="observation.teamworkCommunications > 0">
|
||||
{{ observation.teamworkCommunications.toFixed(1) }}
|
||||
</div>
|
||||
</b-col>
|
||||
<b-col
|
||||
class="centered-image"
|
||||
v-if="observation.teamworkLeadership"
|
||||
>
|
||||
<img
|
||||
src="../assets/Teamwork.svg"
|
||||
class="image-opacity"
|
||||
v-bind:class="{
|
||||
scorewarning: observation.teamworkLeadership < 2.5
|
||||
scorewarning: observation.teamworkLeadership < 2.5 && observation.teamworkLeadership > 0
|
||||
}"
|
||||
/>
|
||||
<div class="image-centered-text">
|
||||
<div class="image-centered-text" v-if="observation.teamworkLeadership > 0">
|
||||
{{ observation.teamworkLeadership.toFixed(1) }}
|
||||
</div>
|
||||
</b-col>
|
||||
<b-col class="centered-image" v-if="observation.teamworkWorkload">
|
||||
<b-col class="centered-image">
|
||||
<img
|
||||
src="../assets/Teamwork.svg"
|
||||
class="image-opacity"
|
||||
v-bind:class="{
|
||||
scorewarning: observation.teamworkWorkload < 2.5
|
||||
scorewarning: observation.teamworkWorkload < 2.5 && observation.teamworkWorkload > 0
|
||||
}"
|
||||
/>
|
||||
<div class="image-centered-text">
|
||||
<div class="image-centered-text" v-if="observation.teamworkWorkload > 0">
|
||||
{{ observation.teamworkWorkload.toFixed(1) }}
|
||||
</div>
|
||||
</b-col>
|
||||
<b-col class="centered-image" v-if="observation.knowledge">
|
||||
<b-col class="centered-image">
|
||||
<img
|
||||
src="../assets/Knowledge.svg"
|
||||
class="image-opacity"
|
||||
v-bind:class="{ scorewarning: observation.knowledge < 2.5 }"
|
||||
v-bind:class="{ scorewarning: observation.knowledge < 2.5 && observation.knowledge > 0 }"
|
||||
/>
|
||||
<div class="image-centered-text">
|
||||
<div class="image-centered-text" v-if="observation.knowledge > 0">
|
||||
{{ observation.knowledge.toFixed(1) }}
|
||||
</div>
|
||||
</b-col>
|
||||
|
Loading…
Reference in New Issue
Block a user