Almost finished!

This commit is contained in:
jay-tux 2025-02-24 09:56:44 +01:00
parent c8b605353c
commit 054970bb79
Signed by: jay-tux
GPG Key ID: 84302006B056926E
8 changed files with 206 additions and 48 deletions

View File

@ -1,8 +1,10 @@
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
import org.jetbrains.exposed.sql.kotlin.datetime.datetime
object Courses : UUIDTable("courses") { object Courses : UUIDTable("courses") {
val name = varchar("name", 50).uniqueIndex() val name = varchar("name", 50).uniqueIndex()
@ -53,12 +55,14 @@ object GroupAssignments : UUIDTable("grpAssgmts") {
val editionId = reference("edition_id", Editions.id) val editionId = reference("edition_id", Editions.id)
val name = varchar("name", 50) val name = varchar("name", 50)
val assignment = text("assignment") val assignment = text("assignment")
val deadline = datetime("deadline")
} }
object SoloAssignments : UUIDTable("soloAssgmts") { object SoloAssignments : UUIDTable("soloAssgmts") {
val editionId = reference("edition_id", Editions.id) val editionId = reference("edition_id", Editions.id)
val name = varchar("name", 50) val name = varchar("name", 50)
val assignment = text("assignment") val assignment = text("assignment")
val deadline = datetime("deadline")
} }
object GroupFeedbacks : CompositeIdTable("grpFdbks") { object GroupFeedbacks : CompositeIdTable("grpFdbks") {

View File

@ -14,6 +14,14 @@ object Database {
GroupAssignments, SoloAssignments, GroupAssignments, SoloAssignments,
GroupFeedbacks, IndividualFeedbacks, SoloFeedbacks GroupFeedbacks, IndividualFeedbacks, SoloFeedbacks
) )
val addMissing = SchemaUtils.addMissingColumnsStatements(
Courses, Editions, Groups,
Students, GroupStudents, EditionStudents,
GroupAssignments, SoloAssignments,
GroupFeedbacks, IndividualFeedbacks, SoloFeedbacks
)
addMissing.forEach { exec(it) }
} }
actual actual
} }

View File

@ -59,6 +59,7 @@ class GroupAssignment(id: EntityID<UUID>) : Entity<UUID>(id) {
var edition by Edition referencedOn GroupAssignments.editionId var edition by Edition referencedOn GroupAssignments.editionId
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
} }
class SoloAssignment(id: EntityID<UUID>) : Entity<UUID>(id) { 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 edition by Edition referencedOn SoloAssignments.editionId
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
} }
class GroupFeedback(id: EntityID<CompositeID>) : Entity<CompositeID>(id) { class GroupFeedback(id: EntityID<CompositeID>) : Entity<CompositeID>(id) {

View File

@ -4,7 +4,6 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material.OutlinedTextField
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment 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.font.FontWeight
import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.DialogProperties
import com.jaytux.grader.viewmodel.GroupAssignmentState 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 @Composable
fun GroupAssignmentView(state: GroupAssignmentState) { fun GroupAssignmentView(state: GroupAssignmentState) {
val (course, edition) = state.editionCourse val (course, edition) = state.editionCourse
val name by state.name val name by state.name
val task by state.task val task by state.task
val deadline by state.deadline
val allFeedback by state.feedback.entities val allFeedback by state.feedback.entities
var idx by remember { mutableStateOf(0) } var idx by remember { mutableStateOf(0) }
@ -45,11 +51,34 @@ fun GroupAssignmentView(state: GroupAssignmentState) {
if(idx == 0) { if(idx == 0) {
var updTask by remember { mutableStateOf(task) } 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") }) 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) } CancelSaveRow(updTask != task, { updTask = task }, "Reset", "Update") { state.updateTask(updTask) }
} }
else { else {
val (group, feedback, individual) = allFeedback[idx - 1].second
groupFeedback(state, allFeedback[idx - 1].second) groupFeedback(state, allFeedback[idx - 1].second)
} }
} }
@ -108,7 +137,24 @@ fun groupFeedback(state: GroupAssignmentState, fdbk: GroupAssignmentState.LocalG
} }
} }
else { 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()) }
}
} }
} }
} }

View File

@ -2,11 +2,11 @@ package com.jaytux.grader.ui
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* 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.Icons
import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Edit 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.Button
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface import androidx.compose.material3.Surface

View File

@ -4,13 +4,8 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material.Checkbox import androidx.compose.material3.*
import androidx.compose.material.OutlinedTextField
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.material3.Text
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign 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 groups by state.groups.entities
val solo by state.solo.entities val solo by state.solo.entities
val groupAs by state.groupAs.entities val groupAs by state.groupAs.entities
val available by state.availableStudents.entities
val toggle = { i: Int, p: Panel -> val toggle = { i: Int, p: Panel ->
idx = if(idx?.p == p && idx?.i == i) null else Current(p, i) 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 { } else {
Box(Modifier.weight(0.5f)) { Box(Modifier.weight(0.5f)) {
StudentsWidget( 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 -> ) { name, note, contact, addToEdition ->
state.newStudent(name, contact, note, addToEdition) state.newStudent(name, contact, note, addToEdition)
} }
@ -135,12 +132,13 @@ fun <T> EditionSideWidget(
@Composable @Composable
fun StudentsWidget( fun StudentsWidget(
course: Course, edition: Edition, students: List<Student>, selected: Int?, onSelect: (Int) -> Unit, 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 onAdd: (name: String, note: String, contact: String, addToEdition: Boolean) -> Unit
) = EditionSideWidget( ) = EditionSideWidget(
course, edition, "Student list", "students", "a student", students, selected, onSelect, course, edition, "Student list", "students", "a student", students, selected, onSelect,
{ Text(it.name, Modifier.padding(5.dp)) } { Text(it.name, Modifier.padding(5.dp)) }
) { onExit -> ) { onExit ->
StudentDialog(course, edition, onExit, onAdd) StudentDialog(course, edition, onExit, availableStudents, onImport, onAdd)
} }
@Composable @Composable
@ -148,29 +146,93 @@ fun StudentDialog(
course: Course, course: Course,
edition: Edition, edition: Edition,
onClose: () -> Unit, onClose: () -> Unit,
availableStudents: List<Student>,
onImport: (List<Student>) -> Unit,
onAdd: (name: String, note: String, contact: String, addToEdition: Boolean) -> Unit onAdd: (name: String, note: String, contact: String, addToEdition: Boolean) -> Unit
) = DialogWindow( ) = DialogWindow(
onCloseRequest = onClose, onCloseRequest = onClose,
state = rememberDialogState(size = DpSize(600.dp, 400.dp), position = WindowPosition(Alignment.Center)) state = rememberDialogState(size = DpSize(600.dp, 400.dp), position = WindowPosition(Alignment.Center))
) { ) {
Surface(Modifier.fillMaxSize().padding(10.dp)) { Surface(Modifier.fillMaxSize()) {
Box(Modifier.fillMaxSize()) { Column(Modifier.padding(10.dp)) {
var name by remember { mutableStateOf("") } var isImport by remember { mutableStateOf(false) }
var contact by remember { mutableStateOf("") } TabRow(if(isImport) 1 else 0) {
var note by remember { mutableStateOf("") } Tab(!isImport, { isImport = false }) { Text("Add new student") }
var add by remember { mutableStateOf(true) } Tab(isImport, { isImport = true }) { Text("Add existing student") }
}
Column(Modifier.align(Alignment.Center)) { if(isImport) {
OutlinedTextField(name, { name = it }, Modifier.fillMaxWidth(), singleLine = true, label = { Text("Student name") }) if(availableStudents.isEmpty()) {
OutlinedTextField(contact, { contact = it }, Modifier.fillMaxWidth(), singleLine = true, label = { Text("Student contact") }) Box(Modifier.fillMaxSize()) {
OutlinedTextField(note, { note = it }, Modifier.fillMaxWidth(), singleLine = false, minLines = 3, label = { Text("Note") }) Text("No students available to add to this course.", Modifier.align(Alignment.Center))
Row { }
Checkbox(add, { add = it })
Text("Add student to ${course.name} ${edition.name}?", Modifier.align(Alignment.CenterVertically))
} }
CancelSaveRow(name.isNotBlank() && contact.isNotBlank(), onClose) { else {
onAdd(name, note, contact, add) var selected by remember { mutableStateOf(setOf<Int>()) }
onClose()
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()
}
}
} }
} }
} }

View File

@ -67,8 +67,8 @@ fun AddStringDialog(label: String, taken: List<String>, onClose: () -> Unit, onS
onCloseRequest = onClose, onCloseRequest = onClose,
state = rememberDialogState(size = DpSize(400.dp, 300.dp), position = WindowPosition(Alignment.Center)) state = rememberDialogState(size = DpSize(400.dp, 300.dp), position = WindowPosition(Alignment.Center))
) { ) {
Surface(Modifier.fillMaxSize().padding(10.dp)) { Surface(Modifier.fillMaxSize()) {
Box(Modifier.fillMaxSize()) { Box(Modifier.fillMaxSize().padding(10.dp)) {
var name by remember { mutableStateOf("") } var name by remember { mutableStateOf("") }
Column(Modifier.align(Alignment.Center)) { Column(Modifier.align(Alignment.Center)) {
androidx.compose.material.OutlinedTextField(name, { name = it }, Modifier.fillMaxWidth(), label = { Text(label) }, isError = name in taken) androidx.compose.material.OutlinedTextField(name, { name = it }, Modifier.fillMaxWidth(), label = { Text(label) }, isError = name in taken)

View File

@ -4,11 +4,18 @@ import androidx.compose.runtime.MutableState
import androidx.compose.runtime.State import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf 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.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.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.transactions.transaction
fun <T> MutableState<T>.immutable(): State<T> = this 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>)) { 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 { class CourseListState {
val courses = RawDbState { Course.all().toList() } val courses = RawDbState { Course.all().sortAsc(Courses.name).toList() }
fun new(name: String) { fun new(name: String) {
transaction { Course.new { this.name = name } } transaction { Course.new { this.name = name } }
@ -39,7 +46,7 @@ class CourseListState {
} }
class EditionListState(val course: Course) { 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) { fun new(name: String) {
transaction { Edition.new { this.name = name; this.course = this@EditionListState.course } } 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) { class EditionState(val edition: Edition) {
val course = transaction { edition.course } val course = transaction { edition.course }
val students = RawDbState { edition.soloStudents.toList() } val students = RawDbState { edition.soloStudents.sortAsc(Students.name).toList() }
val groups = RawDbState { edition.groups.toList() } val groups = RawDbState { edition.groups.sortAsc(Groups.name).toList() }
val solo = RawDbState { edition.soloAssignments.toList() } val solo = RawDbState { edition.soloAssignments.sortAsc(SoloAssignments.name).toList() }
val groupAs = RawDbState { edition.groupAssignments.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) { fun newStudent(name: String, contact: String, note: String, addToEdition: Boolean) {
transaction { transaction {
@ -69,6 +82,18 @@ class EditionState(val edition: Edition) {
} }
if(addToEdition) students.refresh() 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) { fun newGroup(name: String) {
@ -94,8 +119,10 @@ class EditionState(val edition: Edition) {
class StudentState(val student: Student, edition: Edition) { class StudentState(val student: Student, edition: Edition) {
val editionCourse = transaction { edition.course to 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 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 }.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) { fun update(f: Student.() -> Unit) {
transaction { transaction {
@ -105,17 +132,17 @@ class StudentState(val student: Student, edition: Edition) {
} }
class GroupState(val group: Group) { 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 { val availableStudents = RawDbState { Student.find {
// not yet in the group // not yet in the group
(Students.id notInList group.students.map { it.id }) and (Students.id notInList group.students.map { it.id }) and
// but in the same course (edition) // but in the same course (edition)
(Students.id inList group.edition.soloStudents.map { it.id }) (Students.id inList group.edition.soloStudents.map { it.id })
}.toList() } }.sortAsc(Students.name).toList() }
val course = transaction { group.edition.course to group.edition } val course = transaction { group.edition.course to group.edition }
val roles = RawDbState { val roles = RawDbState {
GroupStudents.select(GroupStudents.role).where{ GroupStudents.role.isNotNull() } 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) { fun addStudent(student: Student) {
@ -151,24 +178,25 @@ class GroupAssignmentState(val assignment: GroupAssignment) {
data class LocalGFeedback( data class LocalGFeedback(
val group: Group, val group: Group,
val feedback: LocalFeedback?, 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 } 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() } val feedback = RawDbState { loadFeedback() }
private val _deadline = mutableStateOf(assignment.deadline); val deadline = _deadline.immutable()
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 {
it[GroupFeedbacks.feedback].split('\n') it[GroupFeedbacks.feedback].split('\n')
}.toList() }
val forIndividuals = IndividualFeedbacks.selectAll().where { IndividualFeedbacks.groupAssignmentId eq assignment.id }.flatMap { val forIndividuals = IndividualFeedbacks.selectAll().where { IndividualFeedbacks.groupAssignmentId eq assignment.id }.flatMap {
it[IndividualFeedbacks.feedback].split('\n') it[IndividualFeedbacks.feedback].split('\n')
}.toList() }
(forGroups + forIndividuals).distinct() (forGroups + forIndividuals).distinct().sorted()
} }
private fun Transaction.loadFeedback(): List<Pair<Group, LocalGFeedback>> { private fun Transaction.loadFeedback(): List<Pair<Group, LocalGFeedback>> {
@ -186,8 +214,8 @@ class GroupAssignmentState(val assignment: GroupAssignment) {
val groups = Group.find { val groups = Group.find {
(Groups.editionId eq assignment.edition.id) (Groups.editionId eq assignment.edition.id)
}.map { group -> }.sortAsc(Groups.name).map { group ->
val students = group.studentRoles.associate { sR -> val students = group.studentRoles.sortedBy { it.student.name }.map { sR ->
val student = sR.student val student = sR.student
val role = sR.role val role = sR.role
val feedback = individuals[student.id] val feedback = individuals[student.id]
@ -234,6 +262,14 @@ class GroupAssignmentState(val assignment: GroupAssignment) {
} }
_task.value = t _task.value = t
} }
fun updateDeadline(instant: Long) {
val d = Instant.fromEpochMilliseconds(instant).toLocalDateTime(TimeZone.currentSystemDefault())
transaction {
assignment.deadline = d
}
_deadline.value = d
}
} }