Almost finished!
This commit is contained in:
parent
c8b605353c
commit
054970bb79
|
@ -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") {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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()) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,12 +146,55 @@ 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()) {
|
||||||
|
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") }
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isImport) {
|
||||||
|
if(availableStudents.isEmpty()) {
|
||||||
|
Box(Modifier.fillMaxSize()) {
|
||||||
|
Text("No students available to add to this course.", Modifier.align(Alignment.Center))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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()) {
|
Box(Modifier.fillMaxSize()) {
|
||||||
var name by remember { mutableStateOf("") }
|
var name by remember { mutableStateOf("") }
|
||||||
var contact by remember { mutableStateOf("") }
|
var contact by remember { mutableStateOf("") }
|
||||||
|
@ -161,12 +202,31 @@ fun StudentDialog(
|
||||||
var add by remember { mutableStateOf(true) }
|
var add by remember { mutableStateOf(true) }
|
||||||
|
|
||||||
Column(Modifier.align(Alignment.Center)) {
|
Column(Modifier.align(Alignment.Center)) {
|
||||||
OutlinedTextField(name, { name = it }, Modifier.fillMaxWidth(), singleLine = true, label = { Text("Student name") })
|
OutlinedTextField(
|
||||||
OutlinedTextField(contact, { contact = it }, Modifier.fillMaxWidth(), singleLine = true, label = { Text("Student contact") })
|
name,
|
||||||
OutlinedTextField(note, { note = it }, Modifier.fillMaxWidth(), singleLine = false, minLines = 3, label = { Text("Note") })
|
{ 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 {
|
Row {
|
||||||
Checkbox(add, { add = it })
|
Checkbox(add, { add = it })
|
||||||
Text("Add student to ${course.name} ${edition.name}?", Modifier.align(Alignment.CenterVertically))
|
Text(
|
||||||
|
"Add student to ${course.name} ${edition.name}?",
|
||||||
|
Modifier.align(Alignment.CenterVertically)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
CancelSaveRow(name.isNotBlank() && contact.isNotBlank(), onClose) {
|
CancelSaveRow(name.isNotBlank() && contact.isNotBlank(), onClose) {
|
||||||
onAdd(name, note, contact, add)
|
onAdd(name, note, contact, add)
|
||||||
|
@ -175,6 +235,8 @@ fun StudentDialog(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue