Almost finished!
This commit is contained in:
parent
c8b605353c
commit
054970bb79
|
@ -1,8 +1,10 @@
|
|||
package com.jaytux.grader.data
|
||||
|
||||
import kotlinx.datetime.*
|
||||
import org.jetbrains.exposed.dao.id.CompositeIdTable
|
||||
import org.jetbrains.exposed.dao.id.UUIDTable
|
||||
import org.jetbrains.exposed.sql.Table
|
||||
import org.jetbrains.exposed.sql.kotlin.datetime.datetime
|
||||
|
||||
object Courses : UUIDTable("courses") {
|
||||
val name = varchar("name", 50).uniqueIndex()
|
||||
|
@ -53,12 +55,14 @@ object GroupAssignments : UUIDTable("grpAssgmts") {
|
|||
val editionId = reference("edition_id", Editions.id)
|
||||
val name = varchar("name", 50)
|
||||
val assignment = text("assignment")
|
||||
val deadline = datetime("deadline")
|
||||
}
|
||||
|
||||
object SoloAssignments : UUIDTable("soloAssgmts") {
|
||||
val editionId = reference("edition_id", Editions.id)
|
||||
val name = varchar("name", 50)
|
||||
val assignment = text("assignment")
|
||||
val deadline = datetime("deadline")
|
||||
}
|
||||
|
||||
object GroupFeedbacks : CompositeIdTable("grpFdbks") {
|
||||
|
|
|
@ -14,6 +14,14 @@ object Database {
|
|||
GroupAssignments, SoloAssignments,
|
||||
GroupFeedbacks, IndividualFeedbacks, SoloFeedbacks
|
||||
)
|
||||
|
||||
val addMissing = SchemaUtils.addMissingColumnsStatements(
|
||||
Courses, Editions, Groups,
|
||||
Students, GroupStudents, EditionStudents,
|
||||
GroupAssignments, SoloAssignments,
|
||||
GroupFeedbacks, IndividualFeedbacks, SoloFeedbacks
|
||||
)
|
||||
addMissing.forEach { exec(it) }
|
||||
}
|
||||
actual
|
||||
}
|
||||
|
|
|
@ -59,6 +59,7 @@ class GroupAssignment(id: EntityID<UUID>) : Entity<UUID>(id) {
|
|||
var edition by Edition referencedOn GroupAssignments.editionId
|
||||
var name by GroupAssignments.name
|
||||
var assignment by GroupAssignments.assignment
|
||||
var deadline by GroupAssignments.deadline
|
||||
}
|
||||
|
||||
class SoloAssignment(id: EntityID<UUID>) : Entity<UUID>(id) {
|
||||
|
@ -67,6 +68,7 @@ class SoloAssignment(id: EntityID<UUID>) : Entity<UUID>(id) {
|
|||
var edition by Edition referencedOn SoloAssignments.editionId
|
||||
var name by SoloAssignments.name
|
||||
var assignment by SoloAssignments.assignment
|
||||
var deadline by SoloAssignments.deadline
|
||||
}
|
||||
|
||||
class GroupFeedback(id: EntityID<CompositeID>) : Entity<CompositeID>(id) {
|
||||
|
|
|
@ -4,7 +4,6 @@ import androidx.compose.foundation.clickable
|
|||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.material.OutlinedTextField
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
|
@ -13,13 +12,20 @@ import androidx.compose.ui.text.font.FontStyle
|
|||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import com.jaytux.grader.viewmodel.GroupAssignmentState
|
||||
import kotlinx.datetime.LocalDateTime
|
||||
import kotlinx.datetime.format
|
||||
import kotlinx.datetime.format.FormatStringsInDatetimeFormats
|
||||
import kotlinx.datetime.format.byUnicodePattern
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, FormatStringsInDatetimeFormats::class)
|
||||
@Composable
|
||||
fun GroupAssignmentView(state: GroupAssignmentState) {
|
||||
val (course, edition) = state.editionCourse
|
||||
val name by state.name
|
||||
val task by state.task
|
||||
val deadline by state.deadline
|
||||
val allFeedback by state.feedback.entities
|
||||
|
||||
var idx by remember { mutableStateOf(0) }
|
||||
|
@ -45,11 +51,34 @@ fun GroupAssignmentView(state: GroupAssignmentState) {
|
|||
|
||||
if(idx == 0) {
|
||||
var updTask by remember { mutableStateOf(task) }
|
||||
Row {
|
||||
var showPicker by remember { mutableStateOf(false) }
|
||||
val dateState = rememberDatePickerState()
|
||||
|
||||
Text("Deadline: ${deadline.format(LocalDateTime.Format { byUnicodePattern("dd/MM/yyyy - HH:mm") })}", Modifier.align(Alignment.CenterVertically))
|
||||
Spacer(Modifier.width(10.dp))
|
||||
Button({ showPicker = true }) { Text("Change") }
|
||||
|
||||
if(showPicker) DatePickerDialog(
|
||||
{ showPicker = false },
|
||||
{ Button({ showPicker = false; dateState.selectedDateMillis?.let { state.updateDeadline(it) } }) { Text("Set deadline") } },
|
||||
Modifier,
|
||||
{ Button({ showPicker = false }) { Text("Cancel") } },
|
||||
shape = MaterialTheme.shapes.medium,
|
||||
tonalElevation = 10.dp,
|
||||
colors = DatePickerDefaults.colors(),
|
||||
properties = DialogProperties()
|
||||
) {
|
||||
DatePicker(
|
||||
dateState,
|
||||
Modifier.fillMaxWidth().padding(10.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
OutlinedTextField(updTask, { updTask = it }, Modifier.fillMaxWidth().weight(1f), singleLine = false, minLines = 5, label = { Text("Task") })
|
||||
CancelSaveRow(updTask != task, { updTask = task }, "Reset", "Update") { state.updateTask(updTask) }
|
||||
}
|
||||
else {
|
||||
val (group, feedback, individual) = allFeedback[idx - 1].second
|
||||
groupFeedback(state, allFeedback[idx - 1].second)
|
||||
}
|
||||
}
|
||||
|
@ -108,7 +137,24 @@ fun groupFeedback(state: GroupAssignmentState, fdbk: GroupAssignmentState.LocalG
|
|||
}
|
||||
}
|
||||
else {
|
||||
//
|
||||
val (student, details) = individual[idx - 1]
|
||||
var sGrade by remember { mutableStateOf(details.second?.grade ?: "") }
|
||||
var sMsg by remember { mutableStateOf(TextFieldValue(details.second?.feedback ?: "")) }
|
||||
Row {
|
||||
Text("Grade: ", Modifier.align(Alignment.CenterVertically))
|
||||
OutlinedTextField(sGrade, { sGrade = it }, Modifier.weight(0.2f))
|
||||
Spacer(Modifier.weight(0.6f))
|
||||
Button({ state.upsertIndividualFeedback(student, group, sMsg.text, sGrade) }, Modifier.weight(0.2f).align(Alignment.CenterVertically),
|
||||
enabled = sGrade.isNotBlank() || sMsg.text.isNotBlank()) {
|
||||
Text("Save")
|
||||
}
|
||||
}
|
||||
|
||||
AutocompleteLineField(
|
||||
sMsg, { sMsg = it }, Modifier.fillMaxWidth().weight(1f), { Text("Feedback") }
|
||||
) { filter ->
|
||||
suggestions.filter { x -> x.trim().startsWith(filter.trim()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,11 +2,11 @@ package com.jaytux.grader.ui
|
|||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.Edit
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
|
|
|
@ -4,13 +4,8 @@ import androidx.compose.foundation.clickable
|
|||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.material.Checkbox
|
||||
import androidx.compose.material.OutlinedTextField
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
|
@ -41,6 +36,7 @@ fun EditionView(state: EditionState) = Row(Modifier.padding(0.dp)) {
|
|||
val groups by state.groups.entities
|
||||
val solo by state.solo.entities
|
||||
val groupAs by state.groupAs.entities
|
||||
val available by state.availableStudents.entities
|
||||
|
||||
val toggle = { i: Int, p: Panel ->
|
||||
idx = if(idx?.p == p && idx?.i == i) null else Current(p, i)
|
||||
|
@ -76,7 +72,8 @@ fun EditionView(state: EditionState) = Row(Modifier.padding(0.dp)) {
|
|||
} else {
|
||||
Box(Modifier.weight(0.5f)) {
|
||||
StudentsWidget(
|
||||
state.course, state.edition, students, idx.studentIdx(), { toggle(it, Panel.Student) }
|
||||
state.course, state.edition, students, idx.studentIdx(), { toggle(it, Panel.Student) },
|
||||
available, { state.addToCourse(it) }
|
||||
) { name, note, contact, addToEdition ->
|
||||
state.newStudent(name, contact, note, addToEdition)
|
||||
}
|
||||
|
@ -135,12 +132,13 @@ fun <T> EditionSideWidget(
|
|||
@Composable
|
||||
fun StudentsWidget(
|
||||
course: Course, edition: Edition, students: List<Student>, selected: Int?, onSelect: (Int) -> Unit,
|
||||
availableStudents: List<Student>, onImport: (List<Student>) -> Unit,
|
||||
onAdd: (name: String, note: String, contact: String, addToEdition: Boolean) -> Unit
|
||||
) = EditionSideWidget(
|
||||
course, edition, "Student list", "students", "a student", students, selected, onSelect,
|
||||
{ Text(it.name, Modifier.padding(5.dp)) }
|
||||
) { onExit ->
|
||||
StudentDialog(course, edition, onExit, onAdd)
|
||||
StudentDialog(course, edition, onExit, availableStudents, onImport, onAdd)
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
@ -148,29 +146,93 @@ fun StudentDialog(
|
|||
course: Course,
|
||||
edition: Edition,
|
||||
onClose: () -> Unit,
|
||||
availableStudents: List<Student>,
|
||||
onImport: (List<Student>) -> Unit,
|
||||
onAdd: (name: String, note: String, contact: String, addToEdition: Boolean) -> Unit
|
||||
) = DialogWindow(
|
||||
onCloseRequest = onClose,
|
||||
state = rememberDialogState(size = DpSize(600.dp, 400.dp), position = WindowPosition(Alignment.Center))
|
||||
) {
|
||||
Surface(Modifier.fillMaxSize().padding(10.dp)) {
|
||||
Box(Modifier.fillMaxSize()) {
|
||||
var name by remember { mutableStateOf("") }
|
||||
var contact by remember { mutableStateOf("") }
|
||||
var note by remember { mutableStateOf("") }
|
||||
var add by remember { mutableStateOf(true) }
|
||||
Surface(Modifier.fillMaxSize()) {
|
||||
Column(Modifier.padding(10.dp)) {
|
||||
var isImport by remember { mutableStateOf(false) }
|
||||
TabRow(if(isImport) 1 else 0) {
|
||||
Tab(!isImport, { isImport = false }) { Text("Add new student") }
|
||||
Tab(isImport, { isImport = true }) { Text("Add existing student") }
|
||||
}
|
||||
|
||||
Column(Modifier.align(Alignment.Center)) {
|
||||
OutlinedTextField(name, { name = it }, Modifier.fillMaxWidth(), singleLine = true, label = { Text("Student name") })
|
||||
OutlinedTextField(contact, { contact = it }, Modifier.fillMaxWidth(), singleLine = true, label = { Text("Student contact") })
|
||||
OutlinedTextField(note, { note = it }, Modifier.fillMaxWidth(), singleLine = false, minLines = 3, label = { Text("Note") })
|
||||
Row {
|
||||
Checkbox(add, { add = it })
|
||||
Text("Add student to ${course.name} ${edition.name}?", Modifier.align(Alignment.CenterVertically))
|
||||
if(isImport) {
|
||||
if(availableStudents.isEmpty()) {
|
||||
Box(Modifier.fillMaxSize()) {
|
||||
Text("No students available to add to this course.", Modifier.align(Alignment.Center))
|
||||
}
|
||||
}
|
||||
CancelSaveRow(name.isNotBlank() && contact.isNotBlank(), onClose) {
|
||||
onAdd(name, note, contact, add)
|
||||
onClose()
|
||||
else {
|
||||
var selected by remember { mutableStateOf(setOf<Int>()) }
|
||||
|
||||
val onClick = { idx: Int ->
|
||||
selected = if(idx in selected) selected - idx else selected + idx
|
||||
}
|
||||
|
||||
Text("Select students to add to ${course.name} ${edition.name}")
|
||||
LazyColumn {
|
||||
itemsIndexed(availableStudents) { idx, student ->
|
||||
Surface(
|
||||
Modifier.fillMaxWidth().clickable { onClick(idx) },
|
||||
tonalElevation = if (selected.contains(idx)) 5.dp else 0.dp
|
||||
) {
|
||||
Row {
|
||||
Checkbox(selected.contains(idx), { onClick(idx) })
|
||||
Text(student.name, Modifier.padding(5.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
CancelSaveRow(selected.isNotEmpty(), onClose) {
|
||||
onImport(selected.map { idx -> availableStudents[idx] })
|
||||
onClose()
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
Box(Modifier.fillMaxSize()) {
|
||||
var name by remember { mutableStateOf("") }
|
||||
var contact by remember { mutableStateOf("") }
|
||||
var note by remember { mutableStateOf("") }
|
||||
var add by remember { mutableStateOf(true) }
|
||||
|
||||
Column(Modifier.align(Alignment.Center)) {
|
||||
OutlinedTextField(
|
||||
name,
|
||||
{ name = it },
|
||||
Modifier.fillMaxWidth(),
|
||||
singleLine = true,
|
||||
label = { Text("Student name") })
|
||||
OutlinedTextField(
|
||||
contact,
|
||||
{ contact = it },
|
||||
Modifier.fillMaxWidth(),
|
||||
singleLine = true,
|
||||
label = { Text("Student contact") })
|
||||
OutlinedTextField(
|
||||
note,
|
||||
{ note = it },
|
||||
Modifier.fillMaxWidth(),
|
||||
singleLine = false,
|
||||
minLines = 3,
|
||||
label = { Text("Note") })
|
||||
Row {
|
||||
Checkbox(add, { add = it })
|
||||
Text(
|
||||
"Add student to ${course.name} ${edition.name}?",
|
||||
Modifier.align(Alignment.CenterVertically)
|
||||
)
|
||||
}
|
||||
CancelSaveRow(name.isNotBlank() && contact.isNotBlank(), onClose) {
|
||||
onAdd(name, note, contact, add)
|
||||
onClose()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,8 +67,8 @@ fun AddStringDialog(label: String, taken: List<String>, onClose: () -> Unit, onS
|
|||
onCloseRequest = onClose,
|
||||
state = rememberDialogState(size = DpSize(400.dp, 300.dp), position = WindowPosition(Alignment.Center))
|
||||
) {
|
||||
Surface(Modifier.fillMaxSize().padding(10.dp)) {
|
||||
Box(Modifier.fillMaxSize()) {
|
||||
Surface(Modifier.fillMaxSize()) {
|
||||
Box(Modifier.fillMaxSize().padding(10.dp)) {
|
||||
var name by remember { mutableStateOf("") }
|
||||
Column(Modifier.align(Alignment.Center)) {
|
||||
androidx.compose.material.OutlinedTextField(name, { name = it }, Modifier.fillMaxWidth(), label = { Text(label) }, isError = name in taken)
|
||||
|
|
|
@ -4,11 +4,18 @@ import androidx.compose.runtime.MutableState
|
|||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import com.jaytux.grader.data.*
|
||||
import com.jaytux.grader.data.EditionStudents.editionId
|
||||
import com.jaytux.grader.data.EditionStudents.studentId
|
||||
import kotlinx.datetime.Instant
|
||||
import kotlinx.datetime.LocalDateTime
|
||||
import kotlinx.datetime.TimeZone
|
||||
import kotlinx.datetime.toLocalDateTime
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
|
||||
fun <T> MutableState<T>.immutable(): State<T> = this
|
||||
fun <T> SizedIterable<T>.sortAsc(vararg columns: Expression<*>) = this.orderBy(*(columns.map { it to SortOrder.ASC }.toTypedArray()))
|
||||
|
||||
class RawDbState<T: Any>(private val loader: (Transaction.() -> List<T>)) {
|
||||
|
||||
|
@ -23,7 +30,7 @@ class RawDbState<T: Any>(private val loader: (Transaction.() -> List<T>)) {
|
|||
}
|
||||
|
||||
class CourseListState {
|
||||
val courses = RawDbState { Course.all().toList() }
|
||||
val courses = RawDbState { Course.all().sortAsc(Courses.name).toList() }
|
||||
|
||||
fun new(name: String) {
|
||||
transaction { Course.new { this.name = name } }
|
||||
|
@ -39,7 +46,7 @@ class CourseListState {
|
|||
}
|
||||
|
||||
class EditionListState(val course: Course) {
|
||||
val editions = RawDbState { Edition.find { Editions.courseId eq course.id }.toList() }
|
||||
val editions = RawDbState { Edition.find { Editions.courseId eq course.id }.sortAsc(Editions.name).toList() }
|
||||
|
||||
fun new(name: String) {
|
||||
transaction { Edition.new { this.name = name; this.course = this@EditionListState.course } }
|
||||
|
@ -54,10 +61,16 @@ class EditionListState(val course: Course) {
|
|||
|
||||
class EditionState(val edition: Edition) {
|
||||
val course = transaction { edition.course }
|
||||
val students = RawDbState { edition.soloStudents.toList() }
|
||||
val groups = RawDbState { edition.groups.toList() }
|
||||
val solo = RawDbState { edition.soloAssignments.toList() }
|
||||
val groupAs = RawDbState { edition.groupAssignments.toList() }
|
||||
val students = RawDbState { edition.soloStudents.sortAsc(Students.name).toList() }
|
||||
val groups = RawDbState { edition.groups.sortAsc(Groups.name).toList() }
|
||||
val solo = RawDbState { edition.soloAssignments.sortAsc(SoloAssignments.name).toList() }
|
||||
val groupAs = RawDbState { edition.groupAssignments.sortAsc(GroupAssignments.name).toList() }
|
||||
|
||||
val availableStudents = RawDbState {
|
||||
Student.find {
|
||||
(Students.id notInList edition.soloStudents.map { it.id })
|
||||
}.toList()
|
||||
}
|
||||
|
||||
fun newStudent(name: String, contact: String, note: String, addToEdition: Boolean) {
|
||||
transaction {
|
||||
|
@ -69,6 +82,18 @@ class EditionState(val edition: Edition) {
|
|||
}
|
||||
|
||||
if(addToEdition) students.refresh()
|
||||
else availableStudents.refresh()
|
||||
}
|
||||
|
||||
fun addToCourse(students: List<Student>) {
|
||||
transaction {
|
||||
EditionStudents.batchInsert(students) {
|
||||
this[editionId] = edition.id
|
||||
this[studentId] = it.id
|
||||
}
|
||||
}
|
||||
availableStudents.refresh();
|
||||
this.students.refresh()
|
||||
}
|
||||
|
||||
fun newGroup(name: String) {
|
||||
|
@ -94,8 +119,10 @@ class EditionState(val edition: Edition) {
|
|||
|
||||
class StudentState(val student: Student, edition: Edition) {
|
||||
val editionCourse = transaction { edition.course to edition }
|
||||
val groups = RawDbState { student.groups.map { it to (it.edition.course.name to it.edition.name) }.toList() }
|
||||
val courseEditions = RawDbState { student.courses.map{ it to it.course }.toList() }
|
||||
val groups = RawDbState { student.groups.sortAsc(Groups.name).map { it to (it.edition.course.name to it.edition.name) }.toList() }
|
||||
val courseEditions = RawDbState { student.courses.map{ it to it.course }.sortedWith {
|
||||
(e1, c1), (e2, c2) -> c1.name.compareTo(c2.name).let { if(it == 0) e1.name.compareTo(e2.name) else it }
|
||||
}.toList() }
|
||||
|
||||
fun update(f: Student.() -> Unit) {
|
||||
transaction {
|
||||
|
@ -105,17 +132,17 @@ class StudentState(val student: Student, edition: Edition) {
|
|||
}
|
||||
|
||||
class GroupState(val group: Group) {
|
||||
val members = RawDbState { group.studentRoles.map{ it.student to it.role }.toList() }
|
||||
val members = RawDbState { group.studentRoles.map{ it.student to it.role }.sortedBy { it.first.name }.toList() }
|
||||
val availableStudents = RawDbState { Student.find {
|
||||
// not yet in the group
|
||||
(Students.id notInList group.students.map { it.id }) and
|
||||
// but in the same course (edition)
|
||||
(Students.id inList group.edition.soloStudents.map { it.id })
|
||||
}.toList() }
|
||||
}.sortAsc(Students.name).toList() }
|
||||
val course = transaction { group.edition.course to group.edition }
|
||||
val roles = RawDbState {
|
||||
GroupStudents.select(GroupStudents.role).where{ GroupStudents.role.isNotNull() }
|
||||
.withDistinct(true).map{ it[GroupStudents.role] ?: "" }.toList()
|
||||
.withDistinct(true).sortAsc(GroupStudents.role).map{ it[GroupStudents.role] ?: "" }.toList()
|
||||
}
|
||||
|
||||
fun addStudent(student: Student) {
|
||||
|
@ -151,24 +178,25 @@ class GroupAssignmentState(val assignment: GroupAssignment) {
|
|||
data class LocalGFeedback(
|
||||
val group: Group,
|
||||
val feedback: LocalFeedback?,
|
||||
val individuals: Map<Student, Pair<String?, LocalFeedback?>>
|
||||
val individuals: List<Pair<Student, Pair<String?, LocalFeedback?>>>
|
||||
)
|
||||
|
||||
val editionCourse = transaction { assignment.edition.course to assignment.edition }
|
||||
private val _name = mutableStateOf(assignment.name); val name = _name.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()
|
||||
|
||||
val autofill = RawDbState {
|
||||
val forGroups = GroupFeedbacks.selectAll().where { GroupFeedbacks.groupAssignmentId eq assignment.id }.flatMap {
|
||||
it[GroupFeedbacks.feedback].split('\n')
|
||||
}.toList()
|
||||
}
|
||||
|
||||
val forIndividuals = IndividualFeedbacks.selectAll().where { IndividualFeedbacks.groupAssignmentId eq assignment.id }.flatMap {
|
||||
it[IndividualFeedbacks.feedback].split('\n')
|
||||
}.toList()
|
||||
}
|
||||
|
||||
(forGroups + forIndividuals).distinct()
|
||||
(forGroups + forIndividuals).distinct().sorted()
|
||||
}
|
||||
|
||||
private fun Transaction.loadFeedback(): List<Pair<Group, LocalGFeedback>> {
|
||||
|
@ -186,8 +214,8 @@ class GroupAssignmentState(val assignment: GroupAssignment) {
|
|||
|
||||
val groups = Group.find {
|
||||
(Groups.editionId eq assignment.edition.id)
|
||||
}.map { group ->
|
||||
val students = group.studentRoles.associate { sR ->
|
||||
}.sortAsc(Groups.name).map { group ->
|
||||
val students = group.studentRoles.sortedBy { it.student.name }.map { sR ->
|
||||
val student = sR.student
|
||||
val role = sR.role
|
||||
val feedback = individuals[student.id]
|
||||
|
@ -234,6 +262,14 @@ class GroupAssignmentState(val assignment: GroupAssignment) {
|
|||
}
|
||||
_task.value = t
|
||||
}
|
||||
|
||||
fun updateDeadline(instant: Long) {
|
||||
val d = Instant.fromEpochMilliseconds(instant).toLocalDateTime(TimeZone.currentSystemDefault())
|
||||
transaction {
|
||||
assignment.deadline = d
|
||||
}
|
||||
_deadline.value = d
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue