Added evaluation criteria
This commit is contained in:
parent
034b018e2d
commit
a7aafccd19
|
@ -48,6 +48,7 @@ compose.desktop {
|
||||||
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
|
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
|
||||||
packageName = "com.jaytux.grader"
|
packageName = "com.jaytux.grader"
|
||||||
packageVersion = "1.0.0"
|
packageVersion = "1.0.0"
|
||||||
|
includeAllModules = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package com.jaytux.grader.data
|
package com.jaytux.grader.data
|
||||||
|
|
||||||
import kotlinx.datetime.*
|
|
||||||
import org.jetbrains.exposed.dao.id.CompositeIdTable
|
import org.jetbrains.exposed.dao.id.CompositeIdTable
|
||||||
import org.jetbrains.exposed.dao.id.UUIDTable
|
import org.jetbrains.exposed.dao.id.UUIDTable
|
||||||
import org.jetbrains.exposed.sql.Table
|
import org.jetbrains.exposed.sql.Table
|
||||||
|
@ -59,6 +58,12 @@ object GroupAssignments : UUIDTable("grpAssgmts") {
|
||||||
val deadline = datetime("deadline")
|
val deadline = datetime("deadline")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object GroupAssignmentCriteria : UUIDTable("grpAsCr") {
|
||||||
|
val assignmentId = reference("group_assignment_id", GroupAssignments.id)
|
||||||
|
val name = varchar("name", 50)
|
||||||
|
val desc = text("description")
|
||||||
|
}
|
||||||
|
|
||||||
object SoloAssignments : UUIDTable("soloAssgmts") {
|
object SoloAssignments : UUIDTable("soloAssgmts") {
|
||||||
val editionId = reference("edition_id", Editions.id)
|
val editionId = reference("edition_id", Editions.id)
|
||||||
val number = integer("number").nullable()
|
val number = integer("number").nullable()
|
||||||
|
@ -67,6 +72,12 @@ object SoloAssignments : UUIDTable("soloAssgmts") {
|
||||||
val deadline = datetime("deadline")
|
val deadline = datetime("deadline")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object SoloAssignmentCriteria : UUIDTable("soloAsCr") {
|
||||||
|
val assignmentId = reference("solo_assignment_id", SoloAssignments.id)
|
||||||
|
val name = varchar("name", 50)
|
||||||
|
val desc = text("description")
|
||||||
|
}
|
||||||
|
|
||||||
object PeerEvaluations : UUIDTable("peerEvals") {
|
object PeerEvaluations : UUIDTable("peerEvals") {
|
||||||
val editionId = reference("edition_id", Editions.id)
|
val editionId = reference("edition_id", Editions.id)
|
||||||
val number = integer("number").nullable()
|
val number = integer("number").nullable()
|
||||||
|
@ -75,6 +86,7 @@ object PeerEvaluations : UUIDTable("peerEvals") {
|
||||||
|
|
||||||
object GroupFeedbacks : CompositeIdTable("grpFdbks") {
|
object GroupFeedbacks : CompositeIdTable("grpFdbks") {
|
||||||
val groupAssignmentId = reference("group_assignment_id", GroupAssignments.id)
|
val groupAssignmentId = reference("group_assignment_id", GroupAssignments.id)
|
||||||
|
val criterionId = reference("criterion_id", GroupAssignments.id).nullable()
|
||||||
val groupId = reference("group_id", Groups.id)
|
val groupId = reference("group_id", Groups.id)
|
||||||
val feedback = text("feedback")
|
val feedback = text("feedback")
|
||||||
val grade = varchar("grade", 32)
|
val grade = varchar("grade", 32)
|
||||||
|
@ -84,6 +96,7 @@ object GroupFeedbacks : CompositeIdTable("grpFdbks") {
|
||||||
|
|
||||||
object IndividualFeedbacks : CompositeIdTable("indivFdbks") {
|
object IndividualFeedbacks : CompositeIdTable("indivFdbks") {
|
||||||
val groupAssignmentId = reference("group_assignment_id", GroupAssignments.id)
|
val groupAssignmentId = reference("group_assignment_id", GroupAssignments.id)
|
||||||
|
val criterionId = reference("criterion_id", GroupAssignments.id).nullable()
|
||||||
val groupId = reference("group_id", Groups.id)
|
val groupId = reference("group_id", Groups.id)
|
||||||
val studentId = reference("student_id", Students.id)
|
val studentId = reference("student_id", Students.id)
|
||||||
val feedback = text("feedback")
|
val feedback = text("feedback")
|
||||||
|
@ -94,6 +107,7 @@ object IndividualFeedbacks : CompositeIdTable("indivFdbks") {
|
||||||
|
|
||||||
object SoloFeedbacks : CompositeIdTable("soloFdbks") {
|
object SoloFeedbacks : CompositeIdTable("soloFdbks") {
|
||||||
val soloAssignmentId = reference("solo_assignment_id", SoloAssignments.id)
|
val soloAssignmentId = reference("solo_assignment_id", SoloAssignments.id)
|
||||||
|
val criterionId = reference("criterion_id", SoloAssignments.id).nullable()
|
||||||
val studentId = reference("student_id", Students.id)
|
val studentId = reference("student_id", Students.id)
|
||||||
val feedback = text("feedback")
|
val feedback = text("feedback")
|
||||||
val grade = varchar("grade", 32)
|
val grade = varchar("grade", 32)
|
||||||
|
|
|
@ -11,7 +11,7 @@ object Database {
|
||||||
SchemaUtils.create(
|
SchemaUtils.create(
|
||||||
Courses, Editions, Groups,
|
Courses, Editions, Groups,
|
||||||
Students, GroupStudents, EditionStudents,
|
Students, GroupStudents, EditionStudents,
|
||||||
GroupAssignments, SoloAssignments,
|
GroupAssignments, SoloAssignments, GroupAssignmentCriteria, SoloAssignmentCriteria,
|
||||||
GroupFeedbacks, IndividualFeedbacks, SoloFeedbacks,
|
GroupFeedbacks, IndividualFeedbacks, SoloFeedbacks,
|
||||||
PeerEvaluations, PeerEvaluationContents, StudentToStudentEvaluation,
|
PeerEvaluations, PeerEvaluationContents, StudentToStudentEvaluation,
|
||||||
StudentToGroupEvaluation
|
StudentToGroupEvaluation
|
||||||
|
@ -20,7 +20,7 @@ object Database {
|
||||||
val addMissing = SchemaUtils.addMissingColumnsStatements(
|
val addMissing = SchemaUtils.addMissingColumnsStatements(
|
||||||
Courses, Editions, Groups,
|
Courses, Editions, Groups,
|
||||||
Students, GroupStudents, EditionStudents,
|
Students, GroupStudents, EditionStudents,
|
||||||
GroupAssignments, SoloAssignments,
|
GroupAssignments, SoloAssignments, GroupAssignmentCriteria, SoloAssignmentCriteria,
|
||||||
GroupFeedbacks, IndividualFeedbacks, SoloFeedbacks,
|
GroupFeedbacks, IndividualFeedbacks, SoloFeedbacks,
|
||||||
PeerEvaluations, PeerEvaluationContents, StudentToStudentEvaluation,
|
PeerEvaluations, PeerEvaluationContents, StudentToStudentEvaluation,
|
||||||
StudentToGroupEvaluation
|
StudentToGroupEvaluation
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
package com.jaytux.grader.data
|
package com.jaytux.grader.data
|
||||||
|
|
||||||
|
import com.jaytux.grader.data.GroupAssignment.Companion.referrersOn
|
||||||
import org.jetbrains.exposed.dao.Entity
|
import org.jetbrains.exposed.dao.Entity
|
||||||
import org.jetbrains.exposed.dao.EntityClass
|
import org.jetbrains.exposed.dao.EntityClass
|
||||||
import org.jetbrains.exposed.dao.id.CompositeID
|
|
||||||
import org.jetbrains.exposed.dao.id.EntityID
|
import org.jetbrains.exposed.dao.id.EntityID
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
class Course(id: EntityID<UUID>) : Entity<UUID>(id) {
|
class Course(id: EntityID<UUID>) : Entity<UUID>(id) {
|
||||||
|
@ -61,6 +60,16 @@ class GroupAssignment(id: EntityID<UUID>) : Entity<UUID>(id) {
|
||||||
var name by GroupAssignments.name
|
var name by GroupAssignments.name
|
||||||
var assignment by GroupAssignments.assignment
|
var assignment by GroupAssignments.assignment
|
||||||
var deadline by GroupAssignments.deadline
|
var deadline by GroupAssignments.deadline
|
||||||
|
|
||||||
|
val criteria by GroupAssignmentCriterion referrersOn GroupAssignmentCriteria.assignmentId
|
||||||
|
}
|
||||||
|
|
||||||
|
class GroupAssignmentCriterion(id: EntityID<UUID>) : Entity<UUID>(id) {
|
||||||
|
companion object : EntityClass<UUID, GroupAssignmentCriterion>(GroupAssignmentCriteria)
|
||||||
|
|
||||||
|
var assignment by GroupAssignment referencedOn GroupAssignmentCriteria.assignmentId
|
||||||
|
var name by GroupAssignmentCriteria.name
|
||||||
|
var description by GroupAssignmentCriteria.desc
|
||||||
}
|
}
|
||||||
|
|
||||||
class SoloAssignment(id: EntityID<UUID>) : Entity<UUID>(id) {
|
class SoloAssignment(id: EntityID<UUID>) : Entity<UUID>(id) {
|
||||||
|
@ -71,6 +80,35 @@ class SoloAssignment(id: EntityID<UUID>) : Entity<UUID>(id) {
|
||||||
var name by SoloAssignments.name
|
var name by SoloAssignments.name
|
||||||
var assignment by SoloAssignments.assignment
|
var assignment by SoloAssignments.assignment
|
||||||
var deadline by SoloAssignments.deadline
|
var deadline by SoloAssignments.deadline
|
||||||
|
|
||||||
|
val criteria by SoloAssignmentCriterion referrersOn SoloAssignmentCriteria.assignmentId
|
||||||
|
}
|
||||||
|
|
||||||
|
class SoloAssignmentCriterion(id: EntityID<UUID>) : Entity<UUID>(id) {
|
||||||
|
companion object : EntityClass<UUID, SoloAssignmentCriterion>(SoloAssignmentCriteria)
|
||||||
|
|
||||||
|
var assignment by SoloAssignment referencedOn SoloAssignmentCriteria.assignmentId
|
||||||
|
var name by SoloAssignmentCriteria.name
|
||||||
|
var description by SoloAssignmentCriteria.desc
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (javaClass != other?.javaClass) return false
|
||||||
|
|
||||||
|
other as SoloAssignmentCriterion
|
||||||
|
|
||||||
|
if (name != other.name) return false
|
||||||
|
if (description != other.description) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = assignment.hashCode()
|
||||||
|
result = 31 * result + name.hashCode()
|
||||||
|
result = 31 * result + description.hashCode()
|
||||||
|
return result
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PeerEvaluation(id: EntityID<UUID>) : Entity<UUID>(id) {
|
class PeerEvaluation(id: EntityID<UUID>) : Entity<UUID>(id) {
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
package com.jaytux.grader.ui
|
package com.jaytux.grader.ui
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.border
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.*
|
import androidx.compose.foundation.lazy.*
|
||||||
|
@ -9,8 +7,6 @@ import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.graphics.SolidColor
|
|
||||||
import androidx.compose.ui.graphics.TransformOrigin
|
import androidx.compose.ui.graphics.TransformOrigin
|
||||||
import androidx.compose.ui.graphics.graphicsLayer
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
import androidx.compose.ui.layout.layout
|
import androidx.compose.ui.layout.layout
|
||||||
|
@ -20,19 +16,23 @@ import androidx.compose.ui.text.input.TextFieldValue
|
||||||
import androidx.compose.ui.text.rememberTextMeasurer
|
import androidx.compose.ui.text.rememberTextMeasurer
|
||||||
import androidx.compose.ui.unit.Constraints
|
import androidx.compose.ui.unit.Constraints
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.jaytux.grader.data.GroupAssignment
|
||||||
|
import com.jaytux.grader.data.GroupAssignmentCriterion
|
||||||
|
import com.jaytux.grader.data.SoloAssignmentCriterion
|
||||||
import com.jaytux.grader.data.Student
|
import com.jaytux.grader.data.Student
|
||||||
import com.jaytux.grader.viewmodel.GroupAssignmentState
|
import com.jaytux.grader.viewmodel.GroupAssignmentState
|
||||||
import com.jaytux.grader.viewmodel.PeerEvaluationState
|
import com.jaytux.grader.viewmodel.PeerEvaluationState
|
||||||
import com.jaytux.grader.viewmodel.SoloAssignmentState
|
import com.jaytux.grader.viewmodel.SoloAssignmentState
|
||||||
import com.mohamedrejeb.richeditor.model.rememberRichTextState
|
import com.mohamedrejeb.richeditor.model.rememberRichTextState
|
||||||
import com.mohamedrejeb.richeditor.ui.material3.OutlinedRichTextEditor
|
import com.mohamedrejeb.richeditor.ui.material3.OutlinedRichTextEditor
|
||||||
|
import kotlinx.datetime.LocalDateTime
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun GroupAssignmentView(state: GroupAssignmentState) {
|
fun GroupAssignmentView(state: GroupAssignmentState) {
|
||||||
val task by state.task
|
val task by state.task
|
||||||
val deadline by state.deadline
|
val deadline by state.deadline
|
||||||
val allFeedback by state.feedback.entities
|
val allFeedback by state.feedback.entities
|
||||||
|
val criteria by state.criteria.entities
|
||||||
|
|
||||||
var idx by remember(state) { mutableStateOf(0) }
|
var idx by remember(state) { mutableStateOf(0) }
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ fun GroupAssignmentView(state: GroupAssignmentState) {
|
||||||
}
|
}
|
||||||
|
|
||||||
TabRow(idx) {
|
TabRow(idx) {
|
||||||
Tab(idx == 0, { idx = 0 }) { Text("Assignment") }
|
Tab(idx == 0, { idx = 0 }) { Text("Task and Criteria") }
|
||||||
allFeedback.forEachIndexed { i, it ->
|
allFeedback.forEachIndexed { i, it ->
|
||||||
val (group, feedback) = it
|
val (group, feedback) = it
|
||||||
Tab(idx == i + 1, { idx = i + 1 }) {
|
Tab(idx == i + 1, { idx = i + 1 }) {
|
||||||
|
@ -55,12 +55,75 @@ fun GroupAssignmentView(state: GroupAssignmentState) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if(idx == 0) {
|
if(idx == 0) {
|
||||||
val updTask = rememberRichTextState()
|
groupTaskWidget(
|
||||||
|
task, deadline, criteria,
|
||||||
|
onSetTask = { state.updateTask(it) },
|
||||||
|
onSetDeadline = { state.updateDeadline(it) },
|
||||||
|
onAddCriterion = { state.addCriterion(it) },
|
||||||
|
onModCriterion = { c, n, d -> state.updateCriterion(c, n, d) },
|
||||||
|
onRmCriterion = { state.deleteCriterion(it) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
groupFeedback(state, allFeedback[idx - 1].second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LaunchedEffect(task) { updTask.setMarkdown(task) }
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun groupTaskWidget(
|
||||||
|
taskMD: String,
|
||||||
|
deadline: LocalDateTime,
|
||||||
|
criteria: List<GroupAssignmentCriterion>,
|
||||||
|
onSetTask: (String) -> Unit,
|
||||||
|
onSetDeadline: (LocalDateTime) -> Unit,
|
||||||
|
onAddCriterion: (name: String) -> Unit,
|
||||||
|
onModCriterion: (cr: GroupAssignmentCriterion, name: String, desc: String) -> Unit,
|
||||||
|
onRmCriterion: (cr: GroupAssignmentCriterion) -> Unit
|
||||||
|
) {
|
||||||
|
var critIdx by remember { mutableStateOf(0) }
|
||||||
|
var adding by remember { mutableStateOf(false) }
|
||||||
|
var confirming by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
DateTimePicker(deadline, { state.updateDeadline(it) })
|
Surface(Modifier.weight(0.25f), tonalElevation = 10.dp) {
|
||||||
|
Column(Modifier.padding(10.dp)) {
|
||||||
|
LazyColumn(Modifier.weight(1f)) {
|
||||||
|
item {
|
||||||
|
Surface(
|
||||||
|
Modifier.fillMaxWidth().clickable { critIdx = 0 },
|
||||||
|
tonalElevation = if (critIdx == 0) 50.dp else 0.dp,
|
||||||
|
shape = MaterialTheme.shapes.medium
|
||||||
|
) {
|
||||||
|
Text("Assignment", Modifier.padding(5.dp), fontStyle = FontStyle.Italic)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
itemsIndexed(criteria) { i, crit ->
|
||||||
|
Surface(
|
||||||
|
Modifier.fillMaxWidth().clickable { critIdx = i + 1 },
|
||||||
|
tonalElevation = if (critIdx == i + 1) 50.dp else 0.dp,
|
||||||
|
shape = MaterialTheme.shapes.medium
|
||||||
|
) {
|
||||||
|
Text(crit.name, Modifier.padding(5.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Button({ adding = true }, Modifier.align(Alignment.CenterHorizontally).fillMaxWidth()) {
|
||||||
|
Text("Add evaluation criterion")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Box(Modifier.weight(0.75f).padding(10.dp)) {
|
||||||
|
if (critIdx == 0) {
|
||||||
|
val updTask = rememberRichTextState()
|
||||||
|
|
||||||
|
LaunchedEffect(taskMD) { updTask.setMarkdown(taskMD) }
|
||||||
|
|
||||||
|
Column {
|
||||||
|
Row {
|
||||||
|
DateTimePicker(deadline, onSetDeadline)
|
||||||
}
|
}
|
||||||
RichTextStyleRow(state = updTask)
|
RichTextStyleRow(state = updTask)
|
||||||
OutlinedRichTextEditor(
|
OutlinedRichTextEditor(
|
||||||
|
@ -70,10 +133,53 @@ fun GroupAssignmentView(state: GroupAssignmentState) {
|
||||||
minLines = 5,
|
minLines = 5,
|
||||||
label = { Text("Task") }
|
label = { Text("Task") }
|
||||||
)
|
)
|
||||||
CancelSaveRow(true, { updTask.setMarkdown(task) }, "Reset", "Update") { state.updateTask(updTask.toMarkdown()) }
|
CancelSaveRow(
|
||||||
|
true,
|
||||||
|
{ updTask.setMarkdown(taskMD) },
|
||||||
|
"Reset",
|
||||||
|
"Update"
|
||||||
|
) { onSetTask(updTask.toMarkdown()) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
groupFeedback(state, allFeedback[idx - 1].second)
|
val crit = criteria[critIdx - 1]
|
||||||
|
var name by remember(crit) { mutableStateOf(crit.name) }
|
||||||
|
var desc by remember(crit) { mutableStateOf(crit.description) }
|
||||||
|
|
||||||
|
Column {
|
||||||
|
Row {
|
||||||
|
OutlinedTextField(name, { name = it }, Modifier.weight(0.8f))
|
||||||
|
Spacer(Modifier.weight(0.1f))
|
||||||
|
Button({ onModCriterion(crit, name, desc) }, Modifier.weight(0.1f)) {
|
||||||
|
Text("Update")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
OutlinedTextField(
|
||||||
|
desc, { desc = it }, Modifier.fillMaxWidth().weight(1f),
|
||||||
|
label = { Text("Description") },
|
||||||
|
singleLine = false,
|
||||||
|
minLines = 5
|
||||||
|
)
|
||||||
|
Button({ confirming = true }, Modifier.fillMaxWidth()) {
|
||||||
|
Text("Remove criterion")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(adding) {
|
||||||
|
AddStringDialog(
|
||||||
|
"Evaluation criterion name", criteria.map{ it.name }, { adding = false }
|
||||||
|
) { onAddCriterion(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
if(confirming && critIdx != 0) {
|
||||||
|
ConfirmDeleteDialog(
|
||||||
|
"an evaluation criterion",
|
||||||
|
{ confirming = false }, { onRmCriterion(criteria[critIdx - 1]); critIdx = 0 }
|
||||||
|
) {
|
||||||
|
Text(criteria[critIdx - 1].name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,9 +187,9 @@ fun GroupAssignmentView(state: GroupAssignmentState) {
|
||||||
@Composable
|
@Composable
|
||||||
fun groupFeedback(state: GroupAssignmentState, fdbk: GroupAssignmentState.LocalGFeedback) {
|
fun groupFeedback(state: GroupAssignmentState, fdbk: GroupAssignmentState.LocalGFeedback) {
|
||||||
val (group, feedback, individual) = fdbk
|
val (group, feedback, individual) = fdbk
|
||||||
var grade by remember(fdbk) { mutableStateOf(feedback?.grade ?: "") }
|
|
||||||
var msg by remember(fdbk) { mutableStateOf(TextFieldValue(feedback?.feedback ?: "")) }
|
|
||||||
var idx by remember(fdbk) { mutableStateOf(0) }
|
var idx by remember(fdbk) { mutableStateOf(0) }
|
||||||
|
var critIdx by remember(fdbk) { mutableStateOf(0) }
|
||||||
|
val criteria by state.criteria.entities
|
||||||
val suggestions by state.autofill.entities
|
val suggestions by state.autofill.entities
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
|
@ -112,44 +218,86 @@ fun groupFeedback(state: GroupAssignmentState, fdbk: GroupAssignmentState.LocalG
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Column(Modifier.weight(0.75f).padding(10.dp)) {
|
val updateGrade = { grade: String ->
|
||||||
if(idx == 0) {
|
if(idx == 0) {
|
||||||
Row {
|
state.upsertGroupFeedback(group, feedback.global?.feedback ?: "", grade)
|
||||||
Text("Grade: ", Modifier.align(Alignment.CenterVertically))
|
}
|
||||||
OutlinedTextField(grade, { grade = it }, Modifier.weight(0.2f))
|
else {
|
||||||
Spacer(Modifier.weight(0.6f))
|
val ind = individual[idx - 1]
|
||||||
Button({ state.upsertGroupFeedback(group, msg.text, grade) }, Modifier.weight(0.2f).align(Alignment.CenterVertically),
|
val glob = ind.second.second.global
|
||||||
enabled = grade.isNotBlank() || msg.text.isNotBlank()) {
|
state.upsertIndividualFeedback(ind.first, group, glob?.feedback ?: "", grade)
|
||||||
Text("Save")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AutocompleteLineField(
|
val updateFeedback = { fdbk: String ->
|
||||||
msg, { msg = it }, Modifier.fillMaxWidth().weight(1f), { Text("Feedback") }
|
if(idx == 0) {
|
||||||
) { filter ->
|
if(critIdx == 0) {
|
||||||
suggestions.filter { x -> x.trim().startsWith(filter.trim()) }
|
state.upsertGroupFeedback(group, fdbk, feedback.global?.grade ?: "", null)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
val current = feedback.byCriterion[critIdx - 1]
|
||||||
|
state.upsertGroupFeedback(group, fdbk, current.entry?.grade ?: "", current.criterion)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
val (student, details) = individual[idx - 1]
|
val ind = individual[idx - 1]
|
||||||
var sGrade by remember { mutableStateOf(details.second?.grade ?: "") }
|
if(critIdx == 0) {
|
||||||
var sMsg by remember { mutableStateOf(TextFieldValue(details.second?.feedback ?: "")) }
|
val entry = ind.second.second
|
||||||
Row {
|
state.upsertIndividualFeedback(ind.first, group, fdbk, entry.global?.grade ?: "", null)
|
||||||
Text("Grade: ", Modifier.align(Alignment.CenterVertically))
|
}
|
||||||
OutlinedTextField(sGrade, { sGrade = it }, Modifier.weight(0.2f))
|
else {
|
||||||
Spacer(Modifier.weight(0.6f))
|
val entry = ind.second.second.byCriterion[critIdx - 1]
|
||||||
Button({ state.upsertIndividualFeedback(student, group, sMsg.text, sGrade) }, Modifier.weight(0.2f).align(Alignment.CenterVertically),
|
state.upsertIndividualFeedback(ind.first, group, fdbk, entry.entry?.grade ?: "", entry.criterion)
|
||||||
enabled = sGrade.isNotBlank() || sMsg.text.isNotBlank()) {
|
}
|
||||||
Text("Save")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
groupFeedbackPane(
|
||||||
|
criteria, critIdx, { critIdx = it }, feedback.global,
|
||||||
|
if(critIdx == 0) feedback.global else feedback.byCriterion[critIdx - 1].entry,
|
||||||
|
suggestions, updateGrade, updateFeedback, Modifier.weight(0.75f).padding(10.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun groupFeedbackPane(
|
||||||
|
criteria: List<GroupAssignmentCriterion>,
|
||||||
|
currentCriterion: Int,
|
||||||
|
onSelectCriterion: (Int) -> Unit,
|
||||||
|
globFeedback: GroupAssignmentState.FeedbackEntry?,
|
||||||
|
criterionFeedback: GroupAssignmentState.FeedbackEntry?,
|
||||||
|
autofill: List<String>,
|
||||||
|
onSetGrade: (String) -> Unit,
|
||||||
|
onSetFeedback: (String) -> Unit,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
var grade by remember(globFeedback) { mutableStateOf(globFeedback?.grade ?: "") }
|
||||||
|
var feedback by remember(currentCriterion, criteria) { mutableStateOf(TextFieldValue(criterionFeedback?.feedback ?: "")) }
|
||||||
|
Column(modifier) {
|
||||||
|
Row {
|
||||||
|
Text("Overall grade: ", Modifier.align(Alignment.CenterVertically))
|
||||||
|
OutlinedTextField(grade, { grade = it }, Modifier.weight(0.2f))
|
||||||
|
Spacer(Modifier.weight(0.6f))
|
||||||
|
Button(
|
||||||
|
{ onSetGrade(grade); onSetFeedback(feedback.text) },
|
||||||
|
Modifier.weight(0.2f).align(Alignment.CenterVertically),
|
||||||
|
enabled = grade.isNotBlank() || feedback.text.isNotBlank()
|
||||||
|
) {
|
||||||
|
Text("Save")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TabRow(currentCriterion) {
|
||||||
|
Tab(currentCriterion == 0, { onSelectCriterion(0) }) { Text("General feedback", fontStyle = FontStyle.Italic) }
|
||||||
|
criteria.forEachIndexed { i, c ->
|
||||||
|
Tab(currentCriterion == i + 1, { onSelectCriterion(i + 1) }) { Text(c.name) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Spacer(Modifier.height(5.dp))
|
||||||
AutocompleteLineField(
|
AutocompleteLineField(
|
||||||
sMsg, { sMsg = it }, Modifier.fillMaxWidth().weight(1f), { Text("Feedback") }
|
feedback, { feedback = it }, Modifier.fillMaxWidth().weight(1f), { Text("Feedback") }
|
||||||
) { filter ->
|
) { filter ->
|
||||||
suggestions.filter { x -> x.trim().startsWith(filter.trim()) }
|
autofill.filter { x -> x.trim().startsWith(filter.trim()) }
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -161,13 +309,43 @@ fun SoloAssignmentView(state: SoloAssignmentState) {
|
||||||
val deadline by state.deadline
|
val deadline by state.deadline
|
||||||
val suggestions by state.autofill.entities
|
val suggestions by state.autofill.entities
|
||||||
val grades by state.feedback.entities
|
val grades by state.feedback.entities
|
||||||
|
val criteria by state.criteria.entities
|
||||||
|
|
||||||
var idx by remember(state) { mutableStateOf(0) }
|
var tab by remember(state) { mutableStateOf(0) }
|
||||||
|
var idx by remember(state, tab) { mutableStateOf(0) }
|
||||||
|
var critIdx by remember(state, tab, idx) { mutableStateOf(0) }
|
||||||
|
var adding by remember(state, tab) { mutableStateOf(false) }
|
||||||
|
var confirming by remember(state, tab) { mutableStateOf(false) }
|
||||||
|
|
||||||
|
val updateGrade = { grade: String ->
|
||||||
|
state.upsertFeedback(
|
||||||
|
grades[idx].first,
|
||||||
|
if(critIdx == 0) grades[idx].second.global?.feedback else grades[idx].second.byCriterion[critIdx - 1].second?.feedback,
|
||||||
|
grade,
|
||||||
|
if(critIdx == 0) null else criteria[critIdx - 1]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val updateFeedback = { feedback: String ->
|
||||||
|
state.upsertFeedback(
|
||||||
|
grades[idx].first,
|
||||||
|
feedback,
|
||||||
|
if(critIdx == 0) grades[idx].second.global?.grade else grades[idx].second.byCriterion[critIdx - 1].second?.grade,
|
||||||
|
if(critIdx == 0) null else criteria[critIdx - 1]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
Column(Modifier.padding(10.dp)) {
|
Column(Modifier.padding(10.dp)) {
|
||||||
Row {
|
Row {
|
||||||
Surface(Modifier.weight(0.25f), tonalElevation = 10.dp) {
|
Surface(Modifier.weight(0.25f), tonalElevation = 10.dp) {
|
||||||
LazyColumn(Modifier.fillMaxHeight().padding(10.dp)) {
|
Column(Modifier.padding(10.dp)) {
|
||||||
|
TabRow(tab) {
|
||||||
|
Tab(tab == 0, { tab = 0 }) { Text("Task/Criteria") }
|
||||||
|
Tab(tab == 1, { tab = 1 }) { Text("Students") }
|
||||||
|
}
|
||||||
|
|
||||||
|
LazyColumn(Modifier.weight(1f)) {
|
||||||
|
if (tab == 0) {
|
||||||
item {
|
item {
|
||||||
Surface(
|
Surface(
|
||||||
Modifier.fillMaxWidth().clickable { idx = 0 },
|
Modifier.fillMaxWidth().clickable { idx = 0 },
|
||||||
|
@ -178,11 +356,21 @@ fun SoloAssignmentView(state: SoloAssignmentState) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
itemsIndexed(grades.toList()) { i, (student, _) ->
|
itemsIndexed(criteria) { i, crit ->
|
||||||
Surface(
|
Surface(
|
||||||
Modifier.fillMaxWidth().clickable { idx = i + 1 },
|
Modifier.fillMaxWidth().clickable { idx = i + 1 },
|
||||||
tonalElevation = if (idx == i + 1) 50.dp else 0.dp,
|
tonalElevation = if (idx == i + 1) 50.dp else 0.dp,
|
||||||
shape = MaterialTheme.shapes.medium
|
shape = MaterialTheme.shapes.medium
|
||||||
|
) {
|
||||||
|
Text(crit.name, Modifier.padding(5.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
itemsIndexed(grades.toList()) { i, (student, _) ->
|
||||||
|
Surface(
|
||||||
|
Modifier.fillMaxWidth().clickable { idx = i },
|
||||||
|
tonalElevation = if (idx == i) 50.dp else 0.dp,
|
||||||
|
shape = MaterialTheme.shapes.medium
|
||||||
) {
|
) {
|
||||||
Text(student.name, Modifier.padding(5.dp))
|
Text(student.name, Modifier.padding(5.dp))
|
||||||
}
|
}
|
||||||
|
@ -190,7 +378,16 @@ fun SoloAssignmentView(state: SoloAssignmentState) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (tab == 0) {
|
||||||
|
Button({ adding = true }, Modifier.align(Alignment.CenterHorizontally).fillMaxWidth()) {
|
||||||
|
Text("Add evaluation criterion")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Column(Modifier.weight(0.75f).padding(10.dp)) {
|
Column(Modifier.weight(0.75f).padding(10.dp)) {
|
||||||
|
if(tab == 0) {
|
||||||
if (idx == 0) {
|
if (idx == 0) {
|
||||||
val updTask = rememberRichTextState()
|
val updTask = rememberRichTextState()
|
||||||
|
|
||||||
|
@ -214,29 +411,97 @@ fun SoloAssignmentView(state: SoloAssignmentState) {
|
||||||
"Update"
|
"Update"
|
||||||
) { state.updateTask(updTask.toMarkdown()) }
|
) { state.updateTask(updTask.toMarkdown()) }
|
||||||
} else {
|
} else {
|
||||||
val (student, fg) = grades[idx - 1]
|
val crit = criteria[idx - 1]
|
||||||
var sGrade by remember(idx) { mutableStateOf(fg?.grade ?: "") }
|
var name by remember(crit) { mutableStateOf(crit.name) }
|
||||||
var sMsg by remember(idx) { mutableStateOf(TextFieldValue(fg?.feedback ?: "")) }
|
var desc by remember(crit) { mutableStateOf(crit.description) }
|
||||||
|
|
||||||
|
Column {
|
||||||
Row {
|
Row {
|
||||||
Text("Grade: ", Modifier.align(Alignment.CenterVertically))
|
OutlinedTextField(name, { name = it }, Modifier.weight(0.8f))
|
||||||
OutlinedTextField(sGrade, { sGrade = it }, Modifier.weight(0.2f))
|
Spacer(Modifier.weight(0.1f))
|
||||||
|
Button({ state.updateCriterion(crit, name, desc) }, Modifier.weight(0.1f)) {
|
||||||
|
Text("Update")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
OutlinedTextField(
|
||||||
|
desc, { desc = it }, Modifier.fillMaxWidth().weight(1f),
|
||||||
|
label = { Text("Description") },
|
||||||
|
singleLine = false,
|
||||||
|
minLines = 5
|
||||||
|
)
|
||||||
|
Button({ confirming = true }, Modifier.fillMaxWidth()) {
|
||||||
|
Text("Remove criterion")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
soloFeedbackPane(
|
||||||
|
criteria, critIdx, { critIdx = it }, grades[idx].second.global,
|
||||||
|
if(critIdx == 0) grades[idx].second.global else grades[idx].second.byCriterion[critIdx - 1].second,
|
||||||
|
suggestions, updateGrade, updateFeedback,
|
||||||
|
key = tab to idx
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(adding) {
|
||||||
|
AddStringDialog(
|
||||||
|
"Evaluation criterion name", criteria.map{ it.name }, { adding = false }
|
||||||
|
) { state.addCriterion(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
if(confirming && idx != 0) {
|
||||||
|
ConfirmDeleteDialog(
|
||||||
|
"an evaluation criterion",
|
||||||
|
{ confirming = false }, { state.deleteCriterion(criteria[idx - 1]); idx = 0 }
|
||||||
|
) {
|
||||||
|
Text(criteria[idx - 1].name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun soloFeedbackPane(
|
||||||
|
criteria: List<SoloAssignmentCriterion>,
|
||||||
|
currentCriterion: Int,
|
||||||
|
onSelectCriterion: (Int) -> Unit,
|
||||||
|
globFeedback: SoloAssignmentState.LocalFeedback?,
|
||||||
|
criterionFeedback: SoloAssignmentState.LocalFeedback?,
|
||||||
|
autofill: List<String>,
|
||||||
|
onSetGrade: (String) -> Unit,
|
||||||
|
onSetFeedback: (String) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
key: Any? = null
|
||||||
|
) {
|
||||||
|
var grade by remember(globFeedback, key) { mutableStateOf(globFeedback?.grade ?: "") }
|
||||||
|
var feedback by remember(currentCriterion, criteria, key) { mutableStateOf(TextFieldValue(criterionFeedback?.feedback ?: "")) }
|
||||||
|
Column(modifier) {
|
||||||
|
Row {
|
||||||
|
Text("Overall grade: ", Modifier.align(Alignment.CenterVertically))
|
||||||
|
OutlinedTextField(grade, { grade = it }, Modifier.weight(0.2f))
|
||||||
Spacer(Modifier.weight(0.6f))
|
Spacer(Modifier.weight(0.6f))
|
||||||
Button(
|
Button(
|
||||||
{ state.upsertFeedback(student, sMsg.text, sGrade) },
|
{ onSetGrade(grade); onSetFeedback(feedback.text) },
|
||||||
Modifier.weight(0.2f).align(Alignment.CenterVertically),
|
Modifier.weight(0.2f).align(Alignment.CenterVertically),
|
||||||
enabled = sGrade.isNotBlank() || sMsg.text.isNotBlank()
|
enabled = grade.isNotBlank() || feedback.text.isNotBlank()
|
||||||
) {
|
) {
|
||||||
Text("Save")
|
Text("Save")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
TabRow(currentCriterion) {
|
||||||
|
Tab(currentCriterion == 0, { onSelectCriterion(0) }) { Text("General feedback", fontStyle = FontStyle.Italic) }
|
||||||
|
criteria.forEachIndexed { i, c ->
|
||||||
|
Tab(currentCriterion == i + 1, { onSelectCriterion(i + 1) }) { Text(c.name) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Spacer(Modifier.height(5.dp))
|
||||||
AutocompleteLineField(
|
AutocompleteLineField(
|
||||||
sMsg, { sMsg = it }, Modifier.fillMaxWidth().weight(1f), { Text("Feedback") }
|
feedback, { feedback = it }, Modifier.fillMaxWidth().weight(1f), { Text("Feedback") }
|
||||||
) { filter ->
|
) { filter ->
|
||||||
suggestions.filter { x -> x.trim().startsWith(filter.trim()) }
|
autofill.filter { x -> x.trim().startsWith(filter.trim()) }
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import androidx.compose.runtime.mutableStateOf
|
||||||
import com.jaytux.grader.data.*
|
import com.jaytux.grader.data.*
|
||||||
import com.jaytux.grader.data.EditionStudents.editionId
|
import com.jaytux.grader.data.EditionStudents.editionId
|
||||||
import com.jaytux.grader.data.EditionStudents.studentId
|
import com.jaytux.grader.data.EditionStudents.studentId
|
||||||
|
import com.jaytux.grader.viewmodel.GroupAssignmentState.*
|
||||||
import kotlinx.datetime.*
|
import kotlinx.datetime.*
|
||||||
import kotlinx.datetime.TimeZone
|
import kotlinx.datetime.TimeZone
|
||||||
import org.jetbrains.exposed.dao.id.EntityID
|
import org.jetbrains.exposed.dao.id.EntityID
|
||||||
|
@ -445,18 +446,27 @@ class GroupState(val group: Group) {
|
||||||
}
|
}
|
||||||
|
|
||||||
class GroupAssignmentState(val assignment: GroupAssignment) {
|
class GroupAssignmentState(val assignment: GroupAssignment) {
|
||||||
data class LocalFeedback(val feedback: String, val grade: String)
|
data class FeedbackEntry(val feedback: String, val grade: String)
|
||||||
|
data class LocalCriterionFeedback(
|
||||||
|
val criterion: GroupAssignmentCriterion, val entry: FeedbackEntry?
|
||||||
|
)
|
||||||
|
data class LocalFeedback(
|
||||||
|
val global: FeedbackEntry?, val byCriterion: List<LocalCriterionFeedback>
|
||||||
|
)
|
||||||
data class LocalGFeedback(
|
data class LocalGFeedback(
|
||||||
val group: Group,
|
val group: Group,
|
||||||
val feedback: LocalFeedback?,
|
val feedback: LocalFeedback,
|
||||||
val individuals: List<Pair<Student, Pair<String?, LocalFeedback?>>>
|
val individuals: List<Pair<Student, Pair<String?, LocalFeedback>>>
|
||||||
)
|
)
|
||||||
|
|
||||||
val editionCourse = transaction { assignment.edition.course to assignment.edition }
|
val editionCourse = transaction { assignment.edition.course to assignment.edition }
|
||||||
private val _name = mutableStateOf(assignment.name); val name = _name.immutable()
|
private val _name = mutableStateOf(assignment.name); val name = _name.immutable()
|
||||||
private val _task = mutableStateOf(assignment.assignment); val task = _task.immutable()
|
private val _task = mutableStateOf(assignment.assignment); val task = _task.immutable()
|
||||||
val feedback = RawDbState { loadFeedback() }
|
|
||||||
private val _deadline = mutableStateOf(assignment.deadline); val deadline = _deadline.immutable()
|
private val _deadline = mutableStateOf(assignment.deadline); val deadline = _deadline.immutable()
|
||||||
|
val criteria = RawDbState {
|
||||||
|
assignment.criteria.orderBy(GroupAssignmentCriteria.name to SortOrder.ASC).toList()
|
||||||
|
}
|
||||||
|
val feedback = RawDbState { loadFeedback() }
|
||||||
|
|
||||||
val autofill = RawDbState {
|
val autofill = RawDbState {
|
||||||
val forGroups = GroupFeedbacks.selectAll().where { GroupFeedbacks.groupAssignmentId eq assignment.id }.flatMap {
|
val forGroups = GroupFeedbacks.selectAll().where { GroupFeedbacks.groupAssignmentId eq assignment.id }.flatMap {
|
||||||
|
@ -471,50 +481,63 @@ class GroupAssignmentState(val assignment: GroupAssignment) {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Transaction.loadFeedback(): List<Pair<Group, LocalGFeedback>> {
|
private fun Transaction.loadFeedback(): List<Pair<Group, LocalGFeedback>> {
|
||||||
val individuals = IndividualFeedbacks.selectAll().where {
|
return Group.find {
|
||||||
IndividualFeedbacks.groupAssignmentId eq assignment.id
|
|
||||||
}.map {
|
|
||||||
it[IndividualFeedbacks.studentId] to LocalFeedback(it[IndividualFeedbacks.feedback], it[IndividualFeedbacks.grade])
|
|
||||||
}.associate { it }
|
|
||||||
|
|
||||||
val groupFeedbacks = GroupFeedbacks.selectAll().where {
|
|
||||||
GroupFeedbacks.groupAssignmentId eq assignment.id
|
|
||||||
}.map {
|
|
||||||
it[GroupFeedbacks.groupId] to (it[GroupFeedbacks.feedback] to it[GroupFeedbacks.grade])
|
|
||||||
}.associate { it }
|
|
||||||
|
|
||||||
val groups = Group.find {
|
|
||||||
(Groups.editionId eq assignment.edition.id)
|
(Groups.editionId eq assignment.edition.id)
|
||||||
}.sortAsc(Groups.name).map { group ->
|
}.sortAsc(Groups.name).map { group ->
|
||||||
val students = group.studentRoles.sortedBy { it.student.name }.map { sR ->
|
// step 1: group-level feedback, including criteria
|
||||||
val student = sR.student
|
val forGroup = GroupFeedbacks.selectAll().where {
|
||||||
val role = sR.role
|
(GroupFeedbacks.groupAssignmentId eq assignment.id) and
|
||||||
val feedback = individuals[student.id]
|
(GroupFeedbacks.groupId eq group.id)
|
||||||
|
}.associate {
|
||||||
|
val criterion = it[GroupFeedbacks.criterionId]?.let { id -> GroupAssignmentCriterion[id] }
|
||||||
|
val fe = FeedbackEntry(it[GroupFeedbacks.feedback], it[GroupFeedbacks.grade])
|
||||||
|
criterion to fe
|
||||||
|
}
|
||||||
|
val feedback = LocalFeedback(
|
||||||
|
global = forGroup[null],
|
||||||
|
byCriterion = criteria.entities.value.map { c -> LocalCriterionFeedback(c, forGroup[c]) }
|
||||||
|
)
|
||||||
|
|
||||||
student to (role to feedback)
|
// step 2: individual feedback
|
||||||
|
val individuals = group.studentRoles.map { sr ->
|
||||||
|
val student = sr.student
|
||||||
|
val role = sr.role
|
||||||
|
|
||||||
|
val forStudent = IndividualFeedbacks.selectAll().where {
|
||||||
|
(IndividualFeedbacks.groupAssignmentId eq assignment.id) and
|
||||||
|
(IndividualFeedbacks.groupId eq group.id) and
|
||||||
|
(IndividualFeedbacks.studentId eq student.id)
|
||||||
|
}.associate {
|
||||||
|
val criterion = it[IndividualFeedbacks.criterionId]?.let { id -> GroupAssignmentCriterion[id] }
|
||||||
|
val fe = FeedbackEntry(it[IndividualFeedbacks.feedback], it[IndividualFeedbacks.grade])
|
||||||
|
criterion to fe
|
||||||
|
}
|
||||||
|
val studentFeedback = LocalFeedback(
|
||||||
|
global = forStudent[null],
|
||||||
|
byCriterion = criteria.entities.value.map { c -> LocalCriterionFeedback(c, forStudent[c]) }
|
||||||
|
)
|
||||||
|
|
||||||
|
student to (role to studentFeedback)
|
||||||
|
}.sortedBy { it.first.name }
|
||||||
|
|
||||||
|
group to LocalGFeedback(group, feedback, individuals)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
groupFeedbacks[group.id]?.let { (f, g) ->
|
fun upsertGroupFeedback(group: Group, msg: String, grd: String, criterion: GroupAssignmentCriterion? = null) {
|
||||||
group to LocalGFeedback(group, LocalFeedback(f, g), students)
|
|
||||||
} ?: (group to LocalGFeedback(group, null, students))
|
|
||||||
}
|
|
||||||
|
|
||||||
return groups
|
|
||||||
}
|
|
||||||
|
|
||||||
fun upsertGroupFeedback(group: Group, msg: String, grd: String) {
|
|
||||||
transaction {
|
transaction {
|
||||||
GroupFeedbacks.upsert {
|
GroupFeedbacks.upsert {
|
||||||
it[groupAssignmentId] = assignment.id
|
it[groupAssignmentId] = assignment.id
|
||||||
it[groupId] = group.id
|
it[groupId] = group.id
|
||||||
it[this.feedback] = msg
|
it[this.feedback] = msg
|
||||||
it[this.grade] = grd
|
it[this.grade] = grd
|
||||||
|
it[criterionId] = criterion?.id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
feedback.refresh(); autofill.refresh()
|
feedback.refresh(); autofill.refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun upsertIndividualFeedback(student: Student, group: Group, msg: String, grd: String) {
|
fun upsertIndividualFeedback(student: Student, group: Group, msg: String, grd: String, criterion: GroupAssignmentCriterion? = null) {
|
||||||
transaction {
|
transaction {
|
||||||
IndividualFeedbacks.upsert {
|
IndividualFeedbacks.upsert {
|
||||||
it[groupAssignmentId] = assignment.id
|
it[groupAssignmentId] = assignment.id
|
||||||
|
@ -522,6 +545,7 @@ class GroupAssignmentState(val assignment: GroupAssignment) {
|
||||||
it[studentId] = student.id
|
it[studentId] = student.id
|
||||||
it[this.feedback] = msg
|
it[this.feedback] = msg
|
||||||
it[this.grade] = grd
|
it[this.grade] = grd
|
||||||
|
it[criterionId] = criterion?.id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
feedback.refresh(); autofill.refresh()
|
feedback.refresh(); autofill.refresh()
|
||||||
|
@ -540,16 +564,48 @@ class GroupAssignmentState(val assignment: GroupAssignment) {
|
||||||
}
|
}
|
||||||
_deadline.value = d
|
_deadline.value = d
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun addCriterion(name: String) {
|
||||||
|
transaction {
|
||||||
|
GroupAssignmentCriterion.new {
|
||||||
|
this.name = name;
|
||||||
|
this.description = "";
|
||||||
|
this.assignment = this@GroupAssignmentState.assignment
|
||||||
|
}
|
||||||
|
criteria.refresh()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateCriterion(criterion: GroupAssignmentCriterion, name: String, desc: String) {
|
||||||
|
transaction {
|
||||||
|
criterion.name = name
|
||||||
|
criterion.description = desc
|
||||||
|
}
|
||||||
|
criteria.refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deleteCriterion(criterion: GroupAssignmentCriterion) {
|
||||||
|
transaction {
|
||||||
|
GroupFeedbacks.deleteWhere { criterionId eq criterion.id }
|
||||||
|
IndividualFeedbacks.deleteWhere { criterionId eq criterion.id }
|
||||||
|
criterion.delete()
|
||||||
|
}
|
||||||
|
criteria.refresh()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SoloAssignmentState(val assignment: SoloAssignment) {
|
class SoloAssignmentState(val assignment: SoloAssignment) {
|
||||||
data class LocalFeedback(val feedback: String, val grade: String)
|
data class LocalFeedback(val feedback: String, val grade: String)
|
||||||
|
data class FullFeedback(val global: LocalFeedback?, val byCriterion: List<Pair<SoloAssignmentCriterion, LocalFeedback?>>)
|
||||||
|
|
||||||
val editionCourse = transaction { assignment.edition.course to assignment.edition }
|
val editionCourse = transaction { assignment.edition.course to assignment.edition }
|
||||||
private val _name = mutableStateOf(assignment.name); val name = _name.immutable()
|
private val _name = mutableStateOf(assignment.name); val name = _name.immutable()
|
||||||
private val _task = mutableStateOf(assignment.assignment); val task = _task.immutable()
|
private val _task = mutableStateOf(assignment.assignment); val task = _task.immutable()
|
||||||
val feedback = RawDbState { loadFeedback() }
|
|
||||||
private val _deadline = mutableStateOf(assignment.deadline); val deadline = _deadline.immutable()
|
private val _deadline = mutableStateOf(assignment.deadline); val deadline = _deadline.immutable()
|
||||||
|
val criteria = RawDbState {
|
||||||
|
assignment.criteria.orderBy(SoloAssignmentCriteria.name to SortOrder.ASC).toList()
|
||||||
|
}
|
||||||
|
val feedback = RawDbState { loadFeedback() }
|
||||||
|
|
||||||
val autofill = RawDbState {
|
val autofill = RawDbState {
|
||||||
SoloFeedbacks.selectAll().where { SoloFeedbacks.soloAssignmentId eq assignment.id }.map {
|
SoloFeedbacks.selectAll().where { SoloFeedbacks.soloAssignmentId eq assignment.id }.map {
|
||||||
|
@ -557,24 +613,33 @@ class SoloAssignmentState(val assignment: SoloAssignment) {
|
||||||
}.flatten().distinct().sorted()
|
}.flatten().distinct().sorted()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Transaction.loadFeedback(): List<Pair<Student, LocalFeedback?>> {
|
private fun Transaction.loadFeedback(): List<Pair<Student, FullFeedback>> {
|
||||||
val students = editionCourse.second.soloStudents
|
return editionCourse.second.soloStudents.sortAsc(Students.name).map { student ->
|
||||||
val feedbacks = SoloFeedbacks.selectAll().where {
|
val each = SoloFeedbacks.selectAll().where {
|
||||||
SoloFeedbacks.soloAssignmentId eq assignment.id
|
(SoloFeedbacks.soloAssignmentId eq assignment.id) and
|
||||||
|
(SoloFeedbacks.studentId eq student.id)
|
||||||
}.associate {
|
}.associate {
|
||||||
it[SoloFeedbacks.studentId] to LocalFeedback(it[SoloFeedbacks.feedback], it[SoloFeedbacks.grade])
|
val criterion = it[SoloFeedbacks.criterionId]?.let { id -> SoloAssignmentCriterion[id] }
|
||||||
|
val fe = LocalFeedback(it[SoloFeedbacks.feedback], it[SoloFeedbacks.grade])
|
||||||
|
criterion to fe
|
||||||
|
}
|
||||||
|
val feedback = FullFeedback(
|
||||||
|
global = each[null],
|
||||||
|
byCriterion = criteria.entities.value.map { c -> c to each[c] }
|
||||||
|
)
|
||||||
|
|
||||||
|
student to feedback
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return students.map { s -> s to feedbacks[s.id] }
|
fun upsertFeedback(student: Student, msg: String?, grd: String?, criterion: SoloAssignmentCriterion? = null) {
|
||||||
}
|
|
||||||
|
|
||||||
fun upsertFeedback(student: Student, msg: String, grd: String) {
|
|
||||||
transaction {
|
transaction {
|
||||||
SoloFeedbacks.upsert {
|
SoloFeedbacks.upsert {
|
||||||
it[soloAssignmentId] = assignment.id
|
it[soloAssignmentId] = assignment.id
|
||||||
it[studentId] = student.id
|
it[studentId] = student.id
|
||||||
it[this.feedback] = msg
|
it[this.feedback] = msg ?: ""
|
||||||
it[this.grade] = grd
|
it[this.grade] = grd ?: ""
|
||||||
|
it[criterionId] = criterion?.id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
feedback.refresh(); autofill.refresh()
|
feedback.refresh(); autofill.refresh()
|
||||||
|
@ -593,6 +658,33 @@ class SoloAssignmentState(val assignment: SoloAssignment) {
|
||||||
}
|
}
|
||||||
_deadline.value = d
|
_deadline.value = d
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun addCriterion(name: String) {
|
||||||
|
transaction {
|
||||||
|
SoloAssignmentCriterion.new {
|
||||||
|
this.name = name;
|
||||||
|
this.description = "";
|
||||||
|
this.assignment = this@SoloAssignmentState.assignment
|
||||||
|
}
|
||||||
|
criteria.refresh()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateCriterion(criterion: SoloAssignmentCriterion, name: String, desc: String) {
|
||||||
|
transaction {
|
||||||
|
criterion.name = name
|
||||||
|
criterion.description = desc
|
||||||
|
}
|
||||||
|
criteria.refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deleteCriterion(criterion: SoloAssignmentCriterion) {
|
||||||
|
transaction {
|
||||||
|
SoloFeedbacks.deleteWhere { criterionId eq criterion.id }
|
||||||
|
criterion.delete()
|
||||||
|
}
|
||||||
|
criteria.refresh()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PeerEvaluationState(val evaluation: PeerEvaluation) {
|
class PeerEvaluationState(val evaluation: PeerEvaluation) {
|
||||||
|
|
Loading…
Reference in New Issue